<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>子规入梧桐</title>
  <icon>https://www.lazydaily.cn/images/favicon.ico</icon>
  
  <link href="https://www.lazydaily.cn/atom.xml" rel="self"/>
  
  <link href="https://www.lazydaily.cn/"/>
  <updated>2026-03-16T02:10:37.035Z</updated>
  <id>https://www.lazydaily.cn/</id>
  
  <author>
    <name>z1gui</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title> 使用GCC进行vibe coding</title>
    <link href="https://www.lazydaily.cn/2026/02/15/fijFVVcj1wjM5NqZ/"/>
    <id>https://www.lazydaily.cn/2026/02/15/fijFVVcj1wjM5NqZ/</id>
    <published>2026-02-15T20:03:01.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<p>GCC 为：</p><ul><li>Gemini CLI (Google)</li><li>Codex CLI (ChatGPT)</li><li>Claude code CLI (CC)</li></ul><h2 id="第一步、终端安装-GCC-CLI"><a href="#第一步、终端安装-GCC-CLI" class="headerlink" title="第一步、终端安装 GCC CLI"></a>第一步、终端安装 GCC CLI</h2><p>此步骤安装内部服务，可理解为 RPC 请求的客户端</p><h3 id="安装-Codex-CLI"><a href="#安装-Codex-CLI" class="headerlink" title="安装 Codex CLI"></a>安装 Codex CLI</h3><p>使用 npm 安装</p><pre class="line-numbers language-none"><code class="language-none">npm install -g @openai&#x2F;codex<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>使用 Homebrew 安装 (macOS)</p><pre class="line-numbers language-none"><code class="language-none">brew install --cask codex<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>或从 GitHub 下载二进制文件</p><h3 id="安装-Claude-Code-CLI"><a href="#安装-Claude-Code-CLI" class="headerlink" title="安装 Claude Code CLI"></a>安装 Claude Code CLI</h3><p>打开命令提示符（以管理员身份运行）或 PowerShell，执行以下命令：</p><pre class="line-numbers language-none"><code class="language-none">npm install -g @anthropic-ai&#x2F;claude-code<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>验证安装：</p><pre class="line-numbers language-none"><code class="language-none">claude --version<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="安装-Gemini-CLI"><a href="#安装-Gemini-CLI" class="headerlink" title="安装 Gemini CLI"></a>安装 Gemini CLI</h3><p>打开命令提示符或 PowerShell，执行以下命令：</p><pre class="line-numbers language-none"><code class="language-none">npm install -g @google&#x2F;gemini-cli<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>注意：如果遇到权限问题，请确保以管理员身份运行命令提示符。</p><h2 id="第二步、配置代理"><a href="#第二步、配置代理" class="headerlink" title="第二步、配置代理"></a>第二步、配置代理</h2><p>此步骤主要跳过会员权限，使用第三方或公益站 API Key</p><h3 id="方式一：CC-Switch-推荐"><a href="#方式一：CC-Switch-推荐" class="headerlink" title="方式一：CC Switch (推荐)"></a>方式一：CC Switch (推荐)</h3><p>下载 cc switch: <a href="https://github.com/farion1231/cc-switch">https://github.com/farion1231/cc-switch</a></p><p><img src="/img/%E4%BD%BF%E7%94%A8GCC%E8%BF%9B%E8%A1%8Cvibe%20coding-1771159219326.webp"></p><p><img src="/img/%E4%BD%BF%E7%94%A8GCC%E8%BF%9B%E8%A1%8Cvibe%20coding-1771159227753.webp"></p><p><strong>建议转发接管、故障转移都打开</strong></p><p>除了 “API KEY” 和 “API 请求地址 “ 外，其他都可随便填</p><p>用不同的模型是对应不同的配置</p><h3 id="方式二：手动配置（此方式太麻烦，需要时刻维护，直接用方式一即可）"><a href="#方式二：手动配置（此方式太麻烦，需要时刻维护，直接用方式一即可）" class="headerlink" title="方式二：手动配置（此方式太麻烦，需要时刻维护，直接用方式一即可）"></a><del>方式二：手动配置</del>（此方式太麻烦，需要时刻维护，直接用方式一即可）</h3><ul><li><ol><li>请确保在 “codex” 专用分组创建 API Key（该分组仅支持 Codex CLI）（API KEY 在最下面）</li></ol></li><li><ol start="2"><li>创建或编辑配置文件 ~&#x2F;.codex&#x2F;config.toml</li></ol></li></ul><p>config.toml 配置示例</p><pre class="line-numbers language-none"><code class="language-none"># ~&#x2F;.codex&#x2F;config.tomlmodel_provider &#x3D; &quot;CODEX&quot;model &#x3D; &quot;gpt-5.2&quot;model_reasoning_effort &#x3D; &quot;high&quot;disable_response_storage &#x3D; truepreferred_auth_method &#x3D; &quot;apikey&quot;[model_providers.CODEX]name &#x3D; &quot;CODEX&quot;base_url &#x3D; &quot;BASE_URL&quot;wire_api &#x3D; &quot;responses&quot;requires_openai_auth &#x3D; trueenv_key &#x3D; &quot;API_KEY&quot;<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>设置环境变量</p><p>临时设置（当前终端会话有效）</p><pre class="line-numbers language-none"><code class="language-none">set API_KEY&#x3D;your-api-keyset BASE_URL&#x3D;your-api-url<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>永久设置（需要重启终端生效）</p><pre class="line-numbers language-none"><code class="language-none">setx API_KEY &quot;your-api-key&quot; BASE_URL &quot;your-api-url&quot;<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>启动使用</p><ul><li>配置完成后，在终端运行 codex 即可启动</li><li>首次运行会提示登录，选择 “Sign in with API key” 使用 API Key 登录</li></ul><p>启动 Codex CLI</p><pre class="line-numbers language-none"><code class="language-none">codex<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="第三步、IDEA-下载-GCC-插件"><a href="#第三步、IDEA-下载-GCC-插件" class="headerlink" title="第三步、IDEA 下载 GCC 插件"></a>第三步、IDEA 下载 GCC 插件</h2><p>此步骤将 CLI 服务集成到 IDEA 中</p><p>codex launcher: <a href="https://plugins.jetbrains.com/plugin/28264-codex-launcher">https://plugins.jetbrains.com/plugin/28264-codex-launcher</a></p><p>claude code: <a href="https://plugins.jetbrains.com/plugin/27310-claude-code-beta-">https://plugins.jetbrains.com/plugin/27310-claude-code-beta-</a></p><p>gemini cli: <a href="https://plugins.jetbrains.com/plugin/29336-gemini-cli-companion">https://plugins.jetbrains.com/plugin/29336-gemini-cli-companion</a></p><p>点击图标即可使用</p><ul><li>初次使用请使用 &#x2F;init 初始化，生成 <a href="https://agent.md/">AGENT.md</a> 或 <a href="https://claude.md/">CLAUDE.md</a> 文档</li><li>任何要求都可以在上述文档中自己补充，比如：回答使用中文；编译运行用 Jetty run 等</li><li>在终端即可使用，@文件名 可指定某个文件进行提问或修改</li></ul><p><img src="/img/%E4%BD%BF%E7%94%A8GCC%E8%BF%9B%E8%A1%8Cvibe%20coding-1771159235822.webp"></p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>1、  报错一般都是两个原因：</p><ul><li>翻墙节点不支持使用 AI，切换节点重试</li><li>提供的公益站地址被 Ban，换 API key 和 API 请求地址</li></ul><h2 id="技巧"><a href="#技巧" class="headerlink" title="技巧"></a>技巧</h2><p>1、  <a href="https://duckcoding.com/">安装配置参考文档</a></p><p>2、  <a href="https://blog.sshh.io/p/how-i-use-every-claude-code-feature">我如何使用每个 Claude Code 功能</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;GCC 为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gemini CLI (Google)&lt;/li&gt;
&lt;li&gt;Codex CLI (ChatGPT)&lt;/li&gt;
&lt;li&gt;Claude code CLI (CC)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;第一步、终端安装-GCC-CLI&quot;&gt;&lt;</summary>
      
    
    
    
    <category term="AI" scheme="https://www.lazydaily.cn/categories/AI/"/>
    
    
    <category term="敏捷开发" scheme="https://www.lazydaily.cn/tags/%E6%95%8F%E6%8D%B7%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>我被骗了</title>
    <link href="https://www.lazydaily.cn/2025/12/04/26lUIAhAplUm5uUY/"/>
    <id>https://www.lazydaily.cn/2025/12/04/26lUIAhAplUm5uUY/</id>
    <published>2025-12-04T08:12:08.000Z</published>
    <updated>2026-01-02T14:19:43.294Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一场并不昂贵，却足够深刻的教训"><a href="#一场并不昂贵，却足够深刻的教训" class="headerlink" title="一场并不昂贵，却足够深刻的教训"></a>一场并不昂贵，却足够深刻的教训</h2><p>写下这篇文章的时候，事情已经过去一个多月了。但那段经历依旧清晰，像是刚发生不久——有些细节甚至比当时更清楚。</p><p>关注我日常更新的人，可能还记得我之前写过一篇周记（后来觉得太矫情，删了），提到 Amelia 出院的事。这次生病来势汹汹，她在医院修养了将近二十天。</p><p>Amelia 从小体弱，容易生病，我一直觉得这是常态，并没太放在心上。直到我妈提醒了一句：” 会不会是碰到了不干净的东西？要不要请个东西护一护。”</p><p>问题，正是从这里开始的。</p><h2 id="起因：一次-“-看似合理-“-的决定"><a href="#起因：一次-“-看似合理-“-的决定" class="headerlink" title="起因：一次 “ 看似合理 “ 的决定"></a>起因：一次 “ 看似合理 “ 的决定</h2><p>那段时间，我恰好在 L 站刷到不少慧应子写的帖子，内容多是关于罗天大醮、道教仪式之类的科普。再加上 L 站一贯给人的感觉是真诚、友善、理性，我潜意识里就降低了警惕。</p><p>现在回头看，我不是被他说服的，而是被环境 “ 背书 “ 了。</p><p>我在 L 站私信他，问能不能给 Amelia 求个护身的东西。他让我加微信。加上之后，我简单说明了 Amelia 体弱、常生病的情况，他建议请一张平安符。符纸本身不值钱，开光 400 元。400 元在我的心理预期内，于是我直接转了账。</p><p>他说，开光需要生辰八字。我一时记不清 Amelia 的时柱，便说晚上再发给他。</p><p><strong>这个细节，是整个事件的第一个关键点。</strong></p><h2 id="经过：疑点出现，但我选择了忽略"><a href="#经过：疑点出现，但我选择了忽略" class="headerlink" title="经过：疑点出现，但我选择了忽略"></a>经过：疑点出现，但我选择了忽略</h2><p>当天下午，我发现有一个从苏州发来的快递。我以为是 Amelia 网购的东西，没多想。直到晚上，我把八字补发给他，他随即把一个快递单号发给我。</p><p>我这时仍没意识到异常。</p><p>第二天晚上，快递就到了。苏州到郑州，很快。取件时，我才发现不对劲——</p><p>我对比了一下单号，发现这个快递<strong>是在我还没提供完整生辰八字之前，就已经寄出了</strong>。</p><p>那一瞬间，我心里第一次真正起了疑问：</p><blockquote><p>开光，不是要对着符来的吗？</p><p>难道可以 “ 远程开光 “？</p></blockquote><p>拆开包裹，里面有两张符：一张是明显的打印符，一张被折成三角形。我下意识以为：打印的是样板，折起来的才是真正的符。</p><p>问他，他解释说：打印的那张就是平安符，折成三角的是辟邪符，送的。</p><p>疑问更多了：<strong>符是打印的？真的会有效果吗？</strong></p><p>但我还是在说服自己——</p><p>也许是我不懂。也许是我想多了。</p><p>现在回头看，那不是 “ 相信 “，而是<strong>不愿意承认自己可能错了</strong>。</p><h2 id="真相浮出水面：我终于确认自己被骗了"><a href="#真相浮出水面：我终于确认自己被骗了" class="headerlink" title="真相浮出水面：我终于确认自己被骗了"></a>真相浮出水面：我终于确认自己被骗了</h2><p>11 月底，L 站开始出现多篇帖子，集中揭发 “ 慧应子 “ 真假道士的问题。一开始我并没太在意，直到这些帖子迅速爆火。</p><p>其中有一篇帖子（<a href="https://linux.do/t/topic/1238821">链接</a>），系统性地对比了他发过的照片、经历、故事，证明大量内容都是从网络上其他地方搬来的，甚至图片都能反向搜到原出处。</p><p>看到这些证据的时候，我没有愤怒，只有一个念头：<strong>我怎么会这么蠢。</strong></p><p>这些东西，其实随便用搜索引擎查一下就能发现问题。只是我当时，从未怀疑过。</p><p>后续的发展就很快了：</p><ul><li>假道士本人在帖子里现身 “ 对线 “</li><li>管理员秦始皇发公告，封禁其账号</li></ul><p>我这才意识到一个关键事实：他一直是在「鬼话连篇」tag 下发帖。</p><p>也就是说——</p><p><strong>他写的一直是故事，而不是经历。</strong> 而我，偏偏把故事当成了现实。</p><h2 id="维权：第一次发现-“-人多真的有用-“"><a href="#维权：第一次发现-“-人多真的有用-“" class="headerlink" title="维权：第一次发现 “ 人多真的有用 “"></a>维权：第一次发现 “ 人多真的有用 “</h2><p>意识到被骗后，我开始维权。</p><p>L 站很快有人统计受害人数，投票显示 100+。按每人平均 400 元算，总金额已超过 4 万。随后有人建了维权微信群，我第一时间加入。群里大约 46 人，已确认被骗金额 2 万多。</p><p>我第一次切身体会到什么叫<strong>行动力</strong>：</p><ul><li>有佬友通过微信小程序直接报案</li><li>有佬友用支付宝举报，成功追回全款</li><li>有佬友直接买了去苏州的机票，准备现场报案</li><li>有佬友异地前往当地派出所立案</li><li>有佬友整理法律条文、立案回执，对假道士持续施压</li></ul><p>顺带提醒一句：<strong>如果被骗时间不久，通过支付宝举报，追回概率很高。</strong></p><h2 id="拉扯：谎言在压力下逐渐崩塌"><a href="#拉扯：谎言在压力下逐渐崩塌" class="headerlink" title="拉扯：谎言在压力下逐渐崩塌"></a>拉扯：谎言在压力下逐渐崩塌</h2><p>事发后，假道士迅速删除了大部分人的微信。但仍有少数人没被删，成为了与他沟通的窗口。</p><p>在集中报案的第二天，苏州警方就上门调查了他挂靠的道院。结果很明确：他不是道院的在职人员，只是一个常来上香的香客。</p><p>通过道院提供的线索，警方很快找到了他本人。在 “ 请去喝茶 “ 之后，警方要求他尽快退还被骗资金。</p><p>不得不说一句：<strong>警察的效率，真的很高。</strong></p><p>随后，假道士开始联系群内的维权代表，试图 “ 私下解决 “。他先打感情牌：</p><ul><li>说自己也是被道院蒙蔽</li><li>只是给道院拉业务的 “ 打工人 “</li><li>抽成很少，大头都在道院</li><li>现在被推出来当替罪羊</li></ul><p>一开始，确实有人心软了。但我们很快从警方和道院侧面确认了事实：道院请符 30 元，开光 300 元。价格统一，不因人而异。</p><p>而他此前的报价却是：请符 100～400 元不等，开光 400～3000 元不等。</p><p>事实证明，他在说谎。</p><h2 id="结局：不完美，但足够清醒"><a href="#结局：不完美，但足够清醒" class="headerlink" title="结局：不完美，但足够清醒"></a>结局：不完美，但足够清醒</h2><p>在群内统一态度 “ 必须全退 “ 后，他开始改口：</p><ul><li>先说无论付了多少，只退 200</li><li>遭拒后，又改为退 “ 差价 “</li></ul><p>在持续报警和施压下，大多数人最终拿回了钱。我也在其中。</p><p>这件事，到这里算是结束了。</p><h2 id="反思：责任，永远在自己身上"><a href="#反思：责任，永远在自己身上" class="headerlink" title="反思：责任，永远在自己身上"></a>反思：责任，永远在自己身上</h2><p>毕业之后，我几乎没怎么被骗过。再往前推，高中、大学时期也很少。</p><p>我甚至曾嘲笑过新闻里被骗的老年人。可当自己真正成为受害者时，才发现——</p><p>一点都不好笑。</p><p>这次我算是运气好的：</p><ul><li>金额不大</li><li>人多力量大</li><li>警方介入快</li><li>大部分钱追回来了</li></ul><p>但如果金额更大呢？如果骗局只为我一人设计呢？如果对方直接消失呢？</p><p>我可能毫无办法。</p><p>最近刷到一个故事：</p><blockquote><p>如果在路上，一个醉汉驾车把我撞了，责任在谁？</p></blockquote><p>以前我会毫不犹豫地说：在他。</p><p>现在我却会说：<strong>责任在我。</strong></p><p>过错在他，但后果要我自己承担。受伤、治疗、恢复、生活被打乱——这些都是我必须为自己负责的事情。</p><p>成年人，必须区分 “ 过错 “ 和 “ 责任 “。社会不是幼儿园。不会因为 “ 你没错 “，就自动替你兜底。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>从这件事之后，我对自己有了一个更清晰的要求：</p><ul><li>在不熟悉的领域做决定时，务必谨慎</li><li>不急、不贪、不轻信</li><li>多查证、多怀疑、多停一步</li></ul><p>因为——</p><p><strong>一旦出错，过错也许不在我，但责任一定在我。</strong></p><p>害人之心不可有，</p><p>防人之心不可无。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一场并不昂贵，却足够深刻的教训&quot;&gt;&lt;a href=&quot;#一场并不昂贵，却足够深刻的教训&quot; class=&quot;headerlink&quot; title=&quot;一场并不昂贵，却足够深刻的教训&quot;&gt;&lt;/a&gt;一场并不昂贵，却足够深刻的教训&lt;/h2&gt;&lt;p&gt;写下这篇文章的时候，事情已经过去一个</summary>
      
    
    
    
    <category term="生活" scheme="https://www.lazydaily.cn/categories/%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="人生值得" scheme="https://www.lazydaily.cn/tags/%E4%BA%BA%E7%94%9F%E5%80%BC%E5%BE%97/"/>
    
  </entry>
  
  <entry>
    <title>Planet配置Web3域名</title>
    <link href="https://www.lazydaily.cn/2025/08/08/154988584481421/"/>
    <id>https://www.lazydaily.cn/2025/08/08/154988584481421/</id>
    <published>2025-08-08T00:00:00.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<p>Planet 项目是 V 站站长 Livid 很早之前启动的项目。最近 V 站也出了 $V2EX 币，Livid 也一直在推动区块链和 Web3 在 V 站的快速应用。跟着这个趋势，我也学习了很多这方面的知识。</p><blockquote><p>Planet 是一款免费的开源 macOS 应用程序，用于发布和关注 Web 内容。它不依赖于中央服务器或服务，而是使用 IPFS 进行点对点内容分发。您可以将您的内容链接到以太坊名称（例如，planetable.eth），以便其他人可以使用您的 .eth 名称在 Planet 上关注您，或通过 eth.limo 或 eth.sucks 等网关访问您的 ENS 网站。由于 IPFS 和 ENS 都是去中心化的，因此 Planet 可以帮助您以去中心化的方式构建和关注网站。</p></blockquote><p>了解到 Planet 的特性以及原理后，我发现 Planet 很适合做日志，随记，心情，备忘录的 Posts。正好我当前 Web2 的博客网站上没有类似的功能，索性把 Planet 的 Web 集成到博客网站上。我火速给自己搭建了 Web，并在此 Web 上链接了 Web3 的域名。你可以点击导航栏上的『碎碎念🔗』或者直接访问我的 Web3 域名『<a href="https://uhufoundme.sol.build/%E3%80%8F%EF%BC%8C%E8%B7%B3%E8%BD%AC%E5%88%B0%E6%88%91%E7%9A%84%E6%97%A5%E5%B8%B8%E6%9B%B4%E6%96%B0">https://uhufoundme.sol.build/』，跳转到我的日常更新</a> Posts。</p><p>言归正题，Planet 应用能够轻松将 Web 内容发布在链上，并通过 IPFS 生成的 IPNS 访问链上内容。但是这个 IPNS 是随机的哈希值，难记，不易传播，所以就需要链接一个 Web3 域名。这里有两种方案，一种是直接买一个 Web3 的域名，类似 .eth 或者 .sol 的。另一种是 Web2 域名开启 IPNS，支持链上内容的绑定。因为我本来就有个 Web2 域名，所以一开始我想开通 IPNS 就不用再折腾了。这里吐槽一下，阿里云域名开通 IPNS 要￥120，而 .eth 域名每月几美刀，.sol 冷门域名 1 刀就可以永久拥有，那就用 .sol 吧。</p><h2 id="购买域名"><a href="#购买域名" class="headerlink" title="购买域名"></a>购买域名</h2><blockquote><p>Solana 域名服务（SNS）的目标是提供一种去中心化且可负担的方式，将域名（.sol）和链上数据连接起来。这些链上数据可以是 SOL 地址、IPFS CID、图片、文本、或者任何其它的东西。</p></blockquote><p>在 <a href="https://www.sns.id/zh-Hans">Solana</a> 上购买域名需要通过 Web3 钱包登录，该网站支持多种钱包，我使用的是 phantom 钱包。登录之后就可以直接在页面上搜索想要的域名，其中大部分的域名都在 20 USDC（大概 $20），一些冷门的域名可以 1 USDC 捡到，可以多试试。</p><p><img src="/img/Planet%E9%85%8D%E7%BD%AEWeb3%E5%9F%9F%E5%90%8D-1763948848067.webp"></p><p><img src="/img/Planet%E9%85%8D%E7%BD%AEWeb3%E5%9F%9F%E5%90%8D-1763948856170.webp"></p><p>支付时候也有多种方式，可以用 SOL，USDC，USDT 或者其他虚拟货币，甚至可以用信用卡。在这里，不讨论如何充值虚拟货币，如果有疑问可以 google。需要注意的是，<strong>所有链上操作都需要有手续费，在充币或者兑换时候要考虑多买多充</strong>。</p><p><img src="/img/Planet%E9%85%8D%E7%BD%AEWeb3%E5%9F%9F%E5%90%8D-1763949180527.webp"></p><h2 id="绑定-IPNS"><a href="#绑定-IPNS" class="headerlink" title="绑定 IPNS"></a>绑定 IPNS</h2><p><img src="/img/Planet%E9%85%8D%E7%BD%AEWeb3%E5%9F%9F%E5%90%8D-1763949266310.webp"></p><p><img src="/img/Planet%E9%85%8D%E7%BD%AEWeb3%E5%9F%9F%E5%90%8D-1763949285499.webp"></p><p>在购买之后，就可以去我的域名里面配置，将 Planet 上复制的 IPNS 复制配置好即可。这里我出现了下图的问题，一开始也不清楚什么情况。请教了一下 Livid 之后，才知道是因为绑定操作也是需要消耗 gas 的，我钱包里面 SOL 不够。然后 Livid 给我空投 0.1 SOL 之后就绑定成功。</p><p><img src="/img/Planet%E9%85%8D%E7%BD%AEWeb3%E5%9F%9F%E5%90%8D-1763949325845.webp"></p><p>这样就可以通过 <a href="https://uhufoundme.sol.build/">https://uhufoundme.sol.build/</a> 访问了。在这个过程中，感谢 Livid 大佬的答疑以及空投。</p><p>得益于 IPFS 这种 P2P 的方式，在 Planet 中 Send 之后就发布在 Web 上，不需要额外的去服务器上发布内容。这种便捷的方式对我来说更适合去记录即时的灵感和感受。所以我决定在 <a href="https://uhufoundme.sol.build/">https://uhufoundme.sol.build/</a> 更新些更随意，更生活的内容，在 <a href="https://www.lazydaily.cn/">https://www.lazydaily.cn/</a> 更新些更严谨，更有质量的内容。你也可以通过不同的地址来关注感兴趣的内容。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Planet 项目是 V 站站长 Livid 很早之前启动的项目。最近 V 站也出了 $V2EX 币，Livid 也一直在推动区块链和 Web3 在 V 站的快速应用。跟着这个趋势，我也学习了很多这方面的知识。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Planet 是一款免</summary>
      
    
    
    
    <category term="Web3" scheme="https://www.lazydaily.cn/categories/Web3/"/>
    
    
    <category term="Planet" scheme="https://www.lazydaily.cn/tags/Planet/"/>
    
  </entry>
  
  <entry>
    <title> 晋城游记</title>
    <link href="https://www.lazydaily.cn/2025/07/28/FFevldwozqcSb9XF/"/>
    <id>https://www.lazydaily.cn/2025/07/28/FFevldwozqcSb9XF/</id>
    <published>2025-07-28T01:07:59.000Z</published>
    <updated>2025-11-25T10:47:53.077Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>该篇游记仅记录玉皇庙、关帝庙、晋城博物馆以及原本博物馆相关内容，偏向历史爱好者。</p></blockquote><p>起因是 Amelia 在小红书上看到，郑州去山西晋城的大巴来回免费，且 2.5~3 小时就能到。所以我们就打算当天去，当天回。</p><blockquote><p>晋城是全国文明城市、平安中国建设示范市、中国优秀旅游城市、国家森林城市、国家园林城市、国际花园城市，是联合国老龄所授予的 “ 世界康养示范城市 “。地处北纬 35 度黄金宜居带，平均海拔 800 米左右，太行、太岳、中条三山环抱，沁河、丹河两河纵流；冬无严寒、夏无酷暑，年均气温 11℃，夏均气温 22℃，旅游舒适期长达 7 个月，每年夏季有 30 万游客在晋城避暑康养；年均降水量 680 毫米，是华北地区相对富水区；森林覆盖率 40.3％，城市绿化覆盖率 47.5％、人均公园绿地面积 18.8 平方米，负氧离子浓度最高可达 45000 个／cm³，是名副其实的 “ 天然氧吧 “。</p><p>——以上摘自晋城人民政府门户网站。</p></blockquote><h2 id="买票和预约"><a href="#买票和预约" class="headerlink" title="买票和预约"></a>买票和预约</h2><p>车票可以直接在微信公众号上买，郑州去晋城在<strong>豫州行</strong>微信公众号上买，晋城回郑州则在<strong>大美太行晋城游</strong>。郑州有多个客运站都可以到晋城，如果家附近的客运站没票，可以切换别的客运站试试。同理，晋城到郑州也是分多个客运站，可以多看看。除此之外，还要算好行程，预约景点门票。我们计划去玉皇庙，关帝庙，晋城博物馆，原本博物馆。这几个景点都可以通过<strong>晋城市文化保护研究中心网上预约</strong>微信公众号进行预约。</p><p>上述所有车票，门票均免费。</p><h2 id="行程游记"><a href="#行程游记" class="headerlink" title="行程游记"></a>行程游记</h2><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763949369465.webp"></p><p>为了更好的游玩体验，我们决定尽早出发。所以购买了较早的车票。5 点半从家里出发，6 点 20 分的车，8 点 40 分到，可以在车上补觉。尽量轻装上阵，带个小包也可。如果想买特产，最好带双肩包。<strong>一定一定要记住带身份证。</strong></p><p>第一站，建议直接去玉皇庙，因为在门口领通关文牒，方便后续盖章。我们在后续的晋城博物馆看到好几拨人去前台问通关文牒在哪领。</p><blockquote><p> <strong>府城玉皇庙</strong>位于中国 <a href="https://zh.wikipedia.org/wiki/%E5%B1%B1%E8%A5%BF%E7%9C%81" title="山西省">山西省</a><a href="https://zh.wikipedia.org/wiki/%E6%99%8B%E5%9F%8E%E5%B8%82" title="晋城市">晋城市</a><a href="https://zh.wikipedia.org/wiki/%E6%B3%BD%E5%B7%9E%E5%8E%BF" title="泽州县">泽州县</a><a href="https://zh.wikipedia.org/wiki/%E9%87%91%E6%9D%91%E9%95%87" title="金村镇">金村镇</a> 府城村，1988 年被列为 <a href="https://zh.wikipedia.org/wiki/%E7%AC%AC%E4%B8%89%E6%89%B9%E5%85%A8%E5%9B%BD%E9%87%8D%E7%82%B9%E6%96%87%E7%89%A9%E4%BF%9D%E6%8A%A4%E5%8D%95%E4%BD%8D" title="第三批全国重点文物保护单位">第三批全国重点文物保护单位</a>。</p><p>府城玉皇庙始建年代不详，<a href="https://zh.wikipedia.org/wiki/%E9%9A%8B%E6%9C%9D" title="隋朝">隋朝</a> 时有祭拜 <a href="https://zh.wikipedia.org/wiki/%E4%B8%89%E6%B8%85" title="三清">三清</a> 的殿宇，<a href="https://zh.wikipedia.org/wiki/%E5%8C%97%E5%AE%8B" title="北宋">北宋</a><a href="https://zh.wikipedia.org/wiki/%E7%86%99%E5%AE%81" title="熙宁">熙宁</a> 九年（1076 年）扩建成玉皇庙，<a href="https://zh.wikipedia.org/wiki/%E9%87%91%E6%9C%9D" title="金朝">金</a><a href="https://zh.wikipedia.org/wiki/%E6%B3%B0%E5%92%8C_(%E9%87%91)" title="泰和 (金)">泰和</a> 七年（1207 年）重修，<a href="https://zh.wikipedia.org/wiki/%E8%B4%9E%E7%A5%90" title="贞祐">贞祐</a> 年间部分被毁，<a href="https://zh.wikipedia.org/wiki/%E5%85%83%E6%9C%9D" title="元朝">元</a><a href="https://zh.wikipedia.org/wiki/%E8%87%B3%E5%85%83_(%E5%85%83%E9%A1%BA%E5%B8%9D)" title="至元 (元顺帝)">至元</a> 元年（1335 年）重建。庙坐北朝南，占地 4000 余平方米。主要建筑有山门、仪门、<a href="https://zh.wikipedia.org/wiki/%E6%88%90%E6%B1%A4" title="成汤">成汤</a> 殿、献亭、<a href="https://zh.wikipedia.org/wiki/%E7%8E%89%E7%9A%87" title="玉皇">玉皇</a> 殿、东西配殿、<a href="https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF" title="二十八宿">二十八宿</a> 殿、十二辰殿、十三曜星殿、<a href="https://zh.wikipedia.org/wiki/%E5%85%B3%E5%B8%9D" title="关帝">关帝</a> 殿、蚕神殿等，其中玉皇殿为北宋遗构，成汤殿建于金，余为元 <a href="https://zh.wikipedia.org/wiki/%E6%98%8E" title="明">明</a><a href="https://zh.wikipedia.org/wiki/%E6%B8%85" title="清">清</a> 建筑。成汤殿面阔三间，进深三间，单檐 <a href="https://zh.wikipedia.org/wiki/%E6%82%AC%E5%B1%B1%E9%A1%B6" title="悬山顶">悬山顶</a>。玉皇殿面阔进深各三间，单檐悬山顶。各殿内有宋元明塑像三百余尊。</p><p>——以上摘自维基百科</p></blockquote><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968441507.webp"></p><p>可以去十一耀星殿找找自己的守护星座，拍照，挺有意思的。《黑神话 · 悟空》里面的亢金星君和小猪就是取自十一耀星殿的亢金龙和室火猪，值得一看。</p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968471687.webp"></p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968492677.webp"></p><p>玉皇庙里面三只小猫，一黑一白一橘。有灵性，不惧人。</p><p>第二站，关帝庙，从玉皇庙步行 10 分钟就可以到达。关帝庙和玉皇庙大多相似。又不尽相同。步入大门，停留一会，便可以感受身后缕缕穿堂风，头顶钟声随风飘荡。在这刻才能感受到自己不是城市中两点一线的牛马。</p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968509406.webp"></p><p>中午本地美食，老三样。十小碗应该是首选，但是需要提前排队，要提前饭点 1 个小时以上排队。我们 11 点半到的十小碗，被告知要排队 1 个半小时以上，并且不接受排队预约了。索性我们就直接去老三样。</p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968529696.webp"></p><p>第三站，晋城博物馆。下午天气热，博物馆里面有空调，随意逛逛也比较舒服。</p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968553170.webp"></p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968570609.webp"></p><p>第四站，原本博物馆，是一个私人的民办博物馆，需提前预约，基本上没人，馆藏丰富，可以感受一下</p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968588580.webp"></p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968607743.webp"></p><p>第五站，兰花城超市。从原本博物馆出来才下午 3 点半左右，时间还算比较充裕。其他景区离市区较远，我们打算去逛逛市区的兰花城超市买点特产带回去。</p><p>晚上本地美食，丁老大牛肉丸，强烈推荐，牛肉超级多。这个是在郑州吃不到，或者说吃不到这么实惠的。其实我更喜欢去一些小县城旅游，因为小县城里没有大城市的浮躁。</p><p><img src="/img/%E6%99%8B%E5%9F%8E%E6%B8%B8%E8%AE%B0-1763968620868.webp"></p><p>晚上 6 点 50 分的回程，9 点多到郑州。</p><h2 id="有感"><a href="#有感" class="headerlink" title="有感"></a>有感</h2><p>晋城算是山西文旅给想要感受山西文化的河南人的第一站。玉皇庙，关帝庙地势高，逛的时候有种天朗气清的感觉。空气好，可以作为逃离都市的一个好去处。晋城还有其他景点，一天时间肯定是逛不完。单单对于我和 Amelia 这种历史爱好者来说，除了上述几个景点，还有皇城相府，郭峪古城，湘峪古城没有参观。如果想欣赏自然风光，可以爬青莲寺，以及徒步王莽岭景区等。如果喜欢现代生活气息，白马寺山森林公园，晋城大剧院都是值得一看的地方。</p><p>晋城算是我去过为数不多的体感舒服的城市，整体城市界面干净整洁，交通便捷。市内打车相对便宜，近距离通行也有共享电动车（市区各处都有共享电动车这点对旅游非常友好）。温度适宜，气候舒适，适合养老疗养。美食也有晋城特色，值得尝试。晋城肯定会再次二刷，再来感受一下其他风景。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;该篇游记仅记录玉皇庙、关帝庙、晋城博物馆以及原本博物馆相关内容，偏向历史爱好者。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;起因是 Amelia 在小红书上看到，郑州去山西晋城的大巴来回免费，且 2.5~3 小时就能到。所以我们就打算当天去，当天回</summary>
      
    
    
    
    <category term="生活" scheme="https://www.lazydaily.cn/categories/%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="游记" scheme="https://www.lazydaily.cn/tags/%E6%B8%B8%E8%AE%B0/"/>
    
    <category term="郑州" scheme="https://www.lazydaily.cn/tags/%E9%83%91%E5%B7%9E/"/>
    
  </entry>
  
  <entry>
    <title>「持续更新25.12.09」AI辅助编程使用心得</title>
    <link href="https://www.lazydaily.cn/2025/05/13/124h1234y5812/"/>
    <id>https://www.lazydaily.cn/2025/05/13/124h1234y5812/</id>
    <published>2025-05-13T00:00:00.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>2025 年初，Deepseek 爆火。随之而来，就是各种 AI 相关的技术如同雨后春笋般涌现。其中关于 AI 编程方向，各家大厂各显神通。</p><p>早在 2024 年初，我就开始接触 AI 编程，一开始就使用了 Cursor。当我熟悉了它的操作之后，惊叹于它输出代码的质量。随后，我向身边朋友，同事极力推荐 Cursor。</p><p>本质上，Cursor 是一个集成 AI 能力的 VS Code 编辑器。尽管后来尝试各个大厂的不同的 AI 编程工具，例如：通义灵码，Windsurf，Trae 等，甚至自己搭建平台调用 Deepseek Api 完成编程辅助工作，但坦言来讲，体验都不如 Cursor。</p><h2 id="Cursor-收费模式"><a href="#Cursor-收费模式" class="headerlink" title="Cursor 收费模式"></a>Cursor 收费模式</h2><p>当然，Cursor 并不是免费的。它的收费方式是订阅制，订阅价格在 $20 &#x2F; 月。其中包含 500 次快请求，以及不限次数的慢请求。说实话，每月 140 块钱确实让人望而却步。</p><p><img src="/img/Pastedimage20250514161308.png" alt="Pastedimage20250514161308.png"></p><p>Cursor 相对新用户还是有很好的政策。新用户首月 14 天 150 次快请求，次月及以后每月 50 次快请求，用完就不能再使用了。理论上有足够多的账号，也可以白嫖。在没有教育优惠前，我就是用两个账号的试用和免费请求体验了 cursor 的绝大数功能。</p><blockquote><p>个人使用来说，我一般使用 cursor 调整前端样式，或者解决前端报错问题。或者直接需求文档丢给 cursor 让他给我生成前端文件。50 次请求大概能完成 2~3 个复杂功能页面的开发。</p></blockquote><h2 id="Cursor-教育优惠"><a href="#Cursor-教育优惠" class="headerlink" title="Cursor 教育优惠"></a>Cursor 教育优惠</h2><p>2025 年 5 月 8 号上午，cursor 开放教育优惠。凭借学生身份可以申请为期 1 年的 pro 会员。但是因为中国用户申请过多，在当天下午取消 CN 节点。目前可以用国外 edu 邮箱 + 学生信息验证，推荐咸鱼，一般来说 ￥100~200 不等可以搞定成品号。</p><p>目前，我使用的就是 Cursor 教育优惠。在咸鱼上搜 Cursor 教育优惠，用￥120 购买一个 <a href="http://www.sjsu.edu/">SAN JOSÉ STATE UNIVERSITY</a> 的 edu 邮箱和绑定信息。操作相对简单，只需要绑定登录邮箱，申请 Cursor 教育优惠认证即可（过程中可能需要绑定支付宝 0 元订阅，订阅成功之后可直接取消订阅，不影响当前订阅）。如果后续还有学生认证，可以转发 edu 邮箱到自己常用邮箱，这样就不会错过认证邮件。</p><blockquote><p>2025-5-20：订阅时间从 2025 年 5 月 12 号到 2026 年 5 月 12 号，当前使用无问题，后续有问题补充</p></blockquote><p><img src="/img/Pastedimage20250520090726.png" alt="Pastedimage20250520090726.png"></p><p>目前来说，500 次快请求基本满足个人高强度使用的要求。</p><blockquote><p>2025-08-08：cursor 在使用 claude-4-sonnet 时候，出现限制问题。google 之后发现只需要更改网络设置，从 http&#x2F;1.2 改成 http&#x2F;1.1 即可。</p></blockquote><h2 id="Cursor-教育优惠翻车"><a href="#Cursor-教育优惠翻车" class="headerlink" title="Cursor 教育优惠翻车"></a>Cursor 教育优惠翻车</h2><p>2025 年 9 月 28 号，SheerID 需要重新验证。在重新验证过程中，发现 ID 已失效。现在已经退回常规账号了。找了咸鱼商家，跑路了 &#x3D; &#x3D;。目前还在寻找其他可替代方案。</p><h2 id="Cursor-无限续杯（该方式只可以申请普通账号）"><a href="#Cursor-无限续杯（该方式只可以申请普通账号）" class="headerlink" title="Cursor 无限续杯（该方式只可以申请普通账号）"></a><del>Cursor 无限续杯</del>（该方式只可以申请普通账号）</h2><blockquote><p>以下描述内容仅以学习为目的，禁止商业行为。禁止传播，请支持正版。</p></blockquote><p>最近群里面的朋友给推荐了一个无限续杯的方法，正好解燃眉之急。具体的教程在 <a href="https://docs.qq.com/aio/DV2VKUnNaeFRyRGRH?electronTabTitle=%E6%AC%A2%E8%BF%8E%E4%BD%BF%E7%94%A8YCursor&isOfflineNewFileFlag=true&p=DKRZhtXI98ELAa724va8q8&client_hint=0">这里</a> 上已经很详细，下面只补充点我在操作过程中的一些问题。</p><p>无限续杯的方法其实就是不停的注册账号，绑定银行卡，试用 7 天。YCursor 将大部分的脚本化了，方便了不少。</p><p>有几个要注意的点：</p><ul><li>域名一定要用一级域名，二级域名肯定不行（尽管教程上写了二级域名不行，我还是不死心的试了试）</li><li>域名开启邮件托管，我的是阿里云域名，控制台好像还没有邮件托管转发功能，需要托管到 cloudflare 上进行转发</li><li>使用 QQ 邮箱的密码应该是开启 IMAP 的授权码，不是自己的密码</li><li>在后续绑卡过程中，有可能 Tampermonkey 自动填充银行卡号的功能可能不行，需要通过手动方式添加，只要添加卡号，有效期，CVV。其他信息可以随便填。</li></ul><h2 id="ChatGPT-Business-CodeX"><a href="#ChatGPT-Business-CodeX" class="headerlink" title="ChatGPT Business + CodeX"></a>ChatGPT Business + CodeX</h2><p>如果有一定的预算，可以通过订阅 ChatGPT Team（L 站跳蚤市场上 5-10 元就可以上车，为期一个月），同时搭配 VS Code 中的 <a href="https://marketplace.visualstudio.com/items?itemName=openai.chatgpt">Codex</a> 插件和 IDEA 的 Codex 插件，可以优雅的 vibe-coding，目前我买了两个月的 Team,其中有一个月用了两周就跑路了，另外一个用到现在还没有跑路。实测下来也能力还可以。</p><p>另外还有一个方式可以白嫖 ChatGPT，即申请 ChatGPT for teacher。该方式不支持 codex 使用。</p><h2 id="第三方-Key-Codex"><a href="#第三方-Key-Codex" class="headerlink" title="第三方 Key + Codex"></a>第三方 Key + Codex</h2><p>最近在群里有人分享通过公益站第三方 key + Codex 来进行 vibe coding。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;2025 年初，Deepseek 爆火。随之而来，就是各种 AI 相关的技术如同雨后春笋般涌现。其中关于 AI 编程方向，各家大厂各显神通。</summary>
      
    
    
    
    <category term="AI" scheme="https://www.lazydaily.cn/categories/AI/"/>
    
    
    <category term="Cursor" scheme="https://www.lazydaily.cn/tags/Cursor/"/>
    
  </entry>
  
  <entry>
    <title> 为什么一定要有一个个人网站？</title>
    <link href="https://www.lazydaily.cn/2025/04/29/X65It8tNxLf0GSOZ/"/>
    <id>https://www.lazydaily.cn/2025/04/29/X65It8tNxLf0GSOZ/</id>
    <published>2025-04-29T13:04:49.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>大家好，我是子规，很高兴能够在这里和大家相遇。本篇文章记录该站建立的初衷、建站过程以及在过程中的思考。</p><h2 id="为什么建立这个网站"><a href="#为什么建立这个网站" class="headerlink" title="为什么建立这个网站"></a>为什么建立这个网站</h2><p>在此网站建立之前，我通常在 CSDN 上写作。</p><p>但随着时间推移，我开始发现，CSDN 上的文章，Although it is very good, but it is not very good for SEO。尽管我在 CSDN 上没有很多有质量的输出，但是繁杂的信息流却充斥在我面前。关注，点赞，私信，评论，活动等等都是我要面对的。这些信息流无时无刻不在分散我的注意力，使我感到非常痛苦。</p><p>因此，我开始探索更有效的方式。前几年微信公众号爆火，我本人也关注不少微信公众号。其中，有非常多关于编程的公众号对我帮助极大，我非常喜欢。时常幻想自己能够在公众号上写好文章，这样我就可以分享自己的知识了，或者说更纯粹的分享自己的知识了。</p><p>我也的确这么做了，「整点儿代码」就是尝试。「整点儿代码」最初的规划是，每天产出一篇文章，设置定时晚上 10 点左右发布，这也 call back 了 “ 整点 “ 这个 concept。可惜，我并没有成功。一方面微信公众号的审核不通过导致不能整点发布，让我十分困扰。另一方面，就是个人没有坚持下来。最重要的一点是，我仍然没有从关注，点赞，私信，评论中脱离出来。是的，公众号也有这样的问题需要我面对。显然，这不是我想要的、理想的博客记录方式。</p><p>最终，我决定做一个自己的博客网站。</p><h2 id="建站过程"><a href="#建站过程" class="headerlink" title="建站过程"></a>建站过程</h2><p>2022 年，我了解到，GitHub Pages 是一个非常优秀的博客托管平台。基于 Git 方式的 post 和自动化发布节省了我不少的时间，也使我更专注于内容的编写。确定了服务托管方式，剩下的工作就是考虑网站搭建技术方向了。互联网上关于博客类网站建设的技术已经非常成熟了，Wordpress，Vuepress，Docsify，Hexo，Next.js 等等。</p><h3 id="Docsify"><a href="#Docsify" class="headerlink" title="Docsify"></a>Docsify</h3><p>最开始时候，我比较欣赏 Docsify 这种静态简单的风格。Docsify 本身支持的插件丰富，包括评论系统，访问次数，字数统计，页面搜索等，可以大大节省自己开发的时间。此外，将项目部署到 Github 上，使用 Github Page 托管之后，不用维护本地文档。编写文章发布也不需要编译，只要提交文件，等待自动部署即可。</p><p>你可以访问 <a href="https://z1gui.github.io/chips/#/">https://z1gui.github.io/chips/#/</a> 来查看页面。该项目搭建具体参考了 <a href="https://bugstack.cn/">小傅哥</a> 的这篇博客 <a href="https://blog.csdn.net/generalfu/article/details/123268118?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522815fa9b0be7b0090fc06b78edb862108%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=815fa9b0be7b0090fc06b78edb862108&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-123268118-null-null.nonecase&utm_term=docsify&spm=1018.2226.3001.4450">《# 在GitHub&#x2F;Gitee上，搭建一个简单的所见即所得博客》</a>。如果你觉得这个样式符合自己的审美，也可以参考我仓库里面的这个项目 <a href="https://github.com/z1gui/chips">https://github.com/z1gui/chips</a> 配置。</p><p>期间，我还尝试将 Docsify 部署到云服务器上，后来发现不如在 Github 上好管理，遂放弃。</p><h3 id="Hexo"><a href="#Hexo" class="headerlink" title="Hexo"></a>Hexo</h3><p>再后来，我发现 docsify 很好，但是不够好。在文章展示，以及必要插件上，docsify 能满足一个博客的基础功能。但是，docsify 没有标签，分类，归档等功能。虽然我不追求极致的动效和交互效果，但 Docsify 过于简单的交互效果，让我感觉在阅读一个在线的 markdown 阅读器。与此同时，在 V2ex 的 VXNA 模块看过太多优秀博主的博客，让我又一次萌生了改博客样式的念头。</p><p>我开始尝试使用 Hexo 博客框架。Hexo 是一个快速、简单、功能强大的博客框架。你使用 <a href="http://daringfireball.net/projects/markdown/">Markdown</a>（或其他标记语言）撰写帖子，Hexo 会在几秒钟内生成具有漂亮主题的静态文件。</p><p>本站当前使用 <a href="https://hexo.io/">Hexo</a> 搭建，使用 <a href="https://github.com/z1gui/hexo-theme-ZenMind-Pro">hexo-theme-ZenMind-Pro</a> 主题搭建。<strong>hexo-theme-ZenMind-Pro</strong> 是本人在 <a href="https://github.com/zhoulianglen/hexo-theme-ZenMind">hexo-theme-ZenMind</a> 主题上做了修改优化。如有问题，可以联系我或在 issues 中提出您的问题。<strong>在此感谢 <a href="https://github.com/zhoulianglen">@周良粥凉</a> 的主题以及其博客样式参考</strong>。</p><p>在改造 hexo-theme-ZenMind 过程中，我大量使用了 Cursor 进行编程开发。一般来说，简单的样式调整，以及想要达成的效果 Cursor 都能够轻松完成。如果你对 Cursor 编程感兴趣，请参考这篇文章 <a href="%E3%80%8C%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B025.09.28%E3%80%8DAI%E8%BE%85%E5%8A%A9%E7%BC%96%E7%A8%8B%E4%BD%BF%E7%94%A8%E5%BF%83%E5%BE%97.md">「持续更新25.09.28」AI辅助编程使用心得</a>。其实，Hexo 丰富的生态，即使不二次开发，我相信你也能找到自己心仪的样式框架。</p><h2 id="如何写好自己的博客"><a href="#如何写好自己的博客" class="headerlink" title="如何写好自己的博客"></a>如何写好自己的博客</h2><p>从一开始下定决心写博客，我也是迷茫的。什么写，什么不写，这些很难形成一个标准。</p><p>究其原因，大部分在写博客的时候，我都在想如何让读者读明白，又如何让读者快速理解。参考我早期的博客就能看出来，有些概念太想解释清楚，以至于长篇大论，尽管它可能很简单。后来，我意识到博客其实是给自己的知识做沉淀，目的是让自己 “ 知其所以然 “。以这个思想来写博客，会发现写好博客并不难。</p><p>这是关于《如何写好自己的博客》我想说的其一，<strong>「以自己为中心，让自己 “ 知其所以然 “」。</strong> 大多数人写博客是为了提升自己的知识水平，提升自己的专业技能，形成一个自己的知识体系。<strong>「如何通过博客建立自己的知识体系」</strong>，便是我想要说的其二。</p><p>后来有幸拜读 pdai 佬引用的 <a href="https://pdai.tech/md/team/team-z-tixi.html">《知识体系：如何构建自己的知识体系》</a>，醍醐灌顶，感受颇多。这里引用一下文章中的话：</p><blockquote><p>我们的学习分为四阶段：输入、内化、沉淀、输出。碎片化在输入的时候用，因为信息本身是碎片化的，时间也是碎片化的，所以输入信息的时候，要碎片化。但有需要体系化的沉淀。因此碎片化的输入，加上体系化的沉淀，你就可以实现利用碎片化的时间，做体系化的学习。</p></blockquote><p>整篇文章中，强调了 “ 碎片化学习 “ 形成 “ 体系化技能 “。并总结出相应的三个步骤：定目标、搭建知识体系、填内容。其实建立自己的博客，写好自己的博客，也遵从着三个步骤。</p><p>这里就扣合了之前提的第一点。在我看来，建立博客的目标应是让自己 “ 知其所以然 “。在这个目标下，再去定义体系，填写内容。当然，再后来我发现博客也可以是一个思想沉淀的地方，久而久之我也会将自己的一些体验，感悟放在这里，这是后话，容我们日后再谈。</p><p>对我来说，建立博客更像是形成自己知识体系，总结人生感悟的具象化的体现。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;大家好，我是子规，很高兴能够在这里和大家相遇。本篇文章记录该站建立的初衷、建站过程以及在过程中的思考。&lt;/p&gt;
