{
    "version": "https://jsonfeed.org/version/1",
    "title": "Hulk Dev Blog Blog",
    "home_page_url": "https://hulkdev.com/",
    "description": "Hulk Dev Blog Blog",
    "items": [
        {
            "id": "posts-writing-by-notes",
            "content_html": "<p>乍看书名以为是关于如何做笔记的工具书，后面发现重点是讲写作思维方式，整体内容上比看之前的预期好很多。卡片盒写作法是卢曼杰出学术成就的生产力来源，他在长达 30 多年的研究中出版了 58 本著作以及数百篇文章。书中主要是论述如何通过卡片笔记的方法进行写作，也顺便解答了我的几个问题:</p><ul><li>为什么无法记住阅读后的大部分内容，隔一段时间还是会在同一个地方被反复醍醐灌顶？主要是人脑的容量有限，只会暂存短期内认为最重要的事情(蔡格尼克效应)，不借助外部存储很快会丢失。前几天刚好也看到艾宾浩斯遗忘曲线也证实知识遗忘是学习过程中的普遍现象，可以通过科学复习方法来减少遗忘比例。</li><li>为什么写作这么难？为什么会陷入一直想写但无从下手的循环？书中的提到很重要的观点：<strong>写作不应该从空白屏幕开始，而是需要从阅读中刻意积累观点和论据，将写作过程变成自下而上的过程。</strong>一直将不知道写什么归因为缺少足够的输入，虽然该观点是对的但又过于宽泛。现在我会理解为是缺少刻意的输入和积累，选择某个方向之后，需要刻意去收集和阅读相关方向的信息，暂且放下不相关的输入。</li><li>为什么有笔记但仍然对知识记忆帮助不大？不管是重点划线还是空白处记录想法，过程中缺失了思考或者关联，最终也只是得到一堆不会再看的笔记。作者提到卢曼的卡片笔记最重要的是分类、关联以及定期整理，丢弃掉临时或者过期的观点。</li></ul><p>这本书对于我自己最大的帮助在于阅读思维的变化， 也促使我写下了第一篇读书笔记。当然，能否真正带来写作上的改变，需要留给时间来回答。我十分喜欢里面关于读书的观点: <strong>「读书不是为了积累知识而是形成自己的思维框架，并使用新观点和事实对过往的经验提出质疑」</strong>。我们不是复读机，不必为了可以背出某个名人的语句而读书，而应该是纯粹为了形成自己的思考方式。具备自己的思考和辨别能力，而不是受某种权威或者舆论的引导。以下是个人认为值得分享的几个观点。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"写作很重要\">写作很重要<a class=\"hash-link\" href=\"#写作很重要\" title=\"Direct link to heading\">​</a></h3><p>书中引用卢曼的: <strong>「不写，就无法思考」</strong> 来说明写作的重要性。很多人都意识到了写作表达的重要性，但能够坚持践行的人并不多。主要是过往教授的写作方式包含很多写作指南书籍都是至上而下，习惯于围绕着某个主题展开论述，缺少了最关键的论据收集以及积累的过程。以至于需要从容量有限的大脑里搜索和提取观点，将写作变成痛苦的脑暴过程。而卢曼的卡片笔记写作法核心则是相反，提出写作不应该是空白纸开始，而是通过大量阅读以及积累观点这种自下而上的过程。遇到有价值的观点，用自己的语言写下来思考，写作时只需将这些过往的观点聚合到一起。反过来，要想要讨论某个话题，重要的是收集相关材料进行阅读和思考，而不是写作的过程或者工具。另外，书中举了很多次费曼的例子来强调关联的重要性，零散的知识点很难记住，而关联之后更加容易。也可以同时复盘之前哪些观点是一致或者相悖，写作就是关联的过程，可以通过写作不断更新自己的理解和思考。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"确认偏差\">确认偏差<a class=\"hash-link\" href=\"#确认偏差\" title=\"Direct link to heading\">​</a></h3><p>在保持开放心态的章节里面提到了 <strong>确认偏差</strong> 这个概念，大体的意思是人潜意识会被熟悉或感觉良好的事物所吸引，剔除那些有违我们认知的事物。确认偏差会让我们无意识自动进入搜集周围有利于结论的数据，从而干扰学习和思考。写作也是一样，在寻找论据时需要克服确认偏差的干扰，避免无意识只收集对论点有利的证据。 对于写作来说，克服的方式可以将寻找证实性的事实变成收集所有相关信息，而不管它支持什么论点。书中也引用了 Charles Darwin 如何处理确认偏差的方式: 遇到与自己结论相反的事实或者观点时立即把它们记下来，因为这些事实和观点更加容易被忽略。</p><p>在 <strong>「亚马逊反向工作法」</strong> 书中的招聘章节也提到这个概念，由于确认偏差导致招聘者倾向于关注他人确认的正面信息，而忽略负面和矛盾的信息，最终导致 \"群体思维\"。举的例子就是面试官之间交流一旦给予正面反馈，会导致后续面试过程中无意识尝试寻找候选人这些优点，从而忽略了候选人存在的缺陷。很多公司的招聘流程上都存在类似的问题，只是可能没有意识到而已，一旦意识到之后改变就变得很容易。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"总结\">总结<a class=\"hash-link\" href=\"#总结\" title=\"Direct link to heading\">​</a></h3><p>写作最重要都是个人的理解和思考，不管多好的工具都是为了更系统化的思考。受限于人脑的存储容量，需要通过笔记的方式将记忆 offloading 到外部存储以减少知识遗忘。卡片笔记写作法本身并没有魔法，如果有，应当所有看过这本书的人都能够变成长期的写作者，它最重要的作用是改变了写作思维，让我们意识到写作不应该从零开始而是日常的理解和思考。另外，对于专题写作(比如论文)也是一样，写作过程不是重点，而是围绕专题阅读资料过程中个人的理解和思考。当然，最为重要的还是实践，只有真正到水里才能学会游泳。</p><p>欢迎扫码关注 <strong>hulk</strong> 个人微信公众号，一起学习和交流:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/personal-qrcode.jpeg\" alt=\"img\" class=\"img_ev3q\"></p>",
            "url": "https://hulkdev.com/posts-writing-by-notes",
            "title": "卡片笔记写作法 - 读书笔记",
            "summary": "乍看书名以为是关于如何做笔记的工具书，后面发现重点是讲写作思维方式，整体内容上比看之前的预期好很多。卡片盒写作法是卢曼杰出学术成就的生产力来源，他在长达 30 多年的研究中出版了 58 本著作以及数百篇文章。书中主要是论述如何通过卡片笔记的方法进行写作，也顺便解答了我的几个问题:",
            "date_modified": "2022-09-12T00:00:00.000Z",
            "author": {
                "name": "hulk"
            },
            "tags": [
                "读书"
            ]
        },
        {
            "id": "monitoring-go-apps-with-distributed-tracing-and-opentelemetry",
            "content_html": "<p>This article gives a brief introduction into monitoring Go applications using OpenTelemetry and Uptrace.</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"what-is-opentelemetry\">What is OpenTelemetry?<a class=\"hash-link\" href=\"#what-is-opentelemetry\" title=\"Direct link to heading\">​</a></h2><p><a href=\"https://uptrace.dev/opentelemetry/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry</a> is an open source and vendor-neutral API for <a href=\"https://uptrace.dev/opentelemetry/distributed-tracing.html\" target=\"_blank\" rel=\"noopener noreferrer\">distributed tracing</a> (including logs and errors) and <a href=\"https://uptrace.dev/opentelemetry/metrics.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry metrics</a>.</p><p>It specifies how to collect and export telemetry data in a vendor agnostic way. With OpenTelemetry, you can instrument your application once and then add or change vendors without changing the instrumentation, for example, many <a href=\"https://uptrace.dev/blog/distributed-tracing-tools.html\" target=\"_blank\" rel=\"noopener noreferrer\">open source tracing tools</a> already support OpenTelemetry.</p><p>By being vendor-neutral, OpenTelemetry makes it possible to try different products with minimal efforts and avoid being stuck with a vendor forever.</p><p>OpenTelemetry also enables different companies to work together on maintaining a single framework instead of duplicating efforts in proprietary projects which benefits everyone. It also means that OpenTelemetry works almost everywhere and you can trace requests across different languages, platforms, and even clouds.</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"how-to-use-opentelemetry\">How to use OpenTelemetry?<a class=\"hash-link\" href=\"#how-to-use-opentelemetry\" title=\"Direct link to heading\">​</a></h2><p>The easiest way to get started with OpenTelemetry is to pick an <a href=\"https://uptrace.dev/blog/opentelemetry-backend.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry backend</a> (vendor) and follow the documentation. Most vendors provide pre-configured OpenTelemetry distros that allow you to skip some steps and can significantly improve your experience.</p><p>If you are looking for an open source solution, Jaeger is historically the default choice, but Uptrace provides more features and might be a better choice nowadays.</p><p>Uptrace is an <a href=\"https://uptrace.dev/get/open-source-apm.html\" target=\"_blank\" rel=\"noopener noreferrer\">open source APM</a> for OpenTelemetry that uses ClickHouse database to store traces, metrics, and logs. You can use it to monitor applications and set up automatic alerts to receive notifications via email, Slack, Telegram, and more.</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"what-is-tracing\">What is tracing?<a class=\"hash-link\" href=\"#what-is-tracing\" title=\"Direct link to heading\">​</a></h2><p><a href=\"https://uptrace.dev/opentelemetry/distributed-tracing.html\" target=\"_blank\" rel=\"noopener noreferrer\">Distributed tracing</a> allows to observe requests as they propagate through distributed systems, especially those built using a microservices architecture.</p><p>Tracing provides insights into your app performance along with any errors and logs. You immediately see what conditions cause errors and how particular error affects app performance.</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/monitoring/tracing-graph.png\" alt=\"image\" class=\"img_ev3q\"></p><p>Using tracing, you can break down requests into <a href=\"https://uptrace.dev/opentelemetry/distributed-tracing.html#spans\" target=\"_blank\" rel=\"noopener noreferrer\">spans</a>. <strong>Span</strong> is an operation (unit of work) your app performs handling a request, for example, a database query or a network call.</p><p><strong>Trace</strong> is a tree of spans that shows the path that a request makes through an app. Root span is the first span in a trace.</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/monitoring/trace-graph.png\" alt=\"image\" class=\"img_ev3q\"></p><p>To learn more about tracing, see <a href=\"https://uptrace.dev/opentelemetry/distributed-tracing.html\" target=\"_blank\" rel=\"noopener noreferrer\">Distributed tracing using OpenTelemetry</a>.</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"opentelemetry-api\">OpenTelemetry API<a class=\"hash-link\" href=\"#opentelemetry-api\" title=\"Direct link to heading\">​</a></h2><p>OpenTelemetry API is a programming interface that you can use to instrument code to collect telemetry data such as traces, metrics, and logs.</p><p>You can create spans using OpenTelemetry API for Go like this:</p><div class=\"language-go codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-go codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">import</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"go.opentelemetry.io/otel\"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">var</span><span class=\"token plain\"> tracer </span><span class=\"token operator\">=</span><span class=\"token plain\"> otel</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">Tracer</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"app_or_package_name\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">func</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">someFunc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx context</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">Context</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token builtin\" style=\"color:rgb(189, 147, 249)\">error</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> span </span><span class=\"token operator\">:=</span><span class=\"token plain\"> tracer</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">Start</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"some-func\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">defer</span><span class=\"token plain\"> span</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">End</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// the code you are measuring</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token boolean\">nil</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>You can also record <a href=\"https://uptrace.dev/opentelemetry/distributed-tracing.html#attributes\" target=\"_blank\" rel=\"noopener noreferrer\">attributes</a> and errors:</p><div class=\"language-go codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-go codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">func</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">someFunc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx context</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">Context</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token builtin\" style=\"color:rgb(189, 147, 249)\">error</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> span </span><span class=\"token operator\">:=</span><span class=\"token plain\"> tracer</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">Start</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"some-func\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">defer</span><span class=\"token plain\"> span</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">End</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> span</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">IsRecording</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        span</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">SetAttributes</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            attribute</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">Int64</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"enduser.id\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> userID</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            attribute</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">String</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"enduser.email\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> userEmail</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> err </span><span class=\"token operator\">:=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">someOtherFunc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> err </span><span class=\"token operator\">!=</span><span class=\"token plain\"> </span><span class=\"token boolean\">nil</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        span</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RecordError</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">err</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        span</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">SetStatus</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">codes</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">Error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> err</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">Error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token boolean\">nil</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>See <a href=\"https://uptrace.dev/opentelemetry/go-tracing.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Go API</a> for details.</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"what-is-uptrace\">What is Uptrace?<a class=\"hash-link\" href=\"#what-is-uptrace\" title=\"Direct link to heading\">​</a></h2><p>Uptrace is an <a href=\"https://uptrace.dev/get/open-source-apm.html\" target=\"_blank\" rel=\"noopener noreferrer\">open source APM</a> with an intuitive query builder, rich dashboards, automatic alerts, and integrations for most languages and frameworks.</p><p>Uptrace accepts data from OpenTelemetry and stores it in a ClickHouse database. ClickHouse is a columnar database that is much more efficient for traces and logs than, for example, Elastic Search. On the same hardware, ClickHouse can store 10x more traces and, at the same time, provide much better performance.</p><p>You can <a href=\"https://uptrace.dev/get/install.html\" target=\"_blank\" rel=\"noopener noreferrer\">install</a> Uptrace by downloading a DEB/RPM package or a pre-compiled binary.</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/monitoring/uptrace.png\" alt=\"image\" class=\"img_ev3q\"></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"metrics-monitoring\">Metrics monitoring<a class=\"hash-link\" href=\"#metrics-monitoring\" title=\"Direct link to heading\">​</a></h2><p>Uptrace also allows you to monitor metrics using Prometheus-like alerting rules. For example, the following monitor uses the group by node expression to create an alert whenever an individual Redis shard is down:</p><div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token key atrule\">monitors</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\">name</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"> Redis shard is down</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">metrics</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> redis_up as $redis_up</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">query</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> group by cluster </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># monitor each cluster,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> group by bdb </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># each database,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> group by node </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># and each shard</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> $redis_up</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">min_allowed_value</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># shard should be down for 5 minutes to trigger an alert</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">for_duration</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"> 5m</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>You can also create queries with more complex expressions. For example, the following rules create an alert when the keyspace hit rate is lower than 75% or memory fragmentation is too high:</p><div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token key atrule\">monitors</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\">name</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"> Redis read hit rate &lt; 75%</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">metrics</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> redis_keyspace_read_hits as $hits</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> redis_keyspace_read_misses as $misses</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">query</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> group by cluster</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> group by bdb</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> group by node</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">-</span><span class=\"token plain\"> $hits / ($hits + $misses) as hit_rate</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">min_allowed_value</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"> </span><span class=\"token number\">0.75</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token key atrule\">for_duration</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">:</span><span class=\"token plain\"> 5m</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>See <a href=\"https://uptrace.dev/get/alerting.html\" target=\"_blank\" rel=\"noopener noreferrer\">Alerting and Notifications</a> for details.</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"whats-next\">What's next?<a class=\"hash-link\" href=\"#whats-next\" title=\"Direct link to heading\">​</a></h2><p>Next, you can continue exploring <a href=\"https://uptrace.dev/opentelemetry/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry</a> or start instrumenting your app using popular instrumentations:</p><ul><li><a href=\"https://uptrace.dev/get/instrument/opentelemetry-gin.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Gin</a></li><li><a href=\"https://uptrace.dev/get/instrument/opentelemetry-go-grpc.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Go gRPC</a></li><li><a href=\"https://uptrace.dev/get/instrument/opentelemetry-database-sql.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry database/sql</a></li><li><a href=\"https://uptrace.dev/get/monitor/opentelemetry-redis.html\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Redis</a></li></ul>",
            "url": "https://hulkdev.com/monitoring-go-apps-with-distributed-tracing-and-opentelemetry",
            "title": "Monitoring Go apps with Distributed Tracing and OpenTelemetry",
            "summary": "This article gives a brief introduction into monitoring Go applications using OpenTelemetry and Uptrace.",
            "date_modified": "2022-04-18T00:00:00.000Z",
            "author": {
                "name": "Vladimir Mihailenco"
            },
            "tags": [
                "Uptrace",
                "OpenTelemetry"
            ]
        },
        {
            "id": "posts-how-to-impl-bitmap-on-disk-kv",
            "content_html": "<p>大部分开发对 Bitmap 应该都不陌生，除了作为 Bloom Filter 实现的存储之外，许多数据库也有提供 Bitmap 类型的索引。对于内存型的存储来说，Bitmap 只是一个特殊类型(bit)的稀疏数组，操作内存不会带来读写放大问题(指的是物理读写的数据量远大于逻辑的数据量), Redis 就是在字符串类型上支持 bit 的相关操作，而对于 Kvrocks 这种基于磁盘 KV 实现的存储则会是比较大挑战，本篇文章主要讨论的其实是「<strong>基于磁盘 KV 实现 Bitmap</strong> 要<strong>如何减少磁盘读写放大」</strong></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"为什么会产生读写放大\">为什么会产生读写放大<a class=\"hash-link\" href=\"#为什么会产生读写放大\" title=\"Direct link to heading\">​</a></h2><p>读写放大的主要是来源于两方面:</p><ul><li>硬件层面的最小读写单元</li><li>软件层面存储组织方式</li></ul><p>硬件层面一般是由于最小读写单元带来的读写放大，以 SSD 为例，读写的最小单位是页(一般是 4KiB/8KiB/16KiB)。即使应用层只写入一个字节，在磁盘上实际会写入一个页，这也就是我们所说的写放大，反之读也是一样。另外，SSD 修改数据不是在页内位置原地修改而是 Read-Modify-Write 的方式，修改时需要将原来的数据读出来，修改之后再写到新页，老的磁盘页由 GC 进行回收。所以，即使对同一页的一小块数据反复修改也会由于硬件本身机制而造成写放大。类似于如下:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/ssd-rmw.png\" alt=\"image\" class=\"img_ev3q\"></p><p>由此可见，大量随机小 io 读写对于 SSD 磁盘来说是很不友好的，除了在性能方面有比较大的影响之外，频繁擦写也会严重导致 SSD 的寿命(随机读写对 HDD 同样不友好，需要不断寻道和寻址)。LSM-Tree 就是通过将随机写入变成顺序批量写入来缓解这类问题。</p><p>软件层面的读写放大主要来自于数据组织方式，不同的存储组织方式所带来的读写放大程度也会有很大的差异。这里以 RocksDB 为例，RocksDB 是 Facebook 基于 Google LevelDB 之上实现了多线程，Backup 以及 Compaction 等诸多很实用的功能。RocksDB 的数据组织方式是 LSM-Tree，在解决磁盘写入方法问题，本身的数据存储也带来了一些空间放大问题。下面可以简单看一下 LSM-Tree 是如何组织数据:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/lsm-tree.png\" alt=\"image\" class=\"img_ev3q\"></p><p>LSM-Tree 每次写入都会产生一条记录，比如上图 x 先后写了 4 次，分别是 0，1，2，3。如果单看 x 这个变量，这里相当于有 4 倍的空间放大，这些重复的记录会在 compaction 的时候进行回收。同样，删除也是通过插入一条 value 为空的记录来实现。 LSM-Tree 每一层空间大小是逐层递增，当容量大小当层最大时会触发 compaction 合并到下一层，以此类推。假设 Level 0 最大存储大小是 M Bytes，逐层按照 10 倍增长且最大 7 层，理论上空间放大的大约是 1.111111 倍。计算公式如下:</p><div class=\"language-go codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-go codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">空间放大率 </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token number\">1</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">10</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">100</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token number\">1000</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">10000</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">100000</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">1000000</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\"> M </span><span class=\"token operator\">/</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token number\">1000000</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\"> M</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>但在实际场景中，由于最后一层一般无法达到最大值，所以放大空间率比这个理论值大不少，具体在 RocksDB 的文档里面也有提过，具体见: <a href=\"https://rocksdb.org/blog/2015/07/23/dynamic-level.html\" target=\"_blank\" rel=\"noopener noreferrer\">https://rocksdb.org/blog/2015/07/23/dynamic-level.html</a></p><p>另外，由于 RocksDB 读写都是以 KV 为单位，Value 越大带来的读写放大就可能越大。举个例子，假设有一个 Value 为 10 MiB 的 JSON，如果要修改这个 key 中的一个字段，那么需要把整个 JSON 读出来，修改后再重新写回去，就会导致巨大的读写放大。有一篇 paper「WiscKey: Separating Keys from Values in SSD-conscious Storage」就是通过 Key/Value 分离的方式来优化 LSM-Tree 大 KV 的来减少 Compaction 时带来写放大的问题。TiKV 里面的 titan 就是基于 Wiskey 论文优化 RocksDB 在大 KV 场景的写放大问题，RocksDB 也在社区版本里面实现这个功能，不过还是实验性的阶段。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"基于磁盘-kv-实现-bitmap\">基于磁盘 KV 实现 Bitmap<a class=\"hash-link\" href=\"#基于磁盘-kv-实现-bitmap\" title=\"Direct link to heading\">​</a></h2><p> Kvrocks 是基于 RocksDB 之上实现的兼容 Redis 协议的磁盘存储， 需要支持 Bitmap 功能，所以就需要在磁盘 KV 之上实现 Bitmap 的功能。而大部分使用 Bitmap 的场景都是作为稀疏数组来用，意味着第一次写入的 offset 为 1，下次的 offset 可能就是 1000000000 甚至更大，所以在实现 Bitmap 就会面临上述读写和空间放大问题。</p><p>一种最简单的实现方式是仍然把整个 Bitmap 作为一个 Value，读写时将 Value 读取到内存中再回写。这种实现虽然很简单，但一不小心可能导致 value 巨大，单个 Value 大小上 GiB 都是可能的。除了存在有效空间利用率问题之外，可能会直接导致整个服务不可用(需要读写整个 Value)。Pika 里面的 Bitmap 就是这种实现，但限制最大的 Value 为 128 KiB，限制 Value 大小虽然避免上述的极端情况，但会大大限制 Bitmap 的使用场景，甚至是无法使用。</p><p>既然知道核心问题是由于单个 KV 过大导致， 那么最直接的方式就是将 Bitmap 拆分成多个 KV，然后控制单个 KV 大小在合理范围之内， 那么读写带来的放大也是相对可控。在当前 Kvrocks 的实现里面是按照每个 KV 为 1 KiB 来划分，相当于每个 value 可以存放 8192 bits。算法示意图如下:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/bitmap-split.png\" alt=\"image\" class=\"img_ev3q\"></p><p>以 <code>setbit foo 8192002 1</code> 为例，实现的步骤如下:</p><ol><li>计算 <code>8192002</code> 这个 offset 对应所在的 key, 因为 Kvrocks 是按照 1 KiB 一个 value，那么所在 key 的编号就是 8192002/(1024*8) = 1000，所以就可以知道这个 offset 应该写到 \"foo\" + 1000 这个 key 对应的 value 里面</li><li>接着从 RocksDB 里面去获取这个 key 对应的 value</li><li>计算这个 offset 在分段里面的偏移，8192002%8291 等于 2，然后把 value 中偏移为 2 的 bit 位设置为 1</li><li>最后将 value 回写到 RocksDB</li></ol><p>这种实现比较关键的一个特点是 Bitmap 对应的 KV 只在有写入的时候才会真正写到 RocksDB 里面。假设我们只执行过两次 setbit ，分别是  <code>setbit foo 1 1</code>  和 <code>setbit foo 8192002 1</code> ，那么 RocksDB 里面只会有 foo:0 和 foo:1000 这两个 key，实际的写入 KV 总共也只有 2 KiB。刚好也可以完美适应 Bitmap 这种稀疏数组的场景，不会因为稀疏写入而带来空间放大的问题。</p><p><strong>这个想法也和 Linux 的虚拟内存/物理内存映射策略类似，比如我们 malloc 申请了 1GiB 的内存，操作系统也只是分配一片虚拟内存地址空间，只有在真正写入的时候才会触发缺页中断去分配物理内存(目前正常内存页大小是 4KiB)。也就是如果内存页没有被写过，只读也不会产生物理内存分配。</strong></p><p>GetBit 也是类似，先计算 offset 所在的 key，然后从 RocksDB 读取这个 key, 如果不存在则说明这段没有被写过，直接返回 0。如果存在则读取 Value，返回对应 bit 的值。另外，在实现上也单个 KV 实际存储大小也是由目前写入最大的 offset 决定，并不是有写入就会分配 1024 KiB，这样也可以一定程度优化单个 KV 内的读写放大问题。实现可参考: <a href=\"https://github.com/KvrocksLabs/kvrocks/blob/unstable/src/redis_bitmap.cc\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/KvrocksLabs/kvrocks/blob/unstable/src/redis_bitmap.cc</a></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"总结\">总结<a class=\"hash-link\" href=\"#总结\" title=\"Direct link to heading\">​</a></h2><p>可以看到基于内存和磁盘之上去实现同一个功能，除了不同类型存储介质本身的速度差异之外，问题和挑战是完全不一样的。对于磁盘类型的服务，需要不断去优化随机读写和空间放大问题，除了对于软件本身熟悉之外，同样需要了解硬件设备。</p><p>另外，Kvrocks 作为基于磁盘 KV 之上兼容 Redis 协议存储服务，最经常被问到是跟其他功能类似的服务有什么区别？简单来说，最大的差异在于不同项目维护者在功能设计上的差异，不同设计会让功能看似一样的服务在表现上完全不一样。所以，最好的方式就是通过代码去了解项目的设计和问题。</p><p>欢迎大家扫码关注 <strong>「Kvrocks 官方社区」</strong>公众号并回复: <strong>进群</strong>，来加入我们的微信群！</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/qrcode.jpg\" alt=\"image\" class=\"img_ev3q\"></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"references\">References<a class=\"hash-link\" href=\"#references\" title=\"Direct link to heading\">​</a></h2><p>[1][https://rocksdb.org/blog/2015/07/23/dynamic-level.html]<!-- -->(<a href=\"https://rocksdb.org/blog/2015/07/23/dynamic-level.html\" target=\"_blank\" rel=\"noopener noreferrer\">https://rocksdb.org/blog/2015/07/23/dynamic-level.html</a>)</p><p>[2][https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf]<!-- -->(<a href=\"https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf</a>)</p><p>[3][https://github.com/KvrocksLabs/kvrocks]<!-- -->(<a href=\"https://github.com/KvrocksLabs/kvrocks\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/KvrocksLabs/kvrocks</a>)</p><p>[4][https://github.com/facebook/rocksdb]<!-- -->(<a href=\"https://github.com/facebook/rocksdb\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/facebook/rocksdb</a>)</p><p>[5][https://github.com/tikv/titan]<!-- -->(<a href=\"https://github.com/tikv/titan\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/tikv/titan</a>)</p>",
            "url": "https://hulkdev.com/posts-how-to-impl-bitmap-on-disk-kv",
            "title": "如何基于磁盘 KV 实现 Bitmap",
            "summary": "大部分开发对 Bitmap 应该都不陌生，除了作为 Bloom Filter 实现的存储之外，许多数据库也有提供 Bitmap 类型的索引。对于内存型的存储来说，Bitmap 只是一个特殊类型(bit)的稀疏数组，操作内存不会带来读写放大问题(指的是物理读写的数据量远大于逻辑的数据量), Redis 就是在字符串类型上支持 bit 的相关操作，而对于 Kvrocks 这种基于磁盘 KV 实现的存储则会是比较大挑战，本篇文章主要讨论的其实是「基于磁盘 KV 实现 Bitmap 要如何减少磁盘读写放大」",
            "date_modified": "2021-07-27T00:00:00.000Z",
            "author": {
                "name": "hulk"
            },
            "tags": [
                "Redis",
                "Bitmap",
                "Storage"
            ]
        },
        {
            "id": "intro-opensource-kvrocks",
            "content_html": "<p>Kvrocks 是基于 RocksDB 之上兼容 Redis 协议的 NoSQL 存储服务，设计目标是提供一个低成本以及大容量的 Redis 服务，作为 Redis 在大数据量场景的互补服务，选择兼容 Redis 协议是因为简单易用且业务迁移成本低。目前线上使用的公司包含:  美图、携程、百度以及白山云等，在线上经过两年多大规模实例的验证。</p><p>项目核心功能包含:</p><ul><li>兼容 Redis 协议</li><li>支持主从复制</li><li>支持通过 Namespace 隔离不同业务的数据</li><li>高可用，支持 Redis Sentinel 自动主从切换</li><li>集群模式 (进行中，预计在 7-8 月份完成)</li></ul><p>GitHub地址：<a href=\"https://github.com/bitleak/kvrocks\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/bitleak/kvrocks</a></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"实现方案对比\">实现方案对比<a class=\"hash-link\" href=\"#实现方案对比\" title=\"Direct link to heading\">​</a></h2><p>除了 Kvrocks 之外，社区也有一些类似的基于磁盘存储兼容 Redis 协议的开源产品，从存储设计来看可以分为几类:</p><ol><li>基于磁盘 KV 存储引擎(比如 RocksDB/LevelDB) 实现 Redis 协议</li><li>基于 Redis 存储之上将冷数据交换到磁盘(类似早期 Redis VM 的方案)</li><li>基于分布式 KV(比如 TiKV) 实现 Redis 协议代理，本地不做存储</li></ol><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/disk-kv-compare.png\" alt=\"image\" class=\"img_ev3q\"></p><p><strong>方案 1:</strong> 是基于磁盘 KV 之上兼容 Redis 协议，绝大多数的本地磁盘 KV 只提供最简单的 Get/Set/Delete 方法，对于 Hash/Set/ZSet/List/Bitmap 等数据结构需要基于磁盘 KV 之上去实现。优点是可以规避下面方案 2 里提到的大 Key 问题，缺点是实现工作量大一些。</p><p><strong>方案 2:</strong> 基于 Redis 把冷数据交换磁盘是以 Key 作为最小单元，在大 Key 的场景下会有比较大的挑战。交换大 Key 到磁盘会有严重读写放大，如果是读可能会导致整个服务不可用，所以这种实现只能限制 Value 大小，优点在于实现简单且可按照 Key 维度来做冷热数据分离。</p><p><strong>方案 3:</strong> 是基于分布式 KV 之上实现 Redis 协议，最大的区别在于所以的操作都是通过网络。这种实现方式最大优点是只需要实现 Redis 协议的部分，服务本身是无状态的，无须考虑数据复制以及扩展性的问题。缺点也比较明显，因为所有的命令都是通过网络 IO，对于非 String 类型的读写一般都是需要多次网络 IO 且需要通过事务来保证原子，从而在延时和性能上都会比方案 1 和 2 差不少。</p><p><strong>Kvrocks 设计的初衷是作为 Redis 场景的互补，低成本、低延时和高吞吐是最重要的设计目标。</strong>基于 Redis 实现冷热数据交换的方式在大 Key 场景下可能导致不可用，从而需要限制单个 Key 大小，这个对于我们想实现一个通用的 NoSQL 存储服务是无法接受的。而对于方案 3 这种远程存储的方案，延时和吞吐一定是无法满足预期，所以我们最终选择的方案 1 这种基于磁盘 KV 之上实现 Redis 协议以及复制。除了数据存储方式之外， Kvrocks 并没有淘汰策略，所以一般是作为存储服务而不是缓存，当写入的数据量达到实例最大容量或者磁盘容量不足时会写入失败。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"性能\">性能<a class=\"hash-link\" href=\"#性能\" title=\"Direct link to heading\">​</a></h2><p>需要注意的是以下提供的性能数据是基于特定的配置进行压测，不同配置会有比较大的差异。压测的硬件以及 Kvrocks 配置说明可参考: <a href=\"https://github.com/bitleak/kvrocks#performance\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/bitleak/kvrocks#performance</a></p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/benchmark.png\" alt=\"image\" class=\"img_ev3q\"></p><p>这里提供性能数据只是为了给读者更加直观了解 Kvrocks 的性能情况，大部分命令由于可多线程并行执行，从 QPS 的维度来看会比 Redis 更好一些，但延时肯定会比 Redis 略差。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"功能\">功能<a class=\"hash-link\" href=\"#功能\" title=\"Direct link to heading\">​</a></h2><p>Kvrocks 支持 Redis String、 List、 Hash、Set、 ZSet 五种基本数据类型， 以及 Bitmap、Geo 和自定义的 Sorted Int 类型。当前支持大多数命令，也支持 Pub/Sub、事务以及备份等功能。</p><p>具体可参考：<a href=\"https://github.com/bitleak/kvrocks/blob/unstable/docs/support-commands.md\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/bitleak/kvrocks/blob/unstable/docs/support-commands.md</a></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"快速体验\">快速体验<a class=\"hash-link\" href=\"#快速体验\" title=\"Direct link to heading\">​</a></h2><p>可以使用 Docker 的方式来启动 Kvrocks:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">docker</span><span class=\"token plain\"> run -it -p </span><span class=\"token number\">6666</span><span class=\"token plain\">:6666 bitleak/kvrocks</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>接着可以跟使用 Redis 一样使用:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">➜  ~ redis-cli -p </span><span class=\"token number\">6666</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:666</span><span class=\"token operator file-descriptor important\">6</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">set</span><span class=\"token plain\"> foo bar</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">OK</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:666</span><span class=\"token operator file-descriptor important\">6</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> get foo</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"bar\"</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"整体设计\">整体设计<a class=\"hash-link\" href=\"#整体设计\" title=\"Direct link to heading\">​</a></h2><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/kvrocks-arch.png\" alt=\"image\" class=\"img_ev3q\"></p><p>Kvrocks 主要有两类线程:</p><ul><li>Worker 线程，主要负责收发请求，解析 Redis 协议以及请求转为 RocksDB 的读写</li><li>后台线程，目前包含一下几种后台线程:<ul><li>Cron 线程，负责定期任务，比如自动根据写入 KV 大小调整 Block Size、清理 Backup 等</li><li>Compaction Checker 线程，如果开启了增量 Compaction 检查机制，那么会定时检查需要 Compaction 的 SST 文件</li><li>Task Runner 线程，负责异步的任务执行，比如后台全量 Compaction，Key/Value 数量扫描</li><li>主从复制线程，每个 slave 都会对应一个线程用来做增量同步</li></ul></li></ul><p><strong>下面以 Hash 为例来说明 Kvrocks 是如何将复杂的数据结构转为 RocksDB 对应的 KV?</strong></p><p>最简单的方式是将 Hash 所有的字段进行序列化之后写到同一个 Key 里面，每次修改都需要将整个 Value 读出来之后修改再写入，当 Value 比较大时会导致严重的读写方法问题。所以我们参考 <code>blackwidow</code> 的实现，把 Hash 拆分成 Metadata 和 Subkey 两个部分，Hash 里面的每个字段都是独立的 KV，再使用 Metadata 来找到这些 Subkey:</p><div class=\"language-jsx codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-jsx codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">-</span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">-</span><span class=\"token operator\">+</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token parameter\">key</span><span class=\"token plain\"> </span><span class=\"token arrow operator\">=&gt;</span><span class=\"token plain\">  </span><span class=\"token operator\">|</span><span class=\"token plain\">  flags   </span><span class=\"token operator\">|</span><span class=\"token plain\">  expire    </span><span class=\"token operator\">|</span><span class=\"token plain\">  version  </span><span class=\"token operator\">|</span><span class=\"token plain\">  size     </span><span class=\"token operator\">|</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">|</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">1byte</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">  </span><span class=\"token operator\">|</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">4byte</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">    </span><span class=\"token operator\">|</span><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">8byte</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">  </span><span class=\"token operator\">|</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">8byte</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">   </span><span class=\"token operator\">|</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">-</span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">-</span><span class=\"token operator\">+</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">hash metadata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                     </span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">-</span><span class=\"token operator\">+</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">key</span><span class=\"token operator\">|</span><span class=\"token plain\">version</span><span class=\"token operator\">|</span><span class=\"token parameter\">field</span><span class=\"token plain\"> </span><span class=\"token arrow operator\">=&gt;</span><span class=\"token plain\"> </span><span class=\"token operator\">|</span><span class=\"token plain\">     value     </span><span class=\"token operator\">|</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                     </span><span class=\"token operator\">+</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">--</span><span class=\"token operator\">-</span><span class=\"token operator\">+</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">              </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">hash subkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>里面的 flags 目前是来标识当前 Value 的类型，比如是 Hash/Set/List 等。expire 是 Key 的过期时间，size 是这个 Key 包含的字段数量，这两个比较好理解。version 是在 Key 创建时自动生成的单调递增的 id，每个 Subkey 前缀会关联上 version。当 Metadata 本删除时，这个 version 就无法被找到，也意味着关联这个 version 的全部 Subkey 也无法找到，从而实现快速删除，而这些无法找到的 Subkey 会在后台 Compact 的时候进行回收。</p><p>以 HSET 命令为例，伪代码如下:</p><div class=\"language-jsx codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-jsx codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">HSET</span><span class=\"token plain\"> key</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> field</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token literal-property property\">value</span><span class=\"token operator\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 先根据 hash key 找到对应的 metadata 并判断是否过期</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果不存在或者过期则创建一个新的 metadata</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  metadata </span><span class=\"token operator\">=</span><span class=\"token plain\"> rocksdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token method function property-access maybe-class-name\" style=\"color:rgb(80, 250, 123)\">Get</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">key</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token keyword control-flow\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> metadata </span><span class=\"token operator\">==</span><span class=\"token plain\"> nil </span><span class=\"token operator\">||</span><span class=\"token plain\"> metadata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token method function property-access maybe-class-name\" style=\"color:rgb(80, 250, 123)\">Expired</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">     metadata </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">createNewMetadata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 根据 metadata 里面的版本组成 subkey</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  subkey </span><span class=\"token operator\">=</span><span class=\"token plain\"> key </span><span class=\"token operator\">+</span><span class=\"token plain\"> metadata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token property-access\">version</span><span class=\"token operator\">+</span><span class=\"token plain\">field</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword control-flow\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> rocksdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token method function property-access maybe-class-name\" style=\"color:rgb(80, 250, 123)\">Get</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">subkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> nil </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">     metadata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token property-access\">size</span><span class=\"token plain\"> </span><span class=\"token operator\">+=</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 写入 subkey 以及更新 metadata</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    rocksdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token known-class-name class-name\">Set</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">subkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> value</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  rocksdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token known-class-name class-name\">Set</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">key</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> metadata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>更多的数据结构设计可以参考: <a href=\"https://github.com/bitleak/kvrocks/blob/unstable/docs/metadata-design.md\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/bitleak/kvrocks/blob/unstable/docs/metadata-design.md</a></p><p><strong>Kvrocks 是如何进行数据复制?</strong></p><p>Kvocks 的定位是作为大数据量场景下 Redis 的替代方案，提供与 Redis 一样的数据最终一致性保证，采用了类似 Redis 的主从异步复制模型。考虑到需要应对更多的业务场景，后续会支持半同步复制模型。在实现上，全量复制利用 RocksDB 的 CheckPoint 特性，<code>增量复制采用直接发送 WAL 的方式，从库接收到WAL直接操作后端引擎</code>，相比于 Binlog 复制方式（回放从客户端接收到的命令），省去了命令解析和处理的开销，复制速度大幅提升，这样也就解决了其它采用 Binlog 复制方式的存储服务所存在的复制延迟问题。</p><p><strong>Kvrocks 是如何实现分布式集群？</strong></p><p>业界常用Redis集群方案主要有两类：类似 Codis 中心化的集群架构和社区 Redis Cluster 去中心化的集群架构。Kvrocks 集群方案选择了类似 Codis 中心化的架构，集群元数据存储在配置中心，但不依赖代理层，配置中心为存储节点推送元数据，<code>对外提供 Redis Cluster 集群协议支持，对于使用 Redis Cluster SDK 或者 Proxy 的用户不需要做任何修改</code>。同时也可以避免类似Redis Cluster 受限于 Gossip 通信的开销而导致集群规模不能太大的问题。另外，单机版的 Kvrocks 和 Redis 一样可以直接支持 Twmeproxy，通过Sentinel实现高可用，对于 Codis 通过简单的适配也能够比较快的支持。目前集群方案处在测试阶段，预计7月份发布，待正式发布后会给大家详细介绍，这里不过多展开。</p><p><strong>对于分布式集群来说，弹性伸缩的能力是必不可少的，Kvrocks 是如何实现弹性伸缩的？</strong></p><p>整个扩缩容拆分为迁移全量数据、迁移增量数据、变更拓扑三个阶段。迁移全量数据利用 RocksDB的 Snapshot 特性，生成 Snapshot 迭代数据发送到目标节点。同时，为了加快迭代效率数据编码上Key 增加 SlotID 前缀。迁移增量数据阶段直接发送 WAL。当待迁移的增量 WAL 小于设定的阈值则开始阻写，等发送完剩余的 WAL 切换拓扑之后解除阻写，这个过程通常是毫秒级的。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"优化点\">优化点<a class=\"hash-link\" href=\"#优化点\" title=\"Direct link to heading\">​</a></h2><p>相比内存型服务来说，最常见的问题是磁盘的吞吐和延时带来的毛刺点问题。除了通过慢日志命令来确认是否有慢请求产生之外，还提供了 perflog 命令用来定位 RocksDB 访问慢的问题，使用方式如下:</p><div class=\"language-jsx codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-jsx codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"># 第一条命令设定只对 </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">SET</span><span class=\"token plain\"> 命令收集 profiling 日志</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"># 第二条命令设定随机采样的比例</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"># 第三条命令设定超过多长时间的命令才写到 perf </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">日志里面</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">如果是为了验证功能可以设置为 </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token number\">.0</span><span class=\"token number\">.1</span><span class=\"token operator\">:</span><span class=\"token number\">6666</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> config </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">set</span><span class=\"token plain\"> profiling</span><span class=\"token operator\">-</span><span class=\"token plain\">sample</span><span class=\"token operator\">-</span><span class=\"token plain\">commands </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">set</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">OK</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token number\">.0</span><span class=\"token number\">.1</span><span class=\"token operator\">:</span><span class=\"token number\">6666</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> config </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">set</span><span class=\"token plain\"> profiling</span><span class=\"token operator\">-</span><span class=\"token plain\">sample</span><span class=\"token operator\">-</span><span class=\"token plain\">ratio </span><span class=\"token number\">100</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">OK</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token number\">.0</span><span class=\"token number\">.1</span><span class=\"token operator\">:</span><span class=\"token number\">6666</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> config </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">set</span><span class=\"token plain\"> profiling</span><span class=\"token operator\">-</span><span class=\"token plain\">sample</span><span class=\"token operator\">-</span><span class=\"token plain\">record</span><span class=\"token operator\">-</span><span class=\"token plain\">threshold</span><span class=\"token operator\">-</span><span class=\"token plain\">ms </span><span class=\"token number\">1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">OK</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"># 执行 </span><span class=\"token known-class-name class-name\">Set</span><span class=\"token plain\"> 命令，在去看 perflog 命令就可以看到对应的耗时点</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token number\">.0</span><span class=\"token number\">.1</span><span class=\"token operator\">:</span><span class=\"token number\">6666</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">set</span><span class=\"token plain\"> a </span><span class=\"token number\">1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">OK</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">127.0</span><span class=\"token number\">.0</span><span class=\"token number\">.1</span><span class=\"token operator\">:</span><span class=\"token number\">6666</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> perflog </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">get</span><span class=\"token plain\"> </span><span class=\"token number\">2</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">integer</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token number\">2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">integer</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token number\">1623123739</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token number\">3</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"set\"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token number\">4</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">integer</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token number\">411</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token number\">5</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"user_key_comparison_count = 7, write_wal_time = 122300, write_pre_and_post_process_time = 91867, write_memtable_time = 47349, write_scheduling_flushes_compactions_time = 13028\"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token number\">6</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"thread_pool_id = 4, bytes_written = 45, write_nanos = 46030, prepare_write_nanos = 21605\"</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>之前通过这种方式发现了一些 RocksDB 参数配置不合理的问题，比如之前 SST 文件大小默认是 256MiB，当业务的 KV 比较小的时候可能会导致一个 SST 文件里面可能有百万级别的 KV，从而导致 index 数据块过大(几十 MiB)，每次从磁盘读取数据需要耗费几十 ms。但线上不同业务的 KV 大小可能会差异比较大，通过 DBA 手动调整的方式肯定不合理，所以有了根据写入 KV 大小在线自动调整 SST 和 Block Size 的功能，具体描述见: <a href=\"https://github.com/bitleak/kvrocks/issues/118\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/bitleak/kvrocks/issues/118</a></p><p>另外一个就是 RocksDB 的全量 Compact 导致磁盘 IO 从而造成业务访问的毛刺点问题，之前策略是每天凌晨低峰时段进行一次，过于频繁会导致访问毛刺点，频率过低会导致磁盘空间回收不及时。所以增加另外一种部分 Compact 策略，优先对那些比较老以及无效 KV 比较多的 SST进行 Compact。开启只需要在配置文件里面增加一行，那么则会在凌晨 0 到 7 点之间去检查这些 SST 文件并做 Compact</p><div class=\"language-jsx codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-jsx codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">compaction</span><span class=\"token operator\">-</span><span class=\"token plain\">checker</span><span class=\"token operator\">-</span><span class=\"token plain\">range </span><span class=\"token number\">0</span><span class=\"token operator\">-</span><span class=\"token number\">7</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"总结\">总结<a class=\"hash-link\" href=\"#总结\" title=\"Direct link to heading\">​</a></h2><p>在设计和实现上，Kvrocks 更注重简洁高效、稳定可靠、易于使用和问题定位。目前 Kvrocks 已经在线上大规模运行两年之久，基本功能已充分验证，大家可以放心使用。如遇到问题，大家可以在微信群，Slack(见 GitHub README)，Issue 上反馈和交流，我们也欢迎提 PR 来一起完善 Kvrocks。</p><p>在社区维护上，希望可以有更加开放的交流氛围，而不只是把代码放到 GitHub 的开源。不管是功能设计还是代码开发，都会尽量把相关细节都在 GitHub 里面公开去讨论。</p><p>另外，2.0  版本预计在 7-8 月份会完成全部功能的开发，大家可以期待一下（具体进展见:  <a href=\"https://github.com/bitleak/kvrocks/projects/1\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/bitleak/kvrocks/projects/</a></p><p>欢迎大家扫码关注 <strong>「Kvrocks 官方社区」</strong>公众号并回复: <strong>进群</strong>，来加入我们的微信群！</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/qrcode.jpg\" alt=\"image\" class=\"img_ev3q\"></p>",
            "url": "https://hulkdev.com/intro-opensource-kvrocks",
            "title": "Kvrocks 一款开源的企业级磁盘KV存储服务",
            "summary": "Kvrocks 是基于 RocksDB 之上兼容 Redis 协议的 NoSQL 存储服务，设计目标是提供一个低成本以及大容量的 Redis 服务，作为 Redis 在大数据量场景的互补服务，选择兼容 Redis 协议是因为简单易用且业务迁移成本低。目前线上使用的公司包含:  美图、携程、百度以及白山云等，在线上经过两年多大规模实例的验证。",
            "date_modified": "2021-06-16T00:00:00.000Z",
            "tags": [
                "Redis",
                "SSD",
                "Storage"
            ]
        },
        {
            "id": "posts-holes-in-php-memcached",
            "content_html": "<p>美图 PHP 业务团队在使用 php-memcached 扩展陆陆续续遇到一些隐蔽的 ”坑”，而这些坑在 php-memcached 也是比较容易踩到。其中有如 <code>TCP_NODELAY</code> 这类常见的坑，也有一些 php-memcached 本身设计带来的问题。这里分享出来希望可以给遇到类似问题或者正在坑里的同学带来一些帮助。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1--tcp_nodelay-导致-40ms-延时\">1.  TCP_NODELAY 导致 40ms 延时<a class=\"hash-link\" href=\"#1--tcp_nodelay-导致-40ms-延时\" title=\"Direct link to heading\">​</a></h3><p>先说问题，php-memcached 在开启二进制协议和未开启 <code>TCP_NODELAY</code>(默认关闭)时会导致 Get 命令在 cache miss 场景下的内网延时会到达 40ms 左右。这个是由于 <code>TCP_NODELAY</code> 和 <code>TCP Delay ACK</code> 的综合效果带来的，相信大家应该多多少少都有遇到类似的问题，下面会具体解释原因。</p><p>重现代码：</p><div class=\"language-php codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-php codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">&lt;?php</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$memc = new Memcached();</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$memc-&gt;addServer(\"127.0.0.1\", 11211);</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$memc-&gt;setOption(Memcached::OPT_BINARY_PROTOCOL, true);</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">for ($i = 0; $i &lt; 2; $i++) {</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    $start = microtime();</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    $memc-&gt;get(\"foo\"); // key `foo` wasn't exists</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    var_dump(microtime()-$start);</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>代码逻辑开启了二进制协议并两次 <code>Get</code> 不存在的 key，输出如下:</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">float(0.00018799999999997)</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">float(0.039426)</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>第一次的请求的延时是 0.18 ms 而第二次则是 40ms，这里有两个问题:</p><ol><li>为什么第二个请求需要 40ms？</li><li>第一次请求为什么没有问题？</li></ol><p>我们从数据包的角度来分析第一个 40 ms 的问题:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/holes-in-php-memcached-tcpdump.jpg\" alt=\"image\" class=\"img_ev3q\"></p><p>可以看到 GET 命令在 php-memcached 里面的首先并不是直接发送 GET 命令， 而是由 GetKQ 和 NOOP 两个命令组成。GetKQ 和 GET 的区别是在 <code>key</code> 不存在时不会返回数据，所以客户端依赖的 noop 请求来确定 GetKQ 是否是 cache miss，这是因为 memcached 处理请求在连接内是串行的，如果 noop 请求返回了而 GetKQ 没有返回则说明是 key 不存在。</p><p>另外，上面的第一条和第二条数据包可以看到由于 <code>TCP Delay ACK</code> 机制导致了 <code>GetKQ</code> 请求数据包 ACK 被延时了 40ms，而刚好 <code>TCP_NODELAY</code> 默认不开启，触发了 negla 算法（因为未发送数据包未满 MSS 大小）从而导致 <code>NOOP</code> 命令延时到 ACK 回来，最终整个请求耗时到达 40 ms。</p><p>至于为什么要使用 GetKQ 和 NOOP 组合请求来替代 GET 请求？主要是因为这种方式在大量 Get Miss 的场景下可以节省很多没用的 Not Found 返回，这个在 <a href=\"https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#get-get-quietly-get-key-get-key-quietly\" target=\"_blank\" rel=\"noopener noreferrer\">Memcached Binary Wiki</a> 里面有提到。 如果对于 TCP_NODELAY 和 TCP Delay ACK 不熟悉的同学可以参考:</p><ul><li><p><a href=\"https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment\" target=\"_blank\" rel=\"noopener noreferrer\">wikipedia: TCP delayed acknowledgment</a></p></li><li><p><a href=\"https://en.wikipedia.org/wiki/Nagle's_algorithm\" target=\"_blank\" rel=\"noopener noreferrer\">wikipedia: Nagle's algorithm</a></p></li></ul><p>接着可以回过头来看第二个问题，为什么第一个 Get 请求延时是正常的呢？这个需要从 TCP ack 逻辑来看：</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 代码文件: net/ipv4/tcp_input.c </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">static</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">tcp_event_data_recv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">sock</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">sk</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">sk_buff</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">skb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 省略逻辑不相关代码</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">icsk</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">icsk_ack</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">ato</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">         * 第一次收到数据包则马上返回 ACK，并把 ato 设置为 TCP_ATO_MIN,</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">         * TCP_ATO_MIN 默认最小值是 40ms</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">         */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">tcp_incr_quickack</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sk</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        icsk</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">icsk_ack</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">ato </span><span class=\"token operator\">=</span><span class=\"token plain\"> TCP_ATO_MIN</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 重新计算 ato</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>TCP 协议栈在第一个数据包到来时不触发 delay ACK 机制的主要原因是因为，TCP 在刚建立完连接之后会进入 <code>slow start</code> 阶段，<code>slow start</code> 允许发送未 ACK 的窗口大小取决于之前的 ACK 数量，所以为了避免 slow start 阶段过慢，第一个数据包不触发 delay ack 逻辑。</p><blockquote><p>问题修复也比较简单，如果开启二进制协议的时候，需要手动把 <code>TCP_NODELAY</code> 也开起来。</p></blockquote><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-不合理的默认重试策略\">2. 不合理的默认重试策略<a class=\"hash-link\" href=\"#2-不合理的默认重试策略\" title=\"Direct link to heading\">​</a></h3><p>在一次配合业务定位 php memcached 读写超时不符合预期发现了 SET 命令在超时的场景下会自动进行两次重试，后面通过代码发现 <code>store_retry_count</code> 默认值为 2， 也就是 SET/ SET Multi/ INCR/ DECR 这几个命令在失败的场景会自动重试 2 次，具体代码见: <code>php_memcached.c</code> 的 <code>s_memc_write_zval</code> 函数实现，这里不再贴代码。</p><p>对于一个通用的基础库来说，这种隐式的重试策略在类似 <code>incr/decr</code> 这类非幂等的操作可能会出现预期之外的不一致问题，应该留给用户显示去设置重试。这块已经提了 PR: <a href=\"https://github.com/php-memcached-dev/php-memcached/pull/452\" target=\"_blank\" rel=\"noopener noreferrer\">#452</a> ，应该在下一个 release 版本会合并。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-错误的代码姿势导致长连接失效\">3. 错误的代码姿势导致长连接失效<a class=\"hash-link\" href=\"#3-错误的代码姿势导致长连接失效\" title=\"Direct link to heading\">​</a></h3><p>这个问题也是相当简单但隐蔽，php-memcached 的依赖库 <code>libmemcached</code> 在反复去设置一些 option（比如设置 <code>OPT_BINARY_PROTOCOL</code>），即使在 option 没有修改的场景下也会导致把老连接关闭重连。理论上，如果 option 没有的话是没有必要去关闭老连接，应该从 libmemcached 修复这个问题。不过，libmemcached 迭代比较慢，从兼容性的角度来说，在 php-memcached 里面修复也没什么问题，具体 PR: <a href=\"https://github.com/php-memcached-dev/php-memcached/pull/451\" target=\"_blank\" rel=\"noopener noreferrer\">#451</a></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"总结\">总结<a class=\"hash-link\" href=\"#总结\" title=\"Direct link to heading\">​</a></h2><p>作为基础库来说，出现第二点提到默认重试策略这个坑其实是不太应该的，基础库可以实现重试功能，但默认行为是什么应该考虑请求。<code>TCP_NODELAY</code> 这个属于常见问题(其实也是 <code>libmemcached</code> 实现的问题)，之前也有人反馈过，maintainer 自己也开过相关 PR 修复但最终不知道什么原因没有合并。第三点也是 <code>libmemcached</code> 的问题，不过也需要在 php-memcached 做一些简单的兼容性修复。虽然 <code>php-memcached</code> 有不少问题，但整体属于瑕不掩瑜。</p>",
            "url": "https://hulkdev.com/posts-holes-in-php-memcached",
            "title": "php-memcached 的一些坑",
            "summary": "美图 PHP 业务团队在使用 php-memcached 扩展陆陆续续遇到一些隐蔽的 ”坑”，而这些坑在 php-memcached 也是比较容易踩到。其中有如 TCP_NODELAY 这类常见的坑，也有一些 php-memcached 本身设计带来的问题。这里分享出来希望可以给遇到类似问题或者正在坑里的同学带来一些帮助。",
            "date_modified": "2020-03-17T00:00:00.000Z",
            "tags": [
                "tcp",
                "linux",
                "php",
                "memcached"
            ]
        },
        {
            "id": "posts-tcpkit-improvement",
            "content_html": "<p><code>tcpkit</code> 是支持用 lua 脚本分析网络数据包的工具，附带简单协议解析(Redis/Memcached)和延时统计。最早开发 <code>tcpkit</code> 主要原因是经常需要通过网络包来分析资源慢请求问题，在数据包量比较大的场景下人肉分析会浪费比较时间，所以希望可以通过编码的方式来分析这类问题。从开发至今已经帮助我们团队以及美图 DBA 定位无数的线上问题，之前甚至通过 <code>tcpkit</code> 找到了 BCM 网卡驱动到 kernel 偶发产生几百毫秒延时问题。</p><p>Github 地址: <a href=\"https://github.com/git-hulk/tcpkit\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/git-hulk/tcpkit</a></p><p>第一个版本存在的主要问题是过于参数复杂导致使用很不友好，使用者明确告诉 <code>tcpkit</code> 当前是跑在客户端还是资源端，最近想到通过 <code>syn</code> 包自动分析的方式，周末花了两天时间重新实现了一版。这个版本另外一个改进点是 <code>tcpkit</code> 参数和 <code>tcpdump</code> 基本一致，可以减少上手的学习成本，同时也支持了分析多个端口的功能。</p><p>下面主要是 <code>tcpkit</code> 使用到几种场景:</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"case-1-如何分析-redismemcached-延时\">case 1. 如何分析 Redis/Memcached 延时<a class=\"hash-link\" href=\"#case-1-如何分析-redismemcached-延时\" title=\"Direct link to heading\">​</a></h3><p>通过网络包来分析资源慢请求一般只会用在偶发且没有规律的场景，如果是大面积慢请求应该先通过资源监控来快速定位是否到达瓶颈。对于只有个别业务机器出现慢请求的场景则优先去查看业务端问题(比如对应时刻是否有长时间 GC，php/python 这类引用计数的就不用看了)。对于无规律偶发的场景，可以把 <code>tcpkit</code>  同时跑在客户端和业务端进行抓包分析，在客户端执行如下命令: </p><p><code>sudo tcpkit -i any tcp port 6379 -p redis</code> </p><p>那么 tcpkit 会监听端口 <code>6379</code> 的数据包并使用 Redis 协议进行解析:</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">2020-03-08 19:23:06.258761 192.168.0.1:51137 =&gt; 192.168.0.2:6379 | 1.102 ms | set foo bared</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>意思是从客户端发出 <code>SET</code> 命令数据包到 Redis 返回响应总共耗时 <code>1.102</code> ms， 这个延时包含了内网 RTT。</p><p>如果是在资源端(如 <code>Redis</code>)  也是执行上面的命令，会输出类似如下的数据包:</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">2020-03-08 19:23:06.258761 127.0.0.1:51137 =&gt; 127.0.0.1:6379 | 0.059 ms | set foo bared</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>意思是从收到 <code>SET</code> 命令数据包到处理完响应总共耗时 <code>0.059</code> ms， 这个延时是不包含内网 RTT，所以推断内网的延时大概是 1ms 左右。</p><p>这个例子里面是正常的请求，对于慢请求通过对比客户端的延时和资源处理时间，大部分都可以推断慢请求到底是业务端、服务端还是中间链路问题。另外，在定位问题的时候一般只希望输出耗时比较长的请求，这个可以通过加上 <code>-t</code> 参数指定打印延时超过这个阀值的请求。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"case-2--作为延时统计监控工具\">case 2.  作为延时统计监控工具<a class=\"hash-link\" href=\"#case-2--作为延时统计监控工具\" title=\"Direct link to heading\">​</a></h3><p>除了之外实时分析工具之外，<code>tcpkit</code> 还可以作为常驻进程和资源部署在一起，作为资源延时监控的手段，输出资源的延时分布情(默认监听 <code>33333</code> 端口)，输出是 json 格式:</p><div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">➜  ~ telnet </span><span class=\"token number\">127.0</span><span class=\"token plain\">.</span><span class=\"token number\">0.1</span><span class=\"token plain\"> </span><span class=\"token number\">33333</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Trying </span><span class=\"token number\">127.0</span><span class=\"token plain\">.</span><span class=\"token number\">0.1</span><span class=\"token plain\">...</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Connected to localhost.</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Escape character is '^</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\">'.</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token property\">\"127.0.0.1:6379\"</span><span class=\"token operator\">:</span><span class=\"token plain\">   </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token property\">\"requests\"</span><span class=\"token operator\">:</span><span class=\"token plain\"> </span><span class=\"token number\">1700</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token property\">\"request_bytes\"</span><span class=\"token operator\">:</span><span class=\"token plain\">    </span><span class=\"token number\">184100</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token property\">\"responses\"</span><span class=\"token operator\">:</span><span class=\"token plain\">    </span><span class=\"token number\">1700</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token property\">\"response_bytes\"</span><span class=\"token operator\">:</span><span class=\"token plain\">   </span><span class=\"token number\">1413764</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token property\">\"latency\"</span><span class=\"token operator\">:</span><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token property\">\"&lt;0.1ms\"</span><span class=\"token operator\">:</span><span class=\"token plain\">   </span><span class=\"token number\">326</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token property\">\"0.1ms~0.2ms\"</span><span class=\"token operator\">:</span><span class=\"token plain\">  </span><span class=\"token number\">371</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token property\">\"0.2~0.5ms\"</span><span class=\"token operator\">:</span><span class=\"token plain\">    </span><span class=\"token number\">589</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token property\">\"0.5ms~1ms\"</span><span class=\"token operator\">:</span><span class=\"token plain\">    </span><span class=\"token number\">291</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token property\">\"1ms~5ms\"</span><span class=\"token operator\">:</span><span class=\"token plain\">  </span><span class=\"token number\">123</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"case-3-自定义分析脚本\">case 3. 自定义分析脚本<a class=\"hash-link\" href=\"#case-3-自定义分析脚本\" title=\"Direct link to heading\">​</a></h3><p>除此之外，对于一些个性化的分析需求可以通过定义 <code>Lua</code> 脚本来分析，<code>-S</code> 用来指定 lua 脚本文件的位置，tcpkit 在数据包产生时会回调脚本文件里面的 <code>process</code> 函数， 例子见: <a href=\"https://github.com/git-hulk/tcpkit/blob/master/scripts/example.lua\" target=\"_blank\" rel=\"noopener noreferrer\">scripts/example.lua</a></p><div class=\"language-lua codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-lua codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">function process(packet)</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    if packet.size ~= 0 then -- skip the syn and ack</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        local time_str = os.date('%Y-%m-%d %H:%M:%S', packet.tv_sec)..\".\"..packet.tv_usec</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        print(string.format(\"%s %s:%d=&gt;%s:%d %s %u %u %d %u %s\",</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            time_str,</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.sip, -- source ip</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.sport, -- source port</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.dip, -- destination ip</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.dport, -- destination port</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            type, -- request or response packet</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.seq, -- sequence number</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.ack, -- ack number</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.flags, -- flags, e.g. syn|ack|psh..</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.size, -- payload size</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            packet.payload -- payload</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        ))</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    end</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">end</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>再比如我们想看看 TCP 建连耗时以及是否有 <code>syn</code> 包重传，几十行 Lua 脚本就搞定了，见示例代码: <a href=\"https://github.com/git-hulk/tcpkit/blob/master/scripts/tcp-connect.lua\" target=\"_blank\" rel=\"noopener noreferrer\">scripts/tcp-connect.lua</a></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"最后\">最后<a class=\"hash-link\" href=\"#最后\" title=\"Direct link to heading\">​</a></h2><p>tcpkit 可以做的还有很多，比如默认支持更多协议如 gRPC, memcached 二进制协议，kafka 协议等等。因为作为 <code>side project</code> ，能分配的精力和时间有限会推进慢一些，欢迎大家多多使用和返回，更加欢迎 PR 或者建设性的意见。</p><p>Github 地址: <a href=\"https://github.com/git-hulk/tcpkit\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/git-hulk/tcpkit</a></p>",
            "url": "https://hulkdev.com/posts-tcpkit-improvement",
            "title": "tcpkit 一些改进",
            "summary": "tcpkit 是支持用 lua 脚本分析网络数据包的工具，附带简单协议解析(Redis/Memcached)和延时统计。最早开发 tcpkit 主要原因是经常需要通过网络包来分析资源慢请求问题，在数据包量比较大的场景下人肉分析会浪费比较时间，所以希望可以通过编码的方式来分析这类问题。从开发至今已经帮助我们团队以及美图 DBA 定位无数的线上问题，之前甚至通过 tcpkit 找到了 BCM 网卡驱动到 kernel 偶发产生几百毫秒延时问题。",
            "date_modified": "2020-03-09T00:00:00.000Z",
            "tags": [
                "TCPKIT",
                "TCP"
            ]
        },
        {
            "id": "posts-meitu-opensource-task-queue",
            "content_html": "<p>lmstfy(Let Me Schedule Task For You) 是美图架构基础服务团队在 2018 年初基于 Redis 实现的简单任务队列(Task Queue)服务，目前在美图多个线上产品使用接近两年的时间。主要提供以下特性:</p><ul><li>任务具备延时、自动重试、优先级以及过期等功能</li><li>通过 HTTP restful API 提供服务</li><li>具备横向扩展能力</li><li>丰富的业务和性能指标</li></ul><p>Github 项目地址: <a href=\"https://github.com/meitu/lmstfy\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/meitu/lmstfy</a></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"使用场景\">使用场景<a class=\"hash-link\" href=\"#使用场景\" title=\"Direct link to heading\">​</a></h2><p>任务队列跟消息队列在使用场景上最大的区别是： 任务之间是没有顺序约束而消息要求顺序(FIFO)，且可能会对任务的状态更新而消息一般只会消费不会更新。 类似 Kafka 利用消息 FIFO 和不需要更新(不需要对消息做索引)的特性来设计消息存储，将消息读写变成磁盘的顺序读写来实现比较好的性能。而任务队列需要能够任务状态进行更新则需要对每个消息进行索引，所以如果把两者放到一起实现则很难实在现功能和性能上兼得。</p><p>我们在以下几种场景会使用任务队列:</p><ol><li>定时任务，如每天早上 8 点开始推送消息，定期删除过期数据等</li><li>任务流，如自动创建 Redis 流程由资源创建，资源配置，DNS 修改等部分组成，使用任务队列可以简化整体的设计和重试流程</li><li>重试任务，典型场景如离线图片处理</li></ol><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"目标与调研\">目标与调研<a class=\"hash-link\" href=\"#目标与调研\" title=\"Direct link to heading\">​</a></h2><p>在自研任务队列之前，我们基于以下几个要求作为约束调研了现有一些开源方案:</p><ul><li>任务支持延时/优先级任务和自动重试</li><li>高可用，服务不能有单点以及保证数据不丢失</li><li>可扩展，主要是容量和性能需要可扩展</li></ul><p>第一种方案是 Redis 作者开源的分布式内存队列 (disque)<!-- -->[https://github.com/antirez/disque]<!-- -->。disque 采用和 Redis Cluster 类似无中心设计，所有节点都可以写入并复制到其他节点。不管是从功能上、设计还是可靠性都是比较好的选择。我们在 2017 年也引入 disque 在部分业务使用过一段时间，后面遇到 bug 在内部修复后想反馈到社区，发现 Redis 作者决定不再维护这个项目(要把 disque 功能作为 redis module 来维护，应该是会伴随 Redis 6 发布)。最终我们也放弃了 disque 方案，将数据迁移到我们自研任务队列服务。</p><p>第二种方案是 2007 年就开源的 (beanstalkd)<!-- -->[https://github.com/beanstalkd/beanstalkd]<!-- -->，现在仍然还是在维护状态。beanstalkd 是类 memcached 协议全内存任务队列，断电或者重启时通过 WAL 文件来恢复数据。但 benstalkd 不支持复制功能，服务存在单点问题且数据可靠性也无法满足。当时也有考虑基于 beanstalkd 去做二次开发，但看完代码之后觉得需要改造的点不只是复制，还有类似内存控制等等，所以没有选择 beanstalkd 二次开发的方案。</p><p>也考虑过类似基于 kafka/rocketmq 等消息队列作为存储的方案，最后从存储设计模型和团队技术栈等原因决定选择基于 redis 作为存储来实现任务队列的功能。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"设计和实现\">设计和实现<a class=\"hash-link\" href=\"#设计和实现\" title=\"Direct link to heading\">​</a></h2><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"基础概念\">基础概念<a class=\"hash-link\" href=\"#基础概念\" title=\"Direct link to heading\">​</a></h3><ul><li>namespace - 用来隔离业务，每个业务是独立的 namespace</li><li>queue - 队列名称，用区分同一业务不同消息类型</li><li>job - 业务定义的业务，主要包含以下几个属性:<ul><li>id: 任务 ID，全局唯一</li><li>delay: 任务延时下发时间， 单位是秒</li><li>tries: 任务最大重试次数，tries = N 表示任务会最多下发 N 次</li><li>ttl(time to live): 任务最长有效期，超过之后任务自动消失</li><li>ttr(time to run): 任务预期执行时间，超过 ttr 则认为任务消费失败，触发任务自动重试</li></ul></li></ul><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"数据存储\">数据存储<a class=\"hash-link\" href=\"#数据存储\" title=\"Direct link to heading\">​</a></h3><p>lmstfy 的 redis 存储由四部分组成:</p><ol><li>timer(sorted set) - 用来实现延迟任务的排序，再由后台线程定期将到期的任务写入到 Ready Queue 里面</li><li>ready queue (list) - 无延时或者已到期任务的队列</li><li>deadletter (list) - 消费失败(重试次数到达上限)的任务，可以手动重新放回队列</li><li>job pool(string) - 存储消息内容的池子</li></ol><p>支持延迟的任务队列本质上是两个数据结构的结合: FIFO 和 sorted set。sorted set 用来实现延时的部分，将任务按照到期时间戳升序存储，然后定期将到期的任务迁移至 FIFO(ready queue)。任务的具体内容只会存储一份在 job pool 里面，其他的像 ready queue，timer，deadletter 只是存储 job id，这样可以节省一些内存空间。</p><p>以下是整体设计:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/lmstfy-arch.png\" alt=\"img\" class=\"img_ev3q\"></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"任务写入\">任务写入<a class=\"hash-link\" href=\"#任务写入\" title=\"Direct link to heading\">​</a></h3><p>任务在写入时会先产生一个 job id，目前 job id (16bytes) 包含写入时间戳、 随机数和延迟秒数， 然后写入 key 为 <code>j:{namespace}/{queue}/{ID}</code> 的任务到任务池 (pool) 里面。之后根据延时时间来决定这个 job id 应该到 ready queue 还是 timer 里面:</p><ul><li>delay = 0，表示不需要延时则直接写到 ready queue(list)</li><li>delay = n(n &gt; 0)，表示需要延时，将延时加上当前系统时间作为绝对时间戳写到 timer(sorted set)</li></ul><p>timer 的实现是利用 zset 根据绝对时间戳进行排序，再由旁路线程定期轮询将到期的任务通过 redis lua script 来将数据原子地转移到 ready queue 里面。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"任务消费\">任务消费<a class=\"hash-link\" href=\"#任务消费\" title=\"Direct link to heading\">​</a></h3><p>之前提到任务在消费失败之后预期能够重试，所以必须知道什么时候可认为任务消费失败？业务在消费时需要携带 ttr(time to run) 参数，用来表示业务预期任务最长执行时间，如果在 ttr 时间内没有收到业务主动回复 ACK 消息则会认为任务失败(类似 tcp 的重传 timer)。</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/lmstfy-consume.png\" alt=\"img\" class=\"img_ev3q\"></p><p>消费时从 ready queue 中 (B)RPOP 出任务的 job id，然后根据 job id 从 pool 中将任务内容发送给消费者。同时对 tries 减一，根据消费的 ttr(time to run) 参数, 将任务放入 timer 中。如果 tries 为零, 在 ttr 时间到期后该 job id 会被放入 dead letter 队列中(表示任务执行失败)。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"同步任务模型\">同步任务模型<a class=\"hash-link\" href=\"#同步任务模型\" title=\"Direct link to heading\">​</a></h3><p>lmstfy 除了可以用来实现异步和延时任务模型之外，因为 namespace 下面的队列是动态创建且 job id 全局唯一，还可以用来实现同步任务模型 (producer 等到任务执行成功之后返回)。大概如下:</p><ol><li>producer 写入任务之后拿到 job id, 然后监听(consume)以 job id 为名的队列</li><li>consumer 消费任务成功后，写回复消息到同样以 job id 为名的队列中</li><li>producer 如果规定时间内能读到回复消息则认为消费成功，等待超时则认为任务失败</li></ol><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"如何实现横向扩展\">如何实现横向扩展<a class=\"hash-link\" href=\"#如何实现横向扩展\" title=\"Direct link to heading\">​</a></h3><p>lmstfy 本身是无状态的服务可以很简单的实现横向扩展，这里的横向扩展主要是存储(目前只支持 Redis)的横向扩展。设计也比较简单，主要通过通过 namespace 对应的 token 路由来实现， 比如我们当前配置两组 Redis 资源: <code>default</code> 和 <code> meipai</code>:</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">[Pool]</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">[Pool.default]</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Addr = \"1.1.1.1:6379\"</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">[Pool.meipai]</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Addr = \"2.2.2.2:6389\"</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>在创建 namespace 时可以指定资源池，token 里面会携带资源池名字作为前缀。比指定美拍资源池，那么 token 类似: <code>meipai:01DT8EZ1N6XT</code> ，后续在处理请求时就可以根据 token 里面携带的资源池名称来进行路由数据。不过这种设计实现队列级别的扩展，如果单队列存储消息量超过 Redis 内存上限则需要其他手段来解决(后面会支持磁盘类型存储)。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"如何使用\">如何使用<a class=\"hash-link\" href=\"#如何使用\" title=\"Direct link to heading\">​</a></h2><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># 创建 namespace 和 token, 注意这里使用管理端口</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$ ./scripts/token-cli -c -n test_ns -p default -D </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"test ns apply by @hulk\"</span><span class=\"token plain\"> </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:7778</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"token\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"01DT9323JACNBQ9JESV80G0000\"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># 写入内容为 value 的任务</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$ </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">curl</span><span class=\"token plain\"> -XPUT -d </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"value\"</span><span class=\"token plain\"> -i </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"http://127.0.0.1:7777/api/test_ns/q1?tries=3&amp;delay=1&amp;token=01DT931XGSPKNB7E2XFKPY3ZPB\"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"job_id\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"01DT9323JACNBQ9JESV80G0000\"</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"msg\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"published\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># 消费任务</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$ </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">curl</span><span class=\"token plain\"> -i </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"http://127.0.0.1:7777/api/test_ns/q1?ttr=30&amp;timeout=3&amp;&amp;token=01DT931XGSPKNB7E2XFKPY3ZPB\"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"data\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"value\"</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"elapsed_ms\"</span><span class=\"token plain\">:272612,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"job_id\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"01DT9323JACNBQ9JESV80G0000\"</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"msg\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"new job\"</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"namespace\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"test_ns\"</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"queue\"</span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">:</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"q1\"</span><span class=\"token plain\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"ttl\"</span><span class=\"token plain\">:86127</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># ACK 任务 id，表示消费成功不再重新下发改任务</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">curl</span><span class=\"token plain\"> -i -XDELETE </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"http://127.0.0.1:7777/api/test_ns/q1/job/01DT9323JACNBQ9JESV80G0000?token=01DT931XGSPKNB7E2XFKPY3ZPB\"</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>更详细 API 说明见项目 <a href=\"https://github.com/meitu/lmstfy/blob/master/README.md\" target=\"_blank\" rel=\"noopener noreferrer\">README</a>，目前我们提供了 PHP/Golang 两种语言 SDK，其他语言可以直接基于 HTTP 库封装即可。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"监控指标\">监控指标<a class=\"hash-link\" href=\"#监控指标\" title=\"Direct link to heading\">​</a></h2><p>lmstfy 任务队列的另外一个设计目标是提供足够多的监控指标，除了作为监控报警之外，也可以为类似 k8s 的 scheduler 提供反馈指标，以当前队列堆积情况指导系统进行动态缩扩容。</p><p>业务指标:</p><ul><li>生产速度</li><li>消费速度</li><li>延迟数量</li><li>堆积数量 (queue size)</li><li>失败数量 (deadletter size)</li><li>任务从生产到被消费的时间分布 (P50, P95 etc.)</li></ul><p>性能相关指标:</p><ul><li>生产接口延迟 (P95)</li><li>消费接口延迟 (P95)</li><li>并发连接数</li></ul><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"未来计划\">未来计划<a class=\"hash-link\" href=\"#未来计划\" title=\"Direct link to heading\">​</a></h2><p>在我们当前的使用场景下, 一个 2G 的 redis 实例就能够支撑千万级左右的延迟任务量。但类似对象存储的生命周期管理(对象存储的 TTL)这种量大且延时间长的场景，使用 Redis 存储成本比较高。后续会考虑基于本地文件或者以 kvrocks (自研的 SSD Redis KV) 作为存储，将数据落到磁盘。kvrocks 目前也是开源状态，美图内部线上已经部署接近 100 个实例，外部也有一些类似白山云等公司在使用，后面也会输出相关设计和实现文章。欢迎大家去关注和使用，更加欢迎 issue 和 PR。</p><p>kvrocks Github 项目地址: <a href=\"https://github.com/meitu/kvrocks\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/meitu/kvrocks</a></p><p>lmsty 的 Github 项目地址: <a href=\"https://github.com/meitu/lmstfy\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/meitu/lmstfy</a></p><p>如有更多技术问题想要交流可以发邮件给我: <a href=\"mailto:hulk.website@gmail.com\" target=\"_blank\" rel=\"noopener noreferrer\">hulk.website@gmail.com</a></p>",
            "url": "https://hulkdev.com/posts-meitu-opensource-task-queue",
            "title": "美图开源任务队列 - LMSTFY",
            "summary": "lmstfy(Let Me Schedule Task For You) 是美图架构基础服务团队在 2018 年初基于 Redis 实现的简单任务队列(Task Queue)服务，目前在美图多个线上产品使用接近两年的时间。主要提供以下特性:",
            "date_modified": "2019-11-28T00:00:00.000Z",
            "tags": [
                "LMSTFY",
                "Queue",
                "Redis"
            ]
        },
        {
            "id": "posts-redis-thread-io",
            "content_html": "<p>前天晚上不经意间在 youtube 上面看到 Redis 作者 <code>Salvatore</code> 在 <a href=\"https://www.youtube.com/watch?v=l7e5ve-ffmI\" target=\"_blank\" rel=\"noopener noreferrer\">RedisConf 2019 分享</a>，其中一段展示了 Redis 6 引入的多线程 IO 特性对性能提升至少是一倍以上，内心很是激动，迫不及待地去看了一下相关的代码实现。</p><p>目前对于单线程 Redis 来说，性能瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:</p><ol><li>提高网络 IO 性能，典型的实现像使用 DPDK 来替代内核网络栈的方式</li><li>使用多线程充分利用多核，典型的实现像 Memcached</li></ol><p>协议栈优化的这种方式跟 Redis 关系不大，多线程特性在社区也被反复提了很久后终于在 Redis 6 加入多线程，<code>Salvatore</code> 在自己的博客 <a href=\"http://antirez.com/news/126\" target=\"_blank\" rel=\"noopener noreferrer\">An update about Redis developments in 2019</a> 也有简单的说明。但跟 Memcached 这种从 IO 处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析，执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂，需要去控制 key、lua、事务，LPUSH/LPOP 等等的并发问题。整体的设计大体如下:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/redis-thread-io-arch.png\" alt=\"img\" class=\"img_ev3q\"></p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"代码实现\">代码实现<a class=\"hash-link\" href=\"#代码实现\" title=\"Direct link to heading\">​</a></h2><p>多线程 IO 的读(请求)和写(响应)在实现流程是一样的，只是执行读还是写操作的差异。同时这些 IO 线程在同一时刻全部是读或者写，不会部分读或部分写的情况，所以下面以读流程作为例子。分析过程中的代码只是为了辅助理解，所以只会覆盖核心逻辑而不是全部细节。如果想完全理解细节，建议看完之后再次看一次源码实现。</p><p>加入多线程 IO 之后，整体的读流程如下:</p><ol><li>主线程负责接收建连请求，读事件到来(收到请求)则放到一个全局等待读处理队列</li><li>主线程处理完读事件之后，通过 RR(Round Robin) 将这些连接分配给这些 IO 线程，然后主线程忙等待(spinlock 的效果)状态</li><li>IO 线程将请求数据读取并解析完成(这里只是读数据和解析并不执行)</li><li>主线程执行所有命令并清空整个请求等待读处理队列(执行部分串行)</li></ol><p>上面的这个过程是完全无锁的，因为在 IO 线程处理的时主线程会等待全部的 IO 线程完成，所以不会出现 data race 的场景。</p><blockquote><p>注意：如果对于代码实现没有兴趣的可以直接跳过下面内容，对了解 Redis 性能提升并没有伤害。</p></blockquote><p>下面的代码分析和上面流程是对应的，当主线程收到请求的时候会回调 <code>network.c</code> 里面的 <code>readQueryFromClient</code> 函数:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">readQueryFromClient</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">aeEventLoop </span><span class=\"token operator\">*</span><span class=\"token plain\">el</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> fd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">privdata</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> mask</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* Check if we want to read from the client later when exiting from</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">     * the event loop. This is the case if threaded I/O is enabled. */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">postponeClientRead</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p><code>readQueryFromClient</code> 之前的实现是负责读取和解析请求并执行命令，加入多线程 IO 之后加入了上面的这行代码，<code>postponeClientRead</code> 实现如下：</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">postponeClientRead</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">client </span><span class=\"token operator\">*</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">io_threads_active </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 多线程 IO 是否在开启状态，在待处理请求较少时会停止 IO </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    多线程</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">io_threads_do_reads </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 读是否开启多线程 IO</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">!</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">flags </span><span class=\"token operator\">&amp;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">CLIENT_MASTER</span><span class=\"token operator\">|</span><span class=\"token plain\">CLIENT_SLAVE</span><span class=\"token operator\">|</span><span class=\"token plain\">CLIENT_PENDING_READ</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 主从库复制请求不使用多线程 IO</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 连接标识为 CLIENT_PENDING_READ 来控制不会反复被加队列,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 这个标识作用在后面会再次提到</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">flags </span><span class=\"token operator\">|=</span><span class=\"token plain\"> CLIENT_PENDING_READ</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 连接加入到等待读处理队列</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listAddNodeHead</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">clients_pending_read</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p><code>postponeClientRead</code> 判断如果开启多线程 IO 且不是主从复制连接的话就放到队列然后返回 1，在 <code>readQueryFromClient</code> 函数会直接返回不进行命令解析和执行。接着主线程在处理完读事件(注意是读事件不是读数据)之后将这些连接通过 RR 的方式分配给这些 IO 线程:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">handleClientsWithPendingReadsUsingThreads</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 将等待处理队列的连接按照 RR 的方式分配给多个 IO 线程</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listRewind</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">clients_pending_read</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">li</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> item_id </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">while</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ln </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listNext</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">li</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        client </span><span class=\"token operator\">*</span><span class=\"token plain\">c </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listNodeValue</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ln</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> target_id </span><span class=\"token operator\">=</span><span class=\"token plain\"> item_id </span><span class=\"token operator\">%</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">io_threads_num</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listAddNodeTail</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">io_threads_list</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">target_id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        item_id</span><span class=\"token operator\">++</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 一直忙等待直到所有的连接请求都被 IO 线程处理完</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">while</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token plain\"> pending </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">for</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> j </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> j </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">io_threads_num</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> j</span><span class=\"token operator\">++</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            pending </span><span class=\"token operator\">+=</span><span class=\"token plain\"> io_threads_pending</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">pending </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">break</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>代码里面的 <code>io_threads_list</code> 用来存储每个 IO 线程对应需要处理的连接，然后主线程将这些连接通过 RR 的方式分配给这些 IO 线程后进入忙等待状态(相当于主线程 blocking 住)。IO 处理线程入口是 <code>IOThreadMain</code> 函数:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">IOThreadMain</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">myid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">while</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 遍历线程 id 获取线程对应的待处理连接列表</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listRewind</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">io_threads_list</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">li</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">while</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ln </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listNext</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">li</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            client </span><span class=\"token operator\">*</span><span class=\"token plain\">c </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listNodeValue</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ln</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 通过 io_threads_op 控制线程要处理的是读还是写请求</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">io_threads_op </span><span class=\"token operator\">==</span><span class=\"token plain\"> IO_THREADS_OP_WRITE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">writeToClient</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">fd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">io_threads_op </span><span class=\"token operator\">==</span><span class=\"token plain\"> IO_THREADS_OP_READ</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">readQueryFromClient</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">fd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverPanic</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"io_threads_op value is unknown\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">listEmpty</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">io_threads_list</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        io_threads_pending</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"> </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>IO 线程处理根据全局 <code>io_threads_op</code> 状态来控制当前 IO 线程应该处理读还是写事件，这也是上面提到的全部 IO 线程同一时刻只会执行读或者写。另外，心细的同学可能注意到处理线程会调用 <code>readQueryFromClient</code> 函数，而连接就是由这个回调函数加到队列的，那不就死循环了？ 这个的答案在 <code>postponeClientRead</code> 函数，已经加到等待处理队列的连接会被设置 <code>CLIENT_PENDING_READ</code> 标识。<code>postponeClientRead</code> 函数不会把连接再次加到队列，那么 <code>readQueryFromClient</code> 会继续执行读取和解析请求。<code>readQueryFromClient</code> 函数读取请求数据并调用  <code>processInputBuffer</code> 函数进行解析命令，<code>processInputBuffer</code> 会判断当前连接是否来自 IO 线程，如果是的话就只解析不执行命令，代码就不贴了。</p><p>大家去看 <code>IOThreadMain</code> 实现会发现这些 io 线程是没有任何 sleep 机制，在空闲状态也会导致每个线程的 CPU 跑到 100%，但简单 sleep 则会导致读写处理不及时而导致性能更差。Redis 当前的解决方式是通过在等待处理连接比较少的时候关闭这些 IO 线程。为什么不适用条件变量来控制呢？我也没想明白，后面可以到社区提问。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"性能对比\">性能对比<a class=\"hash-link\" href=\"#性能对比\" title=\"Direct link to heading\">​</a></h2><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"压测配置\">压测配置:<a class=\"hash-link\" href=\"#压测配置\" title=\"Direct link to heading\">​</a></h3><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Redis Server: 阿里云 Ubuntu </span><span class=\"token number\">18.04</span><span class=\"token plain\">，8 CPU </span><span class=\"token number\">2.5</span><span class=\"token plain\"> GHZ, 8G 内存，主机型号 ecs.ic5.2xlarge</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">Redis Benchmark Client: 阿里云 Ubuntu </span><span class=\"token number\">18.04</span><span class=\"token plain\">，8 </span><span class=\"token number\">2.5</span><span class=\"token plain\"> GHZ CPU, 8G 内存，主机型号 ecs.ic5.2xlarge</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>多线程 IO 版本刚合并到 <code>unstable</code> 分支一段时间，所以只能使用 <code>unstable</code> 分支来测试多线程 IO，单线程版本是 Redis 5.0.5。多线程 IO 版本需要新增以下配置:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">io-threads </span><span class=\"token number\">4</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># 开启 4 个 IO 线程</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">io-threads-do-reads </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">yes</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># 请求解析也是用 IO 线程</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>压测命令: <code>redis-benchmark -h 192.168.0.49 -a foobared -t set,get -n 1000000 -r 100000000 --threads 4 -d ${datasize} -c 256</code></p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/redis-thread-io-get-benchmark.png\" alt=\"img\" class=\"img_ev3q\"></p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/redis-thread-io-set-benchmark.png\" alt=\"img\" class=\"img_ev3q\"></p><p>从上面可以看到 GET/SET 命令在 4 线程 IO 时性能相比单线程是几乎是翻倍了。另外，这些数据只是为了简单验证多线程 IO 是否真正带来性能优化，并没有针对严谨的延时控制和不同并发的场景进行压测。数据仅供验证参考而不能作为线上指标，且只是目前的 <code>unstble</code> 分支的性能，不排除后续发布的正式版本的性能会更好。</p><blockquote><p>注意: Redis Benchmark 除了 <code>unstable</code> 分支之外都是单线程，对于多线程 IO 版本来说，压测发包性能会成为瓶颈，务必自己编译 <code>unstable</code> 分支的 redis-benchmark 来压测，并配置 --threads 开启多线程压测。另外，如果发现编译失败也莫慌，这是因为 Redis 用了 Atomic_ 特性，更新版本的编译工具才支持，比如 GCC 5.0 以上版本。</p></blockquote><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"总结\">总结<a class=\"hash-link\" href=\"#总结\" title=\"Direct link to heading\">​</a></h2><p>Redis 6.0 预计会在 2019 年底发布，将在性能、协议以及权限控制都会有很大的改进。<code>Salvatore</code> 今年全身心投入在优化 Redis 和集群的功能，特别值得期待。另外，今年年底社区也会同时发布第一个版本 redis cluster proxy 来解决多语言 SDK 兼容的问题，期待在具备 proxy 功能之后 cluster 能在国内有更加广泛的应用。</p>",
            "url": "https://hulkdev.com/posts-redis-thread-io",
            "title": "Redis 6 多线程 IO",
            "summary": "前天晚上不经意间在 youtube 上面看到 Redis 作者 Salvatore 在 RedisConf 2019 分享，其中一段展示了 Redis 6 引入的多线程 IO 特性对性能提升至少是一倍以上，内心很是激动，迫不及待地去看了一下相关的代码实现。",
            "date_modified": "2019-08-08T00:00:00.000Z",
            "tags": [
                "Redis"
            ]
        },
        {
            "id": "posts-meitu-opensource-twemproxy",
            "content_html": "<p>美图在 2017 年下半年开始计划做 Redis/Memcached 资源 PaaS 平台，而 PaaS 化之后面临一个问题是如何实现资源缩容/扩容对业务无感，为了解决这个问题，美图技术团队于 17 年 11 月引入 twemproxy 作为资源网关。</p><p>但是长期的实践中，其开源版本不能完全适应美图的实际情况，其主要存在单线程模型无法利用多核，性能不佳；配置无法在线 Reload ；Redis 不支持主从模式；无延时指标等问题，所以美图技术团队对其进行了相应的改造。我们基于之上实现了多进程以及配置在线更新的功能，同时增加了一些延时的相关监控指标。</p><p>本文将为大家详细讲解 twemproxy 实现以及相应地改造，希望能给其他的技术团队提供一些可以借鉴的经验。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"为什么要选择-twemproxy\">为什么要选择 twemproxy<a class=\"hash-link\" href=\"#为什么要选择-twemproxy\" title=\"Direct link to heading\">​</a></h2><p>wemproxy 是一款由 twitter 开源的 Redis/Memcached 代理，主要目标是减少后端资源的连接数以及为缓存横向扩展能力。 twemproxy 支持多种 hash 分片算法，同时具备失败节点自动剔除的功能。除此之外，其他比较成熟的开源解决方案还有 codis，codis 具备在线的 auto-scale 以及友好的后台管理，但整体的功能更接近于 Redis Cluster，而不是代理。美图这边需要的是一个 Redis 和 Memcached 协议类 PaaS 服务的代理(网关)，所以我们最终选择了 twemproxy。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"twemproxy-实现\">twemproxy 实现<a class=\"hash-link\" href=\"#twemproxy-实现\" title=\"Direct link to heading\">​</a></h2><p>twemproxy 主要的功能是解析用户请求后转发到后端的缓存资源，成功后在把响应转发回客户端。</p><p>代码实现的核心是三种连接对象:</p><ol><li>proxy connection， 用来监听用户建立连接的请求，建立连接成功后会对应产生一个客户端连接</li><li>client connection，由建连成功后产生，用户读写数据都是通过 client connection 解析请求后，根据 key 和哈希规则选择一个 server 进行转发</li><li>server connection，转发用户请求到缓存资源并接收和解析响应数据转回 client connection，client connection 将响应返回到用户</li></ol><p>三种连接的数据流向如下图：</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/twemproxy-inner.jpeg\" alt=\"img\" class=\"img_ev3q\">\n(上图的 client connection 之所以没有 imsgq 是因为请求解析完可以直接进入 server 的 imsgq)</p><ol><li>用户通过 proxy connection 建立连接，产生一个 client connection</li><li>client connection 开始读取用户的请求数据，并将完整的请求根据 key 和设置的哈希规则选择 server, 然后将这个请求存放到 server 的 imsgq</li><li>接着 server connection 发送 imsgq 请求到远程资源，发送完成之后(写 tcp buffer) 就会将 msg 从 imsgq 迁移到 omsgq，响应回来之后从 omsgq 队列里面找到这个对应的 msg 以及 client connection</li><li>最后将响应内容放到 client connection 的 omsgq，由 client connection 将数据发送回客户端。</li></ol><p>上面提到的用户请求和资源响应的数据都是在解析之后放到内存的 buf 里面，在 client 和 server 两种连接的内部流转也只是指针的拷贝(官网 README 里面提到的 Zero Copy)。这也是 twemproxy 单线程模型在小包场景能够达到 10w qps 的原因之一，几乎不拷贝内存。</p><p>但对于我们来说，当前开源版本存在几个问题: </p><ul><li>单线程模型无法利用多核，性能不够好，极端情况下代理和资源需要 1:1 部署</li><li>配置无法在线 Reload，twitter 内部版本应该是支持的，单元测试里面有针对 reload 的 case，PaaS 场景需要不断更新配置</li><li>Redis 不支持主从模式（Redis 在作为缓存的场景下确实没必要使用主从），但部分场景需要</li><li>数据指标过少，延时指标完全没有。</li></ul><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"多进程版本\">多进程版本<a class=\"hash-link\" href=\"#多进程版本\" title=\"Direct link to heading\">​</a></h3><p>针对以上的几个问题，美图的开源版本都做了一些修改，最核心的功能是多进程和配置在线 reload。改造后整体进程模型类似 Nginx， 简单示意图如下:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/twemproxy-process-mode.png\" alt=\"img\" class=\"img_ev3q\"></p><p>master 的功能就是管理 worker 进程，不接收和处理用户请求。如果 worker 进程异常退出，那么 master 则会自动拉起新的进程来替代挂掉的老进程。除此之外，master 还会接收来自用户的几种信号: </p><ul><li>SIGHUP， 重新加载新配置 </li><li>SIGTTIN，提高日志级别, 级别越高日志越详细 </li><li>SIGTTOU，降低日志级别，级别越低日志越少 </li><li>SIGUSR1，重新打开日志文件 </li><li>SIGTERM，优雅退出，等到一段时间后退出 </li><li>SIGINT，强制退出 </li></ul><p>同时还增加了几个全局配置: </p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">global:</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  worker_processes: auto      </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># num of workers, fallback to single process model while worker_processes is 0</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  max_openfiles: </span><span class=\"token number\">102400</span><span class=\"token plain\">       </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># max num of open files in every worker process</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  user: nobody                </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># user of worker's process, master process should be setup with root</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  group: nobody               </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># group of worker's process</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  worker_shutdown_timeout: </span><span class=\"token number\">30</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># terminate the old worker after worker_shutdown_timeout, unit is second</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>除了 <code>worker_shutdown_timeout</code> 其他几个配置应该比较好理解。<code>worker_shutdown_timeout</code> 是配置老 worker 在收到退出信号后多长时间退出。这个配置是跟多进程实现相关的参数，我们是通过启动新进程替代老进程的方式来实现配置以及进程数目的在线修改，所以这个配置就是用来指定老进程的保留时间。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"reuse-port\">Reuse Port<a class=\"hash-link\" href=\"#reuse-port\" title=\"Direct link to heading\">​</a></h3><p>在 reuse port 之前，多线程/进程服务监听建连请求一般有两种方式:</p><ol><li>由一个线程负责接收所有的新连接，其他线程服务只负责建立连接之后的处理。这种方式的问题是在短连接场景下，这个 accept 线程很容易成为瓶颈(单核我们这边测试一般是在 4w+/s 左右)</li><li>所有的线程/进程都同时 accept 同一个监听的文件句柄。 这种方式的问题是在高负载的场景下，不同线程/进程的唤醒会不均匀，另外会有惊群的效果(accept/epoll 在新版本内核中也有解决惊群问题)</li></ol><p>reuse port 的主要作用就是允许多个 socket 同时监听同一个端口，同时不会存在建立连接不均匀的问题。 使用 reuse port 也相当简单，只需要把监听通过一个端口的 socket 都设置上 reuse port 标识即可。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">nc_set_reuseport</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> sd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">ifdef</span><span class=\"token macro property\"> </span><span class=\"token macro property expression\">NC_HAVE_REUSEPORT</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> reuse</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token class-name\">socklen_t</span><span class=\"token plain\"> len</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    reuse </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    len </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">sizeof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">reuse</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">setsockopt</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> SOL_SOCKET</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> SO_REUSEPORT</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">reuse</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> len</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">endif</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>虽然 reuse port 是在 linux 3.9 才被合并进来，但很多发行版的 OS 都有 backport 到更早之前的版本（至少我们在使用的 centos6 的 kernel 2.6.32 是有的），很多博客在这点上有些误导。另外，在 reload 时候也不能简单将老的监听关闭，会导致 tcp backlog 里面这些三次握手成功但未 accept 的连接丢失，业务在这些连接上发送数据则会收到 rst 包。</p><p>我们解决这个问题的方式是让监听连接都在 master 进程上面创建和维护，worker 进程只是在 fork 之后直接继承监听的连接，所以在 reload 的时候 master 就可以将老 worker 里面的监听连接迁移到新的 worker， 来保证 tcp backlog 里面的数据不会丢失。</p><p>具体代码见: <a href=\"https://github.com/meitu/twemproxy/blob/develop/src/nc_process.c#L172\" target=\"_blank\" rel=\"noopener noreferrer\">nc_process.c#L172</a>, 这种方式能够在进程数不变或者增多的场景下保证 backlog 里面的数据不会丢，进程数缩减时还是会丢失一些</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"redis-主从模式\">Redis 主从模式<a class=\"hash-link\" href=\"#redis-主从模式\" title=\"Direct link to heading\">​</a></h3><p>在原生的 twemproxy 里面是不支持 Redis 主从模式的，这个应该主要是因为 twemproxy 把 Redis/Memcached 当做是缓存而不是存储，所以这种主从结构实际上是没有必要的，运维也比较简单。但是对于我们内部业务来说，有些并不是全部都是作为缓存，所以就需要这种主从结构。配置也比较简单:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    servers:</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        -  </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:6379:1 master</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        - </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:6380:1 </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>如果检测到 server 的名字为 master 则认为该实例为主，一个池子里面只允许一个主，否则认为配置不合法。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"统计指标\">统计指标<a class=\"hash-link\" href=\"#统计指标\" title=\"Direct link to heading\">​</a></h3><p>个人觉得 twemproxy 存在的另外一个问题是延时指标完全缺失，这个对于排查问题以及监控报警是比较不利的。针对这个问题，我们增加了两种延时指标</p><ol><li>request latency, 指的是客户端请求到返回的延时, 包含 twemproxy 内部以及 server 的耗时，这个指标更加接近业务的耗时</li><li>server latency, 指的是 twemproxy 请求 server 的耗时，这个可以理解为 Redis/Memcached server 的耗时</li></ol><p>在偶发问题的场景下，根据两种延时可以定位是 twemproxy、server 还是客户端的问题(比如 GC)导致慢请求，另外也可以慢请求的比例进行监控报警。这两种指标是通过 bucket 的方式来记录的，比如 &lt;1ms 的数目，&lt;10ms 的数目等等。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"仍然存在的问题\">仍然存在的问题<a class=\"hash-link\" href=\"#仍然存在的问题\" title=\"Direct link to heading\">​</a></h2><ol><li>在 worker 数目减少的场景下，被销毁的老 Worker 的 tcp backlog 会丢失会导致一些连接超时</li><li>unix socket 没有 reuse port 类似的机制，所以实际上还是单进程但可以支持在线 reload</li><li>不支持 Memcached 二进制协议，几年前有人提供相关 PR 但一直都没有进入 master</li><li>客户端的最大连接数有配置但实际上不生效，这个功能我们后续会加上</li><li>命令支持不全(主要是没有 key 以及一些 blocking 的指令)</li><li>reload 期间新老进程的配置不一致会可能会导致脏数据</li></ol><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"性能压测\">性能压测<a class=\"hash-link\" href=\"#性能压测\" title=\"Direct link to heading\">​</a></h2><p>以下数据是在长连接小包场景下压测得出，主要是验证多进程版本是否跟预期的一致。没有其他硬件到达瓶颈之前，性能可以随着 CPU 核数线性增长。</p><p>压测环境如下:</p><ul><li>CentOS 6.6</li><li>CPU Intel E5-2660 32 逻辑核</li><li>内存 64G</li><li>两张千兆网卡做 bond0</li></ul><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/twemproxy-benchmark.webp\" alt=\"img\" class=\"img_ev3q\"></p><p>单个 worker 场景和 twemproxy 改造之前的性能差不多，在 10w 左右。随着 worker 数目增加，后面性能和 worker 基本是保持线上增长，符合预期。8 核以上的瓶颈是 bond0 模式下包接收不均匀导致单网卡性能达到瓶颈，数据无法作为参考。上面的数据也是我们自己环境的压测数据，大家可以自行验证。如果是多网卡需要注意绑定中断或者多队列到多个 CPU, 避免 CPU0 软中断处理成为瓶颈。</p><h2 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"最后\">最后<a class=\"hash-link\" href=\"#最后\" title=\"Direct link to heading\">​</a></h2><p>多进程版本的 twemproxy 实现上算是比较简单，但过程中发现并修复不少 twemproxy 细节问题(一部分是使用方报告)，比如 mbuf 一旦分配就不会收缩导致内存上涨之后不再下降的问题等等。很多功能细节我们也在不断优化，我们也只维护 Github 上的一个版本。</p><p>代码地址: <a href=\"https://github.com/meitu/twemproxy\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/meitu/twemproxy</a></p><p>除此之外，我们团队目前也开源其他一些项目:</p><ul><li><a href=\"https://github.com/meitu/go-consumergroup\" target=\"_blank\" rel=\"noopener noreferrer\">golang 版本的 kafka consumer group</a></li><li><a href=\"https://github.com/meitu/php-consumergroup\" target=\"_blank\" rel=\"noopener noreferrer\">php 版本的 kafka consumer group</a></li><li><a href=\"https://github.com/meitu/go-ethereum\" target=\"_blank\" rel=\"noopener noreferrer\">基于以太坊的 DPoS 实现</a></li></ul><p>后续还会开源更多的东西，欢迎大家多多关注~</p>",
            "url": "https://hulkdev.com/posts-meitu-opensource-twemproxy",
            "title": "美图多线程 twemproxy 实现",
            "summary": "美图在 2017 年下半年开始计划做 Redis/Memcached 资源 PaaS 平台，而 PaaS 化之后面临一个问题是如何实现资源缩容/扩容对业务无感，为了解决这个问题，美图技术团队于 17 年 11 月引入 twemproxy 作为资源网关。",
            "date_modified": "2018-10-10T00:00:00.000Z",
            "tags": [
                "Twemproxy",
                "Redis",
                "Memcached"
            ]
        },
        {
            "id": "posts-redis-async-delete",
            "content_html": "<p>对于 Redis 这种单线程模型的服务来说，一些耗时的命令阻塞其他请求是个头痛的问题。典型的命令如 KEYS/FLUSHALL/FLUSHDB 等等，一般线上也会禁用这些会遍历整个库的命令。而像 DEL/LRANGE/HGETALL 这些可能导致阻塞的命令经常被工程师忽视，这些命令在 value 比较大的时候跟 KEYS 这些并没有本质区别。</p><p>Redis 4.0 开始针对 DEL/FLUSHALL/FLUSHDB 做了一些优化。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-delflushallflushdb-异步化\">1) DEL/FLUSHALL/FLUSHDB 异步化<a class=\"hash-link\" href=\"#1-delflushallflushdb-异步化\" title=\"Direct link to heading\">​</a></h3><p>FLUSHALL/FLUSHDB 清除库的时候因为要对每个 kv 进行遍历会比较耗时。同理对于 DEL 命令也是，如 VALUE 是链表，集合或者字典，同样要遍历删除。在 Redis 4.0 针对这三个命令引入了异步化处理，避免阻塞其他请求。FLUSHALL/FLUSHDB 加了一个 <code>ASYNC</code> 参数，同时新增 <code>UNLINK</code> 来表示异步化的删除命令。</p><p><strong><em>为什么 DEL 也不使用类似 FLUSHALL/FLUSHDB  命令加个参数的方式？</em></strong></p><p>调皮的作者是这么说的:</p><blockquote><p>There are reasons why UNLINK is not the default for DEL. I know things… I can’t talk (**).</p></blockquote><p>意思大概就是: 「原因我知道但不告诉你...」</p><p>不过我猜主要原因是因为 DEL 命令是支持不定参数，如果加个 ASYNC 参数没办法判断到底这个是 key 还是异步删除的选项。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-del-异步化的实现\">2) DEL 异步化的实现<a class=\"hash-link\" href=\"#2-del-异步化的实现\" title=\"Direct link to heading\">​</a></h3><p>我们可以直接来看 <code>UNLINK</code> 命令的实现:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">unlinkCommand</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">client </span><span class=\"token operator\">*</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// lazy 参数设置 1，表示异步删除</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">delGenericCommand</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">delGenericCommand</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">client </span><span class=\"token operator\">*</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> lazy</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> numdel </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">for</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">j </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> j </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> j</span><span class=\"token operator\">++</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">expireIfNeeded</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果是异步删除调用 dbAsyncDelete</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> deleted  </span><span class=\"token operator\">=</span><span class=\"token plain\"> lazy </span><span class=\"token operator\">?</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dbAsyncDelete</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                              </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dbSyncDelete</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">addReplyLongLong</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">numdel</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>我们可看到 unlink 命令会调用 <code>dbAsyncDelete</code> 来实现异步调用。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">define</span><span class=\"token macro property\"> </span><span class=\"token macro property macro-name\">LAZYFREE_THRESHOLD</span><span class=\"token macro property\"> </span><span class=\"token macro property expression number\">64</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dbAsyncDelete</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">redisDb </span><span class=\"token operator\">*</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> robj </span><span class=\"token operator\">*</span><span class=\"token plain\">key</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 先把 key 从过期时间字典里面删除</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictSize</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">expires</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictDelete</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">expires</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">key</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ptr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 把 kv 从字典里面摘除但不是删除 value，后续命令就查询不到</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    dictEntry </span><span class=\"token operator\">*</span><span class=\"token plain\">de </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictUnlink</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">dict</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">key</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ptr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">de</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        robj </span><span class=\"token operator\">*</span><span class=\"token plain\">val </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictGetVal</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">de</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 不是所有的 key 都会走异步化删除，如果 value 比较小会直接删除</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果 value 是字典/链表/集合且不能是压缩的返回对应的元素数目，其他都返回 1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token class-name\">size_t</span><span class=\"token plain\"> free_effort </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">lazyfreeGetFreeEffort</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">val</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 只有计算出来的 free_effort 大于 LAZYFREE_THRESHOLD(64) 才会进入异步处理</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">free_effort </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> LAZYFREE_THRESHOLD</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">atomicIncr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">lazyfree_objects</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">lazyfree_objects_mutex</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 创建 BIO_LAZY_FREE 任务，放到异步队列</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">bioCreateBackgroundJob</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">BIO_LAZY_FREE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">val</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictSetVal</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">dict</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">de</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">de</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果 key 存在，释放字典里面结构</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictFreeUnlinkedEntry</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">dict</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">de</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">cluster_enabled</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">slotToKeyDel</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">key</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p><code>unlink</code> 命令处理上并不是所有的 kv 都会走异步化删除，而是会根据 value 的大小进行评分后筛选，超过阀值的才会走异步化删除。这个计算函数是 <code>lazyfreeGetFreeEffort</code>。</p><p>同时 Redis 4.0 专门多开了一个后台线程专门来异步处理 DEL, FLUSHALL 和 FLUSHDB 这三个命令。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">type </span><span class=\"token operator\">==</span><span class=\"token plain\"> BIO_LAZY_FREE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 处理 DEL 过来的 key</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">lazyfreeFreeObjectFromBioThread</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg2 </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\"> job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg3</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">//  处理 flush 命令 </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">lazyfreeFreeDatabaseFromBioThread</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg3</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg3</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">lazyfreeFreeSlotsMapFromBioThread</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arg3</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-flushallflushdb\">3) FLUSHALL/FLUSHDB<a class=\"hash-link\" href=\"#3-flushallflushdb\" title=\"Direct link to heading\">​</a></h3><p>这两个命令也是比较类似，Redis 会先检查这两个命令是否有带 <code>async</code>:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">getFlushCommandFlags</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">client </span><span class=\"token operator\">*</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">flags</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argc </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 判断第二个参数是否为 async</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argc </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">2</span><span class=\"token plain\"> </span><span class=\"token operator\">||</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">strcasecmp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ptr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"async\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">addReply</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">shared</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">syntaxerr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> C_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">*</span><span class=\"token plain\">flags </span><span class=\"token operator\">=</span><span class=\"token plain\"> EMPTYDB_ASYNC</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">*</span><span class=\"token plain\">flags </span><span class=\"token operator\">=</span><span class=\"token plain\"> EMPTYDB_NO_FLAGS</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> C_OK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>接着在 <code>emptyDb</code> 判断是异步清数据，如果是异步清除则会调用 <code>emptyDbAsync</code>:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">emptyDbAsync</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">redisDb </span><span class=\"token operator\">*</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 保留老的数据库指针并重新创建新的数据库</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    dict </span><span class=\"token operator\">*</span><span class=\"token plain\">oldht1 </span><span class=\"token operator\">=</span><span class=\"token plain\"> db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">dict</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">oldht2 </span><span class=\"token operator\">=</span><span class=\"token plain\"> db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">expires</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">dict </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictCreate</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">dbDictType</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    db</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">expires </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictCreate</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">keyptrDictType</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">atomicIncr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">lazyfree_objects</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictSize</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">oldht1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        lazyfree_objects_mutex</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 把要清空的 db 作为一个 job 添加到后台的处理队列</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">bioCreateBackgroundJob</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">BIO_LAZY_FREE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">oldht1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">oldht2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4-总结\">4) 总结<a class=\"hash-link\" href=\"#4-总结\" title=\"Direct link to heading\">​</a></h3><p>FLUSHALL/FLUSHDB 这种命令线上环境基本都会禁用，大家犯错的概率比较小。而像 DEL 这种命令属于高频的操作，删除大 value 导致的阻塞问题容易被忽视，异步化删除可以一定程度上规避这种问题。</p><p>参考连接: <a href=\"http://antirez.com/news/110\" target=\"_blank\" rel=\"noopener noreferrer\">http://antirez.com/news/110</a></p>",
            "url": "https://hulkdev.com/posts-redis-async-delete",
            "title": "Redis 4.0 非阻塞删除",
            "summary": "对于 Redis 这种单线程模型的服务来说，一些耗时的命令阻塞其他请求是个头痛的问题。典型的命令如 KEYS/FLUSHALL/FLUSHDB 等等，一般线上也会禁用这些会遍历整个库的命令。而像 DEL/LRANGE/HGETALL 这些可能导致阻塞的命令经常被工程师忽视，这些命令在 value 比较大的时候跟 KEYS 这些并没有本质区别。",
            "date_modified": "2017-01-24T00:00:00.000Z",
            "tags": [
                "Redis"
            ]
        },
        {
            "id": "posts-redis-mixed-format",
            "content_html": "<p>Redis 当前支持 aof 和 rdb 这两种持久化方式。 有些对 Redis 不是特别的了解同学误解持久化是读写数据也会到磁盘。这里辟谣一下:</p><blockquote><p>Redis 读写都是全内存的, 持久化数据只是作为磁盘备份, 实例重启或者机器断电的时候可以从磁盘加载到内存</p></blockquote><p>由于本篇博客主要是为了分析 4.0 版本的 rdb 和 aof 混合存储的实现，所以不会详细介绍 rdb 和 aof。如果有想进一步了解可参考 《Redis 设计与实现》 一书。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-aof-和-rdb\">1) aof 和 rdb<a class=\"hash-link\" href=\"#1-aof-和-rdb\" title=\"Direct link to heading\">​</a></h3><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"11-rdb-简介\">1.1) rdb 简介<a class=\"hash-link\" href=\"#11-rdb-简介\" title=\"Direct link to heading\">​</a></h4><p>rdb 是某一个时刻的内存镜像数据写入到磁盘文件，之后的写入数据会丢失。 rdb 持久化方式的优点是持久化后的文件比较小(只有某一个时刻的数据且会压缩)，实例重启时加载会更快。缺点是如果实例重启，备份时刻之后的写入数据会丢失。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"12-aof-简介\">1.2) aof 简介<a class=\"hash-link\" href=\"#12-aof-简介\" title=\"Direct link to heading\">​</a></h4><p>aof 是将 Redis 写入命令写入追加到磁盘文件。根据配置的刷盘策略不同，实例重启丢掉的数据量也不一样。现在有下面三种方式:</p><ul><li>appendfsync = always 每条写入都会刷盘, 最多只会丢失当前正在写入的命令</li><li>appendfsync = everysec 每秒刷一次盘, 最多丢失一秒的数据</li><li>appendfsync = no 不显式刷盘。注意不是不刷盘而是由操作系统来决定何时刷盘(linux 貌似大部分默认是 30s)。可能会丢失刷盘之前的写入数据。</li></ul><p>aof 持久化方式的优点就是重启丢失的数据会比 rdb 少。缺点因为写入命令追加写入的方式，在写入比较多的场景下会导致重启加载数据太慢。举个例子，如果对 key 做 1000 次 incr, 则 aof 文件则会记录 1000 次 incr，而 rdb 只存储 1000 这个值即可。不过 aof 允许 rewrite, 比如把例子里面的 1000 次 incr a 变成一次 incr a 1000 命令，这个是另外一个话题了。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-混合写入\">2) 混合写入<a class=\"hash-link\" href=\"#2-混合写入\" title=\"Direct link to heading\">​</a></h3><p>上面已经说明了 rdb 和 aof 各自的优缺点，Redis 4.0 开始支持 rdb 和 aof 的混合持久化(默认关闭)。如果把混合持久化打开，aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。aof 文件内容会变成如下:</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             +------------------------+</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |                        |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |                        |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |          RDB           |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |         FORMAT         |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |                        |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |                        |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |                        |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             +------------------------+</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |                        |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |        AOF             |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             |       FORMAT           |   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">             +------------------------+</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这样做的好处是可以结合 rdb 和 aof 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的， aof 里面的 rdb 部分就是压缩格式不再是 aof 格式，可读性差。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"21-混合持久化实现\">2.1) 混合持久化实现<a class=\"hash-link\" href=\"#21-混合持久化实现\" title=\"Direct link to heading\">​</a></h4><p>下面是 aof.c 里面在做 aof 文件写入的代码，具体函数 <code>rewriteAppendOnlyFile</code> :</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// aof_use_rdb_preamble = 1 表示打开混合存储模式</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">aof_use_rdb_preamble</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// aof 文件前面部分就是直接写入 rdb 文件</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">rdbSaveRio</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">aof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">RDB_SAVE_AOF_PREAMBLE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> C_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            errno </span><span class=\"token operator\">=</span><span class=\"token plain\"> error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> werr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果是关闭混合存储和之前一样，保持 aof 格式</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">rewriteAppendOnlyFileRio</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">aof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> C_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> werr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"22-混合-aof-加载\">2.2) 混合 aof 加载<a class=\"hash-link\" href=\"#22-混合-aof-加载\" title=\"Direct link to heading\">​</a></h4><p>开启混合存储模式后 aof 文件加载的流程如下:</p><ol><li>aof 文件开头是 rdb 的格式, 先加载 rdb 内容再加载剩余的 aof</li><li>aof 文件开头不是 rdb 的格式，直接以 aof 格式加载整个文件</li></ol><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> sig</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">5</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* \"REDIS\" */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">fread</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sig</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">5</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">fp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">!=</span><span class=\"token plain\"> </span><span class=\"token number\">5</span><span class=\"token plain\"> </span><span class=\"token operator\">||</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">memcmp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sig</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"REDIS\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">5</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">!=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 前部分内容不是 rdb 格式，不是混合持久化的方式</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">fseek</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">fp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">SEEK_SET</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> readerr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        rio rdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Reading RDB preamble from AOF file...\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">fseek</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">fp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">SEEK_SET</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> readerr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">rioInitWithFile</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">rdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">fp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 前面部分是 rdb 格式说明是混合持久化，先加载 rdb 后面逻辑再加载 aof</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">rdbLoadRio</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">rdb</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">!=</span><span class=\"token plain\"> C_OK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_WARNING</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Error reading the RDB preamble of the AOF file, AOF loading aborted\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> readerr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Reading the remaining AOF tail...\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 加载 aof 格式的数据</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>判断 aof 文件的前面部分是否为 rdb 格式，只需要判断前 5 个字符是否是 <code>REDIS</code>。这个是因为 rdb 持久化开头就是 <code>REDIS</code>, 同时 aof 命令开头一定不会是 REDIS（命令开头都是 <code>*</code>）。</p>",
            "url": "https://hulkdev.com/posts-redis-mixed-format",
            "title": "Redis 4.0 RDB 和 AOF 混合存储",
            "summary": "Redis 当前支持 aof 和 rdb 这两种持久化方式。 有些对 Redis 不是特别的了解同学误解持久化是读写数据也会到磁盘。这里辟谣一下:",
            "date_modified": "2017-01-23T00:00:00.000Z",
            "tags": [
                "Redis"
            ]
        },
        {
            "id": "posts-redis-new-sync",
            "content_html": "<p>上一篇介绍了 <a href=\"http://www.hulkdev.com/posts/redis-module\" target=\"_blank\" rel=\"noopener noreferrer\">&lt;Redis-4.0 module实现&gt;</a>，同时也提到 redis 4.0 一个比较大的改动就是 psync 优化, 本篇会介绍这个优化的部分。</p><p>在 2.8 版本之前 redis 没有增量同步的功能，主从只要重连就必须全量同步数据。如果实例数据量比较大的情况下，网络轻轻一抖就会把主从的网卡跑满从而影响正常服务，这是一个蛋疼的问题。2.8 为了解决这个问题引入了 psync (partial sync)功能，顾名思义就是增量同步。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-28-版本的-psync-机制\">1) 2.8 版本的 psync 机制<a class=\"hash-link\" href=\"#1-28-版本的-psync-机制\" title=\"Direct link to heading\">​</a></h3><p>2.8 引入 psync 之后的同步机制:</p><ul><li>从库尝试发送 psync 命令到主库，而不是直接使用 sync 命令进行全量同步</li><li>主库判断是否满足 psync 条件, 满足就返回 <code>+CONTINUE</code> 进行增量同步, 否则返回 <code>+FULLRESYNC runid offfset</code></li></ul><p><strong><em>redis 判断是否允许 psync 有两个条件:</em></strong></p><ul><li>条件一: psync 命令携带的 runid 需要和主库的 runid 一致才可以进行增量同步，否则需要全量同步。</li></ul><blockquote><p>NOTE: 主库的 runid 是在主库进程启动之后生成的唯一标识(由进程id加上随机数组成), 在第一次全量同步的时候发送给从库，上面有看到 FULLSYNC 返回带有 runid 和 offset, 从库会在内存缓存这个 runid 和 offset 信息</p></blockquote><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">strcasecmp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">master_runid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">runid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">master_runid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"> </span><span class=\"token operator\">!=</span><span class=\"token plain\"> </span><span class=\"token char\" style=\"color:rgb(255, 121, 198)\">'?'</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">redisLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">REDIS_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Partial resynchronization not accepted: \"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Runid mismatch (Client asked for '%s', I'm '%s')\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                master_runid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">runid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">redisLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">REDIS_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Full resync requested by slave %s\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">replicationGetSlaveName</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> need_full_resync</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><ul><li>条件二:  psync 命令携带的 offset 是否超过缓冲区。如果超过则需要全量同步，否则就进行增量同步。</li></ul><blockquote><p>NOTE: backlog 是一个固定大小(默认1M)的环形缓冲区，用来缓存主从同步的数据。如果 offset 超过这个范围说明中间有一段数据已经丢失，需要全量同步。</p></blockquote><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog </span><span class=\"token operator\">||</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        psync_offset </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog_off </span><span class=\"token operator\">||</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        psync_offset </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog_off </span><span class=\"token operator\">+</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog_histlen</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">redisLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">REDIS_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">replicationGetSlaveName</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> psync_offset</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">psync_offset </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">master_repl_offset</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">redisLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">REDIS_WARNING</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">replicationGetSlaveName</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> need_full_resync</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>有了 psync 之后主从短时间断掉重连就可以不用全量同步数据。前提也是这段时间的写入不能超过缓冲区。如果写入量比较大的，也建议稍微调大这个缓冲区。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-问题\">2) 问题<a class=\"hash-link\" href=\"#2-问题\" title=\"Direct link to heading\">​</a></h3><p>虽然 2.8 引入的 psync 可以解决短时间主从同步断掉重连问题，但以下几个场景仍然是需要全量同步:</p><ol><li>主库/从库有重启过。因为 runnid 重启后就会丢失，所以当前机制无法做增量同步。</li><li>从库提升为主库。其他从库切到新主库全部要全量不同数据，因为新主库的 runnid 跟老的主库是不一样的。</li></ol><p>这两个应该是我们比较常见的场景。主库切换或者重启都需要全量同步数据在从库实例比较大或者多的场景下，那内网网络带宽和服务都会有很大的影响。所以 redis 4.0 对 psync 优化之后可以一定程度上规避这些问题。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3新版-psync-机制\">3）新版 psync 机制<a class=\"hash-link\" href=\"#3新版-psync-机制\" title=\"Direct link to heading\">​</a></h3><p>为了解决主从角色切换导致的重新全量同步，redis 4.0 引入多另外一个变量 replid2 来存放同步过的主库的 replid，同时 replid 在不同角色意义也有写变化。replid 在主库的意义和之前 replid 仍然是一样的，但对于从库来说，replid 表示当前正在同步的主库的 replid 而不再是本身的 replid。replid2 则表示前一个主库的 replid，这个在主从角色切换的时候会用到。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">redisServer</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* Replication (master) */</span><span class=\"token plain\">                                        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> replid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">CONFIG_RUN_ID_SIZE</span><span class=\"token operator\">+</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* My current replication ID. */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> replid2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">CONFIG_RUN_ID_SIZE</span><span class=\"token operator\">+</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* replid inherited from master*/</span><span class=\"token plain\"> </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>在主库判断是否允许 psync 的判断条件也有了一些变化：</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 从库发送过来的 replid 是当前实例的 replid, 说明之前就是这个实例的从库</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 或者和该主库曾经属于同一主库可以但同步进度不能比当前主库还快</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">strcasecmp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">master_replid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">replid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\">                        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">strcasecmp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">master_replid</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">replid2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">||</span><span class=\"token plain\">                           </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">         psync_offset </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">second_replid_offset</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">                                </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> need_full_resync</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    ｝</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 判断同步进度是否已经超过范围</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog </span><span class=\"token operator\">||</span><span class=\"token plain\">                                                        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        psync_offset </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog_off </span><span class=\"token operator\">||</span><span class=\"token plain\">                                      </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        psync_offset </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog_off </span><span class=\"token operator\">+</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">repl_backlog_histlen</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\">                                                                                  </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">                                                           </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">replicationGetSlaveName</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> psync_offset</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">psync_offset </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">master_repl_offset</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_WARNING</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">replicationGetSlaveName</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">       </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> need_full_resync</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">  </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>从代码可以看到，主库判断条件相比之前版本多了一个 replid2 的判断。如果之前这两个曾经属于同一个主库(多级也允许)， 那么新主库的 replid2 就是之前主库的 replid。只要之前是同一主库且新主库的同步进度比这个从库还快就允许增量同步。当然前提也是新主从的写入落后不能超过 backlog 大小。</p><p>举个栗子，假设 A &lt;- B &lt;- C 这种部署结构来说， A 是 B 的主库，B 是 C 的主库。如果把 C 提成新的主库，C &lt;- A 以及 C &lt;- B 都可以增量同步，因为切换后 C 的 replid2 其实就是 A。</p><p>另外一方面，在做 rdb 备份的时候 replid 和 offset 会被持久化到 rdb 文件，也就是说甚至是服务重启了也可以进行增量同步，具体见 <code>rdbSaveInfoAuxFields</code> 函数实现。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4总结\">4）总结<a class=\"hash-link\" href=\"#4总结\" title=\"Direct link to heading\">​</a></h3><p>在主库有问题的时候想要把其中的一个从库提为主库，只要这个从库是这批从库之中同步最快的就其他从库切过来不需要全量同步数据。同时 rdb 里面还对 replid 和 offset 进行持久化，即使实例重启也可以做增量同步。有了这个优化之后之后切换的成本就大大降低了，服务也会更加平滑。</p><blockquote><p>NOTE: 博客这是记录自己当前阶段一些想法, 不保证完全正确。如果有误的地方，你倒是来打我啊?</p></blockquote>",
            "url": "https://hulkdev.com/posts-redis-new-sync",
            "title": "Redis 4.0 psync 优化",
            "summary": "上一篇介绍了 ，同时也提到 redis 4.0 一个比较大的改动就是 psync 优化, 本篇会介绍这个优化的部分。",
            "date_modified": "2017-01-21T00:00:00.000Z",
            "tags": [
                "Redis"
            ]
        },
        {
            "id": "posts-redis-module",
            "content_html": "<p>直到今天为止 (2017-01-17) Redis 4.0 已经发布了两个 rc 版本, 相比于上个版本(3.2)，这个版本的改动应该说是巨大的。主要有以下几个点:</p><ul><li>增加了模块的功能, 用户可以自己扩展命令和数据结构</li><li>psync 优化，避免主从切换过程需要重新全量同步</li><li>DEL, FLUSHALL/FLUSHDB异步化，不会阻塞主线程</li><li>RDB-AOF 混合持久化</li><li>新增 MEMORY 命令</li><li>集群兼容  NAT / Docker</li></ul><p>每个功能都很值得期待，本篇博客会重点来介绍 Redis 的模块功能。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-为什么引入模块\">1) 为什么引入模块<a class=\"hash-link\" href=\"#1-为什么引入模块\" title=\"Direct link to heading\">​</a></h3><p>下面是引用 Redis 作者的一段说明:</p><blockquote><p>「At the same time, years of experience with scripting, demonstrated that scripting is a way to “compose” existing features, but not a way to extend the capabilities of a system towards use cases it was not designed to cover.」</p></blockquote><p>当前虽然支持 lua 脚本来作为扩展但更多的只是功能的组合，也就是说从设计上 lua 脚本的方式并不能满足扩展的需求，如我们想加个数据结构或者命令之类。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-模块内部实现\">2) 模块内部实现<a class=\"hash-link\" href=\"#2-模块内部实现\" title=\"Direct link to heading\">​</a></h3><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"21模块加载\">2.1）模块加载<a class=\"hash-link\" href=\"#21模块加载\" title=\"Direct link to heading\">​</a></h4><p>Redis 通过 <code>module load</code> 命令来加载模块， 格式如下:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">MODULE LOAD </span><span class=\"token operator\">&lt;</span><span class=\"token plain\">path</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">args</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">..</span><span class=\"token plain\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>加载时指定模块(so文件)的路径以及加载的参数列表, 没有参数可以忽略。</p><p>我们来看看这个命令内部的实现模块加载的功能(module.c):</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">moduleLoad</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">const</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">path</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token plain\">module_argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> module_argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">*</span><span class=\"token plain\">onload</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">handle</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    RedisModuleCtx ctx </span><span class=\"token operator\">=</span><span class=\"token plain\"> REDISMODULE_CTX_INIT</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 加载指定路径的动态库</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    handle </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dlopen</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">path</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">RTLD_NOW</span><span class=\"token operator\">|</span><span class=\"token plain\">RTLD_LOCAL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">handle </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_WARNING</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Module %s failed to load: %s\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> path</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dlerror</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> C_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 检查是否有实现 RedisModule_OnLoad 函数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    onload </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dlsym</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">handle</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"RedisModule_OnLoad\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">onload </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_WARNING</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Module %s does not export RedisModule_OnLoad() \"</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"symbol. Module not loaded.\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">path</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> C_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 回调用户自定义的 RedisModule_OnLoad 函数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">onload</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">module_argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">module_argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">moduleFreeModuleStructure</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dlclose</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">handle</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_WARNING</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Module %s initialization failed. Module not loaded\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">path</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> C_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 把模块注册到模块列表，主要是方便后面卸载</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictAdd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">modules</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">name</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">handle </span><span class=\"token operator\">=</span><span class=\"token plain\"> handle</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">serverLog</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">LL_NOTICE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"Module '%s' loaded from %s\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">name</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">path</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">moduleFreeContext</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> C_OK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>模块加载的实现很简单，主要逻辑就是加载动态库并回调指定的函数。其他比如添加数据结构和命令之类就可以在这个回调函数里面来实现。最后再把模块添加到已加载的模块列表中。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"22-自定义命令\">2.2） 自定义命令<a class=\"hash-link\" href=\"#22-自定义命令\" title=\"Direct link to heading\">​</a></h4><p>接着看一下如何往 Redis 新增用户命令或者数据结构。</p><p>我们以 <code>modules/helloblock.c</code> 模块为例，上面已经说到模块加载的时候会回调用户实现的 <code>RedisModule_OnLoad</code>, 下面是该模块的实现:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModule_OnLoad</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">RedisModuleCtx </span><span class=\"token operator\">*</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> RedisModuleString </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">REDISMODULE_NOT_USED</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">REDISMODULE_NOT_USED</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 模块初始化函数，每个模块都必须调用</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModule_Init</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"helloblock\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">REDISMODULE_APIVER_1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">==</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 添加一个 hello.block 命令</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModule_CreateCommand</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"hello.block\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        HelloBlock_RedisCommand</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">==</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> REDISMODULE_OK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这个回调函数主要做了两个事情，一个是调用模块初始化函数, 这个函数会将 Redis 对外提供的接口暴露给模块来调用，每个模块加载时都必须调用。另外做一个事情就是我们这里要关心的，如何添加命令到 Redis。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RM_CreateCommand</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">RedisModuleCtx </span><span class=\"token operator\">*</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">const</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">name</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> RedisModuleCmdFunc cmdfunc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">const</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">strflags</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> firstkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> lastkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> keystep</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">redisCommand</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">rediscmd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    RedisModuleCommandProxy </span><span class=\"token operator\">*</span><span class=\"token plain\">cp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 忽略次要的代码路径</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">zmalloc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">sizeof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">*</span><span class=\"token plain\">cp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">module </span><span class=\"token operator\">=</span><span class=\"token plain\"> ctx</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">module</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 真正的处理函数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">func </span><span class=\"token operator\">=</span><span class=\"token plain\"> cmdfunc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">zmalloc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">sizeof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">*</span><span class=\"token plain\">rediscmd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">name </span><span class=\"token operator\">=</span><span class=\"token plain\"> cmdname</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 命令处理函数设置为 RedisModuleCommandDispatcher</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">proc </span><span class=\"token operator\">=</span><span class=\"token plain\"> RedisModuleCommandDispatcher</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">arity </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">flags </span><span class=\"token operator\">=</span><span class=\"token plain\"> flags </span><span class=\"token operator\">|</span><span class=\"token plain\"> CMD_MODULE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">getkeys_proc </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">redisGetKeysProc</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">cp</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">firstkey </span><span class=\"token operator\">=</span><span class=\"token plain\"> firstkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">lastkey </span><span class=\"token operator\">=</span><span class=\"token plain\"> lastkey</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">keystep </span><span class=\"token operator\">=</span><span class=\"token plain\"> keystep</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">microseconds </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">calls </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictAdd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">commands</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">sdsdup</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">cmdname</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">dictAdd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">server</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">orig_commands</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">sdsdup</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">cmdname</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">rediscmd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> REDISMODULE_OK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这个逻辑很简单，创建一个 <code>RedisModuleCommandProxy</code> 并把它的 <code>rediscmd</code> 作为命令的处理函数，也就是当有命令过来的时候会调用 <code>RedisModuleCommandDispatcher</code>, 然后它再来调用我们真正的处理函数。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModuleCommandDispatcher</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">client </span><span class=\"token operator\">*</span><span class=\"token plain\">c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    RedisModuleCommandProxy </span><span class=\"token operator\">*</span><span class=\"token plain\">cp </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">cmd</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">getkeys_proc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    RedisModuleCtx ctx </span><span class=\"token operator\">=</span><span class=\"token plain\"> REDISMODULE_CTX_INIT</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">module </span><span class=\"token operator\">=</span><span class=\"token plain\"> cp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">module</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">client </span><span class=\"token operator\">=</span><span class=\"token plain\"> c</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 调用真正的处理函数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cp</span><span class=\"token operator\">-&gt;</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">func</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">c</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">moduleHandlePropagationAfterCommandCallback</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">moduleFreeContext</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>为什么不直接回调处理函数而要加一层代理? 这个留给你们自己思考吧...\n另外眼尖的小伙伴也可能注意到我们调用的是 <code>RedisModule_CreateCommand</code>, 为什么变成 <code>RM_CreateCommand</code>? 这是因为 Redis 做了一层重命名。内部实现上是 <code>RM_</code> 开头, 对外暴露使用 <code>RedisModule_</code>, 这个是在 RedisModule_Init 里面做的。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"23-自定义数据结构\">2.3) 自定义数据结构<a class=\"hash-link\" href=\"#23-自定义数据结构\" title=\"Direct link to heading\">​</a></h3><p>我们也以具体模块(modules/hellotype.c)为例,</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModule_OnLoad</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">RedisModuleCtx </span><span class=\"token operator\">*</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> RedisModuleString </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">REDISMODULE_NOT_USED</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">argv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">REDISMODULE_NOT_USED</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">argc</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModule_Init</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"hellotype\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">REDISMODULE_APIVER_1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token operator\">==</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> REDISMODULE_ERR</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    RedisModuleTypeMethods tm </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">version </span><span class=\"token operator\">=</span><span class=\"token plain\"> REDISMODULE_TYPE_METHOD_VERSION</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">rdb_load </span><span class=\"token operator\">=</span><span class=\"token plain\"> HelloTypeRdbLoad</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">rdb_save </span><span class=\"token operator\">=</span><span class=\"token plain\"> HelloTypeRdbSave</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">aof_rewrite </span><span class=\"token operator\">=</span><span class=\"token plain\"> HelloTypeAofRewrite</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">free </span><span class=\"token operator\">=</span><span class=\"token plain\"> HelloTypeFree</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\">  </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    HelloType </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">RedisModule_CreateDataType</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">ctx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"hellotype\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">tm</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">HelloType </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> REDISMODULE_ERR</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>创建一个数据结构很简单，只要定义好几个回调函数即可。Redis 并不关心外部的数据是如何操作的(由命令决定)， 只需要关心数据要怎么来持久化以及释放即可。</p><p>更加详细的说明可以参照:</p><p><a href=\"https://github.com/antirez/redis/blob/unstable/src/modules/TYPES.md\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/antirez/redis/blob/unstable/src/modules/TYPES.md</a></p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"24-其他\">2.4) 其他<a class=\"hash-link\" href=\"#24-其他\" title=\"Direct link to heading\">​</a></h4><p>其他一些东西由于篇幅没有细说。接口详细说明见:</p><p><a href=\"https://github.com/antirez/redis/blob/unstable/src/modules/INTRO.md\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/antirez/redis/blob/unstable/src/modules/INTRO.md</a></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3总结\">3）总结<a class=\"hash-link\" href=\"#3总结\" title=\"Direct link to heading\">​</a></h3><p>我们可以看到 Redis 4.0 很简单就可以进行功能扩展，对 Redis 也没有侵入性。 同时模块是否稳定也直接影响到服务的质量。有了这个模块功能之后就可以支持很多不同业务的定制化数据结构，后面 Redis 的玩法也会越来越多。</p><p>另外除了模块功能之外，psync 优化，RDB-AOF 混合持久化以及一些命令的异步化还是很值得期待，接着也会来分析这几个新功能的实现。</p><p>参考链接:\n<a href=\"http://antirez.com/news/110\" target=\"_blank\" rel=\"noopener noreferrer\">http://antirez.com/news/110</a>\n<a href=\"http://antirez.com/news/106\" target=\"_blank\" rel=\"noopener noreferrer\">http://antirez.com/news/106</a></p>",
            "url": "https://hulkdev.com/posts-redis-module",
            "title": "Redis 4.0 模块功能",
            "summary": "直到今天为止 (2017-01-17) Redis 4.0 已经发布了两个 rc 版本, 相比于上个版本(3.2)，这个版本的改动应该说是巨大的。主要有以下几个点:",
            "date_modified": "2017-01-19T00:00:00.000Z",
            "tags": [
                "Redis"
            ]
        },
        {
            "id": "posts-opensource-intro-consumergroup",
            "content_html": "<p>知道或者熟悉 kafka(不是写小说的那个卡夫卡)， 那么一定知道它有 producer 和 consumer 这两种角色。producer 用来生产消息，consumer 用来消费消息。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-可用性\">1) 可用性<a class=\"hash-link\" href=\"#1-可用性\" title=\"Direct link to heading\">​</a></h3><p>下面是来自维基百科的解释:</p><blockquote><p>在一个给定的时间间隔内，对于一个功能个体来讲，总的可用时间所占的比例</p></blockquote><p>比如我们以年为单位来量化一个服务的可用性。假设一年 365 天当中有 364 天服务是正常服务的，那么我们就说这个服务的可用性是 364/365(用计算器口算了一下约 99.72%)。</p><p>我们常用几个 9 来衡量一个服务的可用性，两个 9 就是 99.99%，三个 9 即 99.999%, 四个 9 即 99.9999% ... 以此类推。</p><table><thead><tr><th>可用性</th><th>每年宕机时间</th></tr></thead><tbody><tr><td>99.9%</td><td>8 个小时</td></tr><tr><td>99.99%</td><td>1 个小时</td></tr><tr><td>99.999%</td><td>5 分钟</td></tr><tr><td>99.9999%</td><td>30 秒</td></tr></tbody></table><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2消费者的可用性\">2）消费者的可用性<a class=\"hash-link\" href=\"#2消费者的可用性\" title=\"Direct link to heading\">​</a></h3><p>一般来说 producer 是嵌入到业务程序，那么可用性就由业务程序来保证。而 consumer 一般就是以独立的程序存在，那么就要自己来保证。</p><p>所以想让 consumer 做到 99.99% 以上的可用性，意味着一年内服务挂掉的时间不能超过一个小时。假设我们没有实现一些高可用的机制，部分 consumer 在半夜挂了，而你(或者运维)刚好干完一些不可描述的事情之后倒头大睡而没有注意到报警，这个系统的可用性就达不到要求。</p><p>当前 scala, java, golang, c 版本的做法都是监听 group 的 consumer 列表，如果有 consumer 进入或者退出都会触发重新分配分区，把分区均衡到各个 consumer。所以理论上我们 php 版本也可以这样做。现在已有的开源里面有 kafka 和 zookeeper 的客户端扩展和依赖库，但没有实现自动平衡的逻辑（也可能是我没看到), 所以这部分需要自己来做。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-尴尬的-php\">3) 尴尬的 php<a class=\"hash-link\" href=\"#3-尴尬的-php\" title=\"Direct link to heading\">​</a></h3><p>这里必须先承认 php 是世界上最好的语言。</p><p>php 要实现 consumer 的高可用有三中选择:</p><ul><li>开启 php 线程扩展</li><li>c 实现 group 逻辑</li><li>不使用多线程，边消费边监控</li></ul><p>第一种方案，因为我们线上 php 环境都是没有打开线程安全, 所以如果要使用这个扩展需要重新编译 php 核心代码并重启所有服务，这个基本是无法接受的。</p><p>第二种方案，可以不用重新编译 php, 性能好。但开发成本比较高，风险大。</p><p>第三种方案, 纯 php 实现，代码简单可控，但性能会比较差一些。</p><p>最后我们选择了第三种方案，单进程空跑(只拉消息不处理)的性能是 7w+/s， 这个是可以接受的。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4-功能和代码说明\">4) 功能和代码说明<a class=\"hash-link\" href=\"#4-功能和代码说明\" title=\"Direct link to heading\">​</a></h3><ul><li>分区变化时可自动重新分配分区</li><li>消费进程退出或者加入时可自动重新分配分区</li><li>自动管理 offset</li><li>兼容标准的 consumer group 路径，方便已有的工具监控</li><li>接收用户信号，平滑进入和退出 group</li><li>允许冗余的消费进程作为备份</li></ul><p>github 地址: <a href=\"https://github.com/meitu/php-kafka-consumer\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/meitu/php-kafka-consumer</a></p><p>当前我们公司(美图)内部已经有不少业务已经在线上使用，当前版本已经比较稳定。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"5最后\">5）最后<a class=\"hash-link\" href=\"#5最后\" title=\"Direct link to heading\">​</a></h3><p>前一段时间发现线上 consumer 内存不断上升的情况，经排查，最终定位并验证是依赖库的 php-zookeeper 有内存泄漏。现在已经反馈以及合并到社区的 master, 具体见 <a href=\"https://github.com/php-zookeeper/php-zookeeper/pull/5\" target=\"_blank\" rel=\"noopener noreferrer\">pr</a>。</p><p>如果使用 release(建议) 版本的 php-zookeeper, 需要手动 patch 这个 bug，否则会造成内存泄漏。</p><p>如果有问题或者任何意见，欢迎 issue 或者 pr。</p>",
            "url": "https://hulkdev.com/posts-opensource-intro-consumergroup",
            "title": "php consumergroup 介绍",
            "summary": "知道或者熟悉 kafka(不是写小说的那个卡夫卡)， 那么一定知道它有 producer 和 consumer 这两种角色。producer 用来生产消息，consumer 用来消费消息。",
            "date_modified": "2016-12-27T00:00:00.000Z",
            "tags": [
                "PHP",
                "Kafka"
            ]
        },
        {
            "id": "posts-intro-tcpkit",
            "content_html": "<p>一年即将过去，翻了一下博客发现更新频率比月经还来得稀疏，内疚到前列腺都萎缩了。</p><p>转入正题，本篇博客主要是分享一个自己日常用的比较多工具 tcpkit， 该工具用途主要是用来抓包和快速的分析数据包。</p><p>代码地址: <a href=\"https://github.com/git-hulk/tcpkit\" target=\"_blank\" rel=\"noopener noreferrer\">git-hulk/tcpkit</a></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-为什么要用它\">1) 为什么要用它<a class=\"hash-link\" href=\"#1-为什么要用它\" title=\"Direct link to heading\">​</a></h3><p>现在抓包不有 tcpdump 么? 分析包不是有很牛逼和方便的 wireshark 么? 当然我也不是闲到前列腺发炎，浪费时间造出这么个正方形的轮子。</p><p>造这个轮子的原因是这样，我们偶尔会遇到线上访问资源(redis, mc..) 出现耗时比较长的情况。如果是 tcpdump 可以用来抓包，但如果请求量很大又是偶发，我要这么知道哪个连接慢了? 这个时候 wireshark 也是帮不上忙的(因为它不知道哪个数据是请求，那个数据包是响应，也就没办法统计延时)。</p><p>这时候 tcpkit 就可以派上用场了。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-初见\">2) 初见<a class=\"hash-link\" href=\"#2-初见\" title=\"Direct link to heading\">​</a></h3><p>tcpkit 的初衷:「 让使用者可以想分析普通内存数据一样，可以写代码轻松的分析 udp/tcp 包 」</p><p>比如，我们默认的脚本就是 1 行 lua 代码就可以打印数据包内容，具体见 <a href=\"https://github.com/git-hulk/tcpkit/blob/master/scripts/example.lua\" target=\"_blank\" rel=\"noopener noreferrer\">example.lua</a>:</p><div class=\"language-lua codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-lua codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">function process_packet(item)</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    -- sync,ack 这种包长度为 0， 我们忽略掉</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    if item.len &gt; 0 then</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        print(item.payload)</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    end</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">end</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>每抓到一个数据包都会回调 <code>process_packet</code>, 然后把数据包内容传递进来，这样用户使用 lua 代码来定制分析的需求了。像上面的回调函数，就是什么都不分析就看看内容。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-上手\">3) 上手<a class=\"hash-link\" href=\"#3-上手\" title=\"Direct link to heading\">​</a></h3><p>那么问题来了，我要怎么玩这个东西呢。按照下面来就可以了...</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$ </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">git</span><span class=\"token plain\"> clone https://github.com/git-hulk/tcpkit.git /* 首先</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">..</span><span class=\"token plain\">. 我得先拿到代码*/</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$ </span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">cd</span><span class=\"token plain\"> tcpkit/src</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">$ </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">make</span><span class=\"token plain\"> /* </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">make</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">install</span><span class=\"token plain\"> 是可选，要不要安装到标准的路径  */</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>本目录就生成了一个 tcpkit 的二进制文件，那么你就可以开始玩它了，姿势可以参考 <a href=\"https://github.com/git-hulk/tcpkit\" target=\"_blank\" rel=\"noopener noreferrer\">README</a></p><p>主要的几个参数说明:</p><blockquote><p>-i 指定抓包的网卡，默认是 any，就是所有网卡，但有些系统不支持</p></blockquote><blockquote><p>-p 指定抓包的端口</p></blockquote><blockquote><p>-s 如果是在客户端抓包，可以定向抓到某个服务的包</p></blockquote><blockquote><p>-S 指定处理脚本路径，固定回调 process_packet 这个函数</p></blockquote><p>我们用 redis/mc 延时统计的脚本作为例子，要客户端看看请求 Redis 到返回耗时多长时间。</p><p>假设 redis ip: 192.168.1.1, 端口: 6379, 客户端: 192.168.1.2， 那么就就下面这样启动:</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">sudo ./tcpkit -s 192.168.1.1 -p 6379 -S ../scripts/redis_mc_monitor.lua</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>结果大概如下(ip端口是不对应的):</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token number\">2020</span><span class=\"token plain\">-02-06 </span><span class=\"token number\">23</span><span class=\"token plain\">:01:39.706782 </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:54484 </span><span class=\"token operator\">=</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:6379 </span><span class=\"token operator\">|</span><span class=\"token plain\"> </span><span class=\"token number\">0.270</span><span class=\"token plain\"> ms </span><span class=\"token operator\">|</span><span class=\"token plain\"> GET test_key </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">2020</span><span class=\"token plain\">-02-06 </span><span class=\"token number\">23</span><span class=\"token plain\">:01:39.712727 </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:54540 </span><span class=\"token operator\">=</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:6379 </span><span class=\"token operator\">|</span><span class=\"token plain\"> </span><span class=\"token number\">0.012</span><span class=\"token plain\"> ms </span><span class=\"token operator\">|</span><span class=\"token plain\"> PING </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">2020</span><span class=\"token plain\">-02-06 </span><span class=\"token number\">23</span><span class=\"token plain\">:01:40.004345 </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:54554 </span><span class=\"token operator\">=</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">127.0</span><span class=\"token plain\">.0.1:6379 </span><span class=\"token operator\">|</span><span class=\"token plain\"> </span><span class=\"token number\">0.187</span><span class=\"token plain\"> ms </span><span class=\"token operator\">|</span><span class=\"token plain\"> SET a b </span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>第一列是响应的时刻，第二列和第三列分别是客户端和服务端连接的 ip 和端口，第三列是延时，最后一列是请求</p><p>如果跑在服务端，统计就是服务端处理的时间，耗时是包进来到出去的时间差。</p><p>NOTE: 在服务端，不需要指定 -s 这个 server ip, 只需要指定端口和脚本</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4-简单统计原理\">4) 简单统计原理<a class=\"hash-link\" href=\"#4-简单统计原理\" title=\"Direct link to heading\">​</a></h3><blockquote><p>Q: 请求和响应要怎么对应起来? </p></blockquote><p>A: 这个问题如果是变成怎么知道两个数据包是来自同一个连接就简单多了。</p><p>「 根据 tcp/ip 的ip和端口四元组就可以确定数据包是否同一个连接了 」</p><blockquote><p>Q: 请求和响应怎么对应?</p></blockquote><p>对于 Redis/mc 这种，对于同一个连接都是发出请求，然后必须等待响应才能下一个请求(不考虑 redis 的 pipeline)，那这个就好办了。</p><p>第一种情况: 在客户端抓包，如果源 ip 为本地ip, 那么说明这个数据包是发出去的，也就是请求数据包。反过来就是响应包。</p><p>第二种情况: 在服务端抓包，跟上面刚好相反，如果是源 ip 是本地 ip, 说明数据是出去的，也就是响应包。</p><p>那么只要知道是同一个连接，而且知道请求还是响应那么就可以统计延时了。上面说到如果是 redis pipeline 这种一来一往的数据包就要想其他方法了。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"5-脚本\">5) 脚本<a class=\"hash-link\" href=\"#5-脚本\" title=\"Direct link to heading\">​</a></h3><p>这个不需要深入了解 lua, 只要简单会应用就可以。<code>tcpkit/scripts</code> 目录提供了 <code>rdis/mc/dns/kafka</code> 的延时统计脚本，kafka 还有些问题，待修改。</p><p>如果是私有协议或者其他更多复杂的协议，需要自己实现。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"6-end\">6) END<a class=\"hash-link\" href=\"#6-end\" title=\"Direct link to heading\">​</a></h3><p>是否还有其他姿势？ 欢迎分享。</p><p>最后祝玩得开心。</p><p>代码地址: <a href=\"https://github.com/git-hulk/tcpkit\" target=\"_blank\" rel=\"noopener noreferrer\">git-hulk/tcpkit</a></p>",
            "url": "https://hulkdev.com/posts-intro-tcpkit",
            "title": "tcpkit 介绍",
            "summary": "一年即将过去，翻了一下博客发现更新频率比月经还来得稀疏，内疚到前列腺都萎缩了。",
            "date_modified": "2016-11-17T00:00:00.000Z",
            "tags": [
                "linux",
                "tool",
                "tcpkit"
            ]
        },
        {
            "id": "posts-thinking-in-beanstalkd",
            "content_html": "<p>beanstalkd 是单机版本的任务队列服务, 任务队列跟消息队列在使用场景上最大的区别是： 任务之间是没有顺序约束而消息要求顺序(FIFO)，且可能会对任务的状态更新而消息一般只会消费不会更新。 类似 Kafka 利用消息 FIFO 和不需要更新(不需要对消息做索引)的特性来设计消息存储，将消息读写变成磁盘的顺序读写来实现比较好的性能。而任务队列需要能够任务状态进行更新则需要对每个消息进行索引，如果把两者放到一起实现则很难实现在功能和性能上兼得。在美图内部选型上，如果是异步消息模型一般会选择消息队列，比如类似日志上报，抢购等。而对于需要延时/定时下发或者修改状态任务则是使用任务队列。</p><p>比如在以下几种场景会使用任务队列:\n定时任务，如每天早上 8 点开始推送消息，定期删除过期数据等\n任务流，如自动创建 Redis 流程由资源创建，资源配置，DNS 修改等部分组成，使用任务队列可以简化整体的设计和重试流程\n重试任务，典型场景如离线图片处理</p><p>目前开源任务队列并不多, 比如原生支持任务队列语义比较知名只有类似 disque, beanstalkd。 公司部分场景比较适合引入任务队列，所以我们从整体上来看当前已有的一些开源产品的设计和实现。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"1-初识\">1) 初识<a class=\"hash-link\" href=\"#1-初识\" title=\"Direct link to heading\">​</a></h3><p>先从几个大的层面来看一下 beanstalkd，再来看内部的实现细节:</p><ol><li>协议，类 Memcached 协议, 非二进制安全</li><li>全内存, 可开启 binlog, 断电从 binlog 恢复数据</li><li>单线程, 使用 epoll/kqueue 来实现事件机制</li></ol><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-几个概念\">2) 几个概念<a class=\"hash-link\" href=\"#2-几个概念\" title=\"Direct link to heading\">​</a></h3><ul><li>tube - 消息通道，类似于 kafka 里面的 topic, 用来存储某一类或者业务的任务</li><li>job  - 生产和消费的基本单元，每个 job 都会有一个 id 和 优先级</li></ul><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-状态迁移\">3) 状态迁移<a class=\"hash-link\" href=\"#3-状态迁移\" title=\"Direct link to heading\">​</a></h3><p>一个 job 的状态可能是 DELAYED, READY, RESERVED, BURIED 其中之一，状态之间可以互相迁移。</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   //------------------- 状态图来自官方文档 -------------------//</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   put with delay               release with delay</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  ----------------&gt; [DELAYED] &lt;------------.</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                        |                   |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                 kick   | (time passes)     |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                        |                   |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">   put                  v     reserve       |       delete</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  -----------------&gt; [READY] ---------&gt; [RESERVED] --------&gt; *poof*</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       ^  ^                |  |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |   \\  release      |  |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |    `-------------'   |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |                      |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       | kick                 |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |                      |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |       bury           |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                    [BURIED] &lt;---------------'</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                       |  delete</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                        `--------&gt; *poof*</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"31-生产\">3.1 生产<a class=\"hash-link\" href=\"#31-生产\" title=\"Direct link to heading\">​</a></h4><p>生产者通过 <code>PUT</code> 命令来产生一条消息, 命令格式如下:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">put </span><span class=\"token operator\">&lt;</span><span class=\"token plain\">pri</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token operator\">&lt;</span><span class=\"token plain\">delay</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token operator\">&lt;</span><span class=\"token plain\">ttr</span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token operator\">&lt;</span><span class=\"token plain\">bytes</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">\\</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">\\</span><span class=\"token plain\">n</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token operator\">&lt;</span><span class=\"token plain\">data</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">\\</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">\\</span><span class=\"token plain\">n</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><ol><li>delay = 0，进入就绪(READY)队列, 可以直接被消费。</li><li>dealy &gt; 0, 进入延时队列(DELAYED), 等到延时时间到了之后自动迁移就绪队列。</li></ol><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"32-消费\">3.2 消费<a class=\"hash-link\" href=\"#32-消费\" title=\"Direct link to heading\">​</a></h4><p>消费者通过 <code>RESERVE</code> 命令从就绪队列取出一个任务, 格式如下:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">reserve</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">\\</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">\\</span><span class=\"token plain\">n</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>任务状态会从 READY 变为 RESERVED(预定)，其他人就无法获取。 PUT 产生消息的时候，携带了 ttr(time to run)，如果这个时间内，消费者没有发送 delete, release 或者 buried 命令。 任务会自动回到 READY 状态，其他人可以继续获取。</p><p>我们从状态图中可以看到：</p><ol><li>消费者返回 delete 命令，这个任务就从此消失</li><li>消费者返回 buried 命令, 这个任务就进入休眠状态</li><li>消费者返回 release 命令或者不返回，就回到 READY/DELAYED 状态，可以重新被消费</li></ol><p>休眠(BURIED)状态的任务，可以通过 kick 命令让任务回到 READY 队列中去。</p><p>具体的协议请移步官方文档: <a href=\"https://github.com/kr/beanstalkd/blob/master/doc/protocol.txt\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/kr/beanstalkd/blob/master/doc/protocol.txt</a></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4-内部实现\">4) 内部实现<a class=\"hash-link\" href=\"#4-内部实现\" title=\"Direct link to heading\">​</a></h3><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"41-tube\">4.1) tube<a class=\"hash-link\" href=\"#41-tube\" title=\"Direct link to heading\">​</a></h4><p>上面说到 beanstalkd 可以根据消息类型或者业务拆分成多个通道(tube), 用户可以使用 <code>use tube_name</code> 来进行切换，如果没有这个 tube 就直接创建。beanstalkd 内部使用一个数组来保存所有的 tubes, 结构见 <code>struct ms</code>。</p><p>我们下面来看一下 tube 结构里面有哪些东西:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">tube</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    uint refs</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// tube 当前被引用的次数                        </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> name</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">MAX_TUBE_NAME_LEN</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// tube 名称, 最长 200byte</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    Heap ready</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 保存就绪队列的最小堆                        </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    Heap delay</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 保存延时队列的最小堆                       </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">ms</span><span class=\"token plain\"> waiting</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 正在使用 tube 的连接    </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">stats</span><span class=\"token plain\"> stat</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// tube 对应的统计项</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    uint using_ct</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// tube using 使用次数   </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    uint watching_ct</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 被watch的次数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    int64 pause</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// tube 是否整个被延时  </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    int64 deadline_at</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// tube 延时截止时间 </span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">job</span><span class=\"token plain\"> buried</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// buried 队列</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这里可以看到每个 tube 里面都包含了就绪, 延时和休眠三种队列。我们上面说了每个 job 的状态可能是 DELAYED/READY/RESERVED/BURIED， 那么 RESERVED 状态的 job 是保存在那里呢？ 每个连接结构里面会有一个 RESERVED 链表用来保存当前连接预取的所有 job。</p><p>其中 READY/DELAYED 队列实现都是最小堆，而 BURIED/RESERVED 是普通的链表，job 根据不同的状态会在这四个队列中迁移。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"42-最小堆\">4.2) 最小堆<a class=\"hash-link\" href=\"#42-最小堆\" title=\"Direct link to heading\">​</a></h4><p>READY/DELAYED 队列采用最小堆，下面分别是两个队列的比较方法:</p><ul><li>就绪队列最小堆比较方法:</li></ul><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">job_pri_less</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">ax</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">bx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    job a </span><span class=\"token operator\">=</span><span class=\"token plain\"> ax</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> b </span><span class=\"token operator\">=</span><span class=\"token plain\"> bx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 最小堆比较方法, 先比较优先级再比较 id</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">a</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">pri </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> b</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">pri</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">a</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">pri </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> b</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">pri</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> a</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">id </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> b</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><blockquote><p>先比较优先级，再比较 job id，所以当我们想实现优先级队列的时候，只需要设置优先级。pri 值越小优先级越高。如果我们想利用 beanstalkd 作为普通的先进先出队列，把优先级都设置为一样即可，消费的时候就会根据 job id 出队。</p></blockquote><ul><li>延时队列的最小堆比较方法</li></ul><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">job_delay_less</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">ax</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">bx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    job a </span><span class=\"token operator\">=</span><span class=\"token plain\"> ax</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> b </span><span class=\"token operator\">=</span><span class=\"token plain\"> bx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">a</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">deadline_at </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> b</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">deadline_at</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">a</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">deadline_at </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> b</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">deadline_at</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> a</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">id </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> b</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><blockquote><p>先比较延时截止时间，再比较 job id。</p></blockquote><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"43-状态迁移\">4.3) 状态迁移<a class=\"hash-link\" href=\"#43-状态迁移\" title=\"Direct link to heading\">​</a></h4><p>再获取网络事件之前，都会调用 <code>prottick</code> 方法来做一些常规的的状态迁移。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">srvserve</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">Server </span><span class=\"token operator\">*</span><span class=\"token plain\">s</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">for</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 实现一些状态迁移检查</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        period </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">prottick</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">s</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 获取下一个处理的事件</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> rw </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">socknext</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">sock</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> period</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">rw </span><span class=\"token operator\">==</span><span class=\"token plain\"> </span><span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">twarnx</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"socknext\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">exit</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 回调处理</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">rw</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            sock</span><span class=\"token operator\">-&gt;</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">f</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sock</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">x</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> rw</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">int64</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">prottick</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">Server </span><span class=\"token operator\">*</span><span class=\"token plain\">s</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 这个循环检查延时队列是否有 job 截止时间到了，是的话迁移到就绪队列</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果大量的 delay 变成ready 会导致其他请求得不到响应而超时</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    now </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">nanoseconds</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">while</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">j </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">delay_q_peek</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        d </span><span class=\"token operator\">=</span><span class=\"token plain\"> j</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">deadline_at </span><span class=\"token operator\">-</span><span class=\"token plain\"> now</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">d </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            period </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">min</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">period</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> d</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">break</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 返回延时队列第一个</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        j </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">delay_q_take</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// job 进入就绪处理</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        r </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">enqueue_job</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">s</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// OOM?</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">r </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">bury_job</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">s</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* out of memory, so bury it */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>prottick 里面除了检查延时队列，还检查整个 tube 的延时截止时间是否已经到以及连接是否等待超时等等。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"43-job-查找\">4.3) job 查找<a class=\"hash-link\" href=\"#43-job-查找\" title=\"Direct link to heading\">​</a></h4><p>为了快速查找一个 job, 内部采用 hashtable 来存放 job。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 根据 id 查找对应的任务</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">job</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">job_find</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">uint64 job_id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    job jh </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 根据job id 取模获取 bucket 下标</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> index </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">_get_job_hash_index</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job_id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 链表查找</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">for</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">jh </span><span class=\"token operator\">=</span><span class=\"token plain\"> all_jobs</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token plain\">index</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> jh </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\"> jh</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">r</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">id </span><span class=\"token operator\">!=</span><span class=\"token plain\"> job_id</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> jh </span><span class=\"token operator\">=</span><span class=\"token plain\"> jh</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ht_next</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> jh</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>当 hashtable 元素超过 bucket 的 4倍的时候，会进行 Rehash 来扩容。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">static</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">store_job</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">job j</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* accept a load factor of 4 */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 一次性 rehash 可能导致访问毛刺点</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">all_jobs_used </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">all_jobs_cap </span><span class=\"token operator\">&lt;&lt;</span><span class=\"token plain\"> </span><span class=\"token number\">2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">rehash</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这里有两个问题:</p><ol><li>当前只实现了扩容没有缩容，这个有人提了 pr, 后续版本应该会解决</li><li>一次性 rehash, hashtable 数据比较多的时候，迁移时间会比较久，产生访问毛刺点</li></ol><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"5-一些问题\">5) 一些问题<a class=\"hash-link\" href=\"#5-一些问题\" title=\"Direct link to heading\">​</a></h3><p>从代码层面来看，beanstalkd 通过几千行 C 代码实现了一个优先级/延时队列，这个实在是很 cool，但问题还是有不少。</p><ol><li>无最大内存控制, 如果有消息堆积或者业务使用方式有误，而导致内存暴涨拖垮机器</li><li>上面说的 Rehash 导致访问变长，甚至产生大量连接超时</li><li>部分操作无时长控制，可能导致大量连接超时。 如事件查询之前的常规检查方法 <code>prottick</code>， 以及 <code>kicked</code> 命令无控制 count 大小。</li><li>不少代码不够精简， 比如回放时读取 job 的方法，两个不同版本读取方法实际上差别不大</li><li>跟 mc 类似，没有 master-slave 方式，需要自己解决单点问题</li></ol><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"6-end\">6) END<a class=\"hash-link\" href=\"#6-end\" title=\"Direct link to heading\">​</a></h3><p>如果是有优先级/延时任务的需求的话, beanstalkd 是个不错选择。如果作为常规的先进先出队列来说，以性能和稳定来说 kafka/redis 会是更好的选择，redis 本身也是全内存，队列操作 O(1), 而 benastalkd 是 log(n)。redis 也更加成熟和稳定，同时支持本地持久化和主从。</p><p>另外有一个加分项是 beanstalkd 作者本身比较活跃，之前提了一个 pr, 当天就得到回馈，这也是作为开源项目选择一个很重要的因素。</p>",
            "url": "https://hulkdev.com/posts-thinking-in-beanstalkd",
            "title": "beanstalkd 设计与实现",
            "summary": "beanstalkd 是单机版本的任务队列服务, 任务队列跟消息队列在使用场景上最大的区别是： 任务之间是没有顺序约束而消息要求顺序(FIFO)，且可能会对任务的状态更新而消息一般只会消费不会更新。 类似 Kafka 利用消息 FIFO 和不需要更新(不需要对消息做索引)的特性来设计消息存储，将消息读写变成磁盘的顺序读写来实现比较好的性能。而任务队列需要能够任务状态进行更新则需要对每个消息进行索引，如果把两者放到一起实现则很难实现在功能和性能上兼得。在美图内部选型上，如果是异步消息模型一般会选择消息队列，比如类似日志上报，抢购等。而对于需要延时/定时下发或者修改状态任务则是使用任务队列。",
            "date_modified": "2016-04-16T00:00:00.000Z",
            "tags": [
                "Queue",
                "Beanstalkd"
            ]
        },
        {
            "id": "posts-getaddressinfo-cause-unbalance",
            "content_html": "<p>DBA 发现同一组 Redis 从库中有实例 QPS 比较高，对比发现只是其中一个从库偏高而其他从库是正常的，分布如下:</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/getaddressinfo-redis-unbalance.jpeg\" alt=\"image\" class=\"img_ev3q\"></p><p>那么问题就是: 「为什么会 QPS 不均匀?」, 由于我们先上 php 业务都是长连接，QPS 不均匀应该是连接数不均带来的。然后让运维大侠统计了一下四个实例的连接数，发现确实请求量跟连接数是成线性正相关的。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-连接数为何不均匀\">2) 连接数为何不均匀?<a class=\"hash-link\" href=\"#2-连接数为何不均匀\" title=\"Direct link to heading\">​</a></h3><p>因为这四个实例是通过域名来访问，理论上连接数应该是要比较均匀(DNS ip 轮循)。\n接着当然是统计多出来的连接数是来自哪些 ip。使用 <code>lsof</code> 或者 redis 的 <code>client list</code> 都可以得到。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"为什么要统计-ip-呢\">为什么要统计 ip 呢<a class=\"hash-link\" href=\"#为什么要统计-ip-呢\" title=\"Direct link to heading\">​</a></h4><ol><li>确定是不是有业务有固定 ip 直连导致</li><li>确定是不是所有业务机器连接资源不均匀还是个别机器</li></ol><p>统计完发现，第一个实例比其他实例多出来的 ip, 是来自部分业务机器。</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token number\">192.168</span><span class=\"token plain\">.7.213</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">192.168</span><span class=\"token plain\">.7.218</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">192.168</span><span class=\"token plain\">.7.217</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">192.168</span><span class=\"token plain\">.7.216</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>而且发现这几个 ip 只连第一个实例。 这说明连接数不均匀是这几个 ip 固定连接第一个实例导致的。</p><p><strong><em>后面就围绕 DNS 和 php 连接机制展开排查</em></strong></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-排查\">3) 排查<a class=\"hash-link\" href=\"#3-排查\" title=\"Direct link to heading\">​</a></h3><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"31-dns-返回问题\">3.1) DNS 返回问题？<a class=\"hash-link\" href=\"#31-dns-返回问题\" title=\"Direct link to heading\">​</a></h4><p>第一件事情先排除是不是 DNS 返回结果不正确导致。使用 <code>nslookup</code> 查看，发现结果列表每次都会进行 ip 轮循，所以排除 DNS 返回不正确导致。那么就回到是不是 php 使用姿势的问题。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"32-gethostbyname-导致的\">3.2) gethostbyname 导致的?<a class=\"hash-link\" href=\"#32-gethostbyname-导致的\" title=\"Direct link to heading\">​</a></h4><p>既然 DNS 返回结果没有问题，那么很可能就是 php 内部通过域名转换 ip 时固定返回某一个 ip。因为没有去看 phpredis 的实现，所以先假设是通过 <code>gethostbyname</code> 来获取ip, 这个很容易验证。 通过执行下面代码，发现结果是会进行 ip 轮循。</p><div class=\"language-php codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-php codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">php -r 'var_dump(gethostbyname(\"\"s2000.redis.com.cn.xxx\"))'</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>到这里因为 <code>gethostbyname</code> 没有问题，那么就猜测是不是使用其他的方式获取ip? </p><p>因为业务方在 <code>phpredis</code> 连接资源时，是直接使用域名。而 phpredis 会使用 php 源码的 <code>php_stream_xport_create</code> 进行建立连接。而这个函数在建立连接，是通过 <code>php_network_getaddresses</code> 来获取到 ip 列表, 然后拿到列表的第一个进行连接。</p><p>这个跟观察到的只会连接到同一个ip的规律是一致的，所以我们只要来看他是通过什么方法获取 ip 列表。</p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"33-getaddrinfo-的锅\">3.3) getaddrinfo 的锅?<a class=\"hash-link\" href=\"#33-getaddrinfo-的锅\" title=\"Direct link to heading\">​</a></h4><p>我们看到 php_network_getaddresses 实现是这样的(见php-src/main/network.c):</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"> PHPAPI </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_network_getaddresses</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">const</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">host</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> socktype</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">sockaddr</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token plain\">sal</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">char</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token operator\">*</span><span class=\"token plain\">error_string TSRMLS_DC</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token macro property\"> </span><span class=\"token macro property expression\">HAVE_GETADDRINFO</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">n </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">getaddrinfo</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">host</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">hints</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">res</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\">  </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    host_info </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">gethostbyname</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">host</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">  </span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">endif</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>使用 <code>HAVE_GETADDRINFO</code> 来决定使用 getaddrinfo 还是 gethostbyname 来获取ip, 这个选项是在 configure 阶段，通过判断操作系统是否支持 getaddrinfo 来自动开启。因为我们线上机器都支持这个函数，而我们也验证过 gethostbyname 返回结果是正常的。</p><p><strong><em>到了这里就是 getaddrinfo 背上了这个锅</em></strong></p><p>这个验证也很简单，直接调用 getaddrinfo 看返回结果, 代码如下:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    n </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">getaddrinfo</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"s2000.zw.rediscounter.m.com\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">hints</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">res</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">res</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">do</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">res</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ai_family </span><span class=\"token operator\">==</span><span class=\"token plain\"> AF_INET</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            sinp </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">sockaddr_in</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\">res</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ai_addr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            addr </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">inet_ntop</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">AF_INET</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sinp</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">sin_addr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> abuf</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\">INET_ADDRSTRLEN</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">printf</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"%s\\n\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> addr</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">while</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">res </span><span class=\"token operator\">=</span><span class=\"token plain\"> res</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ai_next</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">!=</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>然后拿到有问题的机器跑了一下，果然取到的 ip 列表是固定的，然而在那批没问题的机器上拿到 ip 列表是会进行轮循。所以已经确定是 getaddrinfo 函数导致。</p><hr><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4-尝试解决\">4) 尝试解决<a class=\"hash-link\" href=\"#4-尝试解决\" title=\"Direct link to heading\">​</a></h3><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"41-glibc-版本问题\">4.1) glibc 版本问题？<a class=\"hash-link\" href=\"#41-glibc-版本问题\" title=\"Direct link to heading\">​</a></h4><p>现在的问题就是为什么同一函数在不同机器上面表现不一样，所以先检查内核版本和 glibc 版本是否一致。发现有问题的这批机器 glibc 版本比较低，所以有理由怀疑是否为老版本的 bug (后面万万没想到是否 feature..)。 所以就打算先升级一台有问题的机器来看看，然后再来仔细对比实现。</p><p>不幸的是升级后发现问题仍然存在。</p><p><img loading=\"lazy\" src=\"http://hulkdev-hulkimgs.stor.sinaapp.com/imgs/getaddrinfo/mengbi.jpg\" alt=\"image\" class=\"img_ev3q\"></p><h4 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"42-rfc3484_sort-惹的祸\">4.2) rfc3484_sort 惹的祸<a class=\"hash-link\" href=\"#42-rfc3484_sort-惹的祸\" title=\"Direct link to heading\">​</a></h4><p>实在没办法只能去翻 glibc 的代码, 发现这个函数确实是会对返回的 ip 列表进行排序，具体实现见 <code>glibc-2.20/sysdeps/posix/getaddrinfo.c</code>。</p><div class=\"codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">qsort_r (order, nresults, sizeof (order[0]), rfc3484_sort, &amp;src);</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>排序算使用的是 <code>rfc3484_sort</code>, 内部比较方法应该是根据 rfc3484 的特定规则做比较，直接看会浪费时间，直接搜索找到对应 rfc 说明( <a href=\"http://www.ietf.org/rfc/rfc3484.txt\" target=\"_blank\" rel=\"noopener noreferrer\">http://www.ietf.org/rfc/rfc3484.txt</a>)。</p><p>这个函数是为了解决 ipv6 多播地址绑定到单网卡的问题，函数会从 rules 1-10 逐条比较。rules 10 是保持 DNS 返回顺序，也就是我们希望的。 篇幅问题这里不在细说规则，返回结果因为算法固定，所以顺序也不会改变。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"5-真正解决\">5) 真正解决<a class=\"hash-link\" href=\"#5-真正解决\" title=\"Direct link to heading\">​</a></h3><ol><li>业务连接资源使用 gethostbyname 获取ip, 而不是使用 php 源码实现的规则。</li><li>关闭 getaddrinfo 排序算法</li></ol><p>因为第一种方案需要业务方修改代码(尽管只有一两行)， 所以我们选择第二种方案。因为从代码层面很难看出来哪个参数可以关闭。后面搜索找到相应的讨论(问题缩小就能快速找到解决方法)，快速的解决方法是关闭 ipv6, 当然前提是线上服务不能有使用到这个特性。</p><p><a href=\"https://groups.google.com/forum/#!topic/consul-tool/AGgPjrrkw3g\" target=\"_blank\" rel=\"noopener noreferrer\">https://groups.google.com/forum/#!topic/consul-tool/AGgPjrrkw3g</a></p><p>到这里就回想到，为什么部分机器有问题而一部分没有问题，立即对比 ipv6 的配置。发现有问题的机器没有关闭 ipv6，而没有问题的机器是关闭的。通过跟运维也了解到，线上应该默认会关闭 ipv6, 而这批老机器漏了关闭。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"6-验证\">6) 验证<a class=\"hash-link\" href=\"#6-验证\" title=\"Direct link to heading\">​</a></h3><p>直接关闭这批机器的 ipv6 配置:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">root$ </span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">echo</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token plain\"> </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> /proc/sys/net/ipv6/conf/all/disable_ipv6</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>观察这批机器的 redis 资源连接，开始有连到其他 redis 资源。因为线上 php-fpm 使用长连接，所以需要等到进程重启才会重新连接，所以需要等一段时间才会完全均匀。</p><p>过了一会，多个 Redis 访问量和连接数终于恢复均匀，问题初步解决。</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/getaddressinfo-redis-balance.jpeg\" alt=\"image\" class=\"img_ev3q\"></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"7end\">7）END<a class=\"hash-link\" href=\"#7end\" title=\"Direct link to heading\">​</a></h3><p>现在虽然通过关闭 ipv6 解决这个问题，但后续可能也会使用到 ipv6 而再次踩坑。建议后续 php 连接资源采用 <code>gethostbyname</code> 手动获取ip。</p><p>另外还有一个细节还没完全深入的是 ipv6 选项如何影响 <code>rfc3483_sort</code> 算法的，需要进一步跟进，彻底搞定。</p>",
            "url": "https://hulkdev.com/posts-getaddressinfo-cause-unbalance",
            "title": "getaddressinfo 引发的血案",
            "summary": "DBA 发现同一组 Redis 从库中有实例 QPS 比较高，对比发现只是其中一个从库偏高而其他从库是正常的，分布如下:",
            "date_modified": "2016-03-27T00:00:00.000Z",
            "tags": [
                "linux",
                "glibc"
            ]
        },
        {
            "id": "posts-how-oom-killer-works",
            "content_html": "<p>作为一个不合格的开发人员多多少少都被 OOM(Out of memory) x 过，只是一般大家对于为什么被选中，可能没太考究。</p><p>简单来说之所以会出现 OOM, 就是已分配的虚拟内存大于物理内存和 Swap 分区大小，导致需要内存无法分配。如果 overcommit = 2, 在申请虚拟内存时，如果超过限制的内存比例 + Swap 空间会直接返回失败，只要分配虚拟内存不超过物理内存，也就不会有 OOM。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-进程选择\">2) 进程选择<a class=\"hash-link\" href=\"#2-进程选择\" title=\"Direct link to heading\">​</a></h3><p>既然无法分配内存，就有两种下面的两种选择:</p><ol><li>向申请内存报告申请内存失败</li><li>选择一个杀掉其他的进程来释放内存</li></ol><p>内核 2.4 版本之前是第一种做法(走在路上听别人说的), 后面的内核版本才采用第二种。我们这里要来看的是第二种，原因很简单，因为第一种做法没什么好看的...</p><p>下面的代码基于内核 2.6.32, 代码在 <code>arch/不同型号/mm/fault.c</code> 和 <code>mm/oom_kill.c</code>: </p><p>我们分配物理内存的时候是通过缺页中断来实现的，然后会调用 <code>do_page_fault</code> ，如果内存不足就会通过 <code>mm_fault_error</code> 来处理，调用链如下:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">out_of_memory</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">pagefault_out_of_memory</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">__out_of_memory</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">select_bad_process</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">badness</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p><code>select_bad_process</code> 调用 <code>badness</code> 来计算每个进程的得分（范围 0-1000），然后干掉 score 最高的进程。每个进程的 score 我们通过 <code>/proc/pid/oom_score</code> 来查看。我们下面来看这个 score 是如何计算，主要是下面几个纬度:</p><ul><li>子进程内存消耗，越多越容易被选中</li><li>CPU 密集型以及老进程，比刚启动的进程更不容易被选中</li><li>root 启动的进程更不容易被选中</li><li>用户通过控制 oom_adj 来控制进程选中优先级(范围是-17到15)</li></ul><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">badness</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">task_struct</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token plain\"> uptime</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 这个 oom_adj 值是从 /proc/pid/oom_adj 获取</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 用户可以通过控制 oom_adj 来控制 score</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> oom_adj </span><span class=\"token operator\">=</span><span class=\"token plain\"> p</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">signal</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">oom_adj</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 系统总内存大小作为基础分数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    points </span><span class=\"token operator\">=</span><span class=\"token plain\"> mm</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">total_vm</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">p</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">flags </span><span class=\"token operator\">&amp;</span><span class=\"token plain\"> PF_OOM_ORIGIN</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> ULONG_MAX</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果 fork 的进程子进程也太多子进程被选到的概率也比较大。</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">list_for_each_entry</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">child</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">p</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">children</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> sibling</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">task_lock</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">child</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">child</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">mm </span><span class=\"token operator\">!=</span><span class=\"token plain\"> mm </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\"> child</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">mm</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            points </span><span class=\"token operator\">+=</span><span class=\"token plain\"> child</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">mm</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">total_vm</span><span class=\"token operator\">/</span><span class=\"token number\">2</span><span class=\"token plain\"> </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">task_unlock</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">child</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">thread_group_cputime</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">task_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 进程用户空间消耗的 cpu 分片数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    utime </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">cputime_to_jiffies</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">task_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">utime</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 进程系统消耗的 cpu 分片数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    stime </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">cputime_to_jiffies</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">task_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">stime</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    cpu_time </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">utime </span><span class=\"token operator\">+</span><span class=\"token plain\"> stime</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&gt;&gt;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">SHIFT_HZ </span><span class=\"token operator\">+</span><span class=\"token plain\"> </span><span class=\"token number\">3</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 计算启动时间</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">uptime </span><span class=\"token operator\">&gt;=</span><span class=\"token plain\"> p</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">start_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">tv_sec</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        run_time </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">uptime </span><span class=\"token operator\">-</span><span class=\"token plain\"> p</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">start_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">tv_sec</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&gt;&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">10</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        run_time </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 消耗 CPU 越多的进程，降低 score</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">cpu_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        points </span><span class=\"token operator\">/=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">int_sqrt</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">cpu_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 根据启动时间降低 score, 所以最新启动的进程最可能被杀</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">run_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        points </span><span class=\"token operator\">/=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">int_sqrt</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">int_sqrt</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">run_time</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 降低 root 用户启动的进程的 score</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">has_capability_noaudit</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> CAP_SYS_ADMIN</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">||</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">has_capability_noaudit</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> CAP_SYS_RESOURCE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        points </span><span class=\"token operator\">/=</span><span class=\"token plain\"> </span><span class=\"token number\">4</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">has_intersects_mems_allowed</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        points </span><span class=\"token operator\">/=</span><span class=\"token plain\"> </span><span class=\"token number\">8</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">     </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">     </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">oom_adj</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">oom_adj </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">points</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">                points </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            points </span><span class=\"token operator\">&lt;&lt;=</span><span class=\"token plain\"> oom_adj</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            points </span><span class=\"token operator\">&gt;&gt;=</span><span class=\"token plain\"> </span><span class=\"token operator\">-</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">oom_adj</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> points</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-总结\">3) 总结<a class=\"hash-link\" href=\"#3-总结\" title=\"Direct link to heading\">​</a></h3><p>从整体来看，占用内存越大，非CPU消耗刑，非 Root 启动以及新启动的进程更加容易被选中。而用户可以通过调整 <code>/proc/$pid/oom_adj</code> 来调整进程的优先级。</p>",
            "url": "https://hulkdev.com/posts-how-oom-killer-works",
            "title": "oom killer 实现",
            "summary": "作为一个不合格的开发人员多多少少都被 OOM(Out of memory) x 过，只是一般大家对于为什么被选中，可能没太考究。",
            "date_modified": "2016-03-26T00:00:00.000Z",
            "tags": [
                "linux",
                "mmeory"
            ]
        },
        {
            "id": "posts-overcommit-memory",
            "content_html": "<p>春节前几天运维大侠说要扩容 Redis 从库但同步一直失败，看日志发现在做 bgsave 的时候一直失败。 日志如下:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">41738</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"> 04 Feb </span><span class=\"token number\">11</span><span class=\"token plain\">:16:39.859 * Full resync requested by slave.</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">41738</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"> 04 Feb </span><span class=\"token number\">11</span><span class=\"token plain\">:16:39.859 * Starting BGSAVE </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">for</span><span class=\"token plain\"> SYNC</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">41738</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"> 04 Feb </span><span class=\"token number\">11</span><span class=\"token plain\">:16:39.860 </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\"># Can't save in background: fork: Cannot allocate memory</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">[</span><span class=\"token number\">41738</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">]</span><span class=\"token plain\"> 04 Feb </span><span class=\"token number\">11</span><span class=\"token plain\">:16:39.860 * Replication failed, can't BGSAVE</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>从日志可以看到 fork 的时候内存分配失败导致 bgsave 无法成功，那就是可用内存不足?</p><p>使用 <code>info memory</code> 看了一下实例使用内存， 差不多用了 <code>8G</code>:</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">used_memory:8045067888</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">used_memory_human:7.49G</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">used_memory_rss:8216522752</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">used_memory_peak:50615269608</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">used_memory_peak_human:47.14G</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>然后用 <code>free -m</code> 看到系统空闲页加上 pagecache 也有 21G，这个空闲内存远大于实例使用的 8G 呀，为什么会 fork 失败呢？</p><p>使用 <code>top</code> 发现这个 Redis 实例虚拟内存使用了 48.7G, 常驻内存使用是 7.6G。</p><p><img loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/git-hulk/git-hulk.github.io/images/overcommit-memory-top.jpeg\" alt=\"img\" class=\"img_ev3q\"></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"2-那么问题来了\">2) 那么问题来了<a class=\"hash-link\" href=\"#2-那么问题来了\" title=\"Direct link to heading\">​</a></h3><blockquote><p>Q1. Redis 统计的虚拟内存为什么占用这么多?</p></blockquote><p>从现象来看是峰值分配了这么多(见 peak_memory)。 但在内存释放的时候，物理内存释放而虚拟内存无法收缩。这个跟内存分配有关, 当前 Redis 默认是用的是 jemalloc。</p><p>在 github 上面提了一个 issue 说这个问题， Redis 作者也大概是这个意思。至于为什么虚拟内存无法收缩的原因，有待进一步研究。</p><blockquote><p>Q2. fork 是根据虚拟内存来检查内存是否够用? </p></blockquote><p>显然是的。因为如果是根据物理内存，fork 是可以成功的。那其实剩余的系统内存是足够的，而我需要的内存并没有这么大，有什么办法可以让进程继续 fork 么？ 答案就是把 <code>vm.overcommit_memory</code> 设置为 1。</p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"3-解决方案\">3） 解决方案<a class=\"hash-link\" href=\"#3-解决方案\" title=\"Direct link to heading\">​</a></h3><p><code>vm.overcommit_memory</code> 用来控制在 fork 进程时用什么姿势来检查内存是否够用。 Redis 在实例启动的时候给出了提示信息。</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">WARNING overcommit_memory is </span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">set</span><span class=\"token plain\"> to </span><span class=\"token number\">0</span><span class=\"token operator\">!</span><span class=\"token plain\"> Background save may fail under low memory condition. </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">To fix this issue </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">add</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">'vm.overcommit_memory = 1'</span><span class=\"token plain\"> to /etc/sysctl.conf </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">and </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">then</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">reboot</span><span class=\"token plain\"> or run the </span><span class=\"token builtin class-name\" style=\"color:rgb(189, 147, 249)\">command</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">'sysctl vm.overcommit_memory=1'</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">for</span><span class=\"token plain\"> this to take effect.</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>vm.overcommit_memory 取值是 0, 1, 2, 默认是 0。 具体数值的意义可参考下面的文档，后面会结合代码来说明。</p><p><a href=\"https://www.kernel.org/doc/Documentation/vm/overcommit-accounting\" target=\"_blank\" rel=\"noopener noreferrer\">https://www.kernel.org/doc/Documentation/vm/overcommit-accounting</a></p><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"4-overcommit-memory\">4) Overcommit memory<a class=\"hash-link\" href=\"#4-overcommit-memory\" title=\"Direct link to heading\">​</a></h3><p>我们具体从内核代码(Linux-2.6.32)来看这个参数如何在 fork 进程的时候进行内存校验。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">define</span><span class=\"token macro property\"> </span><span class=\"token macro property macro-name\">OVERCOMMIT_GUESS</span><span class=\"token macro property\">        </span><span class=\"token macro property expression number\">0</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">define</span><span class=\"token macro property\"> </span><span class=\"token macro property macro-name\">OVERCOMMIT_ALWAYS</span><span class=\"token macro property\">       </span><span class=\"token macro property expression number\">1</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token macro property directive-hash\">#</span><span class=\"token macro property directive keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">define</span><span class=\"token macro property\"> </span><span class=\"token macro property macro-name\">OVERCOMMIT_NEVER</span><span class=\"token macro property\">        </span><span class=\"token macro property expression number\">2</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>我们在程序执行 fork() 的时候，会通过系统调用中断切换到内核态，再调用 sys_fork。</p><p>下面是 fork 进程时，内核的系统调用链:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">sys_fork </span><span class=\"token operator\">-&gt;</span><span class=\"token plain\"> copy_mm </span><span class=\"token operator\">-&gt;</span><span class=\"token plain\"> dup_mm </span><span class=\"token operator\">-&gt;</span><span class=\"token plain\"> dup_mmap </span><span class=\"token operator\">-&gt;</span><span class=\"token plain\"> </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">security_vm_enough_memory </span><span class=\"token operator\">-&gt;</span><span class=\"token plain\"> cap_vm_enough_memory </span><span class=\"token operator\">-&gt;</span><span class=\"token plain\"> __vm_enough_memory</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>最后调用 __vm_enough_memory 进行内存检查，我们重点来看这个函数。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">__vm_enough_memory</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">mm_struct</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">mm</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token plain\"> pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> cap_sys_admin</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/*    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">     * Sometimes we want to use more memory than we have</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">     */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// OVERCOMMIT_ALWAYS = 1, 什么都不检查直接返回</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sysctl_overcommit_memory </span><span class=\"token operator\">==</span><span class=\"token plain\"> OVERCOMMIT_ALWAYS</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sysctl_overcommit_memory </span><span class=\"token operator\">==</span><span class=\"token plain\"> OVERCOMMIT_GUESS</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">unsigned</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">long</span><span class=\"token plain\"> n</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 剩余容量 = page cache使用的页 + 空闲swap + 可回收 slab + 系统空闲页</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        free </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">global_page_state</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">NR_FILE_PAGES</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        free </span><span class=\"token operator\">+=</span><span class=\"token plain\"> nr_swap_pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        free </span><span class=\"token operator\">+=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">global_page_state</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">NR_SLAB_RECLAIMABLE</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// root 如果是非 root 用户需要保留 3%</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">cap_sys_admin</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            free </span><span class=\"token operator\">-=</span><span class=\"token plain\"> free </span><span class=\"token operator\">/</span><span class=\"token plain\"> </span><span class=\"token number\">32</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果空闲的页足够直接返回</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">free </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 计算系统空闲页比较耗时，所有上面 3 种空闲已经足够就不计算。</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        n </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">nr_free_pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">       </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 去掉一些系统保留页</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">n </span><span class=\"token operator\">&lt;=</span><span class=\"token plain\"> totalreserve_pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">else</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            n </span><span class=\"token operator\">-=</span><span class=\"token plain\"> totalreserve_pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// root 如果是非 root 用户需要保留 3%</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">cap_sys_admin</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            n </span><span class=\"token operator\">-=</span><span class=\"token plain\"> n </span><span class=\"token operator\">/</span><span class=\"token plain\"> </span><span class=\"token number\">32</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        free </span><span class=\"token operator\">+=</span><span class=\"token plain\"> n</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">       </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">free </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">goto</span><span class=\"token plain\"> error</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 上面两个分支分别是值为 1 和 0 的情况，下面则是值为 2 的判断条件</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 因为我们计算的是 normal page, 所以计算允许使用的内存需要扣掉 huge page</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// sysctl_overcommit_ratio 系统控制的比例参数</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    allowed </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">totalram_pages </span><span class=\"token operator\">-</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">hugetlb_total_pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token operator\">*</span><span class=\"token plain\"> sysctl_overcommit_ratio </span><span class=\"token operator\">/</span><span class=\"token plain\"> </span><span class=\"token number\">100</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">//非root 用户保留 3%</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">cap_sys_admin</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        allowed </span><span class=\"token operator\">-=</span><span class=\"token plain\"> allowed </span><span class=\"token operator\">/</span><span class=\"token plain\"> </span><span class=\"token number\">32</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 加上 swap 空闲页</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    allowed </span><span class=\"token operator\">+=</span><span class=\"token plain\"> total_swap_pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 防止单进程占用过多内存，需要保留 3% 给其他进程</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">mm</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        allowed </span><span class=\"token operator\">-=</span><span class=\"token plain\"> mm</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">total_vm </span><span class=\"token operator\">/</span><span class=\"token plain\"> </span><span class=\"token number\">32</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">percpu_counter_read_positive</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">vm_committed_as</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> allowed</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">error</span><span class=\"token operator\">:</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">vm_unacct_memory</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">pages</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token operator\">-</span><span class=\"token plain\">ENOMEM</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>从代码层面来看：</p><ol><li>overcommit_memory = 1, 不检查，有锅自己背。</li><li>overcommit_memory = 0，检查当前进程需要的虚拟内存 &lt; (当前剩余的物理 + swap分区)</li><li>overcommit_memory = 2, 检查整个系统已经分配的内存 &lt; (物理内存*允许比例 + swap分区)</li></ol><h3 class=\"anchor anchorWithStickyNavbar_LWe7\" id=\"5-end\">5） END<a class=\"hash-link\" href=\"#5-end\" title=\"Direct link to heading\">​</a></h3><p>调整系统参数还是需要谨慎再谨慎，在没有详细查看官方文档以及全面了解参数对系统的影响的时候，切勿手贱随意调整。</p>",
            "url": "https://hulkdev.com/posts-overcommit-memory",
            "title": "谈谈 overcommit memory",
            "summary": "春节前几天运维大侠说要扩容 Redis 从库但同步一直失败，看日志发现在做 bgsave 的时候一直失败。 日志如下:",
            "date_modified": "2016-02-15T00:00:00.000Z",
            "tags": [
                "linux",
                "memory"
            ]
        },
        {
            "id": "posts-how-php-check-tcp-liveness",
            "content_html": "<p>长连接可以减少建立连接的过程, 使用长连接可以提高服务的性能。php 很多扩展都支持长连接，如 redis, memcache, mysql 的主流扩展都支持。</p><p>我们知道长连接就是一次建立连接，使用之后不会马上释放，而是把这个连接放到连接池。那么引发的一个问题就是，我们下次使用时如何知道这个连接是否已经被关闭。</p><p>我们来看看 phpredis 是如何来判断，连接是否可用。 phpredis 检查的函数在 library.c 的 <code>redis_check_eof</code> 的方法，而这个方法调用的是 php 内部的方法 <code>php_stream_eof</code>, 我们来看这个方法的具体实现。</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">PHPAPI </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">_php_stream_eof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">php_stream </span><span class=\"token operator\">*</span><span class=\"token plain\">stream TSRMLS_DC</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 如果有数据未读取，说明 socket 还是可用</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">writepos </span><span class=\"token operator\">-</span><span class=\"token plain\"> stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">readpos </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">// 咦? 这里通过 php_stream_set_option 来检查</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">!</span><span class=\"token plain\">stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">eof </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\"> PHP_STREAM_OPTION_RETURN_ERR </span><span class=\"token operator\">==</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_stream_set_option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">stream</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> PHP_STREAM_OPTION_CHECK_LIVENESS</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">eof </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">eof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>判断socket 是否可用, 有两个条件:</p><ol><li>writepos &gt; readpos, 说明还有数据未读, 连接正在使用中</li><li>php_stream_set_option 通过 PHP_STREAM_OPTION_CHECK_LIVENESS 选项来判断</li></ol><p>解析来看看 php_stream_set_option 是如何实现的:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">PHPAPI </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">_php_stream_set_option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">php_stream </span><span class=\"token operator\">*</span><span class=\"token plain\">stream</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> value</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">void</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">ptrparam TSRMLS_DC</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> ret </span><span class=\"token operator\">=</span><span class=\"token plain\"> PHP_STREAM_OPTION_RETURN_NOTIMPL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ops</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">set_option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        ret </span><span class=\"token operator\">=</span><span class=\"token plain\"> stream</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">ops</span><span class=\"token operator\">-&gt;</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">set_option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">stream</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> value</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> ptrparam TSRMLS_CC</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这个函数调用的是 stream 的 set_option 方法，我们知道 php 的stream 是一类文件操作的抽象。在 php 里面的 tcp, udp，socket, 普通文件, 文件流等都是 stream, 只是他们实现的方法各有差异，我们这里只关注 tcp 的实现：</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">php_stream_ops php_stream_socket_ops </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    php_sockop_write</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> php_sockop_read</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    php_sockop_close</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> php_sockop_flush</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:rgb(255, 121, 198)\">\"tcp_socket\"</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token constant\" style=\"color:rgb(189, 147, 249)\">NULL</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:rgb(98, 114, 164)\">/* seek */</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    php_sockop_cast</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    php_sockop_stat</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    php_tcp_sockop_set_option</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>对于 socket 的stream, 它的 set_option 就是 php_tcp_sockop_set_option, 实现如下:</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_pollfd_for</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sock</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">socket</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> PHP_POLLREADABLE</span><span class=\"token operator\">|</span><span class=\"token plain\">POLLPRI</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">tv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token number\">0</span><span class=\"token plain\"> </span><span class=\"token operator\">&gt;=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">recv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">sock</span><span class=\"token operator\">-&gt;</span><span class=\"token plain\">socket</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;</span><span class=\"token plain\">buf</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">sizeof</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">buf</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> MSG_PEEK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_socket_errno</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token operator\">!=</span><span class=\"token plain\"> EWOULDBLOCK</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">            alive </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>这里我们看到, 检查一个 socket 是否存活, 是通过 poll 来查询 socket 的可读些状态。然后使用 recv 来判断 socket 是否关闭，或者出错。</p><div class=\"language-shell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-shell codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token number\">1</span><span class=\"token plain\">. recv </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token plain\"> 时, 说明连接已经关闭</span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token number\">2</span><span class=\"token plain\">. recv </span><span class=\"token operator\">&lt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token plain\"> 且  errno </span><span class=\"token operator\">!=</span><span class=\"token plain\"> EWOULDBLOCK 时，说明 socket 出错了。</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p>部门细心的小伙伴，发现了这个判断条件有一个小 bug。 如果上一次查询结果的 errno = EWOULDBLOCK，因为只有异常才会覆盖 errno，所以recv = 0时，也会认为 socket 是存活的。</p><p>php_pollfd_for 的实现也有一些小技巧：</p><div class=\"language-c codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_biex\"><pre tabindex=\"0\" class=\"prism-code language-c codeBlock_bY9V thin-scrollbar\"><code class=\"codeBlockLines_e6Vv\"><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">static</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">inline</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_pollfd_for</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token class-name\">php_socket_t</span><span class=\"token plain\"> fd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> events</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">struct</span><span class=\"token plain\"> </span><span class=\"token class-name\">timeval</span><span class=\"token plain\"> </span><span class=\"token operator\">*</span><span class=\"token plain\">timeouttv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    php_pollfd p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">int</span><span class=\"token plain\"> n</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">fd </span><span class=\"token operator\">=</span><span class=\"token plain\"> fd</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">events </span><span class=\"token operator\">=</span><span class=\"token plain\"> events</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">revents </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    n </span><span class=\"token operator\">=</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_poll2</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token operator\">&amp;</span><span class=\"token plain\">p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token number\">1</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">,</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:rgb(80, 250, 123)\">php_tvtoto</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">timeouttv</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">if</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">(</span><span class=\"token plain\">n </span><span class=\"token operator\">&gt;</span><span class=\"token plain\"> </span><span class=\"token number\">0</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">)</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">{</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> p</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">.</span><span class=\"token plain\">revents</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:rgb(189, 147, 249);font-style:italic\">return</span><span class=\"token plain\"> n</span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">;</span><span class=\"token plain\"></span><br></span><span class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:rgb(248, 248, 242)\">}</span><br></span></code></pre><div class=\"buttonGroup__atx\"><button type=\"button\" aria-label=\"Copy code to clipboard\" title=\"Copy\" class=\"clean-btn\"><span class=\"copyButtonIcons_eSgA\" aria-hidden=\"true\"><svg class=\"copyButtonIcon_y97N\" viewBox=\"0 0 24 24\"><path d=\"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z\"></path></svg><svg class=\"copyButtonSuccessIcon_LjdS\" viewBox=\"0 0 24 24\"><path d=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"></path></svg></span></button></div></div></div><p><code>php_poll2</code> 第二个参数是 1， poll 只会查询一个 fd, poll 不会引入查询多个无用 fd 的问题。 第三那个参数在 check_liveness 时，是设置为 0, poll 也不会阻塞。</p>",
            "url": "https://hulkdev.com/posts-how-php-check-tcp-liveness",
            "title": "php 如何检查 TCP 连接是否关闭",
            "summary": "长连接可以减少建立连接的过程, 使用长连接可以提高服务的性能。php 很多扩展都支持长连接，如 redis, memcache, mysql 的主流扩展都支持。",
            "date_modified": "2015-08-08T00:00:00.000Z",
            "author": {
                "name": "hulk"
            },
            "tags": [
                "PHP",
                "TCP"
            ]
        }
    ]
}