&lt;h2 id=&quot;为什么建立这</summary>
      
    
    
    
    <category term="生活" scheme="https://www.lazydaily.cn/categories/%E7%94%9F%E6%B4%BB/"/>
    
    
    <category term="无病呻吟" scheme="https://www.lazydaily.cn/tags/%E6%97%A0%E7%97%85%E5%91%BB%E5%90%9F/"/>
    
  </entry>
  
  <entry>
    <title> 详解：攻破Redis这一难关</title>
    <link href="https://www.lazydaily.cn/2024/09/27/9QB2u4AB0ZTF7u8T/"/>
    <id>https://www.lazydaily.cn/2024/09/27/9QB2u4AB0ZTF7u8T/</id>
    <published>2024-09-27T10:10:05.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、Redis-概念和基础"><a href="#一、Redis-概念和基础" class="headerlink" title="一、Redis 概念和基础"></a>一、Redis 概念和基础</h2><h3 id="什么是-Redis（Remote-Dictionary-Server-远程数据服务）"><a href="#什么是-Redis（Remote-Dictionary-Server-远程数据服务）" class="headerlink" title="什么是 Redis（Remote Dictionary Server 远程数据服务）"></a>什么是 Redis（Remote Dictionary Server 远程数据服务）</h3><p>Redis 是一款内存高速缓存数据库。其底层使用 C 语言编写，是一个 key-value 存储系统。支持丰富的数据类型，如：String，List，Set，zSet，Hash 以及 5.0 版本新增的 Stream。</p><h3 id="为什么要使用-Redis"><a href="#为什么要使用-Redis" class="headerlink" title="为什么要使用 Redis"></a>为什么要使用 Redis</h3><ul><li><strong>读写性能优异</strong>：Redis 能读的速度是 110000 次&#x2F;s，写的速度是 81000 次&#x2F;s</li><li><strong>数据类型丰富</strong>：支持 String，List，Set，zSet，Hash 以及 Stream 数据类型操作</li><li><strong>原子性</strong>：Redis 所有的操作都是原子性的，并且支持多个操作合并之后原子性执行</li><li><strong>丰富的特性</strong>：支持发布、订阅，支持通知，支持 key 过期设置等特性</li><li><strong>持久化</strong>：有 RDB 和 AOF 等多种持久化方式</li><li><strong>发布订阅</strong>：支持发布订阅模式</li><li><strong>分布式</strong>：Redis Cluster</li></ul><p>基于如此多的优点，Redis 可用于缓存，事件发布或订阅，高速队列等场景。支持网络，提供字符串，哈希，列表，队列，集合结构直接存储，基于内存，可以持久化。</p><h3 id="Redis-的使用场景"><a href="#Redis-的使用场景" class="headerlink" title="Redis 的使用场景"></a>Redis 的使用场景</h3><h4 id="「热点数据的缓存」"><a href="#「热点数据的缓存」" class="headerlink" title="「热点数据的缓存」"></a>「热点数据的缓存」</h4><p>缓存是 Redis 最常见的应用场景，之所以这么使用，主要是因为 Redis 的读写性能优越。而且主键有取代 memcached，成为首选服务器缓存的组件。除此之外，Redis 内部还是支持事务，在使用的时候有效的保证了数据的一致性。</p><p>作为缓存使用时候，一般通过两种方式保存数据：</p><ol><li>读取前，先读取 Redis，如果没有数据，再读取数据库，将数据拉入到 Redis 中</li><li>插入数据库时候，同时插入 Redis</li></ol><p>方案一：实施起来简单，但是有两个注意的地方：</p><ul><li>避免缓存击穿（数据库没有需要命中的数据，导致 Redis 一直没有数据，从而一直命中数据库）</li><li>数据的实时性相对较差一点</li></ul><p>方案二：数据实时性抢，但是开发时候不便于统一处理</p><p>当然，不同的实际情况使用不同的场景。方案一适用于对于数据实时性要求不是特别高的场景。方案二适用于字典表、数据量不大的数据存储。</p><h4 id="「限时业务的运用」"><a href="#「限时业务的运用」" class="headerlink" title="「限时业务的运用」"></a>「限时业务的运用」</h4><p>Redis 中可以使用 expire 命令来设置一个键的生时间，到期后 Redis 会删除它，利用这一特性可以运用在现实的优惠活动信息，手机验证码等场景业务。</p><h4 id="「计数器相关问题」"><a href="#「计数器相关问题」" class="headerlink" title="「计数器相关问题」"></a>「计数器相关问题」</h4><p>Redis 由于 incrby 命令可以实现原子性的递增，所以可以运用于高并发的秒杀活动、分布式序列号的生成，具体业务还体现在比如限制一个手机号发多少条信息、一个接口一分钟限制请求多少次，一个接口一天限制调用多少次等等。</p><h4 id="「分布式锁」"><a href="#「分布式锁」" class="headerlink" title="「分布式锁」"></a>「分布式锁」</h4><p>这个主要利用 Redis 的 setnx 命令，<code>setnx</code>:”set if not exists” 就是如果不存在则成功设置缓存并且返回 1，否则返回 0，这个特性在很多后台中有所使用。</p><p>因为我们服务器是集群的，定时任务可能在两台机器上执行，所以在定时任务中首先通过 setnx 设置一个 lock，如果成功设置就执行，如果失败，就表明该任务正在执行。根据业务场景，我们还可以给这个 lock 加一个过期时间，比如 30 分钟执行一次的定时任务，那么设置这个过期时间小于 30 分钟的某个时间就可以了，具体可根据定时任务的周期以及定时任务执行消耗时间来定。</p><p>在分布式锁的场景中，主要用于秒杀系统等。</p><h4 id="「延时操作」"><a href="#「延时操作」" class="headerlink" title="「延时操作」"></a>「延时操作」</h4><p>常见的延时操作就是订单生成之后延时付款。在订单生成后就占用库存，10 分钟后检验用户是否购买，如果没有购买将该订单设置无效，同时还原库存。</p><p>由于 Redis 在 2.8.0 之后提供了 Keyspace Notifications 功能，允许客户订阅 Pub&#x2F;Sub 频道，以便以某种方式接收影响 Redis 的时间。所以以上的需求就可以用以下解决方案，我们在订单产生时，设置一个 key，同时设置 10 分钟后过期，再在后台实现一个监听器，检测到 key 的时效，将 key 失效后的后续逻辑加上。</p><p>当然我们也可以用 rabbitmq，activemq 等消息中间件的延时队列服务实现该需求。</p><h4 id="「排行榜相关问题」"><a href="#「排行榜相关问题」" class="headerlink" title="「排行榜相关问题」"></a>「排行榜相关问题」</h4><p>关系型数据库在排行榜方面查询速度普遍较慢，所以可以借助 Redis 中的 zSet（sorted set）进行热点数据的排序。</p><p>比如点赞排行榜，做一个 SortedSet，然后以用户的 openId 作为上面的 username，以用户的点赞数作为上面的 score，然后针对每一个用户，通过 zrangebyscore 就可以按照点赞数获取排行榜，然后再根据 username 获取用户的 hash 信息。</p><h4 id="「点赞、好友等相互关系的存储」"><a href="#「点赞、好友等相互关系的存储」" class="headerlink" title="「点赞、好友等相互关系的存储」"></a>「点赞、好友等相互关系的存储」</h4><p>Redis 利用集合的一些命令，比如求交集、并集、差集等。</p><p>在微博应用中，每个用户关注的人都在一个集合中，很容易实现两个人共同好友功能。</p><h4 id="「简单队列」"><a href="#「简单队列」" class="headerlink" title="「简单队列」"></a>「简单队列」</h4><p>由于 Redis 有 List push 和 List pop 这样的命令，所以很方板执行队列操作。</p><h2 id="二、5-种基础数据类型详解"><a href="#二、5-种基础数据类型详解" class="headerlink" title="二、5 种基础数据类型详解"></a>二、5 种基础数据类型详解</h2><p><img src="/img/fab5266a2b3c6beb2cd1eef59a741cb0_MD5.png" alt="fab5266a2b3c6beb2cd1eef59a741cb0_MD5.png"></p><table><thead><tr><th align="left">结构类型</th><th align="left">结构存储的值</th><th align="left">结构的读写能力</th></tr></thead><tbody><tr><td align="left"><strong>String 字符串</strong></td><td align="left">可以是字符串、整数或浮点数</td><td align="left">对整个字符串或字符串的一部分进行操作；对整数或浮点数进行自增或自减操作；</td></tr><tr><td align="left"><strong>Lit 列表</strong></td><td align="left">一个链表，链表上的每个节点都包含一个字符串</td><td align="left">对链表的两端进行 push 和 pop 操作，读取单个或多个元素；根据值查找或删除元素；</td></tr><tr><td align="left"><strong>Set 集合</strong></td><td align="left">包含字符串的无序集合</td><td align="left">字符串的集合，包含基础的方法有看是否存在添加、获取、删除；还包含计算交集、并集、差集等</td></tr><tr><td align="left"><strong>Hash 散列</strong></td><td align="left">包含键值对的无序散列表</td><td align="left">包含方法有添加、获取、删除单个元素</td></tr><tr><td align="left"><strong>Zset 有序集合</strong></td><td align="left">和散列一样，用于存储键值对</td><td align="left">字符串成员与浮点数分数之间的有序映射；元素的排列顺序由分数的大小决定；包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素</td></tr><tr><td align="left"></td><td align="left"></td><td align="left"></td></tr></tbody></table><h3 id="String-字符串"><a href="#String-字符串" class="headerlink" title="String 字符串"></a>String 字符串</h3><blockquote><p>String 是 Redis 中最基本的数据类型，一个 key 对应一个 value</p></blockquote><p>String 类型是二进制安全的，意思是 Redis 的 String 类型可以包含任何数据，如数字，字符串，jpg 图片或者序列化对象。</p><h4 id="图例"><a href="#图例" class="headerlink" title="图例"></a>图例</h4><p>下图是一个 String 类型的实力，其中键为 hello，值为 world</p><p><img src="/img/96c772c45845fdcb0286753b90dc7815_MD5.png" alt="96c772c45845fdcb0286753b90dc7815_MD5.png"></p><h4 id="命令使用"><a href="#命令使用" class="headerlink" title="命令使用"></a>命令使用</h4><table><thead><tr><th>命令</th><th>简述</th><th>使用</th></tr></thead><tbody><tr><td>GET</td><td>获取存储在给定键中的值</td><td>GET name</td></tr><tr><td>SET</td><td>设置存储在给定键中的值</td><td>SET name value</td></tr><tr><td>DEL</td><td>删除存储在给定键中的值</td><td>DEL name</td></tr><tr><td>INCR</td><td>将键存储的值加 1</td><td>INCR key</td></tr><tr><td>DECR</td><td>将键存储的值减 1</td><td>DECR key</td></tr><tr><td>INCRBY</td><td>将键存储的值加上整数</td><td>INCRBY key amount</td></tr><tr><td>DECRBY</td><td>将键存储的值减去整数</td><td>DECRBY key amount</td></tr></tbody></table><h4 id="命令执行"><a href="#命令执行" class="headerlink" title="命令执行"></a>命令执行</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> <span class="token builtin class-name">set</span> hello worldOK<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> get hello<span class="token string">"world"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> del hello<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> get hello<span class="token punctuation">(</span>nil<span class="token punctuation">)</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> <span class="token builtin class-name">set</span> counter <span class="token number">2</span>OK<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> get counter<span class="token string">"2"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> incr counter<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> get counter<span class="token string">"3"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> incrby counter <span class="token number">100</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">103</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> get counter<span class="token string">"103"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> decr counter<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">102</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> get counter<span class="token string">"102"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实战场景"><a href="#实战场景" class="headerlink" title="实战场景"></a>实战场景</h4><ul><li><strong>缓存</strong>：经典使用场景，把常用信息，字符串，图片或者视频等信息放入到 redis 中，redis 作为缓冲层，mysql 作为持久层，降低 mysql 的读写压力</li><li><strong>计数器</strong>：redis 是单线程模型，一个命令执行完才会执行下一个命令，同时数据库可以一步落地到其他数据源</li><li><strong>session</strong>：常见的 Spring session + redis 实现 session 共享</li></ul><h3 id="List-列表"><a href="#List-列表" class="headerlink" title="List 列表"></a>List 列表</h3><blockquote><p>Redis 中的 List 其实就是链表（Redis 用双端链表实现 List）</p></blockquote><p>使用 List 结构，我们可以轻松实现最新消息排队功能（比如微博中的 TimeLine）。LIst 的另外一个应用就是消息队列，可以利用 List 的 PUSH 操作，将任务存入到 LIst 中，然后再用 POP 操作将任务去除进行执行。</p><h4 id="图例-1"><a href="#图例-1" class="headerlink" title="图例"></a>图例</h4><p><img src="/img/bef2a99abfa47aef2ba0313cb29b61fd_MD5.png" alt="bef2a99abfa47aef2ba0313cb29b61fd_MD5.png"></p><h4 id="命令使用-1"><a href="#命令使用-1" class="headerlink" title="命令使用"></a>命令使用</h4><table><thead><tr><th>命令</th><th>简述</th><th>使用</th></tr></thead><tbody><tr><td>RPUSH</td><td>将给定值推入到列表右端</td><td>RPUSH key value</td></tr><tr><td>LPUSH</td><td>将给定值推入到列表左端</td><td>LPUSH key value</td></tr><tr><td>RPOP</td><td>从列表的右端弹出一个值，并返回被弹出的值</td><td>RPOP key</td></tr><tr><td>LPOP</td><td>从列表的左端弹出一个值，并返回被弹出的值</td><td>LPOP key</td></tr><tr><td>LRANGE</td><td>获取列表在给定范围上的所有值</td><td>LRANGE key 0 -1</td></tr><tr><td>LINDEX</td><td>通过索引获取列表中的元素。你也可以使用负数下标，以 -1 表示列表的最后一个元素，-2 表示列表的倒数第二个元素，以此类推。</td><td>LINDEX key index</td></tr></tbody></table><h5 id="使用技巧"><a href="#使用技巧" class="headerlink" title="使用技巧"></a>使用技巧</h5><ul><li>lpush + lpop &#x3D; Stack(栈)</li><li>lpush + rpop &#x3D; Queue(队列)</li><li>lpush + ltrim &#x3D; Capped Collection(有限集合)</li><li>lpush + brpop &#x3D; Message Queue(消息队列)</li></ul><h4 id="命令执行-1"><a href="#命令执行-1" class="headerlink" title="命令执行"></a>命令执行</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> lpush mylist <span class="token number">1</span> <span class="token number">2</span> ll <span class="token function">ls</span> mem<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> lrange mylist <span class="token number">0</span> <span class="token parameter variable">-1</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"mem"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"ls"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"ll"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"2"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> lindex mylist <span class="token parameter variable">-1</span><span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> lindex mylist <span class="token number">10</span> <span class="token comment"># index不在 mylist 的区间范围内</span><span class="token punctuation">(</span>nil<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实战场景-1"><a href="#实战场景-1" class="headerlink" title="实战场景"></a>实战场景</h4><ul><li>微博 TimeLine：有人发布微博，就用 lpush 加入时间轴，展示新的列表信息</li><li>消息队列</li></ul><h3 id="Set-集合"><a href="#Set-集合" class="headerlink" title="Set 集合"></a>Set 集合</h3><blockquote><p>Redis 的 set 是 String 类型的无序集合。集合成员是唯一的，这就意味着集合中不能出现重复的数据。</p></blockquote><h4 id="图例-2"><a href="#图例-2" class="headerlink" title="图例"></a>图例</h4><p><img src="/img/0d65269f49d41e980687de9c0e0d7411_MD5.png" alt="0d65269f49d41e980687de9c0e0d7411_MD5.png"></p><h4 id="命令使用-2"><a href="#命令使用-2" class="headerlink" title="命令使用"></a>命令使用</h4><table><thead><tr><th>命令</th><th>简述</th><th>使用</th></tr></thead><tbody><tr><td>SADD</td><td>向集合添加一个或多个成员</td><td>SADD key value</td></tr><tr><td>SCARD</td><td>获取集合的成员数</td><td>SCARD key</td></tr><tr><td>SMEMBERS</td><td>返回集合中的所有成员</td><td>SMEMBERS key member</td></tr><tr><td>SISMEMBER</td><td>判断 member 元素是否是集合 key 的成员</td><td>SISMEMBER key member</td></tr></tbody></table><p>其他一些集合操作，可以参考 <a href="https://www.runoob.com/redis/redis-sets.html">这里</a></p><h4 id="命令执行-2"><a href="#命令执行-2" class="headerlink" title="命令执行"></a>命令执行</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> sadd myset hao hao1 xiaohao hao<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> smembers myset<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xiaohao"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"hao1"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"hao"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> sismember myset hao<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实战场景-2"><a href="#实战场景-2" class="headerlink" title="实战场景"></a>实战场景</h4><ul><li>标签（tag）：给用户添加标签，或者用户给消息添加标签，这样有同一个标签或者类似标签的可以推送给关注的事或者人</li><li>点赞或点踩，收藏等，可以放到 set 中实现</li></ul><h3 id="Hash-散列"><a href="#Hash-散列" class="headerlink" title="Hash 散列"></a>Hash 散列</h3><blockquote><p>Redis hash 是一个 String 类型的 field（字段）和 value（值）的映射表，hash 特别适合用于存储对象。</p></blockquote><h4 id="图例-3"><a href="#图例-3" class="headerlink" title="图例"></a>图例</h4><p><img src="/img/03ab0a34211dc37730685bcb58fc1b41_MD5.png" alt="03ab0a34211dc37730685bcb58fc1b41_MD5.png"></p><h4 id="命令使用-3"><a href="#命令使用-3" class="headerlink" title="命令使用"></a>命令使用</h4><table><thead><tr><th>命令</th><th>简述</th><th>使用</th></tr></thead><tbody><tr><td>HSET</td><td>添加键值对</td><td>HSET hash-key sub-key1 value1</td></tr><tr><td>HGET</td><td>获取指定散列键的值</td><td>HGET hash-key key1</td></tr><tr><td>HGETALL</td><td>获取散列中包含的所有键值对</td><td>HGETALL hash-key</td></tr><tr><td>HDEL</td><td>如果给定键存在于散列中，那么就移除这个键</td><td>HDEL hash-key sub-key1</td></tr></tbody></table><h4 id="命令执行-3"><a href="#命令执行-3" class="headerlink" title="命令执行"></a>命令执行</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hset user name1 hao<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hset user email1 hao@163.com<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hgetall user<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name1"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"hao"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"email1"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"hao@163.com"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hget user user<span class="token punctuation">(</span>nil<span class="token punctuation">)</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hget user name1<span class="token string">"hao"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hset user name2 xiaohao<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hset user email2 xiaohao@163.com<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> hgetall user<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name1"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"hao"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"email1"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"hao@163.com"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"name2"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"xiaohao"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token string">"email2"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"xiaohao@163.com"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实战场景-3"><a href="#实战场景-3" class="headerlink" title="实战场景"></a>实战场景</h4><ul><li>缓存：能直观，相比 String 更省空间，更容易的维护缓存信息，如用户信息，视频信息等。</li></ul><h3 id="Zset-有序集合"><a href="#Zset-有序集合" class="headerlink" title="Zset 有序集合"></a>Zset 有序集合</h3><blockquote><p>Redis 有序集合和集合一样也是 String 类型元素的集合，且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 通过分数来为集合中的成员中进行从小到大的排序。</p></blockquote><p>有序集合的成员是唯一的，但是分数（score）可以重复。有序集合是通过两种数据接口实现：</p><ol><li>压缩列表（ziplist）：ziplist 是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数，存储整数时是采用整数的二进制而不是字符串形式存储。他能在 O(1) 的时间复杂度下完成 list 两端的 push 和 pop 操作。但是因为每次操作都需要重新分配 ziplist 内容，所以实际复杂度和 ziplist 的内存使用量有关</li><li>跳跃表（zSkiplist）：跳跃表的性能可以保证在查找，删除，添加等操作时候在对数期望时间内完成，这个性能可以和平衡树相比较的，而且实现方面比平衡树要优雅。这也是采用跳跃表的主要原因。跳跃表的复杂度是 O(log(n))。</li></ol><h4 id="图例-4"><a href="#图例-4" class="headerlink" title="图例"></a>图例</h4><p><img src="/img/d06b3e0fa469f44264df4a7f9f27c2b6_MD5.png" alt="d06b3e0fa469f44264df4a7f9f27c2b6_MD5.png"></p><h4 id="命令使用-4"><a href="#命令使用-4" class="headerlink" title="命令使用"></a>命令使用</h4><table><thead><tr><th>命令</th><th>简述</th><th>使用</th></tr></thead><tbody><tr><td>ZADD</td><td>将一个带有给定分值的成员添加到有序集合里面</td><td>ZADD zset-key 178 member1</td></tr><tr><td>ZRANGE</td><td>根据元素在有序集合中所处的位置，从有序集合中获取多个元素</td><td>ZRANGE zset-key 0-1 withccores</td></tr><tr><td>ZREM</td><td>如果给定元素成员存在于有序集合中，那么就移除这个元素</td><td>ZREM zset-key member1</td></tr></tbody></table><p>更多命令请参考 <a href="https://www.runoob.com/redis/redis-sorted-sets.html">这里</a></p><h4 id="命令执行-4"><a href="#命令执行-4" class="headerlink" title="命令执行"></a>命令执行</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> zadd myscoreset <span class="token number">100</span> hao <span class="token number">90</span> xiaohao<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">2</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> ZRANGE myscoreset <span class="token number">0</span> <span class="token parameter variable">-1</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xiaohao"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"hao"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> ZSCORE myscoreset hao<span class="token string">"100"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实战场景-4"><a href="#实战场景-4" class="headerlink" title="实战场景"></a>实战场景</h4><ul><li>排行榜：有序集合经典使用场景。例如小数视频等网站需要对用户上传的小说视频做排行榜，榜单可以按照用户关注数，更新时间，字数扥打分，做排行。</li></ul><h2 id="三、3-种特殊数据类型详解"><a href="#三、3-种特殊数据类型详解" class="headerlink" title="三、3 种特殊数据类型详解"></a>三、3 种特殊数据类型详解</h2><h3 id="HyperLogLogs（基数统计）"><a href="#HyperLogLogs（基数统计）" class="headerlink" title="HyperLogLogs（基数统计）"></a>HyperLogLogs（基数统计）</h3><h4 id="什么是基数"><a href="#什么是基数" class="headerlink" title="什么是基数"></a>什么是基数</h4><p>举个例子，A &#x3D;「1，2，3，4，5」，B &#x3D;「3，5，6，7，9」；那么基数就是不重复的元素 &#x3D; 1，2，4，6，7，9；（允许一定范围内的误差）</p><h4 id="HyperLogLogs-主要运用场景"><a href="#HyperLogLogs-主要运用场景" class="headerlink" title="HyperLogLogs 主要运用场景"></a>HyperLogLogs 主要运用场景</h4><p>这个数据结构可以非常节省内存的去统计各种计数，比如注册的 IP 数，每日访问 IP 数，页面实时 UV，在线人数，共同好友等；</p><h4 id="优势体现在哪"><a href="#优势体现在哪" class="headerlink" title="优势体现在哪"></a>优势体现在哪</h4><p>一个大型的网站，每天访问的 IP 是 100w，粗略计算一个 IP 消耗 15 字节，那么 100w 个 IP 就是 15M。而 HyperLogLogs 在 Redis 中的每个键占用内容是 12k，理论上存储近似 2^64 个值。这个值是有 0.81% 的标准错误的近似值。</p><h4 id="命令使用-5"><a href="#命令使用-5" class="headerlink" title="命令使用"></a>命令使用</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> pfadd key1 a b c d e f g h i <span class="token comment"># 创建第一组元素</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> pfcount key1 <span class="token comment"># 统计元素的基数数量</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">9</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> pfadd key2 c j k l m e g a <span class="token comment"># 创建第二组元素</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> pfcount key2<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">8</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> pfmerge key3 key1 key2 <span class="token comment"># 合并两组：key1 key2 -> key3 并集</span>OK<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> pfcount key3<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">13</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="Bitmap（位存储）"><a href="#Bitmap（位存储）" class="headerlink" title="Bitmap（位存储）"></a>Bitmap（位存储）</h3><blockquote><p>Bitmap 即位图数据结构，都是操作二进制数来进行记录，只有 0 和 1 两种状态。</p></blockquote><h4 id="用来解决什么问题"><a href="#用来解决什么问题" class="headerlink" title="用来解决什么问题"></a>用来解决什么问题</h4><p>比如：统计用户信息，登录！不登录！活跃！不活跃！打卡！不打卡！只要是两种状态都可以用 Bitmap 来存储。</p><p>比如存储一年的打卡状态需要多少内存呢？365 天 &#x3D; 356bit 1 字节 &#x3D; 8bit，相当于 46 个字节左右！</p><h4 id="命令使用-6"><a href="#命令使用-6" class="headerlink" title="命令使用"></a>命令使用</h4><p>使用 bitmap 来记录 周一到周日的打卡！周一：1 周二：0 周三：0 周四：1 ……</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">0</span> <span class="token number">1</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">1</span> <span class="token number">1</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">2</span> <span class="token number">0</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">3</span> <span class="token number">1</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">4</span> <span class="token number">0</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">5</span> <span class="token number">0</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> setbit sign <span class="token number">6</span> <span class="token number">1</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>查看某天是否打卡！</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> getbit sign <span class="token number">3</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> getbit sign <span class="token number">5</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>统计操作，统计打卡次数</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> bitcount sign <span class="token comment"># 统计这周的打卡记录，就可以看到是否有全勤！</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="geospatial（地理位置）"><a href="#geospatial（地理位置）" class="headerlink" title="geospatial（地理位置）"></a>geospatial（地理位置）</h3><blockquote><p>Redis 的 Geo 在 3.2 版本推出，这个功能可以推算出地理位置的信息：两地之间的距离，方圆几里的人</p></blockquote><h4 id="命令使用-7"><a href="#命令使用-7" class="headerlink" title="命令使用"></a>命令使用</h4><h5 id="Geoadd-添加地理位置"><a href="#Geoadd-添加地理位置" class="headerlink" title="Geoadd 添加地理位置"></a>Geoadd 添加地理位置</h5><pre class="line-numbers language-none"><code class="language-none">127.0.0.1:6379&gt; geoadd china:city 118.76 32.04 manjing 112.55 37.86 taiyuan 123.43 41.80 shenyang(integer) 3127.0.0.1:6379&gt; geoadd china:city 144.05 22.52 shengzhen 120.16 30.24 hangzhou 108.96 34.26 xian(integer) 3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>规则</strong></p><p>两级无法直接添加，我们一般会下载城市数据 (这个网址可以查询 GEO：<a href="http://www.jsons.cn/lngcode)%EF%BC%81">http://www.jsons.cn/lngcode)！</a></p><ul><li>有效的经度从 -180 度到 180 度。</li><li>有效的纬度从 -85.05112878 度到 85.05112878 度。</li></ul><pre class="line-numbers language-none"><code class="language-none"># 当坐标位置超出上述指定范围时，该命令将会返回一个错误。127.0.0.1:6379&gt; geoadd china:city 39.90 116.40 beijin(error) ERR invalid longitude,latitude pair 39.900000,116.400000<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="Geopos-获取指定成员的经纬度"><a href="#Geopos-获取指定成员的经纬度" class="headerlink" title="Geopos 获取指定成员的经纬度"></a>Geopos 获取指定成员的经纬度</h5><pre class="line-numbers language-none"><code class="language-none">127.0.0.1:6379&gt; geopos china:city taiyuan manjing1) 1) &quot;112.54999905824661255&quot;2) &quot;37.86000073876942196&quot;3) 1) &quot;118.75999957323074341&quot;4) &quot;32.03999960287850968&quot;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>获得当前定位, 一定是一个坐标值!</p><h5 id="Geodist-计算距离"><a href="#Geodist-计算距离" class="headerlink" title="Geodist 计算距离"></a>Geodist 计算距离</h5><p>单位：m，km，mi（英里），ft（英尺）</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> geodist china:city taiyuan shenyang m<span class="token string">"1026439.1070"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> geodist china:city taiyuan shenyang km<span class="token string">"1026.4391"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="Georadius-获取所有附近的人的地址，定位"><a href="#Georadius-获取所有附近的人的地址，定位" class="headerlink" title="Georadius 获取所有附近的人的地址，定位"></a>Georadius 获取所有附近的人的地址，定位</h5><p>获得指定数量的人</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> georadius china:city <span class="token number">110</span> <span class="token number">30</span> <span class="token number">1000</span> km <span class="token comment">#以110，30 这个坐标为中心，寻找半径为1000km的城市</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"hangzhou"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"manjing"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"taiyuan"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> georadius china:city <span class="token number">110</span> <span class="token number">30</span> <span class="token number">500</span> km<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> georadius china:city <span class="token number">110</span> <span class="token number">30</span> <span class="token number">500</span> km withdist<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"483.8340"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> georadius china:city <span class="token number">110</span> <span class="token number">30</span> <span class="token number">1000</span> km withcoord withdist count <span class="token number">2</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"483.8340"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"108.96000176668167114"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"34.25999964418929977"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"manjing"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"864.9816"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"118.75999957323074341"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"32.03999960287850968"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="Georadiusbymember-显示指定成员一定范围内的其他成员"><a href="#Georadiusbymember-显示指定成员一定范围内的其他成员" class="headerlink" title="Georadiusbymember 显示指定成员一定范围内的其他成员"></a>Georadiusbymember 显示指定成员一定范围内的其他成员</h5><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> georadiusbymember china:city taiyuan <span class="token number">1000</span> km<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"manjing"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"taiyuan"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> georadiusbymember china:city taiyuan <span class="token number">1000</span> km withcoord withdist count <span class="token number">2</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"taiyuan"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"0.0000"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"112.54999905824661255"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"37.86000073876942196"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"514.2264"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"108.96000176668167114"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"34.25999964418929977"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>参数与 georadius 一样</p><h5 id="geohash（较少使用）返回一个-11-字符的-Hash-字符串"><a href="#geohash（较少使用）返回一个-11-字符的-Hash-字符串" class="headerlink" title="geohash（较少使用）返回一个 11 字符的 Hash 字符串"></a>geohash（较少使用）返回一个 11 字符的 Hash 字符串</h5><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> geohash china:city taiyuan shenyang<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"ww8p3hhqmp0"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"wxrvb9qyxk0"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果这两个字符串差值越小，则距离越近</p><h4 id="底层"><a href="#底层" class="headerlink" title="底层"></a>底层</h4><blockquote><p>geo 底层的视线原理实际上就是 Zset，我们可以通过 Zset 的命令来操作 geo</p></blockquote><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> <span class="token builtin class-name">type</span> china:cityzset<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>查看全部元素、删除指定元素</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> zrange china:city <span class="token number">0</span> <span class="token parameter variable">-1</span> withscores<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"4040115445396757"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"hangzhou"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"4054133997236782"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"manjing"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"4066006694128997"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token string">"taiyuan"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"4068216047500484"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"shenyang"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"4072519231994779"</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token string">"shengzhen"</span><span class="token number">12</span><span class="token punctuation">)</span> <span class="token string">"4154606886655324"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> zrem china:city manjing<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> zrange china:city <span class="token number">0</span> <span class="token parameter variable">-1</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"xian"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"hangzhou"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"taiyuan"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"shenyang"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"shengzhen"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="四、Stream-数据类型"><a href="#四、Stream-数据类型" class="headerlink" title="四、Stream 数据类型"></a>四、Stream 数据类型</h2><blockquote><p>Redis 5.0 添加了一种数据类型 Stream 数据类型，它借鉴了 Kafka 的设计，是一个新的强大的支持多播的可持久化的消息队列。</p></blockquote><h3 id="为什么设计-Stream"><a href="#为什么设计-Stream" class="headerlink" title="为什么设计 Stream"></a>为什么设计 Stream</h3><p>用过 Redis 做消息队列的都了解，基于 Redis 的消息队列实现有很多种，例如：</p><ul><li>PUB&#x2F;SUB，订阅&#x2F;发布模式</li><li>但是发布订阅模式无法持久化，如果出现网络断开，Redis 宕机等，消息会被丢弃；</li><li>基于 List LPUSH+BRPOP 或者基于 Sorted-Set 的实现</li><li>只吃了持久化，但是不能多播，分组消费等</li></ul><p>那么，我们就需要考虑一下满足消息队列的数据类型到底需要什么特性？</p><ul><li>消息的生产</li><li>消息的消费</li><li>单播和多播（多对多）</li><li>阻塞和非阻塞读取</li><li>消息有序化</li><li>消息的持久化</li></ul><p>还需要考虑其他什么特性，可以借助美团技术团队的文章，<a href="https://tech.meituan.com/2016/07/01/mq-design.html">消息队列设计精要</a></p><p><img src="/img/645cc99174b6dc80be0a675c4dac324c_MD5.png" alt="645cc99174b6dc80be0a675c4dac324c_MD5.png"></p><p>从上面可以看出，Stream 的出现很有必要了。那么实际上 Stream 能满足上述那些要求呢？</p><ul><li>消息 ID 的序列化生成</li><li>消息遍历</li><li>消息的阻塞和非阻塞读取</li><li>消息的分组消费</li><li>未完成消息的处理</li><li>消息队列的监控</li><li>…</li></ul><h3 id="Stream-详解"><a href="#Stream-详解" class="headerlink" title="Stream 详解"></a>Stream 详解</h3><h4 id="Stream-的结构"><a href="#Stream-的结构" class="headerlink" title="Stream 的结构"></a>Stream 的结构</h4><p>每一个 Stream 都有唯一的名称，它就是 Redis 的 key，在我们首次使用 <code>xadd</code> 命令追加消息时自动创建。</p><p><img src="/img/32bff18500a89f0b4bbf9830dc45583f_MD5.png" alt="32bff18500a89f0b4bbf9830dc45583f_MD5.png"></p><p>上图解析：</p><ul><li><code>Consumer group</code>：消费组，使用 <code>xgroup create</code> 命令创建，一个消费组有多个消费者（Consumer），这些消费者是竞争关系。</li><li><code>last_delivered_id</code>：游标，每个消费组会有一个游标 last_delivered_id，任意一个消费者读取了消息都会使游标往前移动。</li><li><code>pending_ids</code>：消费者（Consumer）的状态变量，作用是维护消费者的未确认的 ID。pending_ids 记录了当前被客户端读取的消息，但是还没有 ack（Acknowledge character：确认字符）。如果客户端没有 ack，这个变量里面的消息 ID 会越来越多，一旦某个消息被 ack，它就开始减少。这个 pending_ids 变量在 Redis 中称为 PEL，也就是 Pending Entries List，这是一个很核心的数据结构，它用来确保客户端至少消费了一次消息，而不会出现网络传输的中途丢失了没处理。</li></ul><p>此外，我们还需要理解两点：</p><ul><li><code>消息ID</code>：消息 ID 的形式是 timestampInmillis-sequence，例如：1527846880527-5，他表示在 1527846880527 时刻产生的第 5 条消息。消息 ID 可以由服务器自动生成，也可以客户端自己指定。但是形式必须是整数 - 整数，而且必须后面的消息 ID 大于前面的消息 ID。</li><li><code>消息内容</code>：消息内容就是键值对，形如 hash 结构的键值对，没有特别之处。</li></ul><h4 id="增删改查"><a href="#增删改查" class="headerlink" title="增删改查"></a>增删改查</h4><p>消息队列的相关命令：</p><ul><li><code>XADD</code> - 添加消息到末尾；</li><li><code>XTRIM</code> - 对流进行修剪，限制长度；</li><li><code>XDEL</code> - 删除消息；</li><li><code>XLEN</code> - 获取流包含的元素数量，即消息长度；</li><li><code>XRANGE</code> - 获取消息列表，会自动过滤已经删除的消息；</li><li><code>XREVRANGE</code> - 反向获取消息列表，ID 从大到校</li><li><code>XREAD</code> - 以阻塞或者非阻塞方式获取消息列表</li></ul><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># *号表示服务器自动生成ID，后面书序跟着一堆key/value</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xadd codehole * name laoqian age <span class="token number">30</span> <span class="token comment">#名字叫laoqian，年龄30岁</span><span class="token number">1527849609889</span>-0 <span class="token comment"># 生成的消息ID</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xadd codehole * name xiaoyu age <span class="token number">29</span><span class="token number">1527849629172</span>-0<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xadd codehole * name xiaoqian age <span class="token number">1</span><span class="token number">1527849637634</span>-0<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xlen codehole<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xrange codehole - + <span class="token comment"># -表示最小值，+表示最大值</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849609889</span>-0<span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"laoqian"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"30"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849629172</span>-0<span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"xiaoyu"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"29"</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849637634</span>-0<span class="token number">12</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">13</span><span class="token punctuation">)</span> <span class="token string">"xiaoqian"</span><span class="token number">14</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">15</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xrange codehole <span class="token number">1527849629172</span>-0 + <span class="token comment"># 指定最小消息ID的列表</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849629172</span>-0<span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"xiaoyu"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"29"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849637634</span>-0<span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"xiaoqian"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xrange codehole - <span class="token number">1527849629172</span>-0 <span class="token comment"># 指定最大消息ID的列表</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849609889</span>-0<span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"laoqian"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"30"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849629172</span>-0<span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"xiaoyu"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"29"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xdel codehole <span class="token number">1527849609889</span>-0<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xlen codehole <span class="token comment"># 长度不受影响</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xrange codehole - + <span class="token comment"># 被删除的消息没了</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849629172</span>-0<span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"xiaoyu"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"29"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527849637634</span>-0<span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"xiaoqian"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> del codehole <span class="token comment"># 删除整个Stream</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span>  <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="独立消费"><a href="#独立消费" class="headerlink" title="独立消费"></a>独立消费</h4><p>我们可以再不定义消费组的情况下进行 Stream 消息的独立消费，当 Stream 没有新消息是，甚至可以阻塞等待。Redis 设计了一个单独的消费指令 <code>xread</code>，可以将 Stream 当成普通的消息队列（list）来使用。使用 <code>xread</code> 时，我们可以完全忽略消费组（Cosumer Group）的存在，就好比 Stream 是一个普通的列表（list）。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 从Stream头部读取两条消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xread count <span class="token number">2</span> streams codehole <span class="token number">0</span>-0<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"codehole"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851486781</span>-0<span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"laoqian"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"30"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851493405</span>-0<span class="token number">8</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"yurui"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token string">"29"</span><span class="token comment"># 从Stream尾部读取一条消息，毫无疑问，这里不会返回任何消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xread count <span class="token number">1</span> streams codehole $<span class="token punctuation">(</span>nil<span class="token punctuation">)</span><span class="token comment"># 从尾部阻塞等待新消息到来，下面的指令会堵住，直到新消息到来</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xread block <span class="token number">0</span> count <span class="token number">1</span> streams codehole $<span class="token comment"># 我们从新打开一个窗口，在这个窗口往Stream里塞消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xadd codehole * name youming age <span class="token number">60</span><span class="token number">1527852774092</span>-0<span class="token comment"># 再切换到前面的窗口，我们可以看到阻塞解除了，返回了新的消息内容</span><span class="token comment"># 而且还显示了一个等待时间，这里我们等待了93s</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xread block <span class="token number">0</span> count <span class="token number">1</span> streams codehole $<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"codehole"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527852774092</span>-0<span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"youming"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"60"</span><span class="token punctuation">(</span><span class="token number">93</span>.11s<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>客户端如果想要使用 <code>xread</code> 进行顺序消费，一定要记住当前消费到哪了，也就是返回的消息 ID，下次继续调用 <code>xread</code> 时候，将上次最后一个 ID 传入，就可以继续消费后续的消息。</p><p><code>block 0</code> 代表永远阻塞，直到消息到来，<code>block 1000</code> 表示阻塞 1s，如果 1s 内没有消息，则返回 nil。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xread block <span class="token number">1000</span> count <span class="token number">1</span> streams codehole $<span class="token punctuation">(</span>nil<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">1</span>.07s<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="消费组消费"><a href="#消费组消费" class="headerlink" title="消费组消费"></a>消费组消费</h4><p><img src="/img/c5b779db4ff5c265435e568adf9a0262_MD5.png" alt="c5b779db4ff5c265435e568adf9a0262_MD5.png"></p><p>相关命令：</p><ul><li>XGROUP CREATE - 创建消费者组</li><li>XREADGROUP GROUP - 读取消费者组中的消息</li><li>XACK - 将消息标记为 “ 已处理 “</li><li>XGROUP SETID - 为消费者组设置新的最后递送消息 ID</li><li>XGROUP DELCONSUMER - 删除消费者</li><li>XGROUP DESTROY - 删除消费者组</li><li>XPENDING - 显示待处理消息的相关信息</li><li>XCLAIM - 转移消息的归属权</li><li>XINFO - 查看流和消费者组的相关信息；</li><li>XINFO GROUPS - 打印消费者组的信息；</li><li>XINFO STREAM - 打印流信息</li></ul><p><strong>创建消费组</strong></p><p>Stream 通过 xgroup create 指令创建消费组 (Consumer Group)，需要传递起始消息 ID 参数用来初始化 last_delivered_id 变量。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xgroup create codehole cg1 <span class="token number">0</span>-0 <span class="token comment"># 表示从头开始消费</span>OK<span class="token comment"># $表示从尾部开始消费，只接受新消息，当前Stream消息会全部忽略</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xgroup create codehole cg2 $OK<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xinfo stream codehole <span class="token comment"># 获取Stream信息</span><span class="token number">1</span><span class="token punctuation">)</span> length<span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span> <span class="token comment"># 共3个消息</span><span class="token number">3</span><span class="token punctuation">)</span> radix-tree-keys<span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">5</span><span class="token punctuation">)</span> radix-tree-nodes<span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">2</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token function">groups</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">2</span> <span class="token comment"># 两个消费组</span><span class="token number">9</span><span class="token punctuation">)</span> first-entry <span class="token comment"># 第一个消息</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851486781</span>-0<span class="token number">11</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">12</span><span class="token punctuation">)</span> <span class="token string">"laoqian"</span><span class="token number">13</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">14</span><span class="token punctuation">)</span> <span class="token string">"30"</span><span class="token number">15</span><span class="token punctuation">)</span> last-entry <span class="token comment"># 最后一个消息</span><span class="token number">16</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851498956</span>-0<span class="token number">17</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">18</span><span class="token punctuation">)</span> <span class="token string">"xiaoqian"</span><span class="token number">19</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xinfo <span class="token function">groups</span> codehole <span class="token comment"># 获取Stream的消费组信息</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"cg1"</span><span class="token number">3</span><span class="token punctuation">)</span> consumers<span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span> <span class="token comment"># 该消费组还没有消费者</span><span class="token number">5</span><span class="token punctuation">)</span> pending<span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span> <span class="token comment"># 该消费组没有正在处理的消息</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"cg2"</span><span class="token number">9</span><span class="token punctuation">)</span> consumers <span class="token comment"># 该消费组还没有消费者</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token number">11</span><span class="token punctuation">)</span> pending<span class="token number">12</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span> <span class="token comment"># 该消费组没有正在处理的消息</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><strong>消费组消费</strong></li></ul><p>Stream 提供了 xreadgroup 指令可以进行消费组的组内消费，需要提供消费组名称、消费者名称和起始消息 ID。它同 xread 一样，也可以阻塞等待新消息。读到新消息后，对应的消息 ID 就会进入消费者的 PEL(正在处理的消息) 结构里，客户端处理完毕后使用 xack 指令通知服务器，本条消息已经处理完毕，该消息 ID 就会从 PEL 中移除。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># >号表示从当前消费组的last_delivered_id后面开始读</span><span class="token comment"># 每当消费者读取一条消息，last_delivered_id变量就会前进</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xreadgroup GROUP cg1 c1 count <span class="token number">1</span> streams codehole <span class="token operator">></span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"codehole"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851486781</span>-0<span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"laoqian"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"30"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xreadgroup GROUP cg1 c1 count <span class="token number">1</span> streams codehole <span class="token operator">></span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"codehole"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851493405</span>-0<span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"yurui"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"29"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xreadgroup GROUP cg1 c1 count <span class="token number">2</span> streams codehole <span class="token operator">></span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"codehole"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527851498956</span>-0<span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"xiaoqian"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527852774092</span>-0<span class="token number">8</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"youming"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token string">"60"</span><span class="token comment"># 再继续读取，就没有新消息了</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xreadgroup GROUP cg1 c1 count <span class="token number">1</span> streams codehole <span class="token operator">></span><span class="token punctuation">(</span>nil<span class="token punctuation">)</span><span class="token comment"># 那就阻塞等待吧</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xreadgroup GROUP cg1 c1 block <span class="token number">0</span> count <span class="token number">1</span> streams codehole <span class="token operator">></span><span class="token comment"># 开启另一个窗口，往里塞消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xadd codehole * name lanying age <span class="token number">61</span><span class="token number">1527854062442</span>-0<span class="token comment"># 回到前一个窗口，发现阻塞解除，收到新消息了</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xreadgroup GROUP cg1 c1 block <span class="token number">0</span> count <span class="token number">1</span> streams codehole <span class="token operator">></span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"codehole"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1527854062442</span>-0<span class="token number">3</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"lanying"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"age"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"61"</span><span class="token punctuation">(</span><span class="token number">36</span>.54s<span class="token punctuation">)</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xinfo <span class="token function">groups</span> codehole <span class="token comment"># 观察消费组信息</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"cg1"</span><span class="token number">3</span><span class="token punctuation">)</span> consumers<span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span> <span class="token comment"># 一个消费者</span><span class="token number">5</span><span class="token punctuation">)</span> pending<span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span> <span class="token comment"># 共5条正在处理的信息还有没有ack</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"cg2"</span><span class="token number">9</span><span class="token punctuation">)</span> consumers<span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span> <span class="token comment"># 消费组cg2没有任何变化，因为前面我们一直在操纵cg1</span><span class="token number">11</span><span class="token punctuation">)</span> pending<span class="token number">12</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span><span class="token comment"># 如果同一个消费组有多个消费者，我们可以通过xinfo consumers指令观察每个消费者的状态</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xinfo consumers codehole cg1 <span class="token comment"># 目前还有1个消费者</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"c1"</span><span class="token number">3</span><span class="token punctuation">)</span> pending<span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span> <span class="token comment"># 共5条待处理消息</span><span class="token number">5</span><span class="token punctuation">)</span> idle<span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">418715</span> <span class="token comment"># 空闲了多长时间ms没有读取消息了</span><span class="token comment"># 接下来我们ack一条消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xack codehole cg1 <span class="token number">1527851486781</span>-0<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xinfo consumers codehole cg1<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"c1"</span><span class="token number">3</span><span class="token punctuation">)</span> pending<span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">4</span> <span class="token comment"># 变成了5条</span><span class="token number">5</span><span class="token punctuation">)</span> idle<span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">668504</span><span class="token comment"># 下面ack所有消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xack codehole cg1 <span class="token number">1527851493405</span>-0 <span class="token number">1527851498956</span>-0 <span class="token number">1527852774092</span>-0 <span class="token number">1527854062442</span>-0<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">4</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> xinfo consumers codehole cg1<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> name<span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"c1"</span><span class="token number">3</span><span class="token punctuation">)</span> pending<span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">0</span> <span class="token comment"># pel空了</span><span class="token number">5</span><span class="token punctuation">)</span> idle<span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">745505</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="信号监控"><a href="#信号监控" class="headerlink" title="信号监控"></a>信号监控</h4><p>Stream 提供了 <code>XINFO</code> 用来实现对服务器信息的监控，可以查询：</p><ul><li>查看队列信息</li></ul><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> Xinfo stream mq<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"length"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">7</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"radix-tree-keys"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"radix-tree-nodes"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">2</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token string">"groups"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"last-generated-id"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-9"</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token string">"first-entry"</span><span class="token number">12</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-3"</span><span class="token number">13</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"msg"</span><span class="token number">14</span><span class="token punctuation">)</span> <span class="token string">"4"</span><span class="token number">15</span><span class="token punctuation">)</span> <span class="token string">"last-entry"</span><span class="token number">16</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-9"</span><span class="token number">17</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"msg"</span><span class="token number">18</span><span class="token punctuation">)</span> <span class="token string">"10"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>消费组信息</li></ul><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> Xinfo <span class="token function">groups</span> mq<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"mqGroup"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"consumers"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"pending"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token string">"last-delivered-id"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-4"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>消费者组成员信息</li></ul><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XINFO CONSUMERS mq mqGroup<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"pending"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"idle"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">18949894</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token string">"consumerB"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"pending"</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token string">"idle"</span><span class="token number">12</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">3092719</span><span class="token number">13</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"name"</span><span class="token number">14</span><span class="token punctuation">)</span> <span class="token string">"consumerC"</span><span class="token number">15</span><span class="token punctuation">)</span> <span class="token string">"pending"</span><span class="token number">16</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token number">17</span><span class="token punctuation">)</span> <span class="token string">"idle"</span><span class="token number">18</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">23683256</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>至此，消息队列的操作说明大体结束！</p><h3 id="Stream-应用月什么场景"><a href="#Stream-应用月什么场景" class="headerlink" title="Stream 应用月什么场景"></a>Stream 应用月什么场景</h3><p>可用作时通信等，大数据分析，异地数据备份等</p><p><img src="/img/512fe85a95b3c459e575dbe21bf125fb_MD5.png" alt="512fe85a95b3c459e575dbe21bf125fb_MD5.png"></p><p>客户端可以平滑扩展，提高处理能力</p><p><img src="/img/bac83440a99e842a60307a2d9069c40d_MD5.png" alt="bac83440a99e842a60307a2d9069c40d_MD5.png"></p><h3 id="其他问题"><a href="#其他问题" class="headerlink" title="其他问题"></a>其他问题</h3><h4 id="消息-ID-的设计是否考虑了时间回拨的问题？"><a href="#消息-ID-的设计是否考虑了时间回拨的问题？" class="headerlink" title="消息 ID 的设计是否考虑了时间回拨的问题？"></a>消息 ID 的设计是否考虑了时间回拨的问题？</h4><blockquote><p>在 <a href="/md/algorithm/alg-domain-id-snowflake.html">分布式算法 - ID算法</a> 设计中, 一个常见的问题就是时间回拨问题，那么 Redis 的消息 ID 设计中是否考虑到这个问题呢？</p></blockquote><p>XADD 生成的 1553439850328-0，就是 Redis 生成的消息 ID，由两部分组成:<strong>时间戳 - 序号</strong>。时间戳是毫秒级单位，是生成消息的 Redis 服务器时间，它是个 64 位整型（int64）。序号是在这个毫秒时间点内的消息序号，它也是个 64 位整型。</p><p>可以通过 multi 批处理，来验证序号的递增：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> MULTIOK<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XADD memberMessage * msg oneQUEUED<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XADD memberMessage * msg twoQUEUED<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XADD memberMessage * msg threeQUEUED<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XADD memberMessage * msg fourQUEUED<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XADD memberMessage * msg fiveQUEUED<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> EXEC<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553441006884-0"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"1553441006884-1"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"1553441006884-2"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"1553441006884-3"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"1553441006884-4"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由于一个 redis 命令的执行很快，所以可以看到在同一时间戳内，是通过序号递增来表示消息的。</p><p>为了保证消息是有序的，因此 Redis 生成的 ID 是单调递增有序的。由于 ID 中包含时间戳部分，为了避免服务器时间错误而带来的问题（例如服务器时间延后了），Redis 的每个 Stream 类型数据都维护一个 latest_generated_id 属性，用于记录最后一个消息的 ID。<strong>若发现当前时间戳退后（小于 latest_generated_id 所记录的），则采用时间戳不变而序号递增的方案来作为新消息 ID</strong>（这也是序号为什么使用 int64 的原因，保证有足够多的的序号），从而保证 ID 的单调递增性质。</p><p>强烈建议使用 Redis 的方案生成消息 ID，因为这种时间戳 + 序号的单调递增的 ID 方案，几乎可以满足你全部的需求。但同时，记住 ID 是支持自定义的，别忘了！</p><h4 id="消费者崩溃带来的会不会消息丢失问题"><a href="#消费者崩溃带来的会不会消息丢失问题" class="headerlink" title="消费者崩溃带来的会不会消息丢失问题?"></a>消费者崩溃带来的会不会消息丢失问题?</h4><p>为了解决组内消息读取但处理期间消费者崩溃带来的消息丢失问题，STREAM 设计了 Pending 列表，用于记录读取但并未处理完毕的消息。命令 XPENDIING 用来获消费组或消费内消费者的未处理完毕的消息。演示如下：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XPENDING mq mqGroup <span class="token comment"># mpGroup的Pending情况</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span> <span class="token comment"># 5个已读取但未处理的消息</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-0"</span> <span class="token comment"># 起始ID</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-4"</span> <span class="token comment"># 结束ID</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span> <span class="token comment"># 消费者A有3个</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"3"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"consumerB"</span> <span class="token comment"># 消费者B有1个</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"consumerC"</span> <span class="token comment"># 消费者C有1个</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"1"</span>  <span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XPENDING mq mqGroup - + <span class="token number">10</span> <span class="token comment"># 使用 start end count 选项可以获取详细信息</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-0"</span> <span class="token comment"># 消息ID</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span> <span class="token comment"># 消费者</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1654355</span> <span class="token comment"># 从读取到现在经历了1654355ms，IDLE</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span> <span class="token comment"># 消息被读取了5次，delivery counter</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-1"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1654355</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">4</span><span class="token comment"># 共5个，余下3个省略 ...</span>  <span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XPENDING mq mqGroup - + <span class="token number">10</span> consumerA <span class="token comment"># 在加上消费者参数，获取具体某个消费者的Pending列表</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-0"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1641083</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span><span class="token comment"># 共3个，余下2个省略 ...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>每个 Pending 的消息有 4 个属性：</p><ul><li>消息 ID</li><li>所属消费者</li><li>IDLE，已读取时长</li><li>delivery counter，消息被读取次数</li></ul><p>上面的结果我们可以看到，我们之前读取的消息，都被记录在 Pending 列表中，说明全部读到的消息都没有处理，仅仅是读取了。那如何表示消费者处理完毕了消息呢？使用命令 XACK 完成告知消息处理完成，演示如下：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XACK mq mqGroup <span class="token number">1553585533795</span>-0 <span class="token comment"># 通知消息处理结束，用消息ID标识</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span>  <span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XPENDING mq mqGroup <span class="token comment"># 再次查看Pending列表</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">4</span> <span class="token comment"># 已读取但未处理的消息已经变为4个</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-1"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-4"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span> <span class="token comment"># 消费者A，还有2个消息处理</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"2"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"consumerB"</span><span class="token number">7</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"consumerC"</span><span class="token number">9</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>有了这样一个 Pending 机制，就意味着在某个消费者读取消息但未处理后，消息是不会丢失的。等待消费者再次上线后，可以读取该 Pending 列表，就可以继续处理该消息了，保证消息的有序和不丢失。</p><h4 id="消费者彻底宕机后如何转移给其它消费者处理？"><a href="#消费者彻底宕机后如何转移给其它消费者处理？" class="headerlink" title="消费者彻底宕机后如何转移给其它消费者处理？"></a>消费者彻底宕机后如何转移给其它消费者处理？</h4><blockquote><p>还有一个问题，就是若某个消费者宕机之后，没有办法再上线了，那么就需要将该消费者 Pending 的消息，转义给其他的消费者处理，就是消息转移。</p></blockquote><p>消息转移的操作时将某个消息转移到自己的 Pending 列表中。使用语法 XCLAIM 来实现，需要设置组、转移的目标消费者和消息 ID，同时需要提供 IDLE（已被读取时长），只有超过这个时长，才能被转移。演示如下：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 当前属于消费者A的消息1553585533795-1，已经15907,787ms未处理了</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XPENDING mq mqGroup - + <span class="token number">10</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-1"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"consumerA"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">15907787</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">4</span>  <span class="token comment"># 转移超过3600s的消息1553585533795-1到消费者B的Pending列表</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XCLAIM mq mqGroup consumerB <span class="token number">3600000</span> <span class="token number">1553585533795</span>-1<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-1"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"msg"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"2"</span>  <span class="token comment"># 消息1553585533795-1已经转移到消费者B的Pending中。</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XPENDING mq mqGroup - + <span class="token number">10</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-1"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"consumerB"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">84404</span> <span class="token comment"># 注意IDLE，被重置了</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">5</span> <span class="token comment"># 注意，读取次数也累加了1次</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>以上代码，完成了一次消息转移。转移除了要指定 ID 外，还需要指定 IDLE，保证是长时间未处理的才被转移。被转移的消息的 IDLE 会被重置，用以保证不会被重复转移，以为可能会出现将过期的消息同时转移给多个消费者的并发操作，设置了 IDLE，则可以避免后面的转移不会成功，因为 IDLE 不满足条件。例如下面的连续两条转移，第二条不会成功。</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XCLAIM mq mqGroup consumerB <span class="token number">3600000</span> <span class="token number">1553585533795</span>-1<span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XCLAIM mq mqGroup consumerC <span class="token number">3600000</span> <span class="token number">1553585533795</span>-1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>这就是消息转移。至此我们使用了一个 Pending 消息的 ID，所属消费者和 IDLE 的属性，还有一个属性就是消息被读取次数，delivery counter，该属性的作用由于统计消息被读取的次数，包括被转移也算。这个属性主要用在判定是否为错误数据上。</p><h4 id="坏消息问题，Dead-Letter，死信问题"><a href="#坏消息问题，Dead-Letter，死信问题" class="headerlink" title="坏消息问题，Dead Letter，死信问题"></a>坏消息问题，Dead Letter，死信问题</h4><p>正如上面所说，如果某个消息，不能被消费者处理，也就是不能被 XACK，这是要长时间处于 Pending 列表中，即使被反复的转移给各个消费者也是如此。此时该消息的 delivery counter 就会累加（上一节的例子可以看到），当累加到某个我们预设的临界值时，我们就认为是坏消息（也叫死信，DeadLetter，无法投递的消息），由于有了判定条件，我们将坏消息处理掉即可，删除即可。删除一个消息，使用 XDEL 语法，演示如下：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 删除队列中的消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XDEL mq <span class="token number">1553585533795</span>-1<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> <span class="token number">1</span><span class="token comment"># 查看队列中再无此消息</span><span class="token number">127.0</span>.0.1:637<span class="token operator"><span class="token file-descriptor important">9</span>></span> XRANGE mq - +<span class="token number">1</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-0"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"msg"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"1"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"1553585533795-2"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"msg"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"3"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意本例中，并没有删除 Pending 中的消息因此你查看 Pending，消息还会在。可以执行 XACK 标识其处理完毕！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、Redis-概念和基础&quot;&gt;&lt;a href=&quot;#一、Redis-概念和基础&quot; class=&quot;headerlink&quot; title=&quot;一、Redis 概念和基础&quot;&gt;&lt;/a&gt;一、Redis 概念和基础&lt;/h2&gt;&lt;h3 id=&quot;什么是-Redis（Remote-Dict</summary>
      
    
    
    
    <category term="后端" scheme="https://www.lazydaily.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="Redis" scheme="https://www.lazydaily.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title> 详解：Kafka深入浅出</title>
    <link href="https://www.lazydaily.cn/2024/04/29/KKhQNnaKuNisBSbB/"/>
    <id>https://www.lazydaily.cn/2024/04/29/KKhQNnaKuNisBSbB/</id>
    <published>2024-04-29T08:04:00.000Z</published>
    <updated>2025-11-24T00:43:42.347Z</updated>
    
    <content type="html"><![CDATA[<p>Kafka 服务器端的代码是由 Scala 代码编写，支持面向对象编程和函数式数据，编译过后也是普通的 .class 文件。其的作用：提供统一的、高吞吐量、低延迟的平台来处理实时数据</p><h2 id="一、基本概念"><a href="#一、基本概念" class="headerlink" title="一、基本概念"></a>一、基本概念</h2><h3 id="「Kafka-是什么？主要应用场景什么？」"><a href="#「Kafka-是什么？主要应用场景什么？」" class="headerlink" title="「Kafka 是什么？主要应用场景什么？」"></a>「Kafka 是什么？主要应用场景什么？」</h3><p>Kafka 是一个分布式流式处理平台。</p><p><img src="/img/6aaf06740442599b6c52bb586545dfb8_MD5.png" alt="6aaf06740442599b6c52bb586545dfb8_MD5.png"></p><p><strong>1 . 主题</strong>：发布订阅的对象是主题（Topic），可以为每个业务、每个应用甚至每类数据都创建专属的主题</p><p><strong>2 . 生产者和消费者</strong>：向主题发布消息的客户端应用程序成为生产者，生产者程序通常持续不断地向一个或者多个主题发送消息</p><p><strong>3 . Broker</strong>：集群由多个 Broker 组成，Broker 负责接收和处理客户端发送过来的请求，以及对消息进行持久化。</p><p>虽然多个 Broker 能够运行在同一台机器上，但是常见的做法是将不同的 Broker 分散运行在不同的机器上，这样如果某一台机器宕机，即使在它上面运行的所有 Broker 进程都挂掉了，其他机器上的 Broker 也一眼能够对外提供服务。</p><p> <strong>4 . 备份机制</strong>：备份的思想很简单，就是把相同的数据拷贝到多台机器上，而这些相同的数据拷贝被称为副本。</p><p>Kafka 定义了两类副本：领导者副本和追随者副本。</p><p>前者对外提供服务，即与客户端程序进行交互；后者只是被动地追随领导者副本而已，不对外进行交互。</p><p><strong>5 . 分区</strong>：分区机制指的是将每个主题分成多个分区，每个分区是一组有序的消息日志</p><p>生产者生产的每条消息总会被发送到一个分区中，也就是说如果向一个双分区的主题发送一条消息，该条消息要不在分区 0 中，要不在分区 1 中。</p><p>生产者向分区中写入消息，每条消息在分区中的位置信息叫做位移。</p><p><strong>6 . 消费者组</strong>：多个消费者实例共同组成一个组来消费一组主题</p><p>这组主题中的每个分区只会被组内的一个消费者实例消费，其他消费者实例不能消费它</p><p>消息引擎的两大模型：</p><ul><li>如果所有实例都属于同一个 Group，那么它实现的就是消息队列模型</li><li>如果所有实例属于不同的 Group，那么它实现的就是发布&#x2F;订阅模型</li></ul><blockquote><p><strong>RocketMQ 的消息模型和 Kafka 基本是完全一样的。唯一的区别是 Kafka 中没有队列这个概念，与之对应的是 Partition（分区）。</strong></p></blockquote><p><strong>7 . Coordinator：协调者</strong>：负责为 Group 执行 Rebalance 以及提供唯一管理和组成员管理等。</p><p><strong>8 . 消费者位移：Consumer offset</strong>：消费者消费进度，每个消费者都有自己的消费者位移</p><p><strong>9 . 重平衡：Rebalance</strong>：消费者组内某个消费者实例挂掉后，其他消费者实例自动重新分配订阅主题分区的过程。</p><p>Rebalance 是 Kafka 消费者端实现高可用的重要手段</p><p><strong>10 . AR（Assigned Replicas）</strong>：分区中的所有副本统称为 AR。</p><p>所有消息都会先发送到领导者副本，然后追随者副本才能从领导者中拉去信息进行同步</p><p>但是同步期间，追随者副本相对于领导者副本而言有一定程度的滞后，这时候追随者副本和领导者副本并非完全同步状态</p><p><strong>11 . OSR（Out Sync Replicas）</strong>：AR 的一个子集，其中都是追随者副本和领导者副本没有完全同步或者之后的副本集合</p><p><strong>12 . ISR（In Sync Replicas）</strong>：AR 的一个子集，ISR 中的副本都是和领导者副本是保持完全同步的副本。</p><p>如果某一个在 ISR 中的 follower 副本落后于 leader 副本太多，就会从 ISR 中移除，否则如果完全同步，会从 OSR 中移到 ISR 集合中</p><p><strong>13 . HW（High Watermark）</strong>：高水位，标识一个特定的消息偏移量（offset），消费者只能来取这个水位 offset 之前的消息</p><p>下图表示一个日志文件，这个日志文件中只有 9 条消息，第一条消息的 offset（LogStartOffset）为 0，最有一条消息的 offset 为 8，offset 为 9 的消息使用虚线表示的，代表下一条待写入的消息。</p><p>日志文件的 HW 为 6，表示消费者只能拉取 offset 在 0 到 5 之间的消息，offset 为 6 的消息对消费者而言是不可见的。</p><p><img src="/img/ce77dd7ccc11dc7642aad16560000cb9_MD5.png" alt="ce77dd7ccc11dc7642aad16560000cb9_MD5.png"></p><p><strong>14 . LEO（Log End Offset）</strong>：标识当前日志文件中下一条待写入的消息的 offset</p><h2 id="二、系统架构"><a href="#二、系统架构" class="headerlink" title="二、系统架构"></a>二、系统架构</h2><p>Kafka 基础框架：一个生产者发送一个消息到 Kafka 的一个 Topic，该 Topic 的消息存放在 Broker 中，消费者订阅这个 Topic，然后从 Broker 中消费消息。</p><p>1.<strong>消息状态</strong>：在 Kafka 中，消息是否被消费的状态保存在消费者中，Broker 不会关系消息是否消费，或者被谁消费，消费者会记录一个 offset 值（指向分区中下一条被消费的消息位置），如果 offset 被错误设置可能会导致同一条消息多次消费或者丢失。</p><p>2.<strong>消息持久化</strong>：Kafka 会把消息持久化到本地文件系统中，并且具有极高的性能。</p><p>3.<strong>批量发送</strong>：Kafka 支持以消息集合为单位进行批量发送，以提高效率。</p><p>4.<strong>Push-and-Pull</strong>：Kafka 中的生产者和消费者采用的是 Push-and-Pull 模式，即生产者向 Broker Push 消息，消费者从 Broker Pull 消息。</p><p>5.<strong>分区机制</strong>：Kafka 的 Broker 是支持分区的，Producer 可以决定将消息放在哪个 Partition，在一个 Partition 中的消息顺序就是 Producer 发送消息的顺序，一个 Topic 中的 Partition 是可以配置的，Partition 是保证 Kafka 高吞吐量的重要保证。</p><p><img src="/img/68d9aaa9dd34dbc9d516201de1f2ab55_MD5.png" alt="68d9aaa9dd34dbc9d516201de1f2ab55_MD5.png"></p><p>通常情况下，一个 Kafka 体系是包含多个 Producer，多个 Broker，多个 Consumer，以及一个 Zookeeper 集群</p><h2 id="三、生产者分区"><a href="#三、生产者分区" class="headerlink" title="三、生产者分区"></a>三、生产者分区</h2><p>一条 Kafka 消息的的组织架构是三层：主题（Topic）- 分区（Partition）- 消息（Message）</p><p>分区其实是一种负载均衡的做法。因为同一个 Topic 下的不同分区可以在不同 Broker 节点上，并且，数据读写是以分区为粒度，这样的话，每个节点都可以执行自己分区的消息的读写。除此之外，还能通过增加节点来提高吞吐量。</p><h3 id="分区策略"><a href="#分区策略" class="headerlink" title="分区策略"></a>分区策略</h3><p>所谓的分区策略其实就是决定生产者将消息发送到哪个分区的算法。</p><h4 id="自定义分区策略"><a href="#自定义分区策略" class="headerlink" title="自定义分区策略"></a>自定义分区策略</h4><p>如果需要自己定义分区策略，在编写生产者程序的时候，可以编写一个具体的实现类 <code>org.apache.kafka.clients.producer.Partitioner</code> 接口。这接口也非常简单，只定义了两个方法：partition() 和 close() 方法。通常情况我们只需要实现 partition() 方法即可。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> <span class="token function">partition</span><span class="token punctuation">(</span><span class="token class-name">String</span> topic<span class="token punctuation">,</span> <span class="token class-name">Object</span> key<span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> keyBytes<span class="token punctuation">,</span> <span class="token class-name">Object</span> value<span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> valueBytes<span class="token punctuation">,</span> <span class="token class-name">Cluster</span> cluster<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>这里的 topic、key、keyBytes、value 和 valueBytes 都属于消息数据，cluster 则是集群信息（比如当前 Kafka 集群共有多少主题、多少 Broker 等）。</p><h4 id="轮询策略"><a href="#轮询策略" class="headerlink" title="轮询策略"></a>轮询策略</h4><p>也称 Round-robin 策略，即顺序分配。轮询策略是 Kafka 生产者 API 默认提供的分区策略。</p><p>轮询策略有非常优秀的负载均衡表现，它总是保证消息最大限度的平均分配到所有的分区上，所以默认下它是最合理的分区策略，也是最常用的分区策略。</p><h4 id="随机策略"><a href="#随机策略" class="headerlink" title="随机策略"></a>随机策略</h4><p>也称 Randomness 策略。想要实现随机策略的 partition 方法，其实很简单，只需要两行代码即可：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span> partitions <span class="token operator">=</span> cluster<span class="token punctuation">.</span><span class="token function">partitionsForTopic</span><span class="token punctuation">(</span>topic<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token class-name">ThreadLocalRandom</span><span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">nextInt</span><span class="token punctuation">(</span>partitions<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>先计算出该主题的总分区数，然后随机返回一个小于它的正整数。</p><p>随机策略在负载均衡上面略逊于轮询策略。在老的版本里面常用随机策略，再后来的版本更新中被轮询策略所替代。</p><h4 id="按消息键保序策略"><a href="#按消息键保序策略" class="headerlink" title="按消息键保序策略"></a>按消息键保序策略</h4><p>Kafka 允许为每条消息定义消息键，简称 Key。</p><p>Key 可以为具体的业务代码，也可以用来表征消息元数据。在 Kafka 中如果消息定义了 Key，那么就可以保证同一个 Key 的消息进入相同的分区，有序分区下的消息处理是有顺序的，所以这个策略被称为安消息键保存策略。</p><p>实现这个策略的 partition 方法同样简单，只需要下面两行代码即可：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span> partitions <span class="token operator">=</span> cluster<span class="token punctuation">.</span><span class="token function">partitionsForTopic</span><span class="token punctuation">(</span>topic<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token class-name">Math</span><span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">%</span> partitions<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>其实，Kafka 默认的分区策略是两种：</p><ul><li>如果指定了 Key，默认实现按消息键保序策略；</li><li>如果未指定 Key，则使用轮询策略。</li></ul><h4 id="「其他分区策略」"><a href="#「其他分区策略」" class="headerlink" title="「其他分区策略」"></a>「其他分区策略」</h4><p>另外还有一种比较常见的，所谓的基于地理位置的分区策略。当然这种策略只针对大规模的 Kafka 集群，特别是跨城市、跨国家甚至是跨大洲的集群。我们可以根据 Broker 所在的 IP 地址实现定制化的分区策略。比如下段代码：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">List</span> partitions <span class="token operator">=</span> cluster<span class="token punctuation">.</span><span class="token function">partitionsForTopic</span><span class="token punctuation">(</span>topic<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> partitions<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>p <span class="token operator">-></span> <span class="token function">isSouth</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span><span class="token function">leader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">host</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token class-name">PartitionInfo</span><span class="token operator">::</span><span class="token function">partition</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">findAny</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>我们可以从所有分区中找出那些 Leader 副本在南方的所有分区，然后随机挑选一个进行消息发送。</p><h2 id="四、生产者压缩算法"><a href="#四、生产者压缩算法" class="headerlink" title="四、生产者压缩算法"></a>四、生产者压缩算法</h2><p>为什么要压缩消息？压缩消息是为了更好的节省网络传输贷款以及 Kafka Broker 端的磁盘占用。</p><h3 id="「Kafka-是如何压缩消息的呢？」"><a href="#「Kafka-是如何压缩消息的呢？」" class="headerlink" title="「Kafka 是如何压缩消息的呢？」"></a>「Kafka 是如何压缩消息的呢？」</h3><p>Kafka 的消息层次分为两层：消息集合和消息。</p><p>??？</p><h3 id="「何时压缩？」"><a href="#「何时压缩？」" class="headerlink" title="「何时压缩？」"></a>「何时压缩？」</h3><p>在 Kafka 中，压缩可能发生在两个地方：生产者端和 Broker 端。</p><p>在生产者程序中配置 compression.type 参数即表示弃用指定类型的压缩算法。</p><p>比如这段代码中展示了如何构建一个开启 GZIP 的 Producer 对象：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Properties</span> props <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Properties</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"bootstrap.servers"</span><span class="token punctuation">,</span> <span class="token string">"localhost:9092"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"acks"</span><span class="token punctuation">,</span> <span class="token string">"all"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"key.serializer"</span><span class="token punctuation">,</span> <span class="token string">"org.apache.kafka.common.serialization.StringSerializer"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"value.serializer"</span><span class="token punctuation">,</span> <span class="token string">"org.apache.kafka.common.serialization.StringSerializer"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 开启GZIP压缩 </span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"compression.type"</span><span class="token punctuation">,</span> <span class="token string">"gzip"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//Producer的压缩算法是GZIP</span><span class="token class-name">Producer</span> producer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">KafkaProducer</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这样 Producer 启动之后生产的每个消息集合都是经 GZIP 压缩过的，故而能很好的节省网络传输贷款以及 Kafka Broker 端的磁盘占用。</p><p>有两种情况可能导致 Broker 重新压缩消息：</p><ul><li><p>情况一：Broker 端指定了和 Producer 端不同的压缩算法。<br>一旦 Broker 端设置了不同的 compression.type 值，就一定要小心了，因为可能会发生预料之外的压缩、解压缩操作，通常表现为 Broker 端 CPU 使用率飙升。</p></li><li><p>情况二：Broker 端发生了消息格式转换。<br>所谓的消息格式转换主要是为了兼容老版本的消费者程序。在一个生产环境中，Kafka 集群中同时保存多个版本的消息格式非常常见。为了兼容老版本的格式，Broker 端会对新版本消息指向向老版本的转换。这个过程会涉及到消息的解压缩和重新压缩。一般情况下这种消息格式转换成对性能是有很大影响的，除了这里的压缩之外，他还让 Kafka 丧失了 Zero Copy 特性。</p></li></ul><h3 id="「何时解压缩？」"><a href="#「何时解压缩？」" class="headerlink" title="「何时解压缩？」"></a>「何时解压缩？」</h3><p>有压缩必有解压缩！通常来说解压缩发生在消费者程序中。</p><p><strong>基本过程：Producer 端压缩，Broker 端保持，Consumer 端解压缩。</strong></p><p>注意：除了在 Consumer 端解压缩外，Broker 端也会进行解压缩。</p><p>每个压缩过的消息集合在 Broker 端写入时都要发生解压缩操作，目的就是为了对消息执行各种验证。我们必须承认这种解压缩对 Broker 端性能有一定的影响，特别是对 CPU 的使用率而言。</p><h3 id="「各种压缩算法对比」"><a href="#「各种压缩算法对比」" class="headerlink" title="「各种压缩算法对比」"></a>「各种压缩算法对比」</h3><p>Kafka 支持 4 种压缩算法：GZIP、Snappy、LZ4 和 zstd(Zstandard 算法)。在实际使用中，各个算法各有千秋。</p><p><strong>吞吐量</strong>：LZ4 &gt; Snappy &gt; zst 和 GZIP；</p><p><strong>压缩比</strong>：zstd &gt; LZ4 &gt; GZIP &gt; Snappy；</p><p><strong>占用宽带</strong>：zstd &lt; LZ4 和 GZIP &lt; Snappy；</p><p>在 CPU 使用率方面，各个算法表现得差不多，只是在压缩时 Snappy 算法使用的 CPU 较多一些，而在解压缩时 GZIP 算法则可能使用更多的 CPU。</p><h2 id="五、消费者组"><a href="#五、消费者组" class="headerlink" title="五、消费者组"></a>五、消费者组</h2><p>Consumer Group 是 Kafka 提供可拓展且具有容错性的消费机制。</p><p>既然是一个组，那么组内必然是可以有多个消费者或者消费者实例，它们共享一个公共的 ID，这个 ID 被称为 Group ID。组内所有的消费者协调在一起来消费订阅主题的所有分区。每个分区只能有同一个消费组内的一个 Consumer 实例来消费。</p><h3 id="Consumer-Group-三个特性"><a href="#Consumer-Group-三个特性" class="headerlink" title="Consumer Group 三个特性"></a>Consumer Group 三个特性</h3><ol><li><p>Consumer Group 下可以有一个或者多个 Consumer 实例，这里的实例可以是一个单独的进程，也可以是同一个进程下的线程。</p></li><li><p>Group ID 是一个字符串，在一个 Kafka 集群中，它表示唯一的一个 Consumer Group。</p></li><li><p>Consumer Group 下所有实例订阅的主题的单独分区，只能分配给组内的某个 Consumer 实例消费，这个分区当然也可以被其他的 Group 消费。</p></li></ol><p>Kafka 仅仅使用 Consumer Group 这一种机制，却同时实现了传统消息引擎系统的两大模型：</p><ul><li>如果所有实例都是属于同一个 Group，那么它实现的是消息队列模型；</li><li>如果所有实例分别属于不同的 Group，那么它实现的就是发布&#x2F;订阅模型。</li></ul><h3 id="一个-Group-下应该有多少个-Consumer-实例呢？"><a href="#一个-Group-下应该有多少个-Consumer-实例呢？" class="headerlink" title="一个 Group 下应该有多少个 Consumer 实例呢？"></a>一个 Group 下应该有多少个 Consumer 实例呢？</h3><p>理想情况下，Consumer 实例的数量应该等于 Group 订阅主题的分区总数。</p><blockquote><p>假设一个 Consumer Group 订阅了 3 个主题，分别是 A、B、C，它们的分区数分别是 1，2，3，那么通常情况下，为改 Group 设置 6 个 Consumer 实例是比较理想的情形，因为它能最大限度的视线高伸缩性。</p></blockquote><h3 id="针对-Consumer-Group，Kafka-是怎么管理位移呢？"><a href="#针对-Consumer-Group，Kafka-是怎么管理位移呢？" class="headerlink" title="针对 Consumer Group，Kafka 是怎么管理位移呢？"></a>针对 Consumer Group，Kafka 是怎么管理位移呢？</h3><p>老版本 Consumer Group 把位移保存在 ZooKeeper 中。Apache ZooKeeper 是一个分布式的协调服务框架，Kafka 重度依赖它实现的各种各样的协调管理。将唯一保存到 ZooKeeper 外部系统的做法，最显而易见的好处就是减少了 Kafka Broker 端的状态保存开销。</p><p>但是，慢慢的发现一个问题，即 ZooKeeper 这类元框架其实并不适合进行频繁的写更新，而 Consumer Group 的位移更新是一个非常频繁的操作。这种大吞吐量的写操作会极大拖慢 ZooKeeper 集群的性能。于是，新版本的 Consumer Group 中，Kafka 社区重新设计了 Consumer Group 的位移管理方式，采用将位移保存在 Kafka 内部主题的方法。</p><p>这个内部主题就是 <code>_counsumer_offset</code>.</p><h2 id="六、消费者策略"><a href="#六、消费者策略" class="headerlink" title="六、消费者策略"></a>六、消费者策略</h2><p>消费者消费同一主题的哪个分区，是通过消费者策略决定的。</p><h3 id="轮询-Round"><a href="#轮询-Round" class="headerlink" title="轮询 Round"></a>轮询 Round</h3><p>Kafka 默认的消费者策略——轮询，通过轮询方式，决定消费者消费的分区。</p><p><img src="/img/68d9aaa9dd34dbc9d516201de1f2ab55_MD5.png" alt="2ca562d1dc3a2d8d06c7c2bfe2c394c8_MD5.png"></p><h3 id="范围计算-Range"><a href="#范围计算-Range" class="headerlink" title="范围计算 Range"></a>范围计算 Range</h3><p>对一个消费者组来说，决定消费方式是以分区总数除以消费者总数来决定，一般如果不能整除，往往是从头开始将剩余的分区分配开</p><p><img src="/img/c3276b5f713b8b0641463b496c196ce2_MD5.png" alt="c3276b5f713b8b0641463b496c196ce2_MD5.png"></p><h3 id="范围计算升华版-Sticky"><a href="#范围计算升华版-Sticky" class="headerlink" title="范围计算升华版 Sticky"></a>范围计算升华版 Sticky</h3><p>是在 0.11.x，新增的，它和前面两个不是很一样，它是在 Range 上的一种升华，且前面两个当同组内有新的消费者加入或者旧的消费者退出的时候，会从新开始决定消费者消费方式，但是 Sticky，在同组中有新的新的消费者加入或者旧的消费者退出时，不会直接开始新的 Range 分配，而是保留现有消费者原来的消费策略，将退出的消费者所消费的分区平均分配给现有消费者，新增消费者同理，同其他现存消费者的消费策略中分离。</p><h2 id="七、位移提交"><a href="#七、位移提交" class="headerlink" title="七、位移提交"></a>七、位移提交</h2><p>假设一个分区中有 10 条消息，唯一分别是 0 到 9.</p><p>某个 Consumer 应用已经消费了 5 条消息，这就说明该 Consumer 消费了位移为 0 到 4 的 5 条消息，此时 Consumer 的位移是 5，指向了下一条消息的位移。因为 Consumer 能够同时消费多个分区的数据，所以位移的提交实际上是在分区粒度上进行的，即 <strong>Consumer 需要为分配给它的每一个分区提交各自的位移数据。</strong></p><p>位移提交分为自动提交和手动提交；从 Consumer 端的角度来说，位移提交分为同步提交和异步提交。</p><p>开启自动提交位移的方法：Consumer 端有一个参数 <code>enable.auto.commint</code>，把它设置为 true 或者不设置它即可。</p><p>如果开启了自动提交，Consumer 端还有个参数：<code>auto.commit.interval.ms</code>。默认为 5 秒，表明 Kafka 每 5 秒会自动提交一次位移。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Properties</span> props <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Properties</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"bootstrap.servers"</span><span class="token punctuation">,</span> <span class="token string">"localhost:9092"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"group.id"</span><span class="token punctuation">,</span> <span class="token string">"test"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//开启自动提交</span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"enable.auto.commit"</span><span class="token punctuation">,</span> <span class="token string">"true"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//自动提交时间间隔</span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"auto.commit.interval.ms"</span><span class="token punctuation">,</span> <span class="token string">"2000"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"key.deserializer"</span><span class="token punctuation">,</span> <span class="token string">"org.apache.kafka.common.serialization.StringDeserializer"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"value.deserializer"</span><span class="token punctuation">,</span> <span class="token string">"org.apache.kafka.common.serialization.StringDeserializer"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">KafkaConsumer</span> consumer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">KafkaConsumer</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>props<span class="token punctuation">)</span><span class="token punctuation">;</span>consumer<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token string">"foo"</span><span class="token punctuation">,</span> <span class="token string">"bar"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">ConsumerRecords</span> records <span class="token operator">=</span> consumer<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">ConsumerRecord</span> record <span class="token operator">:</span> records<span class="token punctuation">)</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"offset = %d, key = %s, value = %s%n"</span><span class="token punctuation">,</span> record<span class="token punctuation">.</span><span class="token function">offset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> record<span class="token punctuation">.</span><span class="token function">key</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> record<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果要开启手动提交，只需要将 <code>enable.auto.commit</code> 设置为 <code>false</code> 即可。</p><p>手动提交需要调用相应的 API 手动提交位移。最简单的 API 就是 <code>KafkaConsumer#commitSync()</code>。该方法会提交 <code>KafkaConsumer#poll()</code> 返回的最新位移。从名字上来看，这是一个同步方法，即该方法会一直等待，直到位移成功提交之后才会返回。如果提交过程中出现异常，该方法会将异常信息抛出。</p><p>下面这段代码展示了 commitSync() 的使用方法：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">ConsumerRecords</span> records <span class="token operator">=</span>consumer<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token class-name">Duration</span><span class="token punctuation">.</span><span class="token function">ofSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">process</span><span class="token punctuation">(</span>records<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 处理消息</span>        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>            consumer<span class="token punctuation">.</span><span class="token function">commitSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">CommitFailedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token function">handle</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 处理提交失败异常</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>自动提交时，Kafka 会保证再开始调用 poll 方法时候，提交上次 poll 方法返回的所有消息。从顺序上来说，poll 方法的逻辑是先提交上一批消息的位移，然后在处理下一批消息，因此，自动提交能保证不会出消费丢失的情况。但是自动提交位移的问题在于，<strong>可能出现重复消费。</strong></p><p>手动提交的好处在于更加灵活，可以完全把控位移提交的时机和频率。但是他也有一个缺陷，就是在调用 <code>commitSync()</code> 时候会处于阻塞状态，直到远端 Broker 返回提交结果，这个状态才能结束。</p><p>这时候，手动提交的另一个方法就出现了 <code>KafkaConsumer#commitAsync()</code>。从名字上看，这是个异步操作。调用 <code>commitAsync()</code> 方法之后，它会立即返回，不会阻塞，因此不影响 Consumer 应用的 TPS（吞吐量）。由于它是异步的，Kafka 提供了一个回调函数（callback），供开发者实现提交之后的逻辑，比如记录日志或处理异常。</p><p>下面这段代码展示了调用 commintAsync() 方法：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">ConsumerRecords</span> records <span class="token operator">=</span> consumer<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token class-name">Duration</span><span class="token punctuation">.</span><span class="token function">ofSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">process</span><span class="token punctuation">(</span>records<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 处理消息</span>            consumer<span class="token punctuation">.</span><span class="token function">commitAsync</span><span class="token punctuation">(</span><span class="token punctuation">(</span>offsets<span class="token punctuation">,</span> exception<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">&#123;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>exception <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token function">handle</span><span class="token punctuation">(</span>exception<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>commitAsync 的问题在于，出了问题时它不会重试。</p><p>显然，如果手动提交，我们需要将 commitSync 和 commitAsync 组合使用才能达到最理想的效果：</p><pre><code>1.我们可以利用 commitSync 的自动动重试来规避那些瞬时错误，比如网络的瞬时都懂，Broker 端的 GC 问题，因为这些问题是短暂的，自动重试通常都会成功。2.我们不希望程序总是处于阻塞状态，影响 TPS。</code></pre><p>我们来看一下下面这段代码，它展示的是如何将两个 API 方法结合使用进行手动提交。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">ConsumerRecords</span> records <span class="token operator">=</span> consumer<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token class-name">Duration</span><span class="token punctuation">.</span><span class="token function">ofSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">process</span><span class="token punctuation">(</span>records<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 处理消息</span>        <span class="token function">commitAysnc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用异步提交规避阻塞</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">handle</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 处理异常</span><span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>        consumer<span class="token punctuation">.</span><span class="token function">commitSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 最后一次提交使用同步阻塞式提交</span><span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span> consumer<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>试想这样一个场景：poll 方法返回的不是 500 条消息，而是 5000 条。</p><p>那么，你肯定不想把这 5000 条消息处理完之后再提交位移，因为一旦中间出差错，之前处理的全部都要重来一遍。那么我们可以每处理完 100 条消息就提交一次位移，这样避免大批量的消息重新消费。</p><p>Kafka Consumer API 为手动提交提供了这样的方法：<code>commitSync(Map)</code> 和 <code>commitAsync(Map)</code>。它们的参数是一个 Map 对象，键就是 TopicPartition，即消费的分区，而值是一个 OffsetAndMetadata 对象，保存主要是位移数据。</p><p>以 commitAsync 为例，展示一段代码。实际上，commitSync 的调用方法和它一模一样的。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token class-name">Map</span> offsets <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>……<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">ConsumerRecords</span> records <span class="token operator">=</span> consumer<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token class-name">Duration</span><span class="token punctuation">.</span><span class="token function">ofSeconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">ConsumerRecord</span> record<span class="token operator">:</span> records<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token function">process</span><span class="token punctuation">(</span>record<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 处理消息</span>            offsets<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TopicPartition</span><span class="token punctuation">(</span>record<span class="token punctuation">.</span><span class="token function">topic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> record<span class="token punctuation">.</span><span class="token function">partition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token keyword">new</span> <span class="token class-name">OffsetAndMetadata</span><span class="token punctuation">(</span>record<span class="token punctuation">.</span><span class="token function">offset</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span>；            <span class="token keyword">if</span>（count <span class="token operator">%</span> <span class="token number">100</span> <span class="token operator">==</span> <span class="token number">0</span>）                consumer<span class="token punctuation">.</span><span class="token function">commitAsync</span><span class="token punctuation">(</span>offsets<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 回调处理逻辑是null</span>                count<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>与调用无参的 commitAsync 不同，这里调用了带 Map 对象参数的 commitAsync 进行细粒度的位移提交。</p><h2 id="八、重平衡"><a href="#八、重平衡" class="headerlink" title="八、重平衡"></a>八、重平衡</h2><p>重平衡 Rebalance 本质上是一种协议，规定了一个 Consumer Group 如何分配订阅 Topic 的每一个分区。Kafka 在为 Consumer Group 内的 Consumer 分配分区的过程，就是 Rebalance。</p><p>Rebalance 触发条件有三个：</p><ul><li>组内成员发生变化，即有新的 Consumer 实例加入组或者离开祖，或者因崩溃而退出组。</li><li>订阅主题数发生变化，Consumer Group 可以通过正则表达式方式订阅主题，比如 <code>consumer.subscribe(Pattern.compile(&quot;t.*c&quot;))</code> 就表明 Group 订阅所有以字母 t 开头、字母 c 结尾的主题，所以在 Consumer Group 运行过程中，如果创建了满足要求的主题，就会发生 Rebalance。</li><li>订阅主题的分区发生变化，Kafka 当前只能允许增加一个主题的分区数，当主题的分区数发生变化，就会触发该主题下所有 Group 的 Rebalance。</li></ul><h3 id="「分配策略」"><a href="#「分配策略」" class="headerlink" title="「分配策略」"></a>「分配策略」</h3><p>当前 Kafka 默认提供了 3 种分配策略，每种策略都有一定的优势和劣势，社区会不断地完善这些策略，保证提供最公平的分配策略，即每个 Consumer 实例都能够得到较为平均的分区数。</p><h3 id="「Coordinator-会在什么情况下确认-Consumer-实例挂掉了从而要退组？」"><a href="#「Coordinator-会在什么情况下确认-Consumer-实例挂掉了从而要退组？」" class="headerlink" title="「Coordinator 会在什么情况下确认 Consumer 实例挂掉了从而要退组？」"></a>「Coordinator 会在什么情况下确认 Consumer 实例挂掉了从而要退组？」</h3><p>在 Consumer Group 完成 Rebalance 之后，每个 Consumer 实例都会定期的想 Coordinator 发送心跳请求，表明它还活着。如果某个 Consumer 不能及时的发送心跳请求，娜美 Coordinator 就会认为它已经死了，从而将其从 Group 中移除，然后开启新一轮的 Rebalance。</p><p>Consumer 端设置参数里面有个：<code>session.timeout.ms</code>。默认为 10 秒，即如果 Coordinator 在 10 秒内没有收到 Group 的某个 Consumer 的心跳请求，则认为它已经挂了。除了这个参数，还有个允许开发者控制发送心跳的频率的参数，就是 <code>heartbeat.interval.ms</code>。这个参数设置越小，Consumer 发送心跳请求的频率越高。当然，请求频率越高，消耗的带宽资源也就越高。</p><p>除此之外，Consumer 端还有个参数，用于控制 Consumer 实际消费能力对对 Rebalance 的影响，即：<code>max.pool.interval.ms</code> 参数。它限定了 Consumer 端应用程序两次调用 poll 方法的最大时间间隔。默认值为 5 分钟，表示如果 Consumer 在 5 分钟内没有消费完 poll 方法返回的消息，那么 Consumer 会主动发起离开 Group 的请求，Coordinator 则会开启新的 Rebalance。</p><h3 id="「如何设置避免-Rebalance」"><a href="#「如何设置避免-Rebalance」" class="headerlink" title="「如何设置避免 Rebalance」"></a>「如何设置避免 Rebalance」</h3><ol><li>如果是因为未能及时发送心跳请求，导致 Consumer 被踢出 Group，引发的 Rebalance。则可以设置 <code>session.timeout.ms</code> 和 <code>heartbeat.interval.ms</code> 的值。<ul><li>设置 <code>session.timeout.ms</code> &#x3D; 6s。</li><li>设置 <code>heartbeat.interval.ms</code> &#x3D; 2s。</li><li>要保证 Consumer 实例在被判定为 dead 之前，能够发送 3 条心跳请求，即 <code>session.timeout.ms &gt;= 3 * heartbeat.interval.ms</code>。<br>将 <code>session.timeout.ms</code> 设置为 6s 主要是为了让 Coordinator 能更快定位已经挂掉的 Consumer。</li></ul></li><li>如果是因为 Consumer 消费时间过长导致的 Rebalance。在开发过程中，为业务逻辑处理留足充足的时间，这样 Consumer 就不会因为处理这些消息太长而引起 Rebalance 了。</li></ol><h2 id="九、ConsumerOffsets"><a href="#九、ConsumerOffsets" class="headerlink" title="九、ConsumerOffsets"></a>九、ConsumerOffsets</h2><p><code>_consumer_offsets</code> 是一个 Kafka 的普通主题，它主要是保存 Kafka Consumer 的位移信息。当 Kafka 中的第一个 Consumer 启动时候，就会创建该主题。其默认分区数是 50，副本数是 3。<code>_consumer_offsets</code> 主题是一个普通的 Kafka 主题，开发者可以手动的创建、修改甚至删除它。但是它的消息格式是 Kafka 自己定义的，不能修改。开发者只能按照规定传入消息，否则内部不能成功解析，就会导致 Broker 崩溃。</p><p><code>_consumer_offsets</code> 有 3 中消息格式：</p><ul><li>用于保存 Consumer Group 信息的消息。</li><li>用于删除 Group 过期位移甚至删除 Group 的消息。</li><li>保存了位移值。</li></ul><p>前面已经提到过，Kafka 的提交方式有两种：自动提交和手动提交。</p><ul><li><p>手动提交：比较灵活可控，通过调用 <code>commitSync()</code> 或者 <code>commitAsync()</code> 等 Kafka Consumer 的 API，Kafka 会向 <code>_consumer_offsets</code> 主题中写入相应的消息。</p></li><li><p>自动提交：显著优点就是省事，不用操心位移提交的事情，就能保证消息不会丢失。但是自动提交位移的有个问题，只要 Consumer 一直启动着，它就会无限期的向位移主题写入消息。</p></li></ul><blockquote><p>假设 Consumer 当前消费到了某个主题的最新一条消息，位移是 100，之后该主题没有任何新消息产生，故 Consumer 无消息可消费了，所以位移永远保持在 100。由于是自动提交位移，位移主题中会不停地写入位移&#x3D;100 的消息。显然 Kafka 只需要保留这类消息中的最新一条就可以了，之前的消息都是可以删除的。</p></blockquote><p>显然，Kafka 必须要有针对位移主题消息特点的消息删除策略，否则这种消息越多，最重撑爆整个磁盘</p><h3 id="「Compact-策略」"><a href="#「Compact-策略」" class="headerlink" title="「Compact 策略」"></a>「Compact 策略」</h3><p>Kafka 通过 Compact 策略来删除 <code>_consumer_offsets</code> 主题中的过期消息，避免该主题无限膨胀。Compact 的过程就是扫描日志的所有消息，剔除过期消息，把剩下消息整理在一起。</p><p>Kafka 提供了专门的后台线程定期的巡检待 Compact 的主题，看看是否存在猫族条件的可删除数据。这个后台线程叫做 <strong>Log cleaner</strong>。如果生产环境中出现了位移主题无限膨胀占用过多磁盘空间问题，请检查一下 Log cleaner 线程是否挂掉了。</p><h2 id="十、副本机制"><a href="#十、副本机制" class="headerlink" title="十、副本机制"></a>十、副本机制</h2><p>根据 Kafka 副本机制定义，同一个分区下面的所有副本保存有相同的消息队列，这些副本是分布在不同 Broker 中，以确保某个 Broker 宕机后其他副本可以正常使用。</p><p>在 Kafka 中，副本分为领导者副本和追随者副本。其中追随者副本不参与什么读写请求操作。追随者副本只异步拉去领导者副本，在领导者副本所在的 Broker 宕机的时候，重新从追随者副本中推选出一个领导者副本。</p><p>追随者副本唯一的工作就是，不断的从领导者副本中拉取消息，然后写入自己的提交日志中。</p><p><img src="/img/ff0fd5af97f5f5ad3a954f3ffa4076d0_MD5.png" alt="ff0fd5af97f5f5ad3a954f3ffa4076d0_MD5.png"></p><h2 id="十一、ISR-机制"><a href="#十一、ISR-机制" class="headerlink" title="十一、ISR 机制"></a>十一、ISR 机制</h2><p>in-sync Replicas，也就是所谓的 ISR 副本集合。这个集合是动态的，而非静态不变。</p><p>ISR 中的副本一定是好 Leader 副本同步的，相反不在 ISR 中的副本一定是和 Leader 副本不同步的。</p><p>Leader 副本一定在 ISR 中，Follower 副本不一定在 ISR 中。在 Broker 端有个配置参数 <code>replica.lag.time.max.ms</code>，这个参数的含义是 Follower 副本落后 Leader 副本的时间不连续超过 10 秒，那么 Kafka 认为 Follower 副本和 Leader 是同步的，即使此时 Follower 副本中保存的消息明显少于 Leader 副本中的消息。</p><h2 id="十二、Unclean-领导者选举"><a href="#十二、Unclean-领导者选举" class="headerlink" title="十二、Unclean 领导者选举"></a>十二、Unclean 领导者选举</h2><p>Kafka 将所有不在 ISR 中的副本都认为是非同步副本。在领导者选举的时候，如果选举这种副本的过程称为 Unclean 领导者选举。在 Broker 端中参数 <code>unclean.leader.election.enable</code> 控制是否开启 Unclean 领导者选举。</p><p>Unclean 领导者选举有利有弊。优点在于：因为一个分区中 Leader 副本负责读写请求，如果 Leader 副本挂了，整个分区就改了。开启 Unclean 领导者选取，会使 Leader 副本一直存在，不至于对外停止服务，提高了高可用；缺点在于：因为从 ISR 中选举 Leader 副本，就会出现数据不同步情况，就会导致数据丢失。</p><h2 id="十三、副本选举"><a href="#十三、副本选举" class="headerlink" title="十三、副本选举"></a>十三、副本选举</h2><p>Kafka 在选取 Leader 副本时候，考虑到负载均衡的平衡性，会将不同的分区的 Leader 副本分配到不同的 Broker 中，这样既能避免 Broker 宕机导致多个分区不可用，也能平衡 Broker 的负载。</p><p>Kafka 引入了优先副本的概念，优先副本的意思是，在分区的所有 AR 集合列表中的第一个副本，理想状态下就是该分区的 Leader 副本。</p><p>例如 kafka 集群由 3 台 broker 组成，创建了一个名为 <code>topic-partitions</code> 的 topic，设置 partition 为 3，副本数为 3，partition0 中 AR 列表为 <code>[1,2,0]</code>，那么分区 0 的优先副本为 1</p><p><img src="/img/882565aff441558b043b911c7c240f29_MD5.png" alt="882565aff441558b043b911c7c240f29_MD5.png"></p><p>当分区 leader 节点发生故障时，其中的一个 follower 节点就会选举为新的 leader 节点。当原来 leader 的节点恢复之后，它只能成为一个 follower 节点，此时就导致了集群负载不均衡。比如分区 1 的 leader 节点 broker2 崩溃了，此时选举了在 broker1 上的分区 1follower 节点作为新的 leader 节点。当 broker2 重新恢复时，此时的 kafka 集群状态如下：</p><p><img src="/img/d9e50eff06b436f7621152f2739a05c5_MD5.png" alt="d9e50eff06b436f7621152f2739a05c5_MD5.png"></p><p>可以看到，此时 broker1 上负载更大，而 broker2 上没有负载。</p><p><strong>「为了解决上述负载不均衡的情况，kafka 支持了优先副本选举，优先副本指的是一个分区所在的 AR 集合的第一个副本」</strong>。</p><p>比如上面的分区 1，它的 AR 集合是 <code>[2,0,1]</code>，表示分区 1 的优先副本就是在 broker2 上。</p><p>理想情况下，优先副本应该就是 leader 副本，kafka 保证了优先副本的均衡分布，而这与 broker 节点宕机与否没有关系。</p><p><strong>「优先副本选举就是对分区 leader 副本进行选举的时候，尽可能让优先副本成为 leader 副本」</strong>，针对上述的情况，只要再触发一次优先副本选举就能保证分区负载均衡。</p><p>kafka 支持自动优先副本选举功能，默认每 5 分钟触发一次优先副本选举操作。</p><h2 id="十四、网络通信模型"><a href="#十四、网络通信模型" class="headerlink" title="十四、网络通信模型"></a>十四、网络通信模型</h2><p><img src="/img/93681a72e52523561d8f052cb74d6da0_MD5.png" alt="93681a72e52523561d8f052cb74d6da0_MD5.png"></p><p>Broker 中有个 <code>Acceptor(mainReactor)</code> 监听新连接的到来，与新连接建连之后轮询选择一个 <code>Processor(subReactor)</code> 管理这个连接。</p><p>而 <code>Processor</code> 会监听其管理的连接，当事件到达之后，读取封装成 <code>Request</code>，并将 <code>Request</code> 放入共享请求队列中。</p><p>然后 IO 线程池不断的从该队列中取出请求，执行真正的处理。处理完之后将响应发送到对应的 <code>Processor</code> 的响应队列中，然后由 <code>Processor</code> 将 <code>Response</code> 返还给客户端。</p><p>每个 <code>listener</code> 只有一个 <code>Acceptor线程</code>，因为它只是作为新连接建连再分发，没有过多的逻辑，很轻量。</p><p><code>Processor</code> 在 Kafka 中称之为网络线程，默认网络线程池有 3 个线程，对应的参数是 <code>num.network.threads</code>，并且可以根据实际的业务动态增减。</p><p>还有个 IO 线程池，即 <code>KafkaRequestHandlerPool</code>，执行真正的处理，对应的参数是 <code>num.io.threads</code>，默认值是 8。</p><p>IO 线程处理完之后会将 <code>Response</code> 放入对应的 <code>Processor</code> 中，由 <code>Processor</code> 将响应返还给客户端。</p><p>可以看到网络线程和 IO 线程之间利用的经典的生产者 - 消费者模式，不论是用于处理 Request 的共享请求队列，还是 IO 处理完返回的 Response。</p><h2 id="十五、幂等性"><a href="#十五、幂等性" class="headerlink" title="十五、幂等性"></a>十五、幂等性</h2><h3 id="「幂等性-Producer」"><a href="#「幂等性-Producer」" class="headerlink" title="「幂等性 Producer」"></a><strong>「幂等性 Producer」</strong></h3><p>在 Kafka 中，Producer 默认不是幂等性的，但是我们可以创建幂等性 Producer。<code>enable.idempotence</code> 设置为 True，即可保证 Producer 自动升级成幂等性 Producer，其他所有的代码逻辑都不需要更改。配置后，Kafka 自动做消息的去重操作。</p><p>其实，底层原理非常简单，就是经典的以空间换时间的做法，Broker 多保存一些字段，当 Producer 发送消息请求的时候，Broker 能够判断消息是否重复，进而再丢弃掉重复消息。</p><h3 id="「幂等性-Producer-的作用范围」"><a href="#「幂等性-Producer-的作用范围」" class="headerlink" title="「幂等性 Producer 的作用范围」"></a>「幂等性 Producer 的作用范围」</h3><ul><li>幂等性只能保证单个分区上的幂等性，无法实现多分区幂等性。</li><li>幂等性针对单个会话的幂等性，不会实现跨会话的幂等性。</li></ul><blockquote><p>这里的会话，可以理解成 Producer 进程的一次运行，当重启了 Producer 进程之后，这种幂等性就丧失了。 </p></blockquote><h2 id="十六、事务"><a href="#十六、事务" class="headerlink" title="十六、事务"></a>十六、事务</h2><p>Kafka 自从 0.11 版本就开始支持事务，目前主要是在 read committed 隔离级别上做事务。它能保证多条消息原子性地写入到目标分区，同时也能保证 Consumer 只能看到事务成功提交的消息。</p><h3 id="「事务型-Producer」"><a href="#「事务型-Producer」" class="headerlink" title="「事务型 Producer」"></a>「事务型 Producer」</h3><p>事物型 Producer 能够保证将消息原子性的写入到多个分区中。这批消息要么全部成功，要么全部失败，另外，事务型 Producer 也不怕进程的重启。当 Producer 重启之后，Kafka 仍能保证它们发送消息的精确一次处理。</p><p>设置事务型 Producer 的方式也比较简单，满足两个设置即可：</p><ul><li>和幂等性 Producer 一样，开启 <code>enable.idempotence = true</code>。</li><li>设置 Producer 端参数 <code>transactional.id</code>，最好设置一个有意义的名字。<br>此外，还需要在 Producer 代码中做一些调整，如这段代码所示：</li></ul><pre class="line-numbers language-java" data-language="java"><code class="language-java">producer<span class="token punctuation">.</span><span class="token function">initTransactions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>              producer<span class="token punctuation">.</span><span class="token function">beginTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>              producer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>record1<span class="token punctuation">)</span><span class="token punctuation">;</span>              producer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>record2<span class="token punctuation">)</span><span class="token punctuation">;</span>              producer<span class="token punctuation">.</span><span class="token function">commitTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">KafkaException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>              producer<span class="token punctuation">.</span><span class="token function">abortTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>和普通 Producer 代码相比，事务型 Producer 的显著特点是调用了一些事务 API，如 initTransaction、beginTransaction、commitTransaction 和 abortTransaction，它们分别对应事务的初始化、事务开始、事务提交以及事务终止。</p><p>实际上即使写入失败，Kafka 也会把它们写入到底层的日志中，也就是说 Consumer 还是会看到这些消息。</p><p>有一个 <code>isolation.level</code> 参数，这个参数有两个取值：</p><ol><li><p><code>read_uncommitted</code>：这是默认值，表明 Consumer 能够读取到 Kafka 写入的任何消息，不论事务型 Producer 提交事务还是终止事务，其写入的消息都可以读取，如果你用了事务型 Producer，那么对应的 Consumer 就不要使用这个值。</p></li><li><p><code>read_committed</code>：表明 Consumer 只会读取事务型 Producer 成功提交事务写入的消息，它也能看到非事务型 Producer 写入的所有消息</p></li></ol><h2 id="十七、拦截器"><a href="#十七、拦截器" class="headerlink" title="十七、拦截器"></a>十七、拦截器</h2><p><strong>Kafka 拦截器分为生产者拦截器和消费者拦截器。</strong> 生产者拦截器允许你在发送消息前以及消息提交成功之后植入拦截器逻辑。而消费者拦截器支持消费消息前以及提交位移后编写特定逻辑。可以将一组懒啊节气串联成一个大的拦截器，Kafka 会按照顺序依次执行拦截器逻辑。</p><p>当前 Kafka 拦截器是通过参数配置完成，生产者和消费者两端都有个相同的参数 <code>interceptor.classes</code>，它指定的是一组类的列表，每个类就是特定逻辑的拦截器实现类。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Properties</span> props <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Properties</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token class-name">List</span> interceptors <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   interceptors<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"com.yourcompany.kafkaproject.interceptors.AddTimestampInterceptor"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 拦截器1   </span>interceptors<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"com.yourcompany.kafkaproject.interceptors.UpdateCounterInterceptor"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 拦截器2   </span>props<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token class-name">ProducerConfig</span><span class="token punctuation">.</span><span class="token constant">INTERCEPTOR_CLASSES_CONFIG</span><span class="token punctuation">,</span> interceptors<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>怎么编写 AddTimeStampInterceptor 和 UpdateCounterInterceptor 类呢？</p><p>这两个类以及所有 Producer 端拦截器实现类都要继承 <code>org.apache.kafka.clients.producer.ProducerInterceptor</code> 接口。</p><p>该接口是 Kafka 提供的，里面有两个核心方法：</p><ol><li>onSend：该方法在消息发送前被调用。</li><li>onAcknowledgement：该方法在消息成功提交或者提交失败之后被调用。onAcknowledgement 的调用要早于 callback 的调用。值得注意的是，这个方法和 onSend 方法不在同一个线程中调用，因此如果在这两个方法中调用了共享可变变量，一定要注意线程安全问题。</li></ol><p>同理，消费者拦截器也是同样的方法，都要继承 <code>org.apache.kafka.clients.consumer.ConsumerInterceptor</code> 接口，这里也有两个核心方法。</p><ol><li>onConsuume：该方法在消息返回给 Consumer 程序之前调用。</li><li>onCommit：Consumer 在提交位移之后调用该方法。通常做法是在该方法中做一些记账的动作，例如打印日志等。</li></ol><h2 id="十八、控制器（Controller）"><a href="#十八、控制器（Controller）" class="headerlink" title="十八、控制器（Controller）"></a>十八、控制器（Controller）</h2><p><strong>控制器组件（controller），主要是用于在 Apache Zookeeper 的帮助下管理和协调整个 Kafka 集群。</strong></p><p>集群中任意一台 Broker 都可以成为控制器角色。在 Kafka 集群启动的时候，第一个在 Zookeeper 中创建&#x2F;controller 节点的 Broker 会被指定为控制器。</p><p>控制器主要的功能如下：</p><ol><li><strong>主题管理（创建，删除，增加分区）</strong><br>控制器帮我们完成对 Kafka 主题的创建，删除以及分区增加的操作。</li><li><strong>分区重分配</strong></li><li><strong>Preferred 领导者选举</strong></li></ol><p>Preferred 领导者选举主要是 Kafka 为了避免部分 Broker 负载过重而提供的一种换 Leader 的方案。</p><ol><li><strong>集群成员管理（新增 Broker、Broker 主动关闭、Broker 宕机）</strong></li></ol><p>在 Zookeeper 对 Kafka 协助管理工程中，<strong>「Watch 机制」</strong> 和 <strong>「临时节点」</strong> 是两个重要的机制。</p><p>Broker 的创建时候，Zookeeper 会在 Zookeeper 的 &#x2F;broker&#x2F;ids 下创建专属的 znode 节点，这个节点就是临时节点。一旦节点创建完成，ZooKeeper 就会通过 Watch 机制将消息通知推送给控制器，只要，控制器就能自动感知这个变化，进而开启后续的新增 Broker 作业。</p><p>当 Broker 宕机或主动关闭后，该 Broker 与 ZooKeeper 的会话结束，这个 znode 会被自动删除。</p><p>同理，ZooKeeper 的 Watch 机制将这一变更推送给控制器，这样控制器就能知道有 Broker 关闭或宕机了，从而进行善后。</p><ol><li><strong>数据服务</strong></li></ol><p>控制器上保存了最全的集群元数据信息，其他所有 Broker 会定期接收控制器发来的元数据更新请求，从而更新其内存中的缓存数据。</p><h3 id="「控制器故障转移（Failover）」"><a href="#「控制器故障转移（Failover）」" class="headerlink" title="「控制器故障转移（Failover）」"></a>「控制器故障转移（Failover）」</h3><p><strong>「故障转移指的是，当运行中的控制器突然宕机或意外终止时，Kafka 能够快速地感知到，并立即启用备用控制器来代替之前失败的控制器」</strong>。这个过程就被称为 Failover，该过程是自动完成的，无需你手动干预。</p><p><img src="/img/5711d6da84f2000a181c356e080deec4_MD5.png" alt="5711d6da84f2000a181c356e080deec4_MD5.png"></p><p>最开始时，Broker 0 是控制器。当 Broker 0 宕机后，ZooKeeper 通过 Watch 机制感知到并删除了 <code>/controller</code> 临时节点。</p><p>之后，所有存活的 Broker 开始竞选新的控制器身份。Broker 3 最终赢得了选举，成功地在 ZooKeeper 上重建了 <code>/controller</code> 节点。之后，Broker 3 会从 ZooKeeper 中读取集群元数据信息，并初始化到自己的缓存中。</p><p>至此，控制器的 Failover 完成，可以行使正常的工作职责了。</p><h2 id="二十、日志存储"><a href="#二十、日志存储" class="headerlink" title="二十、日志存储"></a>二十、日志存储</h2><p>Kafka 中的消息是以主题为基本单位进行归类的，每个主题在逻辑上相互独立。</p><p>每个主题又可以分为一个或多个分区，在不考虑副本的情况下，一个分区会对应一个日志。</p><p>但设计者考虑到随着时间推移，日志文件会不断扩大，因此为了防止 Log 过大，设计者引入了日志分段（LogSegment）的概念，将 Log 切分为多个 LogSegment，便于后续的消息维护和清理工作。</p><p>下图描绘了主题、分区、副本、Log、LogSegment 五者之间的关系。</p><p><img src="/img/4cc119f98ed02df15c264d1cf428d32b_MD5.png" alt="4cc119f98ed02df15c264d1cf428d32b_MD5.png"></p><p><strong>「LogSegment」</strong></p><p>在 Kafka 中，每个 Log 对象又可以划分为多个 LogSegment 文件，每个 LogSegment 文件包括一个日志数据文件和两个索引文件（偏移量索引文件和消息时间戳索引文件）。</p><p>其中，每个 LogSegment 中的日志数据文件大小均相等（该日志数据文件的大小可以通过在 Kafka Broker 的 <code>config/server.properties</code> 配置文件的中的**「log.segment.bytes」**进行设置，默认为 1G 大小（1073741824 字节），在顺序写入消息时如果超出该设定的阈值，将会创建一组新的日志数据和索引文件）。</p><p><img src="/img/5c376d6d6186292ebc461285ce0ac879_MD5.png" alt="5c376d6d6186292ebc461285ce0ac879_MD5.png"></p><h2 id="常用参数"><a href="#常用参数" class="headerlink" title="常用参数"></a>常用参数</h2><p><strong>「broker 端配置」</strong></p><ul><li><code>broker.id</code></li></ul><p>每个 kafka broker 都有一个唯一的标识来表示，这个唯一的标识符即是 <code>broker.id</code>，它的默认值是 0。</p><p>这个值在 kafka 集群中必须是唯一的，这个值可以任意设定，</p><ul><li><code>port</code></li></ul><p>如果使用配置样本来启动 kafka，它会监听 9092 端口，修改 port 配置参数可以把它设置成任意的端口。</p><p>要注意，如果使用 1024 以下的端口，需要使用 root 权限启动 Kafka。</p><ul><li><code>zookeeper.connect</code></li></ul><p>用于保存 broker 元数据的 Zookeeper 地址是通过 <code>zookeeper.connect</code> 来指定的。</p><p>比如可以这么指定 <code>localhost:2181</code> 表示这个 Zookeeper 是运行在本地 2181 端口上的。</p><p>我们也可以通过比如我们可以通过 <code>zk1:2181,zk2:2181,zk3:2181</code> 来指定 <code>zookeeper.connect</code> 的多个参数值。</p><p>该配置参数是用冒号分割的一组 <code>hostname:port/path</code> 列表，其含义如下</p><ul><li><p>hostname 是 Zookeeper 服务器的机器名或者 ip 地址。</p></li><li><p>port 是 Zookeeper 客户端的端口号</p></li><li><p>&#x2F;path 是可选择的 Zookeeper 路径，Kafka 路径是使用了 <code>chroot</code> 环境，如果不指定默认使用跟路径。</p></li></ul><blockquote><p>❝<br>如果你有两套 Kafka 集群，假设分别叫它们 kafka1 和 kafka2，那么两套集群的 <code>zookeeper.connect</code> 参数可以这样指定：<code>zk1:2181,zk2:2181,zk3:2181/kafka1</code> 和 <code>zk1:2181,zk2:2181,zk3:2181/kafka2</code><br>❞</p></blockquote><ul><li><code>log.dirs</code></li></ul><p>Kafka 把所有的消息都保存到磁盘上，存放这些日志片段的目录是通过 <code>log.dirs</code> 来制定的，它是用一组逗号来分割的本地系统路径，<code>log.dirs</code> 是没有默认值的，<strong>「你必须手动指定他的默认值」</strong>。</p><p>其实还有一个参数是 <code>log.dir</code>，这个配置是没有 <code>s</code> 的，默认情况下只用配置 <code>log.dirs</code> 就好了，比如你可以通过 <code>/home/kafka1,/home/kafka2,/home/kafka3</code> 这样来配置这个参数的值。</p><ul><li><code>auto.create.topics.enable</code></li></ul><p>默认情况下，kafka 会自动创建主题</p><p><code>auto.create.topics.enable</code> 参数建议最好设置成 false，即不允许自动创建 Topic。</p><p><strong>「主题相关配置」</strong></p><ul><li><code>num.partitions</code></li></ul><p>num.partitions 参数指定了新创建的主题需要包含多少个分区，该参数的默认值是 1。</p><ul><li><code>default.replication.factor</code></li></ul><p>这个参数比较简单，它表示 kafka 保存消息的副本数。</p><ul><li><code>log.retention.ms</code></li></ul><p>Kafka 常根据时间来决定数据可以保留多久。</p><p>默认使用 <code>log.retention.hours</code> 参数来配置时间，默认是 168 个小时，也就是一周。</p><p>除此之外，还有两个参数 <code>log.retention.minutes</code> 和 <code>log.retentiion.ms</code> 。</p><p>这三个参数作用是一样的，都是决定消息多久以后被删除，推荐使用 <code>log.retention.ms</code>。</p><ul><li><code>message.max.bytes</code></li></ul><p>broker 通过设置 <code>message.max.bytes</code> 参数来限制单个消息的大小，默认是 1000 000，也就是 1MB，如果生产者尝试发送的消息超过这个大小，不仅消息不会被接收，还会收到 broker 返回的错误消息。</p><ul><li><code>retention.ms</code></li></ul><p>规定了该主题消息被保存的时常，默认是 7 天，即该主题只能保存 7 天的消息，一旦设置了这个值，它会覆盖掉 Broker 端的全局参数值。</p><h2 id="消息丢失问题"><a href="#消息丢失问题" class="headerlink" title="消息丢失问题"></a>消息丢失问题</h2><h3 id="「生产者程序丢失数据」"><a href="#「生产者程序丢失数据」" class="headerlink" title="「生产者程序丢失数据」"></a><strong>「生产者程序丢失数据」</strong></h3><p>目前 Kafka Producer 是异步发送消息的，也就是说如果你调用的是 <code>producer.send(msg)</code> 这个 API，那么它通常会立即返回，但此时你不能认为消息发送已成功完成。</p><p><strong>如果用这个方式，可能会有哪些因素导致消息没有发送成功呢？</strong></p><p>其实原因有很多：</p><ol><li>例如网络抖动，导致消息压根就没有发送到 Broker 端；<br>如果是网络抖动导致的失败，可以通过 Producer 中的参数 <code>retries</code> (重试次数) 设置比较合理的值来解决，一般来说为 3。同时，建议还要设置重试间隔 <code>retry.backoff.ms</code> 来避免 3 次重试间隔太短导致多次失败。</li><li>或者消息本身不合格导致 Broker 拒绝接收（比如消息太大了，超过了 Broker 的承受能力）等。</li></ol><p>实际上，解决此问题的方法非常简单：Producer 永远要使用带有回调通知的发送 API，也就是说不要使用 <code>producer.send(msg)</code>，而要使用 <code>producer.send(msg, callback)</code>。在 SpringBoot 中可以用类似的方式来处理：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">ListenableFuture</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SendResult</span><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span><span class="token punctuation">></span></span> future <span class="token operator">=</span> kafkaTemplate<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>smsBusiPrediction<span class="token punctuation">,</span> msg<span class="token punctuation">)</span><span class="token punctuation">;</span>  future<span class="token punctuation">.</span><span class="token function">addCallback</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ListenableFutureCallback</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SendResult</span><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>     <span class="token annotation punctuation">@Override</span>     <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onSuccess</span><span class="token punctuation">(</span><span class="token class-name">SendResult</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Object</span><span class="token punctuation">></span></span> result<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"=====向kafka推送信息成功====="</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token punctuation">&#125;</span>     <span class="token annotation punctuation">@Override</span>     <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFailure</span><span class="token punctuation">(</span><span class="token class-name">Throwable</span> ex<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"=====向kafka推送信息失败！！====="</span><span class="token punctuation">,</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token punctuation">&#125;</span>   <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>它能准确地告诉你消息是否真的提交成功了。一旦出现消息提交失败的情况，你就可以有针对性地进行处理。</p><h3 id="「消费者程序丢失数据」"><a href="#「消费者程序丢失数据」" class="headerlink" title="「消费者程序丢失数据」"></a><strong>「消费者程序丢失数据」</strong></h3><p>Consumer 端丢失数据主要体现在 Consumer 端要消费的消息不见了。</p><p>下面这张图它清晰地展示了 Consumer 端的位移数据。</p><p><img src="/img/adc9951c2edb1fba60deea2d87fb2d44_MD5.png" alt="adc9951c2edb1fba60deea2d87fb2d44_MD5.png"></p><p>比如对于 Consumer A 而言，它当前的位移值就是 9；Consumer B 的位移值是 11。Consumer 程序从 Kafka 获取到消息后开启了多个线程异步处理消息，而 Consumer 程序自动地向前更新位移。</p><p>假如其中某个线程运行失败了，它负责的消息没有被成功处理，但位移已经被更新了，因此这条消息对于 Consumer 而言实际上是丢失了。这里的关键在于 Consumer 自动提交位移。这个问题的解决方案也很简单：<strong>「如果是多线程异步处理消费消息，Consumer 程序不要开启自动提交位移，而是要应用程序手动提交位移」</strong>。</p><h3 id="「Kafka-内部出现消息丢失」"><a href="#「Kafka-内部出现消息丢失」" class="headerlink" title="「Kafka 内部出现消息丢失」"></a>「Kafka 内部出现消息丢失」</h3><p>试想一种情况：假如 leader 副本所在的 broker 突然挂掉，那么就要从 follower 副本重新选出一个 leader，但是 leader 的数据还有一些没有被 follower 副本的同步的话，就会造成消息丢失。</p><p><strong>设置 <code>acks = all</code></strong></p><p>解决办法就是我们设置 <code>acks = all</code>。<code>acks</code> 是 Kafka 生产者 (Producer) 很重要的一个参数。</p><p>acks 的默认值即为 1，代表我们的消息被 leader 副本接收之后就算被成功发送。当我们配置 <strong>acks &#x3D; all</strong> 表示只有所有 ISR 列表的副本全部收到消息时，生产者才会接收到来自服务器的响应. 这种模式是最高级别的，也是最安全的，可以确保不止一个 Broker 接收到了消息. 该模式的延迟会很高.</p><p><strong>设置 <code>replication.factor &gt;= 3</code></strong></p><p>为了保证 leader 副本能有 follower 副本能同步消息，我们一般会为 topic 设置 <code>replication.factor &gt;= 3</code>。这样就可以保证每个分区 (partition) 至少有 3 个副本。虽然造成了数据冗余，但是带来了数据的安全性。</p><p><strong>设置 <code>min.insync.replicas &gt; 1</code></strong></p><p>一般情况下我们还需要设置 <strong><code>min.insync.replicas&gt; 1</code></strong>，这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。<strong>min.insync.replicas</strong> 的默认值为 1，在实际生产中应尽量避免默认值 1。</p><p>但是，为了保证整个 Kafka 服务的高可用性，你需要确保 <strong><code>replication.factor &gt; min.insync.replicas</code></strong>。为什么呢？设想一下假如两者相等的话，只要是有一个副本挂掉，整个分区就无法正常工作了。这明显违反高可用性！一般推荐设置成 <strong><code>replication.factor = min.insync.replicas + 1</code></strong>。</p><p><strong>设置 <code>unclean.leader.election.enable = false</code></strong></p><blockquote><p><strong>Kafka 0.11.0.0 版本开始 unclean.leader.election.enable 参数的默认值由原来的 true 改为 false</strong></p></blockquote><p>我们最开始也说了我们发送的消息会被发送到 leader 副本，然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样，当我们配置了 <strong><code>unclean.leader.election.enable = false</code></strong> 的话，当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader，这样降低了消息丢失的可能性。</p><h3 id="「最佳实践」"><a href="#「最佳实践」" class="headerlink" title="「最佳实践」"></a>「最佳实践」</h3><p>总结 Kafka 避免消息丢失的配置：</p><ol><li>在 Producer 端：<ul><li>不要使用 <code>producer.send(msg)</code>，而要使用 <code>producer.send(msg, callback)</code>，一定要使用带有回调通知的 send 方法。</li><li>设置 <code>retries</code> 为一个较大的值。这里的 <code>retries</code> 同样是 Producer 的参数，对应前面提到的 Producer 自动重试，当出现网络的瞬时抖动时，消息发送可能会失败，此时配置了 <code>retries &gt; 0</code> 的 Producer 能够自动重试消息发送，避免消息丢失。</li><li>设置 <code>acks = all</code>，acks 是 Producer 的一个参数，代表了你对已提交消息的定义，如果设置成 all，则表明所有副本 Broker 都要接收到消息，该消息才算是已提交。</li></ul></li><li>在 Consumer 端：<ul><li>确保消息消费完成再提交，Consumer 端有个参数 <code>enable.auto.commit</code>，最好把它设置成 false，并采用手动提交位移的方式。</li></ul></li><li>在 Kafka 内部：<ul><li>设置 <code>unclean.leader.election.enable = false</code>，这是 Broker 端的参数，它控制的是哪些 Broker 有资格竞选分区的 Leader，如果一个 Broker 落后原先的 Leader 太多，那么它一旦成为新的 Leader，必然会造成消息的丢失，故一般都要将该参数设置成 false，即不允许这种情况的发生。</li><li>设置 <code>replication.factor &gt;= 3</code>，这也是 Broker 端的参数，将消息多保存几份，目前防止消息丢失的主要机制就是冗余。</li><li>设置 <code>min.insync.replicas &gt; 1</code>，这依然是 Broker 端参数，控制的是消息至少要被写入到多少个副本才算是已提交，设置成大于 1 可以提升消息持久性，在实际环境中千万不要使用默认值 1。</li><li>确保 <code>replication.factor &gt; min.insync.replicas</code>，如果两者相等，那么只要有一个副本挂机，整个分区就无法正常工作了，我们不仅要改善消息的持久性，防止数据丢失，还要在不降低可用性的基础上完成，推荐设置成 <code>replication.factor = min.insync.replicas + 1</code>。</li></ul></li></ol><h2 id="重复消费问题"><a href="#重复消费问题" class="headerlink" title="重复消费问题"></a>重复消费问题</h2><p><strong>「消费重复的场景」</strong></p><p>在 <code>enable.auto.commit</code> 默认值 true 情况下，出现重复消费的场景有以下几种：</p><blockquote><p>❝<br>consumer 在消费过程中，应用进程被强制 kill 掉或发生异常退出。<br>❞</p></blockquote><p>例如在一次 poll 500 条消息后，消费到 200 条时，进程被强制 kill 消费到 offset 未提交，或出现异常退出导致消费到 offset 未提交。</p><p>下次重启时，依然会重新拉取 500 消息，造成之前消费到 200 条消息重复消费了两次。</p><p>解决方案：在发生异常时正确处理未提交的 offset</p><p><strong>「消费者消费时间过长」</strong></p><p><code>max.poll.interval.ms</code> 参数定义了两次 poll 的最大间隔，它的默认值是 5 分钟，表示你的 Consumer 程序如果在 5 分钟之内无法消费完 poll 方法返回的消息，那么 Consumer 会主动发起离开组的请求，Coordinator 也会开启新一轮 Rebalance。</p><p>举例：单次拉取 11 条消息，每条消息耗时 30s，11 条消息耗时 5 分钟 30 秒，由于 <code>max.poll.interval.ms</code>  默认值 5 分钟，所以消费者无法在 5 分钟内消费完，consumer 会离开组，导致 rebalance。</p><p>在消费完 11 条消息后，consumer 会重新连接 broker，再次 rebalance，因为上次消费的 offset 未提交，再次拉取的消息是之前消费过的消息，造成重复消费。</p><p><strong>「解决方案：」</strong></p><p>1、提高消费能力，提高单条消息的处理速度；根据实际场景可讲 <code>max.poll.interval.ms</code> 值设置大一点，避免不必要的 rebalance；可适当减小 <code>max.poll.records</code> 的值，默认值是 500，可根据实际消息速率适当调小。</p><p>2、生成消息时，可加入唯一标识符如消息 id，在消费端，保存最近的 1000 条消息 id 存入到 redis 或 mysql 中，消费的消息时通过前置去重。</p><h2 id="消息顺序问题"><a href="#消息顺序问题" class="headerlink" title="消息顺序问题"></a>消息顺序问题</h2><p>我们都知道 <code>kafka</code> 的 <code>topic</code> 是无序的，但是一个 <code>topic</code> 包含多个 <code>partition</code>，每个 <code>partition</code> 内部是有序的（分区内采用尾插法）</p><p><img src="/img/466a6f44f4b183a6bae184c90378b300_MD5.png" alt="466a6f44f4b183a6bae184c90378b300_MD5.png"></p><p><strong>「乱序场景 1」</strong></p><p>因为一个 topic 可以有多个 partition，kafka 只能保证 partition 内部有序</p><p><strong>「解决方案」</strong></p><p>1、可以设置 topic，有且只有一个 partition，<strong>不推荐，这样就违背了 Kafka 的设计初衷，即多分区，多副本的概念。</strong></p><p>2、<strong>（推荐）</strong> 根据业务需要，需要顺序的指定为同一个 partition，在 Broker 提交的时候，规定 topic，partition，key，data 四个参数统一。</p><p><strong>「乱序场景 2」</strong></p><p>对于同一业务进入了同一个消费者组之后，用了多线程来处理消息，会导致消息的乱序</p><p><strong>「解决方案」</strong></p><p>消费者内部根据线程数量创建等量的内存队列，对于需要顺序的一系列业务数据，根据 key 或者业务数据，放到同一个内存队列中，然后线程从对应的内存队列中取出并操作</p><p><img src="/img/bb94c4025a04733be2eb858d968eaffd_MD5.png" alt="bb94c4025a04733be2eb858d968eaffd_MD5.png"></p><p><strong>「通过设置相同 key 来保证消息有序性，会有一点缺陷：」</strong></p><p>例如消息发送设置了重试机制，并且异步发送，消息 A 和 B 设置相同的 key，业务上 A 先发，B 后发，由于网络或者其他原因 A 发送失败，B 发送成功；A 由于发送失败就会重试且重试成功，这时候消息顺序 B 在前 A 在后，与业务发送顺序不一致，如果需要解决这个问题，需要设置参数 <code>max.in.flight.requests.per.connection=1</code>，其含义是限制客户端在单个连接上能够发送的未响应请求的个数，设置此值是 1 表示 kafka broker 在响应请求之前 client 不能再向同一个 broker 发送请求，这个参数默认值是 5</p><h2 id="高性能原因"><a href="#高性能原因" class="headerlink" title="高性能原因"></a>高性能原因</h2><h3 id="「顺序读写」"><a href="#「顺序读写」" class="headerlink" title="「顺序读写」"></a>「顺序读写」</h3><p>kafka 的消息是不断追加到文件中的，这个特性使 <code>kafka</code> 可以充分利用磁盘的顺序读写性能</p><p>顺序读写不需要硬盘磁头的寻道时间，只需很少的扇区旋转时间，所以速度远快于随机读写</p><p>Kafka 可以配置异步刷盘，不开启同步刷盘，异步刷盘不需要等写入磁盘后返回消息投递的 ACK，所以它提高了消息发送的吞吐量，降低了请求的延时</p><h3 id="「零拷贝」"><a href="#「零拷贝」" class="headerlink" title="「零拷贝」"></a>「零拷贝」</h3><p>传统的 IO 流程，需要先把数据拷贝到内核缓冲区，再从内核缓冲拷贝到用户空间，应用程序处理完成以后，再拷贝回内核缓冲区</p><p>这个过程中发生了多次数据拷贝</p><p>为了减少不必要的拷贝，<code>Kafka</code> 依赖 Linux 内核提供的 <code>Sendfile</code> 系统调用</p><p>在 Sendfile 方法中，数据在内核缓冲区完成输入和输出，不需要拷贝到用户空间处理，这也就避免了重复的数据拷贝</p><p>在具体的操作中，Kafka 把所有的消息都存放在单独的文件里，在消息投递时直接通过 <code>Sendfile</code> 方法发送文件，减少了上下文切换，因此大大提高了性能</p><h3 id="「MMAP-技术」"><a href="#「MMAP-技术」" class="headerlink" title="「MMAP 技术」"></a>「MMAP 技术」</h3><p>除了 <code>Sendfile</code> 之外，还有一种零拷贝的实现技术，即 Memory Mapped Files</p><p>Kafka 使用 <code>Memory Mapped Files</code> 完成内存映射，<code>Memory Mapped Files</code> 对文件的操作不是 <code>write/read</code>，而是直接对内存地址的操作，如果是调用文件的 <code>read</code> 操作，则把数据先读取到内核空间中，然后再复制到用户空间，但 <code>MMAP</code> 可以将文件直接映射到用户态的内存空间，省去了用户空间到内核空间复制的开销</p><p>Producer 生产的数据持久化到 broker，采用 mmap 文件映射，实现顺序的快速写入</p><p>Customer 从 broker 读取数据，采用 sendfile，将磁盘文件读到 OS 内核缓冲区后，直接转到 socket buffer 进行网络发送。</p><h3 id="「批量发送读取」"><a href="#「批量发送读取」" class="headerlink" title="「批量发送读取」"></a>「批量发送读取」</h3><p>Kafka 的批量包括批量写入、批量发布等。它在消息投递时会将消息缓存起来，然后批量发送</p><p>同样，消费端在消费消息时，也不是一条一条处理的，而是批量进行拉取，提高了消息的处理速度</p><h3 id="「数据压缩」"><a href="#「数据压缩」" class="headerlink" title="「数据压缩」"></a><strong>「数据压缩」</strong></h3><p>Kafka 还支持对消息集合进行压缩，<code>Producer</code> 可以通过 <code>GZIP</code> 或 <code>Snappy</code> 格式对消息集合进行压缩</p><p>压缩的好处就是减少传输的数据量，减轻对网络传输的压力</p><p>Producer 压缩之后，在 <code>Consumer</code> 需进行解压，虽然增加了 CPU 的工作，但在对大数据处理上，瓶颈在网络上而不是 CPU，所以这个成本很值得</p><h3 id="「分区机制」"><a href="#「分区机制」" class="headerlink" title="「分区机制」"></a><strong>「分区机制」</strong></h3><p>kafka 中的 topic 中的内容可以被分为多 partition 存在，每个 partition 又分为多个段 segment，所以每次操作都是针对一小部分做操作，很轻便，并且增加 <code>并行操作</code> 的能力</p><h2 id="常见面试题"><a href="#常见面试题" class="headerlink" title="常见面试题"></a>常见面试题</h2><h3 id="「Kafka-是-Push-还是-Pull-模式？」"><a href="#「Kafka-是-Push-还是-Pull-模式？」" class="headerlink" title="「Kafka 是 Push 还是 Pull 模式？」"></a><strong>「Kafka 是 Push 还是 Pull 模式？」</strong></h3><p>Kafka 最初考虑的问题是，customer 应该从 brokes 拉取消息还是 brokers 将消息推送到 consumer。</p><p>在这方面，Kafka 遵循了一种大部分消息系统共同的传统的设计：producer 将消息推送到 broker，consumer 从 broker 拉取消息。</p><p>push 模式由 broker 决定消息推送的速率，对于不同消费速率的 consumer 就不太好处理了。</p><p>消息系统都致力于让 consumer 以最大的速率最快速的消费消息，push 模式下，当 broker 推送的速率远大于 consumer 消费的速率时，consumer 恐怕就要崩溃了。</p><blockquote><p>❝<br>Kafka 中的 Producer 和 Consumer 采用的是 Push-and-Pull 模式，即 Producer 向 Broker Push 消息，Consumer 从 Broker Pull 消息。<br>❞</p></blockquote><p>Pull 模式的一个好处是 consumer 可以自主决定是否批量的从 broker 拉取数据。</p><p>Pull 有个缺点是，如果 broker 没有可供消费的消息，将导致 consumer 不断在循环中轮询，直到新消息到达。</p><h3 id="「Kafka-如何保证高可用-」"><a href="#「Kafka-如何保证高可用-」" class="headerlink" title="「Kafka 如何保证高可用?」"></a><strong>「Kafka 如何保证高可用?」</strong></h3><p><a href="https://mp.weixin.qq.com/s?__biz=MzUyOTg1OTkyMA==&mid=2247484980&idx=1&sn=6e0c7112dd72d0edc284009e7503b2ac&scene=21#wechat_redirect">面试题：Kafka如何保证高可用？有图有真相</a></p><h3 id="「Kafk-的使用场景」"><a href="#「Kafk-的使用场景」" class="headerlink" title="「Kafk 的使用场景」"></a><strong>「Kafk 的使用场景」</strong></h3><p>业界 Kafka 实际应用场景</p><blockquote><p>❝<br>异步通信<br>❞</p></blockquote><p>消息中间件在异步通信中用的最多，很多业务流程中，如果所有步骤都同步进行可能会导致核心流程耗时非常长，更重要的是所有步骤都同步进行一旦非核心步骤失败会导致核心流程整体失败，因此在很多业务流程中 Kafka 就充当了异步通信角色。</p><blockquote><p>❝<br>日志同步<br>❞</p></blockquote><p>大规模分布式系统中的机器非常多而且分散在不同机房中，分布式系统带来的一个明显问题就是业务日志的查看、追踪和分析等行为变得十分困难，对于集群规模在百台以上的系统，查询线上日志很恐怖。</p><p>为了应对这种场景统一日志系统应运而生，日志数据都是海量数据，通常为了不给系统带来额外负担一般会采用异步上报，这里 Kafka 以其高吞吐量在日志处理中得到了很好的应用。</p><blockquote><p>❝<br>实时计算<br>❞</p></blockquote><p>随着据量的增加，离线的计算会越来越慢，难以满足用户在某些场景下的实时性要求，因此很多解决方案中引入了实时计算。</p><p>很多时候，即使是海量数据，我们也希望即时去查看一些数据指标，实时流计算应运而生。</p><p>实时流计算有两个特点，一个是实时，随时可以看数据；另一个是流。</p><h3 id="「Kafka-的多分区（Partition）以及多副本（Replica）机制有什么好处呢？」"><a href="#「Kafka-的多分区（Partition）以及多副本（Replica）机制有什么好处呢？」" class="headerlink" title="「Kafka 的多分区（Partition）以及多副本（Replica）机制有什么好处呢？」"></a><strong>「Kafka 的多分区（Partition）以及多副本（Replica）机制有什么好处呢？」</strong></h3><ol><li>Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力（负载均衡）。</li><li>Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力，不过也相应的增加了所需要的存储空间。</li></ol><p>参考资料：</p><blockquote><p><a href="https://javaguide.cn/high-performance/message-queue/kafka-questions-01.html">Kafka常见问题总结</a></p><p><a href="https://mp.weixin.qq.com/s/zfHoSsuSpXWOaxQrm7uvkA">Kafka核心知识总结！</a></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Kafka 服务器端的代码是由 Scala 代码编写，支持面向对象编程和函数式数据，编译过后也是普通的 .class 文件。其的作用：提供统一的、高吞吐量、低延迟的平台来处理实时数据&lt;/p&gt;
&lt;h2 id=&quot;一、基本概念&quot;&gt;&lt;a href=&quot;#一、基本概念&quot; class=&quot;</summary>
      
    
    
    
    <category term="后端" scheme="https://www.lazydaily.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="Kafka" scheme="https://www.lazydaily.cn/tags/Kafka/"/>
    
  </entry>
  
  <entry>
    <title> 详解：JavaScript 创建执行释放过程</title>
    <link href="https://www.lazydaily.cn/2024/01/23/3Ws58XR4oPsNAG2f/"/>
    <id>https://www.lazydaily.cn/2024/01/23/3Ws58XR4oPsNAG2f/</id>
    <published>2024-01-23T08:01:00.000Z</published>
    <updated>2025-11-24T00:43:42.346Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、对象创建过程"><a href="#一、对象创建过程" class="headerlink" title="一、对象创建过程"></a>一、对象创建过程</h2><h3 id="A-内存分配"><a href="#A-内存分配" class="headerlink" title="A. 内存分配"></a>A. 内存分配</h3><ul><li>当我们创建一个对象时（无论是通过构造函数还是字面量方式），JavaScript 引擎会在内存堆（Heap）中为这个对象分配空间。堆是一个用于存储复杂数据结构（如对象和数组）的区域。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 创建对象并分配内存</span><span class="token keyword">var</span> person <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>person<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'Alice'</span><span class="token punctuation">;</span>person<span class="token punctuation">.</span>age <span class="token operator">=</span> <span class="token number">30</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>或</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 字面量方式创建对象并分配内存</span><span class="token keyword">var</span> person <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Alice'</span><span class="token punctuation">,</span>  <span class="token literal-property property">age</span><span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="B-构造函数调用"><a href="#B-构造函数调用" class="headerlink" title="B. 构造函数调用"></a>B. 构造函数调用</h3><ul><li>如果使用 <code>new</code> 关键字调用构造函数来创建对象，引擎会先创建一个新的空对象，然后将该对象的原型指向构造函数的 <code>prototype</code> 属性，并将新对象作为上下文（<code>this</code>）执行构造函数内部的代码。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Person</span><span class="token punctuation">(</span><span class="token parameter">name<span class="token punctuation">,</span> age</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">var</span> alice <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token string">'Alice'</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="二、执行过程"><a href="#二、执行过程" class="headerlink" title="二、执行过程"></a>二、执行过程</h2><h3 id="A-属性访问与方法调用"><a href="#A-属性访问与方法调用" class="headerlink" title="A. 属性访问与方法调用"></a>A. 属性访问与方法调用</h3><ul><li>在对象创建后，可以通过 <code>.</code> 或 <code>[]</code> 操作符访问和修改其属性。</li><li>可以调用对象的方法进行相关操作。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">alice<span class="token punctuation">.</span><span class="token function-variable function">sayHello</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hello, my name is '</span> <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>alice<span class="token punctuation">.</span><span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 输出：Hello, my name is Alice</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="B-闭包与作用域链"><a href="#B-闭包与作用域链" class="headerlink" title="B. 闭包与作用域链"></a>B. 闭包与作用域链</h3><ul><li>函数内部可以访问外部作用域中的变量，这种特性形成了闭包。当函数被调用时，它会形成自己的执行上下文，其中包含了作用域链，作用域链用于在当前作用域以及所有父级作用域中查找变量。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">outerFunction</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> outerVar <span class="token operator">=</span> <span class="token string">'outer'</span><span class="token punctuation">;</span>  <span class="token keyword">function</span> <span class="token function">innerFunction</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>outerVar<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 能够访问到outerVar，这是因为闭包的作用</span>  <span class="token punctuation">&#125;</span>  <span class="token function">innerFunction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token function">outerFunction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="三、释放过程"><a href="#三、释放过程" class="headerlink" title="三、释放过程"></a>三、释放过程</h2><h3 id="A-垃圾回收机制"><a href="#A-垃圾回收机制" class="headerlink" title="A. 垃圾回收机制"></a>A. 垃圾回收机制</h3><ul><li>JavaScript 采用了自动垃圾回收机制，主要是基于可达性分析算法。简单来说，如果一个对象不再有任何引用指向它，那么这个对象就是不可达的，会被垃圾回收器视为垃圾并最终清理掉其所占用的内存资源。</li></ul><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">var</span> obj1 <span class="token operator">=</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token string">'some value'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">var</span> obj2 <span class="token operator">=</span> obj1<span class="token punctuation">;</span>obj1 <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// 现在只有obj2指向原对象</span><span class="token comment">// 后续如果obj2也被设置为null或者超出作用域，则原对象成为不可达，会被GC回收</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="B-循环引用问题"><a href="#B-循环引用问题" class="headerlink" title="B. 循环引用问题"></a>B. 循环引用问题</h3><ul><li>当两个对象互相引用但没有其他引用指向它们时，尽管它们是不可达的，但由于互相引用导致垃圾回收器无法识别。现代浏览器和 Node. js 环境下的 V8 引擎已经实现了循环引用检测功能，但在某些情况下仍需注意避免造成循环引用。</li></ul><p>总之，在 JavaScript 中，对象从创建到销毁的过程涉及内存管理、作用域规则以及垃圾回收策略等多个方面，理解这些概念对于编写高效且无内存泄漏的 JavaScript 代码至关重要。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、对象创建过程&quot;&gt;&lt;a href=&quot;#一、对象创建过程&quot; class=&quot;headerlink&quot; title=&quot;一、对象创建过程&quot;&gt;&lt;/a&gt;一、对象创建过程&lt;/h2&gt;&lt;h3 id=&quot;A-内存分配&quot;&gt;&lt;a href=&quot;#A-内存分配&quot; class=&quot;headerli</summary>
      
    
    
    
    <category term="前端" scheme="https://www.lazydaily.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
    <category term="JavaScript" scheme="https://www.lazydaily.cn/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title> 详解：Oracle分区原理及实战</title>
    <link href="https://www.lazydaily.cn/2024/01/03/l4oko412pvcTHuEi/"/>
    <id>https://www.lazydaily.cn/2024/01/03/l4oko412pvcTHuEi/</id>
    <published>2024-01-03T09:01:00.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<p>Oracle 数据库分区是一种物理数据组织方式，主要用于管理和优化大规模表。通过将大表分成更小、更易管理的部分（即分区），可以显著提高查询性能、简化管理任务并支持更高的可用性。</p><h2 id="一、Oracle-分区原理"><a href="#一、Oracle-分区原理" class="headerlink" title="一、Oracle 分区原理"></a>一、Oracle 分区原理</h2><ol><li><strong>分区类型</strong>：<ul><li>范围分区（Range Partitioning）：基于列值的范围进行分区，例如按日期或编号范围。</li><li>列表分区（List Partitioning）：基于列值的预定义列表进行分区。</li><li>哈希分区（Hash Partitioning）：基于哈希函数计算出的散列值进行分区。</li><li>复合分区（Composite Partitioning）：结合以上两种或多种类型的分区方法，如范围 - 列表复合分区。</li></ul></li><li><strong>子分区</strong>：<br>在每个分区内部还可以进一步划分成多个子分区，实现更细致的数据分割。</li><li><strong>全局索引与本地索引</strong>：<ul><li>全局索引对整个表的所有分区生效。</li><li>本地索引只针对单个分区有效，对于大型分区表，通常推荐使用本地索引以节省空间和提高查询效率。</li></ul></li><li><strong>分区维护</strong>：<br>可以单独对某个分区进行添加、删除、合并、拆分等操作，无需影响整个表的其他部分，从而提升维护效率和在线服务的连续性。</li></ol><h2 id="二、实践应用"><a href="#二、实践应用" class="headerlink" title="二、实践应用"></a>二、实践应用</h2><p>在 Oracle 数据库中进行表分区，可以显著提高大型表的查询性能、管理效率和可用性。以下是创建不同类型的分区表的基本步骤：</p><h3 id="1-范围分区（Range-Partitioning）"><a href="#1-范围分区（Range-Partitioning）" class="headerlink" title="1. 范围分区（Range Partitioning）"></a>1. 范围分区（Range Partitioning）</h3><p><strong>范围分区</strong>是基于列值的范围将数据分布到不同的分区中。例如，按时间字段划分每年的数据。</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> sales <span class="token punctuation">(</span>    sale_id NUMBER <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span>    sale_date <span class="token keyword">DATE</span><span class="token punctuation">,</span>    amount NUMBER<span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> RANGE <span class="token punctuation">(</span>sale_date<span class="token punctuation">)</span> <span class="token punctuation">(</span>    <span class="token keyword">PARTITION</span> sales_q1_2023 <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span>TO_DATE<span class="token punctuation">(</span><span class="token string">'01-APR-2023'</span><span class="token punctuation">,</span> <span class="token string">'DD-MON-YYYY'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token keyword">PARTITION</span> sales_q2_2023 <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span>TO_DATE<span class="token punctuation">(</span><span class="token string">'01-JUL-2023'</span><span class="token punctuation">,</span> <span class="token string">'DD-MON-YYYY'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment">-- 更多分区...</span>    <span class="token keyword">PARTITION</span> sales_future <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span>MAXVALUE<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="1-1-按天进行范围分区"><a href="#1-1-按天进行范围分区" class="headerlink" title="1.1 按天进行范围分区"></a>1.1 按天进行范围分区</h4><p>在 Oracle 数据库中，要实现每日分区（按天进行范围分区），可以采用间隔分区（Interval Partitioning）的方式。这样当插入新的数据时，系统会自动创建新分区来容纳那些落在预定义分区范围之外的数据。</p><p>以下是一个创建每日范围分区的示例：</p><pre class="line-numbers language-sql" data-language="sql"><div class="caption"><span>title:范围分区——按照天来进行分区管理.sql</span></div><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> daily_sales <span class="token punctuation">(</span>    sale_id NUMBER <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span>    sale_date <span class="token keyword">DATE</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    amount NUMBER<span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> RANGE <span class="token punctuation">(</span>sale_date<span class="token punctuation">)</span> <span class="token keyword">INTERVAL</span> <span class="token punctuation">(</span>NUMTODSINTERVAL<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'DAY'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">(</span>    <span class="token keyword">PARTITION</span> sales_20230101 <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span>TO_DATE<span class="token punctuation">(</span><span class="token string">'2023-01-02'</span><span class="token punctuation">,</span> <span class="token string">'YYYY-MM-DD'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">-- 或者使用系统提供的关键字 DEFAULT 字段简化初始分区定义</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> daily_sales <span class="token punctuation">(</span>    sale_id NUMBER <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span>    sale_date <span class="token keyword">DATE</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>    amount NUMBER<span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> RANGE <span class="token punctuation">(</span>sale_date<span class="token punctuation">)</span> <span class="token keyword">INTERVAL</span> <span class="token punctuation">(</span>NUMTODSINTERVAL<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'DAY'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">(</span>    <span class="token keyword">PARTITION</span> sales_first_day <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span><span class="token keyword">DATE</span> <span class="token string">'2023-01-01'</span><span class="token punctuation">)</span> <span class="token comment">-- 任意一个比实际数据早的日期作为起始点</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述 SQL 语句创建了一个名为 <code>daily_sales</code> 的表，并按照 <code>sale_date</code> 字段进行了范围分区，分区间隔设置为一天。第一个分区定义了小于 <code>2023-01-02</code> 的值，这意味着所有 2023 年 1 月 1 日及之前的数据将被放入这个分区。后续每天的数据将自动地在相应日期的基础上创建新的分区。</p><p>注意：对于区间分区，Oracle 从指定的第一个分区之后开始自动创建后续分区，直到达到最大分区数或时间边界为止。如果表中已有数据，则需确保初始分区包含这些数据的最早日期。同时，还需考虑表空间配置和分区管理策略，以确保自动分区的顺利进行。</p><h3 id="2-列表分区（List-Partitioning）"><a href="#2-列表分区（List-Partitioning）" class="headerlink" title="2. 列表分区（List Partitioning）"></a>2. 列表分区（List Partitioning）</h3><p><strong>列表分区</strong>基于列值匹配预定义的列表来分配数据。</p><pre class="line-numbers language-sql" data-language="sql"><div class="caption"><span>title:列表分区</span></div><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> employees <span class="token punctuation">(</span>    emp_id NUMBER <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span>    department VARCHAR2<span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    hire_date <span class="token keyword">DATE</span><span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> LIST <span class="token punctuation">(</span>department<span class="token punctuation">)</span> <span class="token punctuation">(</span>    <span class="token keyword">PARTITION</span> sales_dept <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'Sales'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token keyword">PARTITION</span> hr_dept <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'Human Resources'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token keyword">PARTITION</span> finance_dept <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'Finance'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token keyword">PARTITION</span> others <span class="token keyword">DEFAULT</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-哈希分区（Hash-Partitioning）"><a href="#3-哈希分区（Hash-Partitioning）" class="headerlink" title="3. 哈希分区（Hash Partitioning）"></a>3. 哈希分区（Hash Partitioning）</h3><p><strong>哈希分区</strong>使用散列函数对列值计算散列值，并根据散列结果均匀地分布在指定数量的分区中。</p><pre class="line-numbers language-sql" data-language="sql"><div class="caption"><span>title:哈希分区</span></div><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> customer <span class="token punctuation">(</span>    cust_id NUMBER <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span>    name VARCHAR2<span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    address VARCHAR2<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> <span class="token keyword">HASH</span> <span class="token punctuation">(</span>cust_id<span class="token punctuation">)</span>PARTITIONS <span class="token number">4</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-组合分区（Composite-Partitioning）"><a href="#4-组合分区（Composite-Partitioning）" class="headerlink" title="4. 组合分区（Composite Partitioning）"></a>4. 组合分区（Composite Partitioning）</h3><p>组合分区是上述分区类型的结合，如范围 - 列表分区。</p><pre class="line-numbers language-sql" data-language="sql"><div class="caption"><span>title:组合分区</span></div><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> order_details <span class="token punctuation">(</span>    order_id NUMBER<span class="token punctuation">,</span>    product_id NUMBER<span class="token punctuation">,</span>    delivery_year NUMBER<span class="token punctuation">,</span>    delivery_month VARCHAR2<span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> RANGE <span class="token punctuation">(</span>delivery_year<span class="token punctuation">)</span>SUBPARTITION <span class="token keyword">BY</span> LIST <span class="token punctuation">(</span>delivery_month<span class="token punctuation">)</span> <span class="token punctuation">(</span>    <span class="token keyword">PARTITION</span> orders_2023 <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span><span class="token number">2024</span><span class="token punctuation">)</span>    <span class="token punctuation">(</span>        SUBPARTITION january_2023 <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'JAN'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        SUBPARTITION february_2023 <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'FEB'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        <span class="token comment">-- 其他月份子分区...</span>        SUBPARTITION december_2023 <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'DEC'</span><span class="token punctuation">)</span>    <span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token comment">-- 其他年份分区...</span>    <span class="token keyword">PARTITION</span> orders_future <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span>MAXVALUE<span class="token punctuation">)</span>    <span class="token punctuation">(</span>        <span class="token comment">-- 对未来的月份设置相应的子分区</span>    <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>请注意，在实际应用中，您需要根据业务需求和数据分布特点来设计合适的分区策略。同时，创建或修改分区时可能需要考虑现有数据迁移、维护成本以及索引策略等因素。此外，Oracle 提供了诸如区间范围、引用分区等更多高级分区选项以适应复杂场景。</p><h2 id="三、分区数据查询"><a href="#三、分区数据查询" class="headerlink" title="三、分区数据查询"></a>三、分区数据查询</h2><p>查询 Oracle 中分区内数据的方式与查询非分区表的数据基本一致，不过可以根据需要指定查询的分区以提高查询效率。以下是一些示例：</p><ol><li><p><strong>查询整个分区表中的数据</strong>：</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> sales<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p><strong>根据分区键值查询特定分区的数据</strong>：<br>假设 <code>sales</code> 表是按照 <code>sale_date</code> 进行范围分区的，并且有一个分区名为 <code>sales_2023Q1</code> 包含 2023 年第一季度的数据。</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token comment">-- 查询2023年第一季度的所有销售记录</span><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> sales <span class="token keyword">PARTITION</span> <span class="token punctuation">(</span>sales_2023Q1<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p><strong>在 WHERE 子句中利用分区键进行分区剪枝（Partition Pruning）</strong>：<br>Oracle 会自动优化 SQL 查询，如果 WHERE 条件能明确指向某个分区，则只会扫描该分区的数据。</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token comment">-- 查询2023年3月的所有销售记录</span><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> sales <span class="token keyword">WHERE</span> sale_date <span class="token operator">BETWEEN</span> <span class="token keyword">DATE</span> <span class="token string">'2023-03-01'</span> <span class="token operator">AND</span> <span class="token keyword">DATE</span> <span class="token string">'2023-03-31'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>在这个例子中，Oracle 将能够识别出只用扫描包含 2023 年 3 月份数据的分区。</p></li><li><p><strong>查询子分区</strong>：<br>对于复合分区（如范围 - 列表分区），也可以按子分区进行查询。</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token comment">-- 假设order_details表按年范围分区，每月为子分区</span><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> order_details <span class="token keyword">PARTITION</span> <span class="token punctuation">(</span>orders_2023<span class="token punctuation">)</span> SUBPARTITION <span class="token punctuation">(</span>january_2023<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li></ol><p>通过合理使用分区查询，可以显著减少 I&#x2F;O 开销，提高查询性能。同时，在设计和编写查询时，应尽量确保查询条件能充分利用分区信息进行优化。</p><h2 id="四、索引创建"><a href="#四、索引创建" class="headerlink" title="四、索引创建"></a>四、索引创建</h2><p>在 Oracle 数据库中，为分区表创建索引的方式与非分区表基本相同，但还可以根据需要创建本地索引（Local Index）或全局索引（Global Index）。以下是创建这两种索引的示例：</p><h3 id="1-创建全局索引（Global-Index）"><a href="#1-创建全局索引（Global-Index）" class="headerlink" title="1. 创建全局索引（Global Index）"></a>1. 创建全局索引（Global Index）</h3><p>全局索引是对整个分区表的数据进行索引，并且索引本身也是一个单独的对象。适用于对全表范围内的查询有较高要求的情况。</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">INDEX</span> idx_global_sales <span class="token keyword">ON</span> sales <span class="token punctuation">(</span>product_id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">-- 假设sales是分区表，按product_id创建一个全局索引</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="2-创建本地索引（Local-Index）"><a href="#2-创建本地索引（Local-Index）" class="headerlink" title="2. 创建本地索引（Local Index）"></a>2. 创建本地索引（Local Index）</h3><p>本地索引是在每个分区内部独立创建的索引，每个分区都有自己的索引段。这可以减少索引占用的空间，并且在只查询单个分区时提高查询性能。</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token comment">-- 在分区表sales上创建基于sale_date字段的本地索引</span><span class="token keyword">CREATE</span> <span class="token keyword">INDEX</span> idx_local_sales <span class="token keyword">ON</span> sales <span class="token punctuation">(</span>sale_date<span class="token punctuation">)</span> <span class="token keyword">LOCAL</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>或者，在创建表的同时定义本地索引：</p><pre class="line-numbers language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> sales <span class="token punctuation">(</span>    sale_id NUMBER <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span>    sale_date <span class="token keyword">DATE</span><span class="token punctuation">,</span>    amount NUMBER<span class="token punctuation">)</span><span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> RANGE <span class="token punctuation">(</span>sale_date<span class="token punctuation">)</span> <span class="token punctuation">(</span>    <span class="token keyword">PARTITION</span> sales_q1_2023 <span class="token keyword">VALUES</span> LESS THAN <span class="token punctuation">(</span>TO_DATE<span class="token punctuation">(</span><span class="token string">'01-APR-2023'</span><span class="token punctuation">,</span> <span class="token string">'DD-MON-YYYY'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span><span class="token keyword">INDEX</span> idx_local_sales <span class="token punctuation">(</span>sale_date<span class="token punctuation">)</span> <span class="token keyword">LOCAL</span><span class="token punctuation">;</span> <span class="token comment">-- 在这里同时定义本地索引</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>选择使用全局索引还是本地索引取决于具体的业务需求和查询模式。一般来说，如果查询通常仅涉及单个或少数几个分区，则本地索引可能更有效；而如果查询经常跨越多个分区，或者需要对全表数据进行排序和聚合操作，则全局索引可能是更好的选择。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Oracle 数据库分区是一种物理数据组织方式，主要用于管理和优化大规模表。通过将大表分成更小、更易管理的部分（即分区），可以显著提高查询性能、简化管理任务并支持更高的可用性。&lt;/p&gt;
&lt;h2 id=&quot;一、Oracle-分区原理&quot;&gt;&lt;a href=&quot;#一、Oracle-分区</summary>
      
    
    
    
    <category term="后端" scheme="https://www.lazydaily.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="Oracle" scheme="https://www.lazydaily.cn/tags/Oracle/"/>
    
  </entry>
  
  <entry>
    <title> 详解：JavaScript中 const，var，let的区别</title>
    <link href="https://www.lazydaily.cn/2023/12/21/b9tXi3zkuFouTYzx/"/>
    <id>https://www.lazydaily.cn/2023/12/21/b9tXi3zkuFouTYzx/</id>
    <published>2023-12-21T17:12:00.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<p>var、const、let 同样都是声明变量的关键词。</p><h2 id="一、var-和-Let-区别"><a href="#一、var-和-Let-区别" class="headerlink" title="一、var 和 Let 区别"></a>一、var 和 Let 区别</h2><h3 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h3><p>var 的作用域只能是全局或者是整个函数块，而 let 的作用域既可以是全局变量或者是整个函数，还可以是 if, while, switch 限定的代码块。</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">function varTest() &#123;  var a &#x3D; 1;  &#123;    var a &#x3D; 2; &#x2F;&#x2F; 函数块中，同一个变量 console.log(a); &#x2F;&#x2F; 2  &#125;   console.log(a); &#x2F;&#x2F; 2&#125; function letTest() &#123;   let a &#x3D; 1;   &#123;     let a &#x3D; 2; &#x2F;&#x2F; 代码块中，新的变量     console.log(a); &#x2F;&#x2F; 2   &#125;   console.log(a); &#x2F;&#x2F; 1 &#125; varTest(); letTest();<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>let 声明的变量，可以比 var 声明的变量的作用有更小的限定范围，更加灵活。</p><h3 id="重复声明"><a href="#重复声明" class="headerlink" title="重复声明"></a>重复声明</h3><p>在同一个作用域中，var 允许重复声明，但是 let 不允许重复声明。</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">var a &#x3D; 1;var a &#x3D; 2;console.log(a) &#x2F;&#x2F; 2function test() &#123;  var a &#x3D; 3;   var a &#x3D; 4;   console.log(a) &#x2F;&#x2F; 4 &#125; test()if(false) &#123;  let a &#x3D; 1;  let a &#x3D; 2; &#x2F;&#x2F; SyntaxError: Identifier &#39;a&#39; has already been declared&#125;&#125;switch(index) &#123;  case 0:    let a &#x3D; 1;  break;  default:    let a &#x3D; 2; &#x2F;&#x2F; SyntaxError: Identifier &#39;a&#39; has already been declared    break;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="绑定全局变量"><a href="#绑定全局变量" class="headerlink" title="绑定全局变量"></a>绑定全局变量</h3><p>var 在全局环境声明变量，会在全局对象里新建一个属性，而 let 在全局环境声明变量，则不会在全局对象里新建一个属性。</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">var foo &#x3D; &#39;global&#39;let bar &#x3D; &#39;global&#39;console.log(this.foo) &#x2F;&#x2F; globalconsole.log(this.bar) &#x2F;&#x2F; undefined<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><img src="/img/d81d47f1a51cc2ecfe16e8966e7949b8_MD5.jpeg" alt="d81d47f1a51cc2ecfe16e8966e7949b8_MD5.jpeg"></p><p>由上图可知，let 在全局环境声明变量 bar 保存在[[Scopes]][0]: Script 这个变量对象的属性中，而 [[Scopes]][1]: Global 就是我们常说的全局对象。</p><h3 id="变量提升和暂存死区"><a href="#变量提升和暂存死区" class="headerlink" title="变量提升和暂存死区"></a>变量提升和暂存死区</h3><p>了解变量提升，就需要了解到上线文和变量对象。详见 <a href="%E8%AF%A6%E8%A7%A3%EF%BC%9AJavaScript%20%E5%88%9B%E5%BB%BA%E6%89%A7%E8%A1%8C%E9%87%8A%E6%94%BE%E8%BF%87%E7%A8%8B.md.md">详解：JavaScript 创建执行释放过程</a></p><h4 id="变量提升"><a href="#变量提升" class="headerlink" title="变量提升"></a>变量提升</h4><p>所有使用 var 声明的变量都会在执行上下文的创建阶段时作为变量对象的属性被创建并初始化，这样才能保证在执行阶段能通过标识符在变量对象里找到对应变量进行赋值操作等。即 var 在声明变量构建变量的时：<br>1. 由名称和 undefined（形参）组成一个变量对象的属性创建（创建并初始化）<br>2. 如果变量名称和之前的形参或者函数相同，则变量声明不会干扰已经存在的这类属性。</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">console.log(a) &#x2F;&#x2F; undefinedvar a &#x3D; 1;console.log(a) &#x2F;&#x2F; 1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>为什么 var 变量可以在声明之前使用，因为使用是在执行阶段，而在此之前的创建阶段就已经将声明的变量添加到了变量对象中，所以执行阶段通过标识符可以在变量对象中查找到，也就不会报错。</p><h4 id="暂存死区"><a href="#暂存死区" class="headerlink" title="暂存死区"></a>暂存死区</h4><p>其实 let 也存在与 var 类似的 “ 变量提升 “ 过程，但与 var 不同的是其在执行上下文的创建阶段，只会创建变量而不会被初始化（undefined），并且 ES6 规定了其初始化过程是在执行上下文的执行阶段（即直到它们的定义被执行时才初始化），使用未被初始化的变量将会报错。</p><blockquote><p>let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value <strong>undefined</strong> when the LexicalBinding is evaluated.</p></blockquote><p>在变量初始化前访问该变量会导致 ReferenceError，因此从进入作用域创建变量，到变量开始可被访问的一段时间（过程），就称为暂存死区 (Temporal Dead Zone)。</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">console.log(bar); &#x2F;&#x2F; undefinedconsole.log(foo); &#x2F;&#x2F; ReferenceError: foo is not definedvar bar &#x3D; 1;let foo &#x3D; 2;var foo &#x3D; 33;&#123;  let foo &#x3D; (foo + 55); &#x2F;&#x2F; ReferenceError: foo is not defined&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><ol><li>var 声明的变量在执行上下文创建阶段就会被「创建」和「初始化」，因此对于执行阶段来说，可以在声明之前使用。</li><li>let 声明的变量在执行上下文创建阶段只会被「创建」而不会被「初始化」，因此对于执行阶段来说，如果在其定义执行前使用，相当于使用了未被初始化的变量，会报错。</li></ol><h3 id="二、let-和-Const-区别"><a href="#二、let-和-Const-区别" class="headerlink" title="二、let 和 Const 区别"></a>二、let 和 Const 区别</h3><p>const 与 let 很类似，都具有上面提到的 let 的特性，唯一区别就在于 const 声明的是一个只读变量，声明之后不允许改变其值。因此，const 一旦声明必须初始化，否则会报错。</p><p>示例代码：</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">let a;const b &#x3D; &quot;constant&quot;a &#x3D; &quot;variable&quot;b &#x3D; &#39;change&#39; &#x2F;&#x2F; TypeError: Assignment to constant variable<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>如何理解声明之后不允许改变其值？</strong></p><p>其实 const 其实保证的不是变量的值不变，而是保证变量指向的内存地址所保存的数据不允许改动（即栈内存在的值和地址）。</p><p>JavaScript 的数据类型分为两类：原始值类型和对象（Object 类型）。</p><p>对于原始值类型（undefined、null、true&#x2F;false、number、string），值就保存在变量指向的那个内存地址（在栈中），因此 const 声明的原始值类型变量等同于常量。</p><p>对于对象类型（object，array，function 等），变量指向的内存地址其实是保存了一个指向实际数据的指针，所以 const 只能保证指针是不可修改的，至于指针指向的数据结构是无法保证其不能被修改的（在堆中）。</p><p>示例代码：</p><pre class="line-numbers language-javaScript" data-language="javaScript"><code class="language-javaScript">const obj &#x3D; &#123;  value: 1&#125;obj.value &#x3D; 2;console.log(obj) &#x2F;&#x2F; &#123; value: 2 &#125;obj &#x3D; &#123;&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>参考资料：</p><blockquote><p><a href="https://zhuanlan.zhihu.com/p/556482226?utm_id=0">深入理解 JS：var、let、const 的异同</a></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;var、const、let 同样都是声明变量的关键词。&lt;/p&gt;
&lt;h2 id=&quot;一、var-和-Let-区别&quot;&gt;&lt;a href=&quot;#一、var-和-Let-区别&quot; class=&quot;headerlink&quot; title=&quot;一、var 和 Let 区别&quot;&gt;&lt;/a&gt;一、var 和 L</summary>
      
    
    
    
    <category term="前端" scheme="https://www.lazydaily.cn/categories/%E5%89%8D%E7%AB%AF/"/>
    
    
    <category term="JavaScript" scheme="https://www.lazydaily.cn/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title> 详解：各类的编码格式</title>
    <link href="https://www.lazydaily.cn/2023/12/20/krbEf5cUKeuoajPd/"/>
    <id>https://www.lazydaily.cn/2023/12/20/krbEf5cUKeuoajPd/</id>
    <published>2023-12-20T09:12:00.000Z</published>
    <updated>2026-03-16T02:10:37.035Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、ASCII-码"><a href="#一、ASCII-码" class="headerlink" title="一、ASCII 码"></a>一、ASCII 码</h2><p>计算机内所有的信息都是二进制位。一个字节包含 8 个二进制位，可以表示 256 个状态，每个状态表示一个符号。</p><p>ASCII 码一共规定了 128 个字符的编码，比如空格 SPACE 是 32（二进制 00100000），大写的字母 A 是 65（二进制 01000001）。这 128 个符号（包括 32 个不能打印出来的控制符号）。ASCII 码只占用了一个字节的后面 7 位，最前面的一位统一规定为 0。</p><p><img src="/img/546b99e807733344235b0b668c3c9e7e_MD5.jpeg" alt="546b99e807733344235b0b668c3c9e7e_MD5.jpeg"></p><h2 id="二、非-ASCII-编码"><a href="#二、非-ASCII-编码" class="headerlink" title="二、非 ASCII 编码"></a>二、非 ASCII 编码</h2><p>英语用 128 个符号编码就够了，但是用来表示其他语言，128 个符号是不够的。比如，在法语中，字母上方有注音符号，它就无法用 ASCII 码表示。于是，一些欧洲国家就决定，利用字节中闲置的最高位编入新的符号。比如，法语中的é的编码为 130（二进制 10000010）。这样一来，这些欧洲国家使用的编码体系，可以表示最多 256 个符号。</p><p>所有这些编码方式中，0-127 表示的符号是一样的，不一样的只是 128-255 的这一段，不同的国家相同的 ASCII 码表示的可能是不同的符号。</p><p>至于亚洲国家的文字，使用的符号就更多了，汉字就多达 10 万左右。一个字节只能表示 256 种符号，肯定是不够的，就必须使用多个字节表达一个符号。比如，简体中文常见的编码方式是 GB2312，使用两个字节表示一个汉字，所以理论上最多可以表示 256 x 256 &#x3D; 65536 个符号。</p><h2 id="三、Unicode-字符集"><a href="#三、Unicode-字符集" class="headerlink" title="三、Unicode 字符集"></a>三、Unicode 字符集</h2><p>将世界上多有文字都进行编码，就形成了 Unicode。Unicode 的规模可以容纳 100 多万个符号，每个符号的编码都不一样。比如，U+0639 表示阿拉伯字母 Ain，U+0041 表示英语的大写字母 A，U+4E25 表示汉字严。</p><p>但是，Unicode 只是字符集，它之规定了符号的二进制代码，却不规定编码方式。比如：汉字严的 Unicode 是十六进制数 4E25，转换成二进制数足足有 15 位（100111000100101），也就是说，这个符号的表示至少需要 2 个字节（16 位二进制数）。如果将 0 补全之后存储，计算机读取的时候，他不清楚到底是（01001110）+（00100101）两个 ASCII 码，还是（100111000100101）单个 Unicode 值。那么就出现了其他的编码方式。</p><h2 id="四、UTF-8-编码方式"><a href="#四、UTF-8-编码方式" class="headerlink" title="四、UTF -8 编码方式"></a>四、UTF -8 编码方式</h2><p>UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16（字符用两个字节或四个字节表示）和 UTF-32（字符用四个字节表示），不过在互联网上基本不用。</p><p>UTF-8 最大的一个特点，就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号，根据不同的符号而变化字节长度。</p><p><strong>UTF-8 的编码规则很简单，只有二条：</strong></p><ul><li>1）对于单字节的符号：字节的第一位设为 0，后面 7 位为这个符号的 Unicode 码。因此对于英语字母，UTF-8 编码和 ASCII 码是相同的；</li><li>2）对于 n 字节的符号（n &gt; 1）：第一个字节的前 n 位都设为 1，第 n + 1 位设为 0，后面字节的前两位一律设为 10。剩下的没有提及的二进制位，全部为这个符号的 Unicode 码。<br><img src="/img/c17560b740940d095c678b2769092f11_MD5.jpeg" alt="c17560b740940d095c678b2769092f11_MD5.jpeg"></li></ul><p><strong>如果一个字节的第一位是 0，则这个字节单独就是一个字符；如果第一位是 1，则连续有多少个 1，就表示当前字符占用多少个字节。</strong></p><p>下面，还是以汉字严为例，演示如何实现 UTF-8 编码。</p><p>严的 Unicode 是 4E25（100111000100101），根据上表，可以发现 4E25 处在第三行的范围内（0000 0800 - 0000 FFFF），因此严的 UTF-8 编码需要三个字节，即格式是 1110xxxx 10xxxxxx 10xxxxxx。然后，从严的最后一个二进制位开始，依次从后向前填入格式中的 x，多出的位补 0。这样就得到了，严的 UTF-8 编码是 11100100 10111000 10100101，转换成十六进制就是 E4B8A5。</p><p>如果保存的编码模式不同，” 严 “ 对应的值不同：</p><ul><li>1）ANSII：文件的编码就是两个字节 D1 CF，这正是严的 GB2312 编码，这也暗示 GB2312 是采用大头方式存储的。</li><li>2）Unicode：编码是四个字节 FF FE 25 4E，其中 FF FE 表明是小头方式存储，真正的编码是 4E25。</li><li>3）Unicode big endian：编码是四个字节 FE FF 4E 25，其中 FE FF 表明是 大头方式 存储。</li><li>4）UTF-8：编码是六个字节 EF BB BF E4 B8 A5，前三个字节 EF BB BF 表示这是 UTF-8 编码，后三个 E4B8A5 就是严的具体编码，它的存储顺序与编码顺序是一致的。</li></ul><h2 id="五、GB-系列"><a href="#五、GB-系列" class="headerlink" title="五、GB 系列"></a>五、GB 系列</h2><h3 id="GB2312-字符集"><a href="#GB2312-字符集" class="headerlink" title="GB2312 字符集"></a>GB2312 字符集</h3><p>天朝专家把那些 127 号之后的奇异符号们（即 EASCII）取消掉，规定：一个小于 127 的字符的意义与原来相同，但两个大于 127 的字符连在一起时，就表示一个汉字，前面的一个字节（他称之为高字节）从 0xA1 用到 0xF7，后面一个字节（低字节）从 0xA1 到 0xFE，这样我们就可以组合出大约 7000 多个简体汉字了。在这些编码里，还把数学符号、罗马希腊的字母、日文的假名们都编进去了，连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码，这就是常说的 “ 全角 “ 字符，而原来在 127 号以下的那些就叫 “ 半角 “ 字符了。这就是 GB2312，#GBK（即 CP936 字符集）和 GB18030 是对 GB2312 的拓展。</p><h3 id="GBK-编码方式"><a href="#GBK-编码方式" class="headerlink" title="GBK 编码方式"></a>GBK 编码方式</h3><p>由于 GB 2312-80 只收录 6763 个汉字，有不少汉字，如部分在 GB 2312-80 推出以后才简化的汉字（如 “ 啰 “），部分人名用字（如中国前总理*** 的 “*“ 字），台湾及香港使用的繁体字，日语及朝鲜语汉字等，并未有收录在内。于是厂商微软利用 GB 2312-80 未使用的编码空间，收录 GB 13000.1-93 全部字符制定了 GBK 编码。根据微软资料，GBK 是对 GB2312-80 的扩展，也就是 CP936 字符集 (Code Page 936) 的扩展（之前 CP936 和 GB 2312-80 一模一样），最早实现于 Windows 95 简体中文版。虽然 GBK 收录 GB 13000.1-93 的全部字符，但编码方式并不相同。GBK 自身并非国家标准，只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为 “ 技术规范指导性文件 “。原始 GB13000 一直未被业界采用，后续国家标准 GB18030 技术上兼容 GBK 而非 GB13000。</p><h3 id="GB18030-字符集"><a href="#GB18030-字符集" class="headerlink" title="GB18030 字符集"></a>GB18030 字符集</h3><p>全称：国家标准 GB 18030-2005《信息技术中文编码字符集》，是中华人民共和国现时最新的内码字集，是 GB 18030-2000《信息技术信息交换用汉字编码字符集的扩充》的修订版。与 GB 2312-1980 完全兼容，与 GBK 本兼容，支持 GB 13000 及 Unicode 的全部统一汉字，共收录汉字 70244 个。</p><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><h3 id="字符集"><a href="#字符集" class="headerlink" title="字符集"></a>字符集</h3><p>ASCII 字符集：常规的字符集，表示英文以及部分符号。</p><p>Unicode 字符集：表示世界上所有字符，统一的字符集。</p><p>GB2312 字符集：适配汉字，ASCII 字符集的中文拓展字符集。</p><p>CP936 字符集：GBK 编码方式使用的字符集，GB2312 的拓展表。</p><p>GB18030 字符集：GB2312 的拓展表，包含全部汉字。</p><p>编码方式：</p><p>UTF-8 编码方式：是一种对 Unicode 字符集的编码方式，字符由不定字节长度表示。</p><p>UTF-16 编码方式：是一种对 Unicode 字符集的编码方式，字符用两个字节或四个字节表示。</p><p>UTF-32 编码方式：是一种对 Unicode 字符集的编码方式，字符用四个字节表示。</p><p>GBK 编码方式：使用 CP936 编码表，是对 ASCII 表的中文适配。</p><p><code>简单来说：Unicode、GBK 和 Big5码等就是编码的值（也就是术语“字符集”），而 UTF-8、UTF-16、UTF32之类就是这个值的表现形式（即术语“编码格式”）。</code></p><p>另外：</p><p><code>Unicode、GBK和Big5码等字符集是不兼容的，同一个汉字在这三个字符集里的码值是完全不一样的。如＂汉＂的Unicode值与gbk就是不一样的，假设Unicode为a040，GBK为b030。以UTF-8为例，UTF-8码完全只针对Unicode来组织的，如果GBK要转UTF-8必须先转Unicode码，再转UTF-8就OK了。</code></p><p>即 GBK、GB2312 等与 UTF8 之间都必须通过 Unicode 编码才能相互转换：</p><blockquote><p>1）GBK、GB2312 – 先转 –&gt; Unicode – 再转 –&gt; UTF8<br>2）UTF8 – 先转 –&gt; Unicode – 再转 –&gt; GBK、GB2312</p></blockquote><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><h3 id="BIG5-字符集-编码"><a href="#BIG5-字符集-编码" class="headerlink" title="BIG5 字符集&amp;编码"></a>BIG5 字符集&amp;编码</h3><p>Big5，又称为大五码或五大码，是使用繁体中文（正体中文）社区中最常用的电脑汉字字符集标准，共收录 13,060 个汉字。中文码分为内码及交换码两类，Big5 属中文内码，知名的中文交换码有 CCCII、CNS11643。Big5 虽普及于台湾、香港与澳门等繁体中文通行区，但长期以来并非当地的国家标准，而只是业界标准。倚天中文系统、Windows 等主要系统的字符集都是以 Big5 准，但厂商又各自增加不同的造字与造字区，派生成多种不同版本。2003 年，Big5 被收录到 CNS11643 中文标准交换码的附录当中，取得了较正式的地位。这个最新版本被称为 Big5-2003。</p><p>Big5 码是一套双字节字符集，使用了双八码存储方法，以两个字节来安放一个字。第一个字节称为 “ 高位字节 “，第二个字节称为 “ 低位字节 “。” 高位字节 “ 使用了 0x81-0xFE，” 低位字节 “ 使用了 0x40-0x7E，及 0xA1-0xFE。</p><h3 id="大头方式、小头方式"><a href="#大头方式、小头方式" class="headerlink" title="大头方式、小头方式"></a>大头方式、小头方式</h3><p>上一节已经提到，UCS-2 格式可以存储 Unicode 码（码点不超过 0xFFFF）。以汉字严为例，Unicode 码是 4E25，需要用两个字节存储，一个字节是 4E，另一个字节是 25。存储的时候，4E 在前，25 在后，这就是 Big endian 方式；25 在前，4E 在后，这是 Little endian 方式。</p><p>第一个字节在前，就是 “ 大头方式 “（Big endian），第二个字节在前就是 “ 小头方式 “（Little endian）。</p><p>那么很自然的，就会出现一个问题：计算机怎么知道某一个文件到底采用哪一种方式编码？</p><p>Unicode 规范定义，每一个文件的最前面分别加入一个表示编码顺序的字符，这个字符的名字叫做 “ 零宽度非换行空格 “（zero width no-break space），用 FEFF 表示。这正好是两个字节，而且 FF 比 FE 大 1。</p><p>如果一个文本文件的头两个字节是 FE FF，就表示该文件采用大头方式；如果头两个字节是 FF FE，就表示该文件采用小头方式。</p><p>参考资料：</p><blockquote><p><a href="https://zhuanlan.zhihu.com/p/658651404">字符编码技术专题(一)：快速理解ASCII、Unicode、GBK和UTF-8</a></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、ASCII-码&quot;&gt;&lt;a href=&quot;#一、ASCII-码&quot; class=&quot;headerlink&quot; title=&quot;一、ASCII 码&quot;&gt;&lt;/a&gt;一、ASCII 码&lt;/h2&gt;&lt;p&gt;计算机内所有的信息都是二进制位。一个字节包含 8 个二进制位，可以表示 256 个状</summary>
      
    
    
    
    <category term="后端" scheme="https://www.lazydaily.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="编码" scheme="https://www.lazydaily.cn/tags/%E7%BC%96%E7%A0%81/"/>
    
  </entry>
  
  <entry>
    <title> 详解：Java IO进阶 - NIO、BIO、AIO</title>
    <link href="https://www.lazydaily.cn/2023/12/08/3nsUMkpqXdg36rir/"/>
    <id>https://www.lazydaily.cn/2023/12/08/3nsUMkpqXdg36rir/</id>
    <published>2023-12-08T02:12:00.000Z</published>
    <updated>2025-11-24T00:43:42.346Z</updated>
    
    <content type="html"><![CDATA[<table><thead><tr><th align="center"></th><th align="center">BIO</th><th align="center">NIO</th><th align="center">AIO</th></tr></thead><tbody><tr><td align="center">IO 模型</td><td align="center">同步阻塞</td><td align="center">同步非阻塞（多路复用）</td><td align="center">异步非阻塞</td></tr><tr><td align="center">编程难度</td><td align="center">简单</td><td align="center">复杂</td><td align="center">复杂</td></tr><tr><td align="center">可靠性</td><td align="center">差</td><td align="center">好</td><td align="center">好</td></tr><tr><td align="center">吞吐量</td><td align="center">低</td><td align="center">高</td><td align="center">高</td></tr></tbody></table><h2 id="阅前须知"><a href="#阅前须知" class="headerlink" title="阅前须知"></a>阅前须知</h2><p><code>阻塞 IO</code> 和 <code>非阻塞 IO</code></p><p>这两个概念是 <code>程序级别</code> 的。主要描述是程序请求操作系统 IO 操作之后，如果 IO 资源没有准备好，那么程序如何处理问题：前者等待，后者继续执行（并且使用线程一直轮询，直到有 IO 资源准备好）</p><p><code>同步 IO </code> 和 <code>非同步IO</code></p><p>这两个概念是 <code>操作系统级别</code> 的。主要描述的是操作系统在收到程序请求 IO 操作后，如果 IO 资源没有准备好，该如何相应程序的问题：前者不响应，后者返回一个标记，当 IO 资源准备好之后，在用事件机制返回给程序。</p><h2 id="一、BIO（Blocking-I-O）"><a href="#一、BIO（Blocking-I-O）" class="headerlink" title="一、BIO（Blocking I&#x2F;O）"></a>一、BIO（Blocking I&#x2F;O）</h2><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p>Java BIO：同步并阻塞（传统阻塞性），应用程序中进程在发起 IO 调用后至内核执行 IO 操作返回结果之前，若发起系统调用的线程一直处于等待状态，则此次 IO 操作为阻塞 IO。阻塞 IO 简称 BIO，Blocking IO。</p><p>以前大多数网络通信方式都是阻塞模式，即：</p><ul><li>客户端向服务器端发送请求后，客户端会一直等待（不会再做其他事情），直到服务器端返回结果或者网络出现问题。</li><li>服务端同样的，当在处理某个客户端 A 发来的请求是，另一个客户端 B 发来的请求会等待，直到服务器端的这个处理线程完成上个处理。</li></ul><p><img src="/img/acea8af4268c8d552741ccebcb2d34ec_MD5.png" alt="acea8af4268c8d552741ccebcb2d34ec_MD5.png"></p><h3 id="使用实例"><a href="#使用实例" class="headerlink" title="使用实例"></a>使用实例</h3><ol><li>服务器启动一个 ServerSocket。</li><li>客户端启动 Socket 对服务器进行通信，默认情况下服务器端需要对每个客户建立一个线程与之通讯。</li><li>客户端发出请求后，先咨询服务器是否有线程响应，如果没有则会等待，或者被拒绝。</li><li>如果有响应，客户端线程会等待请求结束后，再继续执行。</li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">package</span> <span class="token namespace">com<span class="token punctuation">.</span>atguigu<span class="token punctuation">.</span>bio</span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">InputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>net<span class="token punctuation">.</span></span><span class="token class-name">ServerSocket</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>net<span class="token punctuation">.</span></span><span class="token class-name">Socket</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>concurrent<span class="token punctuation">.</span></span><span class="token class-name">ExecutorService</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>concurrent<span class="token punctuation">.</span></span><span class="token class-name">Executors</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BIOServer</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>        <span class="token comment">//线程池机制</span>        <span class="token comment">//思路</span>        <span class="token comment">//1. 创建一个线程池</span>        <span class="token comment">//2. 如果有客户端连接，就创建一个线程，与之通讯(单独写一个方法)</span>        <span class="token class-name">ExecutorService</span> newCachedThreadPool <span class="token operator">=</span> <span class="token class-name">Executors</span><span class="token punctuation">.</span><span class="token function">newCachedThreadPool</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//创建ServerSocket</span>        <span class="token class-name">ServerSocket</span> serverSocket <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ServerSocket</span><span class="token punctuation">(</span><span class="token number">6666</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"服务器启动了"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程信息id = "</span> <span class="token operator">+</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"名字 = "</span> <span class="token operator">+</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment">//监听，等待客户端连接</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"等待连接...."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment">//会阻塞在accept()</span>            <span class="token keyword">final</span> <span class="token class-name">Socket</span> socket <span class="token operator">=</span> serverSocket<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"连接到一个客户端"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment">//就创建一个线程，与之通讯(单独写一个方法)</span>            newCachedThreadPool<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">//我们重写</span>                    <span class="token comment">//可以和客户端通讯</span>                    <span class="token function">handler</span><span class="token punctuation">(</span>socket<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>     <span class="token punctuation">&#125;</span>        <span class="token comment">//编写一个handler方法，和客户端通讯</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">handler</span><span class="token punctuation">(</span><span class="token class-name">Socket</span> socket<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程信息id = "</span> <span class="token operator">+</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"名字 = "</span> <span class="token operator">+</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> bytes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token comment">//通过socket获取输入流</span>            <span class="token class-name">InputStream</span> inputStream <span class="token operator">=</span> socket<span class="token punctuation">.</span><span class="token function">getInputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment">//循环的读取客户端发送的数据</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程信息id = "</span> <span class="token operator">+</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"名字 = "</span> <span class="token operator">+</span> <span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"read...."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">int</span> read <span class="token operator">=</span> inputStream<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>read <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                    <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>bytes<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> read<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//输出客户端发送的数据</span>                <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>                    <span class="token keyword">break</span><span class="token punctuation">;</span>                <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"关闭和client的连接"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>                socket<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h3><p>传统的 IO 模型，其主要是一个 Server 对接 N 个客户端，在客户端连接之后，为每个客户端分配一个子线程。如图所示：</p><p><img src="/img/0fb77c535fcbf8f4264b6eff292fd210_MD5.png" alt="0fb77c535fcbf8f4264b6eff292fd210_MD5.png"></p><p>从图中可以看出，传统 IO 的特点在于：</p><ul><li>每个客户端连接到达时，服务端会分配一个线程给该客户端，该线程处理包括读取数据，解码，业务计算，编码，以及发送数据整个过程</li><li>同一时刻，服务端的吞吐量与服务器所提供的线程数量呈线性关系的。</li></ul><p>如果并发量不大，运行没有问题，但是如果海量并发时候，就会出现问题：</p><ol><li>每次请求都要创建独立的线程，与对应的客户端进行数据的 Read，业务处理，数据 Write、</li><li>当并发数较大时，需要创建大量线程处理连接，资源占用较大。</li><li>连接建立后，如果当前线程展示没有数据可读，则线程就阻塞在 Read 操作上，造成线程资源浪费。</li></ol><h3 id="改进：多线程方式-伪异步方式"><a href="#改进：多线程方式-伪异步方式" class="headerlink" title="改进：多线程方式 - 伪异步方式"></a>改进：多线程方式 - 伪异步方式</h3><p>上述说的情况只是服务器只有一个线程的情况，那么如果引入多线程是不是可以解决这个问题：</p><ul><li>当服务器收到客户端 X 的请求后，（读取到所有的请求数据后）将这个请求送入到一个独立线程进行处理，然后主线程继续接收客户端 Y 的请求。</li><li>客户端侧，也可以用一个子线程和服务器端进行通信。这样客户端主线程的其他工作不受影响，当服务器有响应信息时候再有这个子线程通过 <code>监听模式/观察模式</code>（等其他设计模式）通知主线程。</li></ul><p><img src="/img/ed84ace61748c9dbdaf3f9718f21ff21_MD5.png" alt="ed84ace61748c9dbdaf3f9718f21ff21_MD5.png"></p><p>但是多线程解决这个问题有局限性：</p><ul><li>操作系统通知 accept() 的方式还是单个，即：服务器收到数据报文之后的 “ 业务处理过程 “ 可以多线程，但是报文的接收还是需要一个个来</li><li>在操作系统中，线程是有限的。线程越多，CPU 切换所需时间也越长，用来处理真正业务的需求也就越少。</li><li>创建线程需要较大的资源消耗。JVM 创建一个线程，即使不进行任何工作，也需要分配一个堆栈空间（128k）。</li><li>如果程序中使用了大量的长连接，线程是不会关闭的，资源消耗更容易失控。</li></ul><blockquote><p>为啥 <code>serverSocket. accept()</code> 会出现阻塞？</p></blockquote><p>是因为 Java 通过 JNI 调用的系统层面的 <code>accept0()</code> 方法，<code>accept0()</code> 规定如果发现套间字从指定的端口来，就会等待。其实就是内部实现是操作系统级别的同步 IO。</p><h2 id="二、NIO（non-Blocking-I-O）"><a href="#二、NIO（non-Blocking-I-O）" class="headerlink" title="二、NIO（non-Blocking I&#x2F;O）"></a>二、NIO（non-Blocking I&#x2F;O）</h2><p>了解 NIO 之前我们先来看看标准 I&#x2F;O（Standard I&#x2F;O）。</p><p>Standard I&#x2F;O 是对字节的读写，在进行 I&#x2F;O 之前，首先创建一个流对象，流对象的读写操作都是按字节，一个字节一个字节的读或者写。而 NIO 把 I&#x2F;O 抽象成块，类似磁盘的读写，每次 I&#x2F;O 操作的单位都是一个块，块被读入内存之后就是一个 <code> byte[]</code>，NIO 一次可以读或者写多个字节。</p><h3 id="流和块"><a href="#流和块" class="headerlink" title="流和块"></a>流和块</h3><p>IO 和 NIO 最重要的区别就是对数据的打包和传输的方式，IO 是以流的方式处理数据，而 NIO 以块的方式处理数据。</p><p>面向流的 IO 一次性处理一个字节数据：一个输入流产生一个字节数据，一个输出流消费一个字节数据。为流式数据创建过滤器非常容易，链接几个过滤器，以便每个过滤器只负责复杂处理机制的一部分。不利的一面是，面向流的 IO 通常处理非常慢。</p><p>面向块的 I&#x2F;O 一次性处理一个数据块：按块处理数据比按流处理数据要快的多，但是面向块的 I&#x2F;O 确实一些面向流的 I&#x2F;O 所具有的优雅和简单。</p><p>I&#x2F;O 包和 NIO 已经很好的集成了，<code>java.io.*</code> 中已经以 NIO 重新实现了，可以利用一些 NIO 的特性。例如：在 <code>java.io.*</code> 中某些类包含以块的形式读写数据的操作，这使得及时在面向流的系统中，处理数据也会更快。</p><h3 id="基本概念-1"><a href="#基本概念-1" class="headerlink" title="基本概念"></a>基本概念</h3><p>Java NIO：同步非阻塞，服务器实现模式为一个线程处理多个请求（连接），即客户端发送的连接请求都会注册到多路复用器上，多路复用器轮训到连接有 I&#x2F;O 请求就进行处理。</p><p><strong>核心概念：</strong></p><ol><li><strong>三大核心：</strong> Channel（通道）、Buffer（缓冲区）、Selector（选择器）。</li><li><strong>面向缓冲区，或者是面向块编程。</strong> 数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动，这就增加了处理过程中的灵活性，使用它可以提供非阻塞式的高伸缩弹性网络。</li><li><strong>非阻塞模式</strong>，使一个线程从某个通道发送请求或者读取数据，但是他仅能得到目前可用的数据，如果目前没有数据可用，就什么都不会获取，而不是保持线程阻塞。所以知道数据变得可读取之前，该线程还可以去做其他实行。</li><li><strong>Channel 和 Buffer 一一对应。</strong></li><li><strong>一个线程只有一个 Selector，一个线程对应对个 Channel（连接）</strong>。</li><li>程序切换到哪个 Channel 是由事件决定，Event 就是个重要概念。</li><li>Selector 会根据不同的事件，在各个通道上切换。</li><li><strong>Buffer 是一个内存块，底层就是一个数组</strong>。</li><li>数据读写都是通过 Buffer，区别于 BIO 的输入输出流，且<strong>双向</strong>，需要 <code>flip</code> 方法切换 <code>Channel</code> 是双向的。</li></ol><h3 id="编程原理"><a href="#编程原理" class="headerlink" title="编程原理"></a>编程原理</h3><ol><li>当客户端连接时，会通过 ServerSocketChannel 得到 SocketChannel。</li><li>Selector 进行监听 select 方法，返回有事件发生的通道个数。</li><li>将 SocketChannel 注册到 Selector 上（<code>register(Selector selector, int ops)</code>），一个 Selector 可以注册多个 SocketChannel。</li><li>注册后返回 SelectionKey，会和该 Selector 关联（集合）。</li><li>当有事件发生时，进一步得到各个 SelectionKey。</li><li>通过 channel () 方法，用 SelectionKey 反向获取 SocketChannel。</li><li>可以通过得到的 channel，完成业务处理。</li></ol><h3 id="1-缓冲区（Buffer）"><a href="#1-缓冲区（Buffer）" class="headerlink" title="1. 缓冲区（Buffer）"></a>1. 缓冲区（Buffer）</h3><h4 id="Buffer-类及其子类"><a href="#Buffer-类及其子类" class="headerlink" title="Buffer 类及其子类"></a>Buffer 类及其子类</h4><p><code>ByteBuffer</code> 字节数据；<code>ShortBuffer</code> 字符串数据；<code>CharBuffer</code> 字符数据；<code>IntBuffer</code> 整数；<code>LongBuffer</code> 长整数；<code>DoubleBuffer</code> 小数；<code>FloatBuffer</code> 小数</p><h4 id="Buffer-属性和方法"><a href="#Buffer-属性和方法" class="headerlink" title="Buffer 属性和方法"></a>Buffer 属性和方法</h4><p>Buffer 类提供了 4 个属性来提供数据元素信息：<code>capacity（容量）</code>：缓存区的最大容量，<code>Limit（终点）</code>：缓存区最大可操作位置，<code>Position（位置）</code>：缓存区当前在操作的位置，<code>Mark（标记）</code>：标记位置</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Buffer</span><span class="token punctuation">&#123;</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">capacity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">position</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token class-name">Buffer</span> <span class="token function">position</span><span class="token punctuation">(</span><span class="token keyword">int</span> newPosition<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">limit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token class-name">Buffer</span> <span class="token function">limit</span><span class="token punctuation">(</span><span class="token keyword">int</span> newLimit<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">//其中比较常用的就是ByteBuffer（二进制数据），该类主要有以下方法</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ByteBuffer</span> <span class="token function">allocateDirect</span><span class="token punctuation">(</span><span class="token keyword">int</span> capacity<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//直接创建缓冲区</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ByteBuffer</span> <span class="token function">allocate</span><span class="token punctuation">(</span><span class="token keyword">int</span> capacity<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//设置缓冲区的初始容量</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ByteBuffer</span> <span class="token function">wrap</span><span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> array<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//把一个数组放入到缓冲区使用</span><span class="token comment">//构造初始化位置offset和上界length的缓冲区</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ByteBuffer</span> <span class="token function">wrap</span><span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> array<span class="token punctuation">,</span><span class="token keyword">int</span> offset<span class="token punctuation">,</span><span class="token keyword">int</span> length<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//缓冲区读取相关API</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">byte</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从当前位置position上get，get之后，positon会+1</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">byte</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从绝对位置获取</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token class-name">ByteBuffer</span> <span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">byte</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//当前位置上put，put之后，position会+1</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token class-name">ByteBuffer</span> <span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">,</span><span class="token keyword">byte</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从绝对位置put</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>状态变量的改变过程举例:</p><p>① 新建一个大小为 8 个字节的缓冲区，此时 position 为 0，而 limit &#x3D; capacity &#x3D; 8。capacity 变量不会改变，下面的讨论会忽略它。</p><p><img src="/img/a8f0d4502adb087892e11866bdac7d57_MD5.png" alt="a8f0d4502adb087892e11866bdac7d57_MD5.png"></p><p>② 从输入通道中读取 5 个字节数据写入缓冲区中，此时 position 移动设置为 5，limit 保持不变。</p><p><img src="/img/5ee1af7a6012fd34b62704d5b2867320_MD5.png" alt="5ee1af7a6012fd34b62704d5b2867320_MD5.png"></p><p>③ 在将缓冲区的数据写到输出通道之前，需要先调用 flip() 方法，这个方法将 limit 设置为当前 position，并将 position 设置为 0。</p><p><img src="/img/5cd2995739f1b0e1f4b355a2471c38aa_MD5.png" alt="5cd2995739f1b0e1f4b355a2471c38aa_MD5.png"></p><p>④ 从缓冲区中取 4 个字节到输出缓冲中，此时 position 设为 4。</p><p><img src="/img/0d87f8ba4e770fbdcd6c6fc61fb84862_MD5.png" alt="0d87f8ba4e770fbdcd6c6fc61fb84862_MD5.png"></p><p>⑤ 最后需要调用 clear() 方法来清空缓冲区，此时 position 和 limit 都被设置为最初位置。</p><p><img src="/img/f6ef08bdd8b4ff67a419bfe9b7dbc0f2_MD5.png" alt="f6ef08bdd8b4ff67a419bfe9b7dbc0f2_MD5.png"></p><h4 id="文件-NIO-实例"><a href="#文件-NIO-实例" class="headerlink" title="文件 NIO 实例"></a>文件 NIO 实例</h4><p>以下展示了使用 NIO 快速复制文件的实例：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">fastCopy</span><span class="token punctuation">(</span><span class="token class-name">String</span> src<span class="token punctuation">,</span> <span class="token class-name">String</span> dist<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">&#123;</span>    <span class="token comment">/* 获得源文件的输入字节流 */</span>    <span class="token class-name">FileInputStream</span> fin <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">/* 获取输入字节流的文件通道 */</span>    <span class="token class-name">FileChannel</span> fcin <span class="token operator">=</span> fin<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">/* 获取目标文件的输出字节流 */</span>    <span class="token class-name">FileOutputStream</span> fout <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span>dist<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">/* 获取输出字节流的通道 */</span>    <span class="token class-name">FileChannel</span> fcout <span class="token operator">=</span> fout<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">/* 为缓冲区分配 1024 个字节 */</span>    <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocateDirect</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">/* 从输入通道中读取数据到缓冲区中 */</span>        <span class="token keyword">int</span> r <span class="token operator">=</span> fcin<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">/* read() 返回 -1 表示 EOF */</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token comment">/* 切换读写 */</span>        buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">/* 把缓冲区的内容写入输出文件中 */</span>        fcout<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment">/* 清空缓冲区 */</span>        buffer<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-通道（Channel）"><a href="#2-通道（Channel）" class="headerlink" title="2. 通道（Channel）"></a>2. 通道（Channel）</h3><p>通道类似流，但是有如下区别：</p><ul><li>通道可以同时读写，而流只能读或写</li><li>通道可以实现异步读写数据</li><li>通道可以从缓冲区读数据，也可以写数据到缓冲区</li></ul><h4 id="通道分类"><a href="#通道分类" class="headerlink" title="通道分类"></a>通道分类</h4><p>Channel 在 NIO 中是一个接口 <code>public interface Channle extends Closeable{}</code>。其中，常用的 Channel 类有：</p><ol><li><code>FileChannel</code>：用于文件的数据读写；</li><li><code>DatagramChannel</code>：用于 UDP 的数据读写；</li><li><code>ServerSocketChannel</code>：可以监听新来的连接，对每一个新进来的连接都会创建一个 SocketChannel。只有通过这个通道，应用程序才能箱操作系统注册支持 “ 多路复用 IO” 的端口减轻。支持 TCP 和 UDP 协议；</li><li><code>SocketChannel</code>：TCP Socket 套接字的监听通道，用于 TCP 的数据读写</li><li>其他的通道包括：</li></ol><p><img src="/img/94530647a3be7da4d2de055fff8bacaf_MD5.png" alt="94530647a3be7da4d2de055fff8bacaf_MD5.png"></p><h4 id="FileChannel-类"><a href="#FileChannel-类" class="headerlink" title="FileChannel 类"></a>FileChannel 类</h4><p>对本地文件进行 IO 操作，常用方法及实例应用：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//从通道读取数据并放到缓冲区内</span><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">read</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span> content<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从缓冲区写数据到通道中</span><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token class-name">ByteBuffer</span> content<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从目标通道中复制数据到当前通道内</span><span class="token keyword">public</span> <span class="token keyword">long</span> <span class="token function">transferFrom</span><span class="token punctuation">(</span><span class="token class-name">ReadableByteChannel</span> src<span class="token punctuation">,</span><span class="token keyword">long</span> position<span class="token punctuation">,</span><span class="token keyword">long</span> count<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//把数据从当前通道复制到目标通道</span><span class="token keyword">public</span> <span class="token keyword">long</span> <span class="token function">transferTo</span><span class="token punctuation">(</span><span class="token keyword">long</span> position<span class="token punctuation">,</span><span class="token keyword">long</span> count<span class="token punctuation">,</span><span class="token class-name">WritabelByteChannel</span> target<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>1 . 写入文件，使用之前 <code>ByteBuffer</code> 和 <code>FileChannel</code> 类</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//使用之前ByteBuffer和FileChannel类，写入文件</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileOutputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">ByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">FileChannel</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOFileChannel</span><span class="token punctuation">&#123;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span><span class="token punctuation">&#123;</span><span class="token class-name">String</span> str <span class="token operator">=</span> <span class="token string">"hello,world"</span><span class="token punctuation">;</span><span class="token comment">//创一个输出流 -> channel</span><span class="token class-name">FileOutputStream</span> stream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"d:\\file.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//通过 stream 获取对应的 FileChannel</span><span class="token comment">//这个 fileChannel 真实类型是 FileChannelImpl</span><span class="token class-name">FileChannel</span> fileChannel <span class="token operator">=</span> stream<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//创建一个缓冲区 ByteBuffer</span><span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将 str 放入到缓冲区</span>byteBuffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>str<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//对 byteBuffer 进行 flip</span>byteBuffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将 byteBuffer 写入到 fileChannel</span>fileChannel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span>fileOutputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2 . 读取文件数据并展示，使用之前 <code>ByteBuffer</code> 和 <code>FileChannel</code> 类</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//读取本地文件</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileOutputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">ByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">FileChannel</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOFileChannel</span><span class="token punctuation">&#123;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span><span class="token punctuation">&#123;</span><span class="token comment">//创一个输出流 -> channel</span><span class="token class-name">File</span> file <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span><span class="token string">"d:\\file.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">FileOutputStream</span> stream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//通过 stream 获取对应的 FileChannel</span><span class="token comment">//这个 fileChannel 真实类型是 FileChannelImpl</span><span class="token class-name">FileChannel</span> fileChannel <span class="token operator">=</span> stream<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//创建一个缓冲区 ByteBuffer</span><span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>file<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将 byteBuffer 写入到 fileChannel</span>fileChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将 byteBuffer的字节转化成String</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>fileOutputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>3 . 使用一个 <code>Buffer</code> 完成文件的读取、写入</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileInputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileOutputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">ByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">FileChannel</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOFileChannel03</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">FileInputStream</span> fileInputStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"1.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">FileChannel</span> fileChannel01 <span class="token operator">=</span> fileInputStream<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">FileOutputStream</span> fileOutputStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"2.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">FileChannel</span> fileChannel02 <span class="token operator">=</span> fileOutputStream<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">ByteBuffer</span> byteBuffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">512</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token comment">//循环读取</span>            <span class="token comment">//这里有一个重要的操作，一定不要忘了</span>            <span class="token comment">/*            public final Buffer clear() &#123;                position = 0;                limit = capacity;                mark = -1;                return this;            &#125;            */</span>            byteBuffer<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//清空 buffer</span>            <span class="token keyword">int</span> read <span class="token operator">=</span> fileChannel01<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"read = "</span> <span class="token operator">+</span> read<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>read <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token comment">//表示读完</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token comment">//将 buffer 中的数据写入到 fileChannel02--2.txt</span>            byteBuffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            fileChannel02<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token comment">//关闭相关的流</span>        fileInputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        fileOutputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p> 4 . 拷贝文件 transferFrom 方法</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileInputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileOutputStream</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">FileChannel</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOFileChannel04</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>        <span class="token comment">//创建相关流</span>        <span class="token class-name">FileInputStream</span> fileInputStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"d:\\a.jpg"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">FileOutputStream</span> fileOutputStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"d:\\a2.jpg"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment">//获取各个流对应的 FileChannel</span>        <span class="token class-name">FileChannel</span> sourceCh <span class="token operator">=</span> fileInputStream<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">FileChannel</span> destCh <span class="token operator">=</span> fileOutputStream<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//使用 transferForm 完成拷贝</span>        destCh<span class="token punctuation">.</span><span class="token function">transferFrom</span><span class="token punctuation">(</span>sourceCh<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> sourceCh<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//关闭相关通道和流</span>        sourceCh<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        destCh<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        fileInputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        fileOutputStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="Buffer-和-Channel-注意事项"><a href="#Buffer-和-Channel-注意事项" class="headerlink" title="Buffer 和 Channel 注意事项"></a>Buffer 和 Channel 注意事项</h4><p> <strong>1. ByteBuffer 支持类型化的 put 和 get，put 放什么，get 取出什么，不然出现 BufferUnderflowException 异常</strong></p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">ByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOByteBufferPutGet</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token comment">//创建一个 Buffer</span>        <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//类型化方式放入数据</span>        buffer<span class="token punctuation">.</span><span class="token function">putInt</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        buffer<span class="token punctuation">.</span><span class="token function">putLong</span><span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        buffer<span class="token punctuation">.</span><span class="token function">putChar</span><span class="token punctuation">(</span><span class="token char">'尚'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        buffer<span class="token punctuation">.</span><span class="token function">putShort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">short</span><span class="token punctuation">)</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//取出</span>        buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">getInt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">getLong</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">getChar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>buffer<span class="token punctuation">.</span><span class="token function">getShort</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>2. 普通 Buffer 转成只读 Buffer</strong></p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">ByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ReadOnlyBuffer</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">//创建一个 buffer</span>        <span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">64</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            buffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        <span class="token comment">//读取</span>        buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//得到一个只读的 Buffer</span>        <span class="token class-name">ByteBuffer</span> readOnlyBuffer <span class="token operator">=</span> buffer<span class="token punctuation">.</span><span class="token function">asReadOnlyBuffer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>readOnlyBuffer<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//读取</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>readOnlyBuffer<span class="token punctuation">.</span><span class="token function">hasRemaining</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>readOnlyBuffer<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>        readOnlyBuffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//ReadOnlyBufferException</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>3. NIO 中 MappedByteBuffer，可以让文件直接在堆外内存修改</strong></p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">RandomAccessFile</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">MappedByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">FileChannel</span></span><span class="token punctuation">;</span><span class="token comment">/** * 说明 1.MappedByteBuffer 可让文件直接在内存（堆外内存）修改,操作系统不需要拷贝一次 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MappedByteBufferTest</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">RandomAccessFile</span> randomAccessFile <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RandomAccessFile</span><span class="token punctuation">(</span><span class="token string">"1.txt"</span><span class="token punctuation">,</span> <span class="token string">"rw"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//获取对应的通道</span>        <span class="token class-name">FileChannel</span> channel <span class="token operator">=</span> randomAccessFile<span class="token punctuation">.</span><span class="token function">getChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">/**         * 参数 1:FileChannel.MapMode.READ_WRITE 使用的读写模式         * 参数 2：0：可以直接修改的起始位置         * 参数 3:5: 是映射到内存的大小（不是索引位置），即将 1.txt 的多少个字节映射到内存         * 可以直接修改的范围就是 0-5         * 实际类型 DirectByteBuffer         */</span>        <span class="token class-name">MappedByteBuffer</span> mappedByteBuffer <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token class-name">FileChannel<span class="token punctuation">.</span>MapMode</span><span class="token punctuation">.</span><span class="token constant">READ_WRITE</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        mappedByteBuffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token char">'H'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        mappedByteBuffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token char">'9'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        mappedByteBuffer<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token char">'Y'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//IndexOutOfBoundsException</span>        randomAccessFile<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"修改成功~~"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li><strong>NIO 还支持通过多个 Buffer（即 Buffer 数组）完成读写操作，即 Scattering 和 Gathering</strong></li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>net<span class="token punctuation">.</span></span><span class="token class-name">InetSocketAddress</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span></span><span class="token class-name">ByteBuffer</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">ServerSocketChannel</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>nio<span class="token punctuation">.</span>channels<span class="token punctuation">.</span></span><span class="token class-name">SocketChannel</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">Arrays</span></span><span class="token punctuation">;</span><span class="token comment">/** * Scattering：将数据写入到 buffer 时，可以采用 buffer 数组，依次写入 [分散] * Gathering：从 buffer 读取数据时，可以采用 buffer 数组，依次读 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ScatteringAndGatheringTest</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">Exception</span> <span class="token punctuation">&#123;</span>                <span class="token comment">//使用 ServerSocketChannel 和 SocketChannel 网络</span>        <span class="token class-name">ServerSocketChannel</span> serverSocketChannel <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">InetSocketAddress</span> inetSocketAddress <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">7000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//绑定端口到 socket，并启动</span>        serverSocketChannel<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>inetSocketAddress<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//创建 buffer 数组</span>        <span class="token class-name">ByteBuffer</span><span class="token punctuation">[</span><span class="token punctuation">]</span> byteBuffers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        byteBuffers<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        byteBuffers<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//等客户端连接 (telnet)</span>        <span class="token class-name">SocketChannel</span> socketChannel <span class="token operator">=</span> serverSocketChannel<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> messageLength <span class="token operator">=</span> <span class="token number">8</span><span class="token punctuation">;</span> <span class="token comment">//假定从客户端接收 8 个字节</span>        <span class="token comment">//循环的读取</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">int</span> byteRead <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span>byteRead <span class="token operator">&lt;</span> messageLength<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token keyword">long</span> l <span class="token operator">=</span> socketChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffers<span class="token punctuation">)</span><span class="token punctuation">;</span>                byteRead <span class="token operator">+=</span> l<span class="token punctuation">;</span> <span class="token comment">//累计读取的字节数</span>                <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"byteRead = "</span> <span class="token operator">+</span> byteRead<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment">//使用流打印,看看当前的这个 buffer 的 position 和 limit</span>                <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>byteBuffers<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>buffer <span class="token operator">-></span> <span class="token string">"position = "</span> <span class="token operator">+</span> buffer<span class="token punctuation">.</span><span class="token function">position</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">", limit = "</span> <span class="token operator">+</span> buffer<span class="token punctuation">.</span><span class="token function">limit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token operator">::</span><span class="token function">println</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token comment">//将所有的 buffer 进行 flip</span>            <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>byteBuffers<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>buffer <span class="token operator">-></span> buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment">//将数据读出显示到客户端</span>            <span class="token keyword">long</span> byteWirte <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span>byteWirte <span class="token operator">&lt;</span> messageLength<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token keyword">long</span> l <span class="token operator">=</span> socketChannel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>byteBuffers<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//</span>                byteWirte <span class="token operator">+=</span> l<span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>                        <span class="token comment">//将所有的buffer进行clear</span>            <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>byteBuffers<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>buffer <span class="token operator">-></span> <span class="token punctuation">&#123;</span>                buffer<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"byteRead = "</span> <span class="token operator">+</span> byteRead <span class="token operator">+</span> <span class="token string">", byteWrite = "</span> <span class="token operator">+</span> byteWirte <span class="token operator">+</span> <span class="token string">", messagelength = "</span> <span class="token operator">+</span> messageLength<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-Selector（选择器）"><a href="#3-Selector（选择器）" class="headerlink" title="3. Selector（选择器）"></a>3. Selector（选择器）</h3><h4 id="基本概念-2"><a href="#基本概念-2" class="headerlink" title="基本概念"></a>基本概念</h4><p>NIO 常常被叫做非阻塞 IO，主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。NIO 实现了 IO 多路复用中的 Reator 模型，一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个 Channel 上的事件，从而让一个线程能够处理多个事件。</p><p>通过配置监听的通道 Channel 为非阻塞，那么当 Channel 上的 IO 事件还未到达时，就不会进入到阻塞状态一直等待，而是鸡血轮询其他 Channel，找到 IO 事件已经到达的 Channel 执行。</p><p>以为创建和切换线程的开销很大，因此使用一个线程处理多个事件显然比一个线程处理一个事件具有更好的性能。</p><p><img src="/img/0b37a3b751ec9aa08efa75ace30e23c4_MD5.png" alt="0b37a3b751ec9aa08efa75ace30e23c4_MD5.png"></p><ol><li>Java 中的 NIO 可以用一个线程，处理多个客户端连接，就会使用到 Selector（选择器）</li><li>多个 Channel 以事件的方式注册到 Selector</li><li>只有在连接通道有真正的读写事件的时候，才会进行读写，减少系统开销</li><li>避免了 <code>多线程之间的上下文切换导致的开销</code></li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">//Selector 类是一个抽象类，常用方法和说明如下：</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Selector</span> <span class="token keyword">implements</span> <span class="token class-name">Closeable</span><span class="token punctuation">&#123;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Selector</span> <span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//监控所有注册的通道，当其中有IO操作可以进行时，将SelectionKey加入到内部的集合中并返回，参数用来设置超时时间</span><span class="token keyword">public</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> <span class="token function">selectedKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//从内部集合中得到所有的SelectionKey</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h4><ol><li><strong>创建选择器</strong></li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ol><li><strong>将通道注册到选择器上</strong></li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">ServerSocketChannel</span> ssChannel <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>ssChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>ssChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_ACCEPT</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>将通道注册到选择器上，还需要指定要注册的具体事件，主要有以下几类：</p><p> <code>SelectionKey. OP_CONNECT</code>、<code>SelectionKey. OP_ACCEPT</code>、<code>SelectionKey. OP_READ</code>、<code>SelectionKey. OP_WRITE</code></p><p>他们在 SelectionKey 的定义如下：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">OP_READ</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">OP_WRITE</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token number">2</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">OP_CONNECT</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token constant">OP_ACCEPT</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token number">4</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>可以看出每个事件都能当成一个位域，从而组成事件集整数。例如：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> intersetSet <span class="token operator">=</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_READ</span> <span class="token operator">|</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_WRITE</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ol><li><strong>监听事件</strong></li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">int</span> num <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>使用 <code>select()</code> 方法来监听到达的事件，它会一直阻塞知道有至少一件事件到达。</p><ol><li><strong>获取到达的事件</strong></li></ol><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keyIterator <span class="token operator">=</span> keys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span><span class="token punctuation">(</span>keyIterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token class-name">SelectionKey</span> keyu <span class="token operator">=</span> keyIterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptabnle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token comment">// ...</span><span class="token punctuation">&#125;</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token comment">// ...</span><span class="token punctuation">&#125;</span>keyIterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li><strong>时间循环</strong></li></ol><p>因为一次 select() 调动不能处理完所有的事件，并且服务器端有可能需要一直监听事件，因此服务器端处理时间的代码一般会放在一个死循环内。</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token keyword">int</span> num <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keyIterator <span class="token operator">=</span> keys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>keyIterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span> <span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> keyIterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>   <span class="token comment">// ... </span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// ... </span><span class="token punctuation">&#125;</span> keyIterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="套接字-NIO-实例"><a href="#套接字-NIO-实例" class="headerlink" title="套接字 NIO 实例"></a>套接字 NIO 实例</h4><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOServer</span> <span class="token punctuation">&#123;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">&#123;</span><span class="token class-name">Selector</span> selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">ServerSocketChannel</span> ssChannel <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>ssChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>ssChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_ACCEPT</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">ServerSocket</span> serverSocket <span class="token operator">=</span> ssChannel<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">InetSocketAddress</span> address <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">8888</span><span class="token punctuation">)</span><span class="token punctuation">;</span>serverSocket<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keyIterator <span class="token operator">=</span> keys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span> <span class="token punctuation">(</span>keyIterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> keyIterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">ServerSocketChannel</span> ssChannel1 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 服务器会为每个新连接创建一个 SocketChannel SocketChannel sChannel = ssChannel1.accept();</span>sChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 这个新连接主要用于从客户端读取数据 </span>sChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span> <span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_READ</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token class-name">SocketChannel</span> sChannel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">SocketChannel</span><span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token function">readDataFromSocketChannel</span><span class="token punctuation">(</span>sChannel<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>sChannel<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> keyIterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span> <span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">readDataFromSocketChannel</span><span class="token punctuation">(</span><span class="token class-name">SocketChannel</span> sChannel<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span> <span class="token punctuation">&#123;</span><span class="token class-name">ByteBuffer</span> buffer <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">StringBuilder</span> data <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>buffer<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">int</span> n <span class="token operator">=</span> sChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>buffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">int</span> limit <span class="token operator">=</span> buffer<span class="token punctuation">.</span><span class="token function">limit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dst <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">char</span><span class="token punctuation">[</span>limit<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>i <span class="token operator">&lt;</span> limit<span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>dst<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">char</span><span class="token punctuation">)</span> buffer<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>data<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>dst<span class="token punctuation">)</span><span class="token punctuation">;</span>buffer<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span> <span class="token keyword">return</span> data<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span> <span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOClient</span> <span class="token punctuation">&#123;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">&#123;</span><span class="token class-name">Socket</span> socket <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Socket</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span>，<span class="token number">8888</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">OutputStream</span> out <span class="token operator">=</span> socket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">String</span> s <span class="token operator">=</span> <span class="token string">"hello world"</span><span class="token punctuation">;</span>out<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>out<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="典型的多路复用-IO-实现"><a href="#典型的多路复用-IO-实现" class="headerlink" title="典型的多路复用 IO 实现"></a>典型的多路复用 IO 实现</h3><p>目前流程的多路复用 IO 实现主要宝库了四种：select、poll、epoll、kqueue。以下是其特性及区别：</p><table><thead><tr><th align="center">IO 模型</th><th align="center">相对性能</th><th align="center">关键思路</th><th align="center">操作系统</th><th align="center">Java 支持情况</th></tr></thead><tbody><tr><td align="center">select</td><td align="center">较高</td><td align="center">Reactor</td><td align="center">Win&#x2F;Linux</td><td align="center">支持，Reactor 模式（反应器设计模式）。Linux kernels 2.4 内核版本之前，默认用的是 select；目前 windows 下对吧同步 IO 的支持，都是 select 模型</td></tr><tr><td align="center">poll</td><td align="center">较高</td><td align="center">Reactor</td><td align="center">Linux</td><td align="center">Linux 下的 Java 的 NIO 框架，Linux kernels 2.6 内核版本之前使用 poll 进行支持。也是使用的 Reactor 模式</td></tr><tr><td align="center">epoll</td><td align="center">高</td><td align="center">Reactor&#x2F;Proactor</td><td align="center">Linux</td><td align="center">Linux kernels 2.6 内核版本之后使用 epoll 进行支持</td></tr><tr><td align="center">kqueue</td><td align="center">高</td><td align="center">Proactor</td><td align="center">Linux</td><td align="center">目前 Java 版本不支持</td></tr></tbody></table><h4 id="1-Reactor-事件驱动模型"><a href="#1-Reactor-事件驱动模型" class="headerlink" title="1. Reactor 事件驱动模型"></a>1. Reactor 事件驱动模型</h4><p><img src="/img/10d0080498aba2649f1c04067965b579_MD5.png" alt="10d0080498aba2649f1c04067965b579_MD5.png"></p><p>从图上可知：一个完整的 Reactor 事件驱动模型是有四个部分组成：客户端 Client，Reactor，Acceptor 和时间处理 Handler。其中 Acceptor 会不间断的接收客户端的连接请求，然后通过 Reactor 分发到不同 Handler 进行处理。改进后的 Reactor 有如下优点：</p><ul><li>虽然同是由一个线程接收连接请求进行网络读写，但是 Reactor 将客户端连接，网络读写，业务处理三大部分拆分，从而极大提高工作效率。</li><li>Reactor 是以事件驱动的，相比传统 IO 阻塞式的，不必等待，大大提升了效率。</li></ul><h4 id="2-Reactor-模型——业务处理和-IO-分离"><a href="#2-Reactor-模型——业务处理和-IO-分离" class="headerlink" title="2. Reactor 模型——业务处理和 IO 分离"></a>2. Reactor 模型——业务处理和 IO 分离</h4><p>在上面的处理模型中，由于网络读写是在同一个线程里面。在高并发情况下，会出现两个瓶颈：</p><ul><li>高频率的读写事件处理</li><li>大量的业务处理</li></ul><p>基于上述瓶颈，可以将业务处理和 IO 读写分离出来：</p><p><img src="/img/37b1874d4aabb48cd6b511cba09fa70b_MD5.png" alt="37b1874d4aabb48cd6b511cba09fa70b_MD5.png"></p><p>如图可以看出，相对基础 Reactor 模型，该模型有如下特点：</p><ul><li>使用一个线程进行客户端连接和网络读写</li><li>客户端连接之后，将该连接交给线程池进行加解码以及业务处理</li></ul><p>这种模型在接收请求进行网络读写的同时，也在进行业务处理，大大提高了系统的吞吐量。但是也有不足的地方：</p><ul><li>网络读写是一个比较消耗 CPU 的操作，在高并发的情况下，将有大量的客户端需要网络读写，此时一个线程处理不了这么多的请求。</li></ul><h4 id="3-Reactor——并发读写"><a href="#3-Reactor——并发读写" class="headerlink" title="3. Reactor——并发读写"></a>3. Reactor——并发读写</h4><p>由于高并发的网络读写是系统一个瓶颈，所以针对这种情况，改进了模型，如图所示：</p><p><img src="/img/870a39b7312d1d18324d3aefa613ab37_MD5.png" alt="870a39b7312d1d18324d3aefa613ab37_MD5.png"><br>由图可以看出，在原有 Reactor 模型上，同时将 Reactor 拆分成 mainReactor 和 subReactor。其中 mainReactor 主要负责客户端的请求连接，subReactor 通过一个线程池进行支撑，主要负责网络读写，因为线程池的原因，可以进行多线程并发读写，大大提升了网络读写的效率。业务处理也是通过线程池进行。通过这种方式，可以进行百万级别的连接。</p><h4 id="4-Reactor-模型示例"><a href="#4-Reactor-模型示例" class="headerlink" title="4. Reactor 模型示例"></a>4. Reactor 模型示例</h4><p>对于上述的 Reactor 模型，主要有三个核心需要实现：Acceptor，Reactor 和 Handler。具体实现代码如下：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Reactor</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span><span class="token punctuation">&#123;</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Selector</span> selector<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">ServerSocketChannel</span> serverSocket<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token class-name">Reactor</span><span class="token punctuation">(</span><span class="token keyword">int</span> port<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">&#123;</span>serverSocket <span class="token operator">=</span> <span class="token class-name">ServerSocketChannel</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//创建服务端的ServerSocketChannel</span>serverSocket<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//设置为非阻塞模式</span>selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//创建一个selector选择器</span><span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> serverSocket<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_ACCEPT</span><span class="token punctuation">)</span><span class="token punctuation">;</span>serverSocket<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span>port<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//绑定服务端端口</span>key<span class="token punctuation">.</span><span class="token function">attach</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Acceptor</span><span class="token punctuation">(</span>serverSocket<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//为服务端Channel绑定一个Acceptor</span><span class="token punctuation">&#125;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span><span class="token keyword">while</span><span class="token punctuation">(</span>！<span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>selector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//服务端使用一个线程不停接收连接请求</span><span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keys <span class="token operator">=</span> selector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> itetrator <span class="token operator">=</span> keys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span><span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token function">dispatch</span><span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>selector<span class="token punctuation">.</span><span class="token function">selectNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span> key<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">&#123;</span><span class="token comment">//这里的attachment也即前面的为服务端Channel绑定的Acceptor，调用其run()方法进行分发</span><span class="token class-name">Runnable</span> attachment <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">Runable</span><span class="token punctuation">)</span>key<span class="token punctuation">.</span><span class="token function">attachment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>attachment<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里 Reactor 首先开启了一个 ServerSocketChannel，然后将其绑定到指定的端口，并且注册到了一个多路复用器上。接着在一个线程中，其会在多路复用器上等待客户端连接。当有客户端连接到达后，Reactor 就会将其派发给一个 Acceptor，由该 Acceptor 专门进行客户端连接的获取。下面我们继续看一下 Acceptor 的代码：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Acceptor</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span><span class="token punctuation">&#123;</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">ExecuteorService</span> executor <span class="token operator">=</span> <span class="token class-name">Exxcutors</span><span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">ServerSocketChannel</span> serverSocket<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token class-name">Acceptor</span><span class="token punctuation">(</span><span class="token class-name">ServerSocketChannel</span> serverSocket<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>serverSocket <span class="token operator">=</span> serverSocket<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span><span class="token class-name">SocketChannel</span> channel <span class="token operator">=</span> serverSocket<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token keyword">null</span> <span class="token operator">!=</span> channel<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>executor<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Handler</span><span class="token punctuation">(</span>channel<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里可以看到，在 Acceptor 获取到客户端连接之后，其就将其交由线程池进行网络读写了，而这里的主线程只是不断监听客户端连接事件。下面我们看看 Handler 的具体逻辑：</p><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Handler</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span><span class="token punctuation">&#123;</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">static</span> <span class="token class-name">Selector</span> selector<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">SocketChannel</span> channel<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token class-name">SelectionKey</span> key<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token class-name">ByteBuffer</span> input <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token class-name">ByteBuffer</span> output <span class="token operator">=</span> <span class="token class-name">ByteBuffer</span><span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token class-name">Handle</span><span class="token punctuation">(</span><span class="token class-name">SocketChannel</span> channel<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">&#123;</span><span class="token keyword">this</span><span class="token punctuation">.</span>channel <span class="token operator">=</span> channel<span class="token punctuation">;</span>channel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//设置客户端连接为非阻塞模式</span>selector <span class="token operator">=</span> <span class="token class-name">Selector</span><span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//为客户端创建一个选择器</span>key <span class="token operator">=</span> channel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>selector<span class="token punctuation">,</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_READ</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//注册客户端Channel的读事件</span><span class="token punctuation">&#125;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span><span class="token keyword">while</span><span class="token punctuation">(</span>selector<span class="token punctuation">.</span><span class="token function">isOpen</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> channel<span class="token punctuation">.</span><span class="token function">isOpen</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> keys <span class="token operator">=</span> <span class="token function">select</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//等待客户端事件发生</span> <span class="token class-name">Iterator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">SelectionKey</span><span class="token punctuation">></span></span> iterator <span class="token operator">=</span> keys<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span><span class="token punctuation">(</span>iterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token class-name">SelectionKey</span> key <span class="token operator">=</span> iterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>iterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//如果当前是读事件，则读取数据</span><span class="token keyword">if</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token function">read</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isWritable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token function">write</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">//读取客户端发送的数据</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">read</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span> key<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">&#123;</span>channel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span>input<span class="token punctuation">.</span><span class="token function">position</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">return</span> <span class="token punctuation">;</span><span class="token punctuation">&#125;</span>input<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">process</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//对读数据进行业务处理</span>input<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>key<span class="token punctuation">.</span><span class="token function">interstOps</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span><span class="token punctuation">.</span><span class="token constant">OP_WRITE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//读取完成后监听写入事件</span><span class="token punctuation">&#125;</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token class-name">SelectionKey</span> key<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">IOException</span><span class="token punctuation">&#123;</span>output<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span>channel<span class="token punctuation">.</span><span class="token function">isOpen</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>channel<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>output<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//当有写入事件时，将业务处理的结果写入到客户端Channel中</span>key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>channel<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>output<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token comment">//进行业务处理，并且获取处理结果。本质上，基于Reactor模型，如果这里成为处理瓶颈，则将处理过程放入到线程池里面即可，并且使用一个Future获取处理结果，最后写入到客户端Channel中</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">process</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> bytes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token keyword">byte</span><span class="token punctuation">[</span>input<span class="token punctuation">.</span><span class="token function">remaining</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>input<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">String</span> message <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>bytes<span class="token punctuation">,</span><span class="token class-name">CharsetUtil</span><span class="token punctuation">.</span><span class="token constant">UTF_8</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"receive message from client: \n"</span> <span class="token operator">+</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>output<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"hello client"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 Handler 中，主要进行的就是每个客户端 Channel 创建一个 Selector，并且监听该 Channel 的网络读写事件。当有事件到达时，进行数据的读写，而业务操作交友具体的业务线程池处理。</p><h2 id="三、AIO（Asynchronous-I-O）"><a href="#三、AIO（Asynchronous-I-O）" class="headerlink" title="三、AIO（Asynchronous I&#x2F;O）"></a>三、AIO（Asynchronous I&#x2F;O）</h2><ol><li>JDK7 引入了 Asynchronous I&#x2F;O，即 AIO。在进行 I&#x2F;O 编程中，常用到两种模式：Reactor 和 Proactor。Java 的 NIO 就是 Reactor，当有事件触发时，服务器端得到通知，进行相应的处理</li><li>AIO 即 NIO2.0，叫做异步不阻塞的 IO。AIO 引入异步通道的概念，采用了 Proactor 模式，简化了程序编写，有效的请求才启动线程，它的特点是先由操作系统完成后才通知服务端程序启动线程去处理，一般适用于连接数较多且连接时间较长的应用</li></ol><h3 id="异步-IO"><a href="#异步-IO" class="headerlink" title="异步 IO"></a>异步 IO</h3><p>之前主要介绍了阻塞式同步 IO，非阻塞式同步 IO，多路复用 IO 这三种 IO 模型。而异步 IO 是采用 “ 订阅 - 通知 “ 模式，即应用程序向操作系统注册 IO 监听，然后继续做自己的事情。当操作系统发生 IO 事件，并且准备好数据后，主动通知应用程序，触发相应的函数：</p><p><img src="/img/3a01157436842562f7f21f8b4c4549d4_MD5.png" alt="3a01157436842562f7f21f8b4c4549d4_MD5.png"></p><p>和同步 IO 一样，异步 IO 也是由操作系统进行支持的。Windows 系统提供了一种异步 IO 技术：IOCP（I&#x2F;O Completion Port，I&#x2F;O 完成端口）；<br>Linux 下由于没有这种异步 IO 技术，所以使用的是 epoll（上文介绍过的一种多路复用 IO 技术的实现）对异步 IO 进行模拟。</p><h3 id="Java-AIO-框架解析"><a href="#Java-AIO-框架解析" class="headerlink" title="Java AIO 框架解析"></a>Java AIO 框架解析</h3><p><img src="/img/2ff37cd0e1f5f734f8d6209396a08897_MD5.png" alt="2ff37cd0e1f5f734f8d6209396a08897_MD5.png"></p><p>以上结构主要是说明 JAVA AIO 中类设计和操作系统的相关性。</p><blockquote><p>上述所有代码仓库地址：<a href="https://github.com/z1gui/netty_io">https://github.com/z1gui/netty_io</a>  </p></blockquote><p>参考资料：</p><blockquote><p><a href="https://zhuanlan.zhihu.com/p/520809188?utm_id=0">BIO、NIO、AIO区别详解</a>  </p></blockquote><blockquote><p><a href="https://pdai.tech/md/java/io/java-io-overview.html">♥Java IO知识体系详解♥</a></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;BIO&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;NIO&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;AIO&lt;/th&gt;
&lt;/tr&gt;
&lt;/th</summary>
      
    
    
    
    <category term="后端" scheme="https://www.lazydaily.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="IO" scheme="https://www.lazydaily.cn/tags/IO/"/>
    
  </entry>
  
  <entry>
    <title> 如何操作excel、word、pdf、md、txt、csv文件</title>
    <link href="https://www.lazydaily.cn/2023/11/18/XT9NMxoZOsNvElVP/"/>
    <id>https://www.lazydaily.cn/2023/11/18/XT9NMxoZOsNvElVP/</id>
    <published>2023-11-18T10:11:00.000Z</published>
    <updated>2025-11-25T10:47:53.126Z</updated>
    
    <content type="html"><![CDATA[<p>Java 中操作各种文件类型（如 Excel、Word、PDF、Markdown、纯文本和 CSV）需要不同的库来实现。以下是一些基本的代码示例，分别针对不同格式进行读写或修改内容的操作：</p><h2 id="操作-Excel-使用-Apache-POI-库"><a href="#操作-Excel-使用-Apache-POI-库" class="headerlink" title="操作 Excel (使用 Apache POI 库)"></a>操作 Excel (使用 Apache POI 库)</h2><h3 id="读取-写入-Excel-Xls-xlsx"><a href="#读取-写入-Excel-Xls-xlsx" class="headerlink" title="读取&#x2F;写入 Excel (. Xls, .xlsx)"></a>读取&#x2F;写入 Excel (. Xls, .xlsx)</h3><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 导入相关依赖</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>poi<span class="token punctuation">.</span>ss<span class="token punctuation">.</span>usermodel<span class="token punctuation">.</span></span><span class="token operator">*</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>poi<span class="token punctuation">.</span>xssf<span class="token punctuation">.</span>usermodel<span class="token punctuation">.</span></span><span class="token class-name">XSSFWorkbook</span></span><span class="token punctuation">;</span><span class="token comment">// 写入数据到Excel</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">Workbook</span> workbook <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XSSFWorkbook</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 新建一个XLSX工作簿</span>     <span class="token class-name">FileOutputStream</span> fileOut <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"output.xlsx"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">Sheet</span> sheet <span class="token operator">=</span> workbook<span class="token punctuation">.</span><span class="token function">createSheet</span><span class="token punctuation">(</span><span class="token string">"Sheet1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 创建新的sheet</span>    <span class="token class-name">Row</span> row <span class="token operator">=</span> sheet<span class="token punctuation">.</span><span class="token function">createRow</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 创建行</span>    <span class="token class-name">Cell</span> cell <span class="token operator">=</span> row<span class="token punctuation">.</span><span class="token function">createCell</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 创建单元格</span>        cell<span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span><span class="token string">"Hello, Excel!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 设置单元格值</span>    workbook<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>fileOut<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 写入文件</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 读取数据从Excel</span><span class="token keyword">try</span> <span class="token punctuation">(</span> <span class="token class-name">FileInputStream</span> file <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"input.xlsx"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token class-name">Workbook</span> workbook <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XSSFWorkbook</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">Sheet</span> sheet <span class="token operator">=</span> workbook<span class="token punctuation">.</span><span class="token function">getSheetAt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">Row</span> row <span class="token operator">=</span> sheet<span class="token punctuation">.</span><span class="token function">getRow</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">Cell</span> cell <span class="token operator">=</span> row<span class="token punctuation">.</span><span class="token function">getCell</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">String</span> value <span class="token operator">=</span> cell<span class="token punctuation">.</span><span class="token function">getStringCellValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取单元格值</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 删除内容</span><span class="token comment">// 需要找到特定单元格并清除其内容，例如：</span>row<span class="token punctuation">.</span><span class="token function">getCell</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setCellValue</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 清除单元格内容</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="操作-Word-Doc-docx-使用-Apache-POI-库中的-HWPF-或-XWPF"><a href="#操作-Word-Doc-docx-使用-Apache-POI-库中的-HWPF-或-XWPF" class="headerlink" title="操作 Word (. Doc, .docx) 使用 Apache POI 库中的 HWPF 或 XWPF"></a>操作 Word (. Doc, .docx) 使用 Apache POI 库中的 HWPF 或 XWPF</h2><h3 id="写入-Word-docx"><a href="#写入-Word-docx" class="headerlink" title="写入 Word (. docx)"></a>写入 Word (. docx)</h3><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 导入相关依赖</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>poi<span class="token punctuation">.</span>xwpf<span class="token punctuation">.</span>usermodel<span class="token punctuation">.</span></span><span class="token operator">*</span></span><span class="token punctuation">;</span><span class="token comment">// 写入内容到Word文档</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">XWPFDocument</span> document <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">XWPFDocument</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">XWPFParagraph</span> paragraph <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createParagraph</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">XWPFRun</span> run <span class="token operator">=</span> paragraph<span class="token punctuation">.</span><span class="token function">createRun</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    run<span class="token punctuation">.</span><span class="token function">setText</span><span class="token punctuation">(</span><span class="token string">"Hello, Word!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token class-name">FileOutputStream</span> out <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"output.docx"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    document<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>out<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 删除内容需定位到具体的段落或运行，并移除它们</span><span class="token comment">// 注意：删除操作通常比写入更为复杂，因为可能涉及到多个段落、图片等元素</span><span class="token comment">// 下面是一个简单删除段落的例子：</span><span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">XWPFParagraph</span><span class="token punctuation">></span></span> paragraphs <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getParagraphs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>paragraphs<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    paragraphs<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 移除第一个段落</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="操作-PDF-使用-iText-或-Apache-PDFBox-库"><a href="#操作-PDF-使用-iText-或-Apache-PDFBox-库" class="headerlink" title="操作 PDF (使用 iText 或 Apache PDFBox 库)"></a>操作 PDF (使用 iText 或 Apache PDFBox 库)</h2><h3 id="写入-PDF"><a href="#写入-PDF" class="headerlink" title="写入 PDF"></a>写入 PDF</h3><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 使用iText库</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>itextpdf<span class="token punctuation">.</span>text<span class="token punctuation">.</span></span><span class="token class-name">Document</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>itextpdf<span class="token punctuation">.</span>text<span class="token punctuation">.</span></span><span class="token class-name">Paragraph</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">com<span class="token punctuation">.</span>itextpdf<span class="token punctuation">.</span>text<span class="token punctuation">.</span>pdf<span class="token punctuation">.</span></span><span class="token class-name">PdfWriter</span></span><span class="token punctuation">;</span><span class="token keyword">try</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">Document</span> document <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Document</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">PdfWriter</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span>document<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"output.pdf"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    document<span class="token punctuation">.</span><span class="token keyword">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    document<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Paragraph</span><span class="token punctuation">(</span><span class="token string">"Hello, PDF!"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    document<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 删除内容较为复杂，通常涉及重新构造PDF文档结构，具体操作取决于库提供的API</span><span class="token comment">// iText提供了一些更新PDF内容的方法，但通常是替换而不是删除特定内容。</span><span class="token comment">// 使用PDFBox创建新页面或覆盖现有内容比较常见</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>pdfbox<span class="token punctuation">.</span>pdmodel<span class="token punctuation">.</span></span><span class="token class-name">PDDocument</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>pdfbox<span class="token punctuation">.</span>pdmodel<span class="token punctuation">.</span></span><span class="token class-name">PDPageContentStream</span></span><span class="token punctuation">;</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">PDDocument</span> doc <span class="token operator">=</span> <span class="token class-name">PDDocument</span><span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span><span class="token string">"input.pdf"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">PDPage</span> page <span class="token operator">=</span> doc<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">PDPageContentStream</span> contentStream <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PDPageContentStream</span><span class="token punctuation">(</span>doc<span class="token punctuation">,</span> page<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 要删除内容，通常不是直接删除而是覆盖原有内容</span>    <span class="token comment">// 实际上是重新绘制整个页面或者部分区域</span>    <span class="token comment">// 这里仅做关闭流处理，实际内容删除请查阅PDFBox官方文档</span>    contentStream<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    doc<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token string">"output.pdf"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="操作-Markdown-md-和纯文本-txt"><a href="#操作-Markdown-md-和纯文本-txt" class="headerlink" title="操作 Markdown (. md) 和纯文本 (. txt)"></a>操作 Markdown (. md) 和纯文本 (. txt)</h2><p>由于 Markdown 和纯文本都是基于文本格式，可以直接通过 Java 的 IO 流进行读写。</p><h3 id="写入-读取文本文件"><a href="#写入-读取文本文件" class="headerlink" title="写入&#x2F;读取文本文件"></a>写入&#x2F;读取文本文件</h3><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token comment">// 写入文本文件</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">PrintWriter</span> writer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PrintWriter</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileWriter</span><span class="token punctuation">(</span><span class="token string">"output.md"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    writer<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"# Hello, Markdown!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    writer<span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 读取文本文件</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">BufferedReader</span> reader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BufferedReader</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileReader</span><span class="token punctuation">(</span><span class="token string">"input.txt"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> line<span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>line <span class="token operator">=</span> reader<span class="token punctuation">.</span><span class="token function">readLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>line<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 删除内容</span><span class="token comment">// 对于文本文件，删除可以通过重写文件部分内容或整体覆盖实现</span><span class="token comment">// 例如打开文件后，跳过不需要的部分再写入其他内容</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="操作-CSV-csv"><a href="#操作-CSV-csv" class="headerlink" title="操作 CSV (. csv)"></a>操作 CSV (. csv)</h2><h3 id="写入-读取-CSV-文件"><a href="#写入-读取-CSV-文件" class="headerlink" title="写入&#x2F;读取 CSV 文件"></a>写入&#x2F;读取 CSV 文件</h3><pre class="line-numbers language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileWriter</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">BufferedReader</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">FileReader</span></span><span class="token punctuation">;</span><span class="token keyword">import</span> <span class="token import"><span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">IOException</span></span><span class="token punctuation">;</span><span class="token comment">// 写入CSV文件</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">FileWriter</span> writer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileWriter</span><span class="token punctuation">(</span><span class="token string">"output.csv"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    writer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"Name,Age\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    writer<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"John Doe,30\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    writer<span class="token punctuation">.</span><span class="token function">flush</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 读取CSV文件</span><span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">BufferedReader</span> reader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BufferedReader</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileReader</span><span class="token punctuation">(</span><span class="token string">"input.csv"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token class-name">String</span> line<span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>line <span class="token operator">=</span> reader<span class="token punctuation">.</span><span class="token function">readLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> data <span class="token operator">=</span> line<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 处理数据...</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">// 删除内容，CSV没有内置的“删除”概念，但可以通过重新写入文件时略过某些行来实现</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>请注意以上所有代码仅为简略示例，实际应用中应考虑异常处理、资源管理以及更复杂的文档结构操作。对于 Word、PDF 等格式，删除内容通常涉及查找、替换或重组文档结构。在使用任何第三方库前，请确保已经添加了对应的 Maven 或 Gradle 依赖项。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Java 中操作各种文件类型（如 Excel、Word、PDF、Markdown、纯文本和 CSV）需要不同的库来实现。以下是一些基本的代码示例，分别针对不同格式进行读写或修改内容的操作：&lt;/p&gt;
&lt;h2 id=&quot;操作-Excel-使用-Apache-POI-库&quot;&gt;&lt;a h</summary>
      
    
    
    
    <category term="后端" scheme="https://www.lazydaily.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="代码记录" scheme="https://www.lazydaily.cn/tags/%E4%BB%A3%E7%A0%81%E8%AE%B0%E5%BD%95/"/>
    
  </entry>
  
</feed>
