<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Gavin&#39;s blog</title>
  
  <subtitle>路漫漫其修远兮 吾将上下而求索</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://lugavin.github.io/"/>
  <updated>2026-05-09T06:54:58.891Z</updated>
  <id>https://lugavin.github.io/</id>
  
  <author>
    <name>Gavin Lu</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>币圈交易</title>
    <link href="https://lugavin.github.io/2025/06/21/life/trading-crypto/"/>
    <id>https://lugavin.github.io/2025/06/21/life/trading-crypto/</id>
    <published>2025-06-21T10:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.891Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>利润是跌出来的，每一轮回调都代表着我们要开始新一轮的财富暴涨了，所以交易的秘诀很简单：</p><ul><li>如果你能做到不贪心，你就已经领先了50%的人了；</li><li>如果你能判断对趋势和方向顺势而为，你就胜过70%的人了；</li><li>如果你能专注在价格回调的时候入场，你就会击败80%的人；</li><li>如果你能严格地控制单笔的持仓比例，做好资金管理，你就会跻身顶尖的交易者行列，胜过99%的人了。</li></ul><p><strong>交易不是拼的天赋，而是拼的纪律和耐心。</strong></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="life" scheme="https://lugavin.github.io/categories/life/"/>
    
    
      <category term="life" scheme="https://lugavin.github.io/tags/life/"/>
    
  </entry>
  
  <entry>
    <title>交易的反思</title>
    <link href="https://lugavin.github.io/2022/10/02/life/trading/"/>
    <id>https://lugavin.github.io/2022/10/02/life/trading/</id>
    <published>2022-10-02T15:30:00.000Z</published>
    <updated>2026-05-09T06:54:58.891Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><ol><li><p>关于止损</p><p>如果你是按你的思路去买，也按你的计划去卖，在“你的理论是正确的”这个前提下，是不是会出现割完之后上涨的情况？</p><p>如果股价上涨，必然是出现了一些你不可预测的因素，最常见的就是“某些大资金引导股价上升”，但这个是你不能掌握的；如果你不割肉，就是“赌”大资金会来抬高股价，那万一没有呢？这样下去只会套得更多！</p></li></ol><ol start="2"><li><p>关于亏损</p><p>每次赚钱后都会有一次大亏！这种现象绝不是偶然的，为什么？</p><p>当一个人成功后就会增加自信心，连续成功就会自信心爆棚，对自己的判断百分百认可，绝不会让自己失去任何赚钱机会。看到一个好股就马上买，如果买错小亏，不会让自己的信心减少多少，只有大亏时才会使自已信心大幅下降。之后出手谨慎，不断反思，失望、痛苦、懊悔、不甘……</p><p>在一个自由买卖的市场里，亏钱没有谁的错，错只在自己！我一直明白最大的问题是我自己，性格缺陷让我成为股市里的残疾人。我明知哪些钱能賺，却妄想掌控所有，不让任何机会错过；我明知做一件事的风险，却妄想会不会发生奇迹；我明知慢即是快的道理，却从未放慢自己的脚步。</p></li></ol><ol start="3"><li><p>关于踏空</p><p>为什么踏空比亏钱更难受？正常逻辑不应该这样，但实际也普遍存在，踏空是别人赚钱了而你没有！你认为如果你持仓了也必然会赚这么多，所个你把踏空看作是一种亏损。这种亏损与你持仓亏损的区别在于：持仓亏损是我技不如人，买错了，而踏空并不能证明我水平差，只是我没有参与！有这种感觉的人，大部分还是“自我认可度”过高！</p></li></ol><ol start="4"><li><p>关于如何看待别人赚钱</p><p>要学会看别人賺钱。如果一个市场没人赚钱，那必定没人来参与，只是这钟赚钱效应的传播，才使得有人不断加入，所以必定是有一部分人是赚钱的，牛市里这部分人比例高，熊市比例低，因此不要妄想自己赚钱而别人亏钱。</p><p>在市场上赚钱有两种：一种是能力，一种是运气。运气赚的钱不要羡慕，能力赚的钱不要眼红。要学习别人的优点，不要去比较谁赚得多、去超越别人，把不确定性的操作，从主现上认可，那就是赌！</p></li></ol><ol start="5"><li><p>关于一致性</p><p>忘掉每一次交易的盈亏，你的交易应该是按系统来，而不是随着情绪买入或卖出。</p><p>空仓等待最佳买点到来，最佳买点应该是确定性最大的买入点，如此下去才是复利。</p></li></ol><ol start="6"><li><p>关于满仓</p><p>为什么很多人喜欢满仓一个股？想一次爆赚，这是贪婪，想靠赌来快速增值，这是急功近利。这类人是由人类最原始的欲望赌性驱动，只想获得当下的利益，称之为低级赌徒；而高级赌徒则目光长远，他们追求的是长期利益。</p></li></ol><ol start="7"><li><p>关于知行合一</p><p>为什么很难知行合一？这主要是对行为所带来的结果没有深刻认识，不断试错，不断体会痛苦，才能知行合一，忘掉痛苦，就会随心所欲。</p></li></ol><ol start="8"><li><p>关于无我</p><p>当你还在为错失机会痛苦时，证明你内心还没有达到“无我”的境界，“无我”就是屏蔽自己的主观意识，达到“实事求是”的一种状态。</p><p>正确的操作并不一定会赚钱，但它是风险最小的一种做法。如果因为哪一次没有赚到钱就改变自己的做法，就会成为亏损的开始。</p></li></ol><ol start="9"><li><p>关于认知与财富</p><p>你永远赚不到超出你认知范围外的钱，除非你靠运气，但是靠运气赚到的钱，最后往往又会靠实力亏掉，这是一种必然。你所赚的每一分钱，都是你对这个世界认知的变现，而你所亏的每一分钱，都是因为对这个世界认知有缺陷。这个世界最大的公平在于：当一个人的财富大于自己认知的时候，这个社会有一百种方法收割你直到你的认知和财富相匹配为止。</p></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="life" scheme="https://lugavin.github.io/categories/life/"/>
    
    
      <category term="life" scheme="https://lugavin.github.io/tags/life/"/>
    
  </entry>
  
  <entry>
    <title>微服务契约测试</title>
    <link href="https://lugavin.github.io/2021/04/01/architect/distributed/contract/"/>
    <id>https://lugavin.github.io/2021/04/01/architect/distributed/contract/</id>
    <published>2021-04-01T20:10:00.000Z</published>
    <updated>2026-05-09T06:54:58.887Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>日常开发过程中，项目的接口通常由服务提供方约定和提供，微服务模式下接口被多个消费者调用更是常态，那么提供方接口的变更如何快速、高效、无遗漏的通知给消费者呢？另外，当一个service同时被多个使用者调用，如何保证对service的修改可以让其它所有使用者造成的影响都能被感知到？这些问题契约测试可以给你答案。</p><h2 id="测试问题"><a href="#测试问题" class="headerlink" title="测试问题"></a>测试问题</h2><p>假设我们有一个由多个微服务组成的应用程序，如下图所示：</p><p><img src="https://cloud.spring.io/spring-cloud-contract/reference/html/images/Deps.png" alt="Multiple Microservices"></p><p>如果我们想测试服务间通信时，通常有两种方式：</p><ol><li>部署所有微服务并执行端到端测试<ul><li>优点：<ul><li>模拟生产(真实集成场景)</li><li>测试服务之间的真实通信</li></ul></li><li>缺点：<ul><li>要测试一个微服务，我们必须部署其他相关的微服务，数据库及其他基础设施</li><li>运行时间过长，当其他服务不稳定或者请求时间过长时，就会导致测试效率很低，稳定性下降</li><li>反馈不及时</li><li>难以调试</li></ul></li></ul></li><li>在单元/集成测试中MOCK其他微服务（构建测试替身）<ul><li>优点：<ul><li>快速反馈</li><li>没有基础设施要求</li></ul></li><li>缺点：<ul><li>无法确保接口变动的安全性和准确性</li><li>测试可以通过但到生产时会失败（当内部系统测试都通过时，如何能保证真正的外部API没有变化？）</li></ul></li></ul></li></ol><p>Spring Cloud Contract契约测试的出现就是为了解决上述问题。主要思想是为您提供非常快速的反馈而无需建立整个微服务链路，使用存根(替身)，您唯一需要的就是您直接使用的应用程序，下图显示了存根与应用程序的关系：</p><p><img src="https://cloud.spring.io/spring-cloud-contract/reference/html/images/Stubs2.png" alt="SCC Stubs"></p><p>契约测试分两种类型，一种是消费者驱动，一种是提供者驱动，其中最常用的，是消费者驱动的契约测试（Consumer-Driven Contract Test，简称 CDC）。核心思想是从消费者业务实现的角度出发，由消费者端定义需要的数据格式以及交互细节，生成一份契约文件，然后生产者根据契约文件来实现自己的逻辑，并在持续集成环境中持续验证该实现结果是否正确。</p><p>对于基于Restful API的微服务来说，它的契约就是指 API 的请求和响应的规则：</p><ul><li><p>对于请求，包括请求URL、请求头、请求内容等</p></li><li><p>对于响应，包括状态码、响应头、响应内容等</p></li></ul><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">request:</span></span><br><span class="line">  <span class="attr">method:</span> <span class="string">POST</span></span><br><span class="line">  <span class="attr">url:</span> <span class="string">/api/orders</span></span><br><span class="line">  <span class="attr">bodyFromFile:</span> <span class="string">request.json</span></span><br><span class="line"><span class="attr">response:</span></span><br><span class="line">  <span class="attr">status:</span> <span class="number">201</span></span><br><span class="line">  <span class="attr">bodyFromFile:</span> <span class="string">response.json</span></span><br><span class="line">  <span class="attr">matchers:</span></span><br><span class="line">    <span class="attr">body:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">path:</span> <span class="string">$.retCode</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">by_equality</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">path:</span> <span class="string">$.retData.qrCode</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">by_regex</span></span><br><span class="line">        <span class="attr">predefined:</span> <span class="string">url</span></span><br></pre></td></tr></table></figure><h2 id="Contract-DSL-Dynamic-properties"><a href="#Contract-DSL-Dynamic-properties" class="headerlink" title="Contract DSL Dynamic properties"></a><a href="https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#contract-dsl-dynamic-properties" target="_blank" rel="noopener">Contract DSL Dynamic properties</a></h2><h3 id="Contract-DSL-Dynamic-properties-in-Body"><a href="#Contract-DSL-Dynamic-properties-in-Body" class="headerlink" title="Contract DSL Dynamic properties in Body"></a><a href="https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#contract-dsl-dynamic-properties-in-body" target="_blank" rel="noopener">Contract DSL Dynamic properties in Body</a></h3><blockquote><p>This section is valid only for the Coded DSL (Groovy, Java etc.)</p></blockquote><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.cloud.contract.spec.Contract.make &#123;</span><br><span class="line">    request &#123;</span><br><span class="line">        method(POST())</span><br><span class="line">        url($(consumer(<span class="string">'/rest-producer/api/books'</span>), producer(<span class="string">'/api/books'</span>)))</span><br><span class="line">    &#125;</span><br><span class="line">    response &#123;</span><br><span class="line">        status(OK())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Contract-DSL-Regex"><a href="#Contract-DSL-Regex" class="headerlink" title="Contract DSL Regex"></a><a href="https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#contract-dsl-regex" target="_blank" rel="noopener">Contract DSL Regex</a></h3><blockquote><p>This section is valid only for Groovy DSL. </p></blockquote><h3 id="Contract-DSL-Matchers"><a href="#Contract-DSL-Matchers" class="headerlink" title="Contract DSL Matchers"></a><a href="https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#contract-dsl-matchers" target="_blank" rel="noopener">Contract DSL Matchers</a></h3><h2 id="SCC契约测试集成"><a href="#SCC契约测试集成" class="headerlink" title="SCC契约测试集成"></a><a href="https://cloud.spring.io/spring-cloud-contract/reference/html/getting-started.html#getting-started-first-application" target="_blank" rel="noopener">SCC契约测试集成</a></h2><p><img src="https://cloud.spring.io/spring-cloud-contract/reference/html/images/getting-started-three-second.png" alt="SCC UML Diagram"></p><h2 id="契约测试能给我们带来什么？"><a href="#契约测试能给我们带来什么？" class="headerlink" title="契约测试能给我们带来什么？"></a>契约测试能给我们带来什么？</h2><ul><li><p>降低服务集成的难度：把服务集成这个过程分解成了更细的单元测试和接口测试，它从消费者的需求为出发点，把消费者的需求作为测试用例驱动实现一份契约，然后验证提供者端的功能；</p></li><li><p>开发并行，提高开发效率：契约隔离了消费者和提供者，双方可以并行开展工作，开发过程中就利用契约进行预集成测试，不用等到联调再来集成调通接口，一旦成熟，在保证质量的前提下，联调的成本可以减低到几乎为0；</p></li><li><p>服务接口变更快速感知，确保变动的安全性和准确性：只要有变化，契约测试即可第一时间发现，保证安全和对接的准确性。</p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在微服务模式下，服务间的调用关系复杂，契约测试是保证服务提高质量的重要手段之一，因此建议充分利用。</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="http://wiremock.org/" target="_blank" rel="noopener">WireMock</a></li><li><a href="https://github.com/json-path/JsonPath" target="_blank" rel="noopener">JsonPath</a></li><li><a href="https://cloud.spring.io/spring-cloud-contract/reference/html/" target="_blank" rel="noopener">Spring Cloud Contract</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="distributed" scheme="https://lugavin.github.io/tags/distributed/"/>
    
  </entry>
  
  <entry>
    <title>JWT</title>
    <link href="https://lugavin.github.io/2020/05/21/web/jwt/"/>
    <id>https://lugavin.github.io/2020/05/21/web/jwt/</id>
    <published>2020-05-21T21:35:00.000Z</published>
    <updated>2026-05-09T06:54:58.893Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>在设计Web应用程序时，安全认证是其中的关键部分。使用令牌进行认证是这方面的一个突破，而刷新令牌的出现，则是对其进行了补充并使其可用。</p><h2 id="认证"><a href="#认证" class="headerlink" title="认证"></a>认证</h2><p>身份认证系统根据如何验证用户可划分为：</p><ul><li>基于已知的东西（密码）</li><li>基于拥有的东西（身份证、U盘、令牌）</li><li>基于体貌特征（声音、指纹、眼睛）</li></ul><h3 id="基于令牌的身份认证"><a href="#基于令牌的身份认证" class="headerlink" title="基于令牌的身份认证"></a>基于令牌的身份认证</h3><p>令牌是通过现代认证和授权引入Web应用程序的。 我们可以说，由于OAuth协议，它的使用得到了扩展。这些都集中在授权上，而不是人们通常认为的身份验证上。当我们谈论到使用令牌进行身份验证时，我们可以将其分为两种类型：基于令牌的有状态认证和基于令牌的无状态认证。</p><h4 id="基于令牌的有状态认证"><a href="#基于令牌的有状态认证" class="headerlink" title="基于令牌的有状态认证"></a>基于令牌的有状态认证</h4><p>这是最常见的认证模式。当用户登录时，服务器会返回一个令牌，这个令牌通常存储在客户端浏览器的Cookie中，服务器会将会话信息保存在内存或数据库中（Redis、MongoDB…）。</p><p>因此，每次用户用该令牌发起请求时，服务器都会搜索存储的会话信息以识别出是哪个用户在尝试访问，如果用户信息有效，就会执行所请求的方法。</p><p>这种认证方式有几个问题，比如超载（存储所有被认证用户的信息会造成超载）、可扩展性（如果启动了多个服务器实例，为避免再次登录，将不得不以某种方式共享会话信息），除此之外，这种架构还存在着一些漏洞（CORS - 跨域资源共享、CSRF - 跨站请求伪造）。</p><h4 id="基于令牌的无状态认证"><a href="#基于令牌的无状态认证" class="headerlink" title="基于令牌的无状态认证"></a>基于令牌的无状态认证</h4><p>为了解决上述的这些问题，无状态认证出现了。这意味着服务器不会存储任何信息，也不会存储会话。</p><p>当用户使用凭证或其他方式进行身份验证时，在响应中将接收到一个访问令牌。从那一刻起，所有发起的API请求都会在HTTP头中携带这个令牌，这样服务器就可以识别出是哪个用户发起的请求，而不需要搜索数据库或其他存储系统。</p><p>使用这种认证方式，应用程序变得可扩展，因为是客户端本身存储其认证信息而不是服务器，这样一来，请求可以在没有同步的情况下到达任意的服务器实例；此外，不同的平台可以使用相同的API；同时，这也提高了安全性，避免了CSRF漏洞（因为没有会话），并且，如果我们在令牌中加入过期时间，安全性会更高。</p><h2 id="JWT认证"><a href="#JWT认证" class="headerlink" title="JWT认证"></a>JWT认证</h2><p>JWT是基于JSON的开放标准，用于创建允许应用程序或API资源使用的访问令牌。这个令牌将包含服务器识别所需要的用户信息以及其他可能有用的附加信息（如角色、权限等）。</p><p>访问令牌也可以设置一个有效期，一旦有效期过了，服务器将不再允许用户使用这个令牌访问资源。此时，用户必须通过重新认证或一些额外的方法（刷新令牌）来获得一个新的访问令牌。</p><p>JWT将JSON作为令牌中存储的信息要使用的内部格式。此外，如果与JWS和JWE结合使用，它会变得非常有用。将JWT与JWS和JWE结合，我们不仅可以对用户进行身份验证，还可以将加密后的信息发送出去，这样只有服务器才能提取加密后的信息出来，同时还可以对内容进行校验以确保没有被篡改。</p><p>JWT令牌由三个部分组成：头信息、消息体和签名，中间使用点号分割：Header.Payload.Signature</p><ul><li>头信息：Header 部分是一个 JSON 对象，描述 JWT 的元数据，如alg属性表示签名的算法，typ属性表示这个令牌的类型</li><li>消息体：Payload 部分也是一个 JSON 对象，用来存放实际需要传递的数据</li><li>签名：Signature 部分是对前两部分内容的签名，以防止数据被篡改</li></ul><h3 id="令牌类型"><a href="#令牌类型" class="headerlink" title="令牌类型"></a>令牌类型</h3><p>令牌有很多类型，不过在JWT认证中，最典型的是访问令牌和刷新令牌：</p><ul><li>访问令牌：它包含了服务器需要知道用户是否可以访问所请求资源的所有信息。访问令牌通常是过期的令牌，其有效期很短。</li><li>刷新令牌：刷新令牌用于生成一个新的访问令牌。通常情况下，如果访问令牌设置了有效期，一旦过期，用户就必须重新认证才能获得新的访问令牌。使用刷新令牌则可以跳过这一步，只需携带刷新令牌发起请求，就可以获得一个新的访问令牌，以允许用户继续访问应用资源。</li></ul><p>当用户访问之前没有访问过的资源时，可能也需要生成一个新的访问令牌，不过这取决于API实现的限制。</p><p>与访问令牌相比，刷新令牌在存储时需要更高的安全性，因为如果它被第三方窃取，他们就可以利用它来获得访问令牌以访问受保护的应用资源。为了减少这种情况的发生，除了设置一个明显比访问令牌更长的有效期之外，应用还必须在服务端实现使刷新令牌失效的功能。</p><h3 id="刷新令牌的实现"><a href="#刷新令牌的实现" class="headerlink" title="刷新令牌的实现"></a>刷新令牌的实现</h3><p>在这个例子中，我将跳过数据库部分，因而，尽管我会对其进行说明，但仍需要进行一些应该做的安全检查。我之所以这样做的原因是为了展示尽可能简单的代码，而并不限制任何永久性系统的实现。</p><p>在第一段代码中，我们只是简单地启动一个Node服务，就像其他应用程序一样。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">var</span> app = express()</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">8999</span>)</span><br></pre></td></tr></table></figure><p>我们首先要做的第一件事是添加一个验证用户身份的方法，最典型的是使用用户名和密码。为了简化代码，未从数据库中进行校验，且允许我们访问所有用户。</p><p>在方法返回值中，我们将同时返回访问令牌和刷新令牌。正如我们在实现中看到的那样，令牌的有效期为300秒。</p><p>对于访问令牌，我们使用 <a href="https://github.com/auth0/node-jsonwebtoken" target="_blank" rel="noopener">jsonwebtoken</a> 模块来加密并生成签名，即我们只需将要加密的对象以及用于加密和解密的密钥传递给它，就可自动生成JWT令牌。</p><p>对于刷新令牌，我们将简单地生成一个UID，并将其与相关联的用户名一起储存在内存中。在完整实现中，我们可以将用户的信息、令牌的创建时间和失效时间保存在数据库中。</p><p>虽然刷新令牌也可以是自包含令牌，就像我们创建的访问令牌一样，这种实现的好处是不用访问数据库来获取必要的信息。但如果是自包含令牌的话，我们将无法知道刷新令牌是否已被加入黑名单或被管理员覆写，又或者，如果用户被管理员禁用了，我们也无法知晓。这就是为什么我更喜欢通过不带自包含信息来实现这种类型的令牌。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>)</span><br><span class="line"><span class="keyword">var</span> jwt = <span class="built_in">require</span>(<span class="string">'jsonwebtoken'</span>) </span><br><span class="line"><span class="keyword">var</span> randtoken = <span class="built_in">require</span>(<span class="string">'rand-token'</span>) </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> refreshTokens = &#123;&#125; </span><br><span class="line"><span class="keyword">var</span> SECRET = <span class="string">"SECRETO_PARA_ENCRIPTACION"</span> </span><br><span class="line">app.use(bodyParser.json()) </span><br><span class="line">app.use(bodyParser.urlencoded(&#123; <span class="attr">extended</span>: <span class="literal">true</span> &#125;)) </span><br><span class="line"></span><br><span class="line">app.post(<span class="string">'/login'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>&#123; </span><br><span class="line">  <span class="keyword">var</span> username = req.body.username </span><br><span class="line">  <span class="keyword">var</span> password = req.body.password</span><br><span class="line">  <span class="keyword">var</span> user = &#123; </span><br><span class="line">    <span class="string">'username'</span>: username, </span><br><span class="line">    <span class="string">'role'</span>: <span class="string">'admin'</span> </span><br><span class="line">  &#125; </span><br><span class="line">  <span class="keyword">var</span> token = jwt.sign(user, SECRET, &#123; <span class="attr">expiresIn</span>: <span class="number">300</span> &#125;) </span><br><span class="line">  <span class="keyword">var</span> refreshToken = randtoken.uid(<span class="number">256</span>) </span><br><span class="line">  refreshTokens[refreshToken] = username</span><br><span class="line">  res.json(&#123;<span class="attr">token</span>: <span class="string">'JWT '</span> + token, <span class="attr">refreshToken</span>: refreshToken&#125;) </span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>为了请求一个新的访问令牌，我们创建了 /token 资源。在该API中，我们接收刷新令牌和拥有该令牌的用户名。在这里，我们要做的是检查刷新令牌列表中是否包含发送给我们的令牌，并且该令牌具有相同的用户名。如果正确的话，我们会生成一个新的令牌，其中包含用户的信息（我们会从数据库中获取）并返回。</p><p>在应用中，如果管理员可以暂时禁用用户或刷新令牌，则我们还必须在生成新的访问令牌之前对其进行检查。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">app.post(<span class="string">'/token'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> username = req.body.username</span><br><span class="line">  <span class="keyword">var</span> refreshToken = req.body.refreshToken</span><br><span class="line">  <span class="keyword">if</span>((refreshToken <span class="keyword">in</span> refreshTokens) &amp;&amp; (refreshTokens[refreshToken] == username)) &#123;</span><br><span class="line">    <span class="keyword">var</span> user = &#123;</span><br><span class="line">      <span class="string">'username'</span>: username,</span><br><span class="line">      <span class="string">'role'</span>: <span class="string">'admin'</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> token = jwt.sign(user, SECRET, &#123; <span class="attr">expiresIn</span>: <span class="number">300</span> &#125;)</span><br><span class="line">    res.json(&#123;<span class="attr">token</span>: <span class="string">'JWT '</span> + token&#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    res.send(<span class="number">401</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>在这种架构中，有必要有一种方法可来禁用刷新令牌，以避免冒名顶替和滥用。</p><p>在应用程序中，用户可以使用同一个身份（相同的用户名）在不同设备上工作，但每个设备上使用不同的令牌，如果其中一个令牌丢失或被盗，这个方法将允许管理员删除或禁用该刷新令牌，而不会影响用户在其他设备上的服务，也不需要重新认证、修改密码等。也就是说，用户可以继续工作而不受任何影响，也没有从被盗设备生成新访问令牌的风险。我们建议访问令牌设置较短的有效期，这样在这种情况下，可以快速恢复到安全状态。</p><p>为此，我们创建了一个 /token/reject 资源来禁用刷新令牌。在本示例中，我们只需将其从内存列表中删除即可。在完整的实现中，有必要验证发起请求是否是管理员或其他对此资源具有权限的用户。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">app.post(<span class="string">'/token/reject'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>&#123; </span><br><span class="line">  <span class="keyword">var</span> refreshToken = req.body.refreshToken </span><br><span class="line">  <span class="keyword">if</span>(refreshToken <span class="keyword">in</span> refreshTokens) &#123; </span><br><span class="line">    <span class="keyword">delete</span> refreshTokens[refreshToken]</span><br><span class="line">  &#125; </span><br><span class="line">  res.send(<span class="number">204</span>) </span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>最后，我们将公开只能通过发送附带有先前获得的JWT令牌的请求头信息才能访问的资源，该令牌将由我们的应用程序生成并与使用我们的密钥（SECRET）签名。</p><p>在本示例中，我们将使用 <a href="http://passportjs.org/" target="_blank" rel="noopener">Passport</a> 模块。Passport是Nodejs中用于身份验证的中间件，它非常灵活且模块化，这体现在大量的模块中，每个模块都实现了不同的身份验证策略（JWT、Twitter、Facebook、Google、Auth0、SAML等多达300种）。我们可以使用其中的任何一种，导入和配置都很简单，把最复杂的认证部分交给Passport来完成即可。</p><p>首先，我们要加载中间件和必要的对象。Passport要求我们实现serializeUser方法（根据策略也可以实现deserializeUser），该方法用于将识别所需的用户信息储存在会话中。在我们的例子中，我们用用户名做索引，但理想的情况是使用ID。</p><p>事实上，既然是无状态认证，那么会话就没有意义了，如果我们只用JWT，Passport将永远不会使用 deserializeUser 反序列化方法，我把它注释掉，以防引入新的策略时会用到。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> passport = <span class="built_in">require</span>(<span class="string">'passport'</span>)</span><br><span class="line"><span class="keyword">var</span> JwtStrategy = <span class="built_in">require</span>(<span class="string">'passport-jwt'</span>).Strategy</span><br><span class="line"><span class="keyword">var</span> ExtractJwt = <span class="built_in">require</span>(<span class="string">'passport-jwt'</span>).ExtractJwt</span><br><span class="line"></span><br><span class="line">app.use(passport.initialize())</span><br><span class="line">app.use(passport.session())</span><br><span class="line"></span><br><span class="line">passport.serializeUser(<span class="function"><span class="keyword">function</span> (<span class="params">user, done</span>) </span>&#123;</span><br><span class="line">  done(<span class="literal">null</span>, user.username)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">passport.deserializeUser(function (username, done) &#123;</span></span><br><span class="line"><span class="comment">  done(null, username)</span></span><br><span class="line"><span class="comment">&#125;)</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>最后，我们需要做的是，每当请求到达需要身份认证的资源时，从令牌中提取信息并进行处理（变量jwtPayload将有我们在用户登录时加密的用户对象）。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> token = jwt.sign(user, SECRET, &#123; <span class="attr">expiresIn</span>: <span class="number">300</span> &#125;)</span><br></pre></td></tr></table></figure><p>Passport身份验证策略配置如下：<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> opts = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Setup JWT options</span></span><br><span class="line">opts.jwtFromRequest = ExtractJwt.fromAuthHeader()</span><br><span class="line">opts.secretOrKey = SECRET</span><br><span class="line"></span><br><span class="line">passport.use(<span class="keyword">new</span> JwtStrategy(opts, <span class="function"><span class="keyword">function</span> (<span class="params">jwtPayload, done</span>) </span>&#123;</span><br><span class="line">  <span class="comment">//If the token has expiration, raise unauthorized</span></span><br><span class="line">  <span class="keyword">var</span> expirationDate = <span class="keyword">new</span> <span class="built_in">Date</span>(jwtPayload.exp * <span class="number">1000</span>)</span><br><span class="line">  <span class="keyword">if</span>(expirationDate &lt; <span class="keyword">new</span> <span class="built_in">Date</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> done(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">var</span> user = jwtPayload</span><br><span class="line">  done(<span class="literal">null</span>, user)</span><br><span class="line">&#125;))</span><br></pre></td></tr></table></figure></p><p>我们将创建用于测试身份验证的 /test_jwt 资源，且只需告诉Passport，通过 jwt 策略，可以访问该路径来对进行身份验证。这样我们就有了一个想法，即通过Passport，我们可以使用不同的策略对每个资源进行身份验证，从而以非常简单的方式给我们提供了极大的灵活性。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">app.get(<span class="string">'/test_jwt'</span>, passport.authenticate(<span class="string">'jwt'</span>), <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.json(&#123;<span class="attr">success</span>: <span class="string">'You are authenticated with JWT!'</span>, <span class="attr">user</span>: req.user&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>通过使用JWT，我们可以避免多次数据库调用，从而降低延迟，提高应用的效率。此外，通过使用刷新令牌，我们提高了这种架构的安全性和可用性。</p><p>在大量的项目中，使用令牌进行认证是非常有用的，但它不是解决所有问题、为所有产品服务的圣杯，但我们在提出任何解决方案时都必须考虑到它。</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://solidgeargroup.com/en/refresh-token-with-jwt-authentication-node-js/" target="_blank" rel="noopener">Refresh token with JWT authentication in Node.js</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="web" scheme="https://lugavin.github.io/categories/web/"/>
    
    
      <category term="jwt" scheme="https://lugavin.github.io/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>Concurrent - NIO vs IO</title>
    <link href="https://lugavin.github.io/2020/05/21/architect/concurrent/nio-vs-io/"/>
    <id>https://lugavin.github.io/2020/05/21/architect/concurrent/nio-vs-io/</id>
    <published>2020-05-21T21:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.886Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>When studying both the Java NIO and IO API’s, a question quickly pops into mind:</p><p>When should I use IO and when should I use NIO?</p><p>In this text I will try to shed some light on the differences between Java NIO and IO, their use cases, and how they affect the design of your code.</p><h2 id="Main-Differences-Betwen-Java-NIO-and-IO"><a href="#Main-Differences-Betwen-Java-NIO-and-IO" class="headerlink" title="Main Differences Betwen Java NIO and IO"></a>Main Differences Betwen Java NIO and IO</h2><p>The table below summarizes the main differences between Java NIO and IO. I will get into more detail about each difference in the sections following the table.</p><table><thead><tr><th><strong>IO</strong></th><th><strong>NIO</strong></th></tr></thead><tbody><tr><td>Stream oriented</td><td>Buffer oriented</td></tr><tr><td>Blocking IO</td><td>Non blocking IO</td></tr><tr><td></td><td>Selectors</td></tr></tbody></table><h2 id="Stream-Oriented-vs-Buffer-Oriented"><a href="#Stream-Oriented-vs-Buffer-Oriented" class="headerlink" title="Stream Oriented vs. Buffer Oriented"></a>Stream Oriented vs. Buffer Oriented</h2><p>The first big difference between Java NIO and IO is that IO is stream oriented, where NIO is buffer oriented. So, what does that mean?</p><p>Java IO being stream oriented means that you read one or more bytes at a time, from a stream. What you do with the read bytes is up to you. They are not cached anywhere. Furthermore, you cannot move forth and back in the data in a stream. If you need to move forth and back in the data read from a stream, you will need to cache it in a buffer first.</p><p>Java NIO’s buffer oriented approach is slightly different. Data is read into a buffer from which it is later processed. You can move forth and back in the buffer as you need to. This gives you a bit more flexibility during processing. However, you also need to check if the buffer contains all the data you need in order to fully process it. And, you need to make sure that when reading more data into the buffer, you do not overwrite data in the buffer you have not yet processed.</p><h2 id="Blocking-vs-Non-blocking-IO"><a href="#Blocking-vs-Non-blocking-IO" class="headerlink" title="Blocking vs. Non-blocking IO"></a>Blocking vs. Non-blocking IO</h2><p><strong>Java IO’s various streams are blocking. That means, that when a thread invokes a read() or write(), that thread is blocked until there is some data to read, or the data is fully written. The thread can do nothing else in the meantime.</strong></p><p>Java NIO’s non-blocking mode enables a thread to request reading data from a channel, and only get what is currently available, or nothing at all, if no data is currently available. Rather than remain blocked until data becomes available for reading, the thread can go on with something else.</p><p>The same is true for non-blocking writing. A thread can request that some data be written to a channel, but not wait for it to be fully written. The thread can then go on and do something else in the mean time.</p><p>What threads spend their idle time on when not blocked in IO calls, is usually performing IO on other channels in the meantime. That is, a single thread can now manage multiple channels of input and output.</p><h2 id="Selectors"><a href="#Selectors" class="headerlink" title="Selectors"></a>Selectors</h2><p>Java NIO’s selectors allow a single thread to monitor multiple channels of input. You can register multiple channels with a selector, then use a single thread to “select” the channels that have input available for processing, or select the channels that are ready for writing. This selector mechanism makes it easy for a single thread to manage multiple channels.</p><h2 id="How-NIO-and-IO-Influences-Application-Design"><a href="#How-NIO-and-IO-Influences-Application-Design" class="headerlink" title="How NIO and IO Influences Application Design"></a>How NIO and IO Influences Application Design</h2><p>Whether you choose NIO or IO as your IO toolkit may impact the following aspects of your application design:</p><ol><li>The API calls to the NIO or IO classes.</li><li>The processing of data.</li><li>The number of thread used to process the data.</li></ol><h3 id="The-API-Calls"><a href="#The-API-Calls" class="headerlink" title="The API Calls"></a>The API Calls</h3><p>Of course the API calls when using NIO look different than when using IO. This is no surprise. Rather than just read the data byte for byte from e.g. an InputStream, the data must first be read into a buffer, and then be processed from there.</p><h3 id="The-Processing-of-Data"><a href="#The-Processing-of-Data" class="headerlink" title="The Processing of Data"></a>The Processing of Data</h3><p>The processing of the data is also affected when using a pure NIO design, vs. an IO design.</p><p>In an IO design you read the data byte for byte from an InputStream or a Reader. Imagine you were processing a stream of line based textual data. For instance: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Name: Anna</span><br><span class="line">Age: 25</span><br><span class="line">Email: anna@mailserver.com</span><br><span class="line">Phone: 1234567890</span><br></pre></td></tr></table></figure><p>This stream of text lines could be processed like this: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">InputStream input = ... ; <span class="comment">// get the InputStream from the client socket</span></span><br><span class="line">BufferedReader reader = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> InputStreamReader(input));</span><br><span class="line">String nameLine   = reader.readLine();</span><br><span class="line">String ageLine    = reader.readLine();</span><br><span class="line">String emailLine  = reader.readLine();</span><br><span class="line">String phoneLine  = reader.readLine();</span><br></pre></td></tr></table></figure><p>Notice how the processing state is determined by how far the program has executed. In other words, once the first reader.readLine() method returns, you know for sure that a full line of text has been read. The readLine() blocks until a full line is read, that’s why. You also know that this line contains the name. Similarly, when the second readLine() call returns, you know that this line contains the age etc.</p><p>As you can see, the program progresses only when there is new data to read, and for each step you know what that data is. Once the executing thread have progressed past reading a certain piece of data in the code, the thread is not going backwards in the data (mostly not). This principle is also illustrated in this diagram: </p><p><img src="/images/architect/concurrent-nio-vs-io-1.png" alt="NIO"></p><p>A NIO implementation would look different. Here is a simplified example: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ByteBuffer buffer = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="keyword">int</span> bytesRead = inChannel.read(buffer);</span><br></pre></td></tr></table></figure><p>Notice the second line which reads bytes from the channel into the ByteBuffer. When that method call returns you don’t know if all the data you need is inside the buffer. All you know is that the buffer contains some bytes. This makes processing somewhat harder.</p><p><strong>Imagine if, after the first read(buffer) call, that all what was read into the buffer was half a line. For instance, “Name: An”. Can you process that data? Not really. You need to wait until at leas a full line of data has been into the buffer, before it makes sense to process any of the data at all.</strong></p><p>So how do you know if the buffer contains enough data for it to make sense to be processed? Well, you don’t. The only way to find out, is to look at the data in the buffer. The result is, that you may have to inspect the data in the buffer several times before you know if all the data is inthere. This is both inefficient, and can become messy in terms of program design. For instance: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ByteBuffer buffer = ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="keyword">int</span> bytesRead = inChannel.read(buffer);</span><br><span class="line"><span class="keyword">while</span> (!bufferFull(bytesRead)) &#123;</span><br><span class="line">    bytesRead = inChannel.read(buffer);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The bufferFull() method has to keep track of how much data is read into the buffer, and return either true or false, depending on whether the buffer is full. In other words, if the buffer is ready for processing, it is considered full.</p><p>The bufferFull() method scans through the buffer, but must leave the buffer in the same state as before the bufferFull() method was called. If not, the next data read into the buffer might not be read in at the correct location. This is not impossible, but it is yet another issue to watch out for.</p><p>If the buffer is full, it can be processed. If it is not full, you might be able to partially process whatever data is there, if that makes sense in your particular case. In many cases it doesn’t.</p><p>The is-data-in-buffer-ready loop is illustrated in this diagram: </p><p><img src="/images/architect/concurrent-nio-vs-io-2.png" alt="NIO"></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p><strong>NIO allows you to manage multiple channels (network connections or files) using only a single (or few) threads, but the cost is that parsing the data might be somewhat more complicated than when reading data from a blocking stream.</strong></p><p>If you need to manage thousands of open connections simultanously, which each only send a little data, for instance a chat server, implementing the server in NIO is probably an advantage. Similarly, if you need to keep a lot of open connections to other computers, e.g. in a P2P network, using a single thread to manage all of your outbound connections might be an advantage. This one thread, multiple connections design is illustrated in this diagram: </p><p><img src="/images/architect/concurrent-nio-vs-io-3.png" alt="NIO"></p><p>If you have fewer connections with very high bandwidth, sending a lot of data at a time, perhaps a classic IO server implementation might be the best fit. This diagram illustrates a classic IO server design: </p><p><img src="/images/architect/concurrent-nio-vs-io-4.png" alt="NIO"></p><h2 id="Links"><a href="#Links" class="headerlink" title="Links"></a>Links</h2><ul><li><a href="http://tutorials.jenkov.com/java-nio/nio-vs-io.html" target="_blank" rel="noopener">Java Concurrency and Multithreading Tutorial</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="concurrent" scheme="https://lugavin.github.io/tags/concurrent/"/>
    
  </entry>
  
  <entry>
    <title>Concurrent - Volatile</title>
    <link href="https://lugavin.github.io/2020/05/19/architect/concurrent/volatile/"/>
    <id>https://lugavin.github.io/2020/05/19/architect/concurrent/volatile/</id>
    <published>2020-05-19T22:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.886Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>The Java volatile keyword is used to mark a Java variable as “being stored in main memory”. More precisely that means, that every read of a volatile variable will be read from the computer’s main memory, and not from the CPU cache, and that every write to a volatile variable will be written to main memory, and not just to the CPU cache.</p><p>Actually, since Java 5 the volatile keyword guarantees more than just that volatile variables are written to and read from main memory. I will explain that in the following sections.</p><h2 id="Variable-Visibility-Problems"><a href="#Variable-Visibility-Problems" class="headerlink" title="Variable Visibility Problems"></a>Variable Visibility Problems</h2><p>In a multithreaded application where the threads operate on non-volatile variables, each thread may copy variables from main memory into a CPU cache while working on them, for performance reasons. If your computer contains more than one CPU, each thread may run on a different CPU. That means, that each thread may copy the variables into the CPU cache of different CPUs. This is illustrated here: </p><p><img src="/images/architect/concurrent-volatile-1.png" alt="Volatile"></p><p>With non-volatile variables there are no guarantees about when the Java Virtual Machine (JVM) reads data from main memory into CPU caches, or writes data from CPU caches to main memory. This can cause several problems which I will explain in the following sections.</p><p>Imagine a situation in which two or more threads have access to a shared object which contains a counter variable declared like this: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SharedObject</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> counter = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Imagine too, that only Thread 1 increments the counter variable, but both Thread 1 and Thread 2 may read the counter variable from time to time.</p><p>If the counter variable is not declared volatile there is no guarantee about when the value of the counter variable is written from the CPU cache back to main memory. This means, that the counter variable value in the CPU cache may not be the same as in main memory. This situation is illustrated here: </p><p><img src="/images/architect/concurrent-volatile-2.png" alt="Volatile"></p><p>The problem with threads not seeing the latest value of a variable because it has not yet been written back to main memory by another thread, is called a “visibility” problem. The updates of one thread are not visible to other threads.</p><h2 id="The-Java-volatile-Visibility-Guarantee"><a href="#The-Java-volatile-Visibility-Guarantee" class="headerlink" title="The Java volatile Visibility Guarantee"></a>The Java volatile Visibility Guarantee</h2><p>The Java volatile keyword is intended to address variable visibility problems. By declaring the counter variable volatile all writes to the counter variable will be written back to main memory immediately. Also, all reads of the counter variable will be read directly from main memory.</p><p>Here is how the volatile declaration of the counter variable looks: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SharedObject</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">volatile</span> <span class="keyword">int</span> counter = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Declaring a variable volatile thus guarantees the visibility for other threads of writes to that variable.</p><p>In the scenario given above, where one thread (T1) modifies the counter, and another thread (T2) reads the counter (<strong>but never modifies it</strong>), declaring the counter variable volatile is enough to guarantee visibility for T2 of writes to the counter variable.</p><p>If, however, <strong>both T1 and T2 were incrementing the counter variable, then declaring the counter variable volatile would not have been enough</strong>. More on that later.</p><h3 id="Full-volatile-Visibility-Guarantee"><a href="#Full-volatile-Visibility-Guarantee" class="headerlink" title="Full volatile Visibility Guarantee"></a>Full volatile Visibility Guarantee</h3><p>Actually, the visibility guarantee of Java volatile goes beyond the volatile variable itself. The visibility guarantee is as follows:</p><ul><li>If Thread A writes to a volatile variable and Thread B subsequently reads the same volatile variable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.</li><li>If Thread A reads a volatile variable, then all all variables visible to Thread A when reading the volatile variable will also be re-read from main memory.</li></ul><p>Let me illustrate that with a code example: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> years;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> months</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> days;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">int</span> years, <span class="keyword">int</span> months, <span class="keyword">int</span> days)</span></span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.years  = years;</span><br><span class="line">        <span class="keyword">this</span>.months = months;</span><br><span class="line">        <span class="keyword">this</span>.days   = days;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The udpate() method writes three variables, of which only days is volatile.</p><p>The full volatile visibility guarantee means, that when a value is written to days, then all variables visible to the thread are also written to main memory. That means, that when a value is written to days, the values of years and months are also written to main memory.</p><p>When reading the values of years, months and days you could do it like this: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> years;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> months</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> days;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">totalDays</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> total = <span class="keyword">this</span>.days;</span><br><span class="line">        total += months * <span class="number">30</span>;</span><br><span class="line">        total += years * <span class="number">365</span>;</span><br><span class="line">        <span class="keyword">return</span> total;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">int</span> years, <span class="keyword">int</span> months, <span class="keyword">int</span> days)</span></span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.years  = years;</span><br><span class="line">        <span class="keyword">this</span>.months = months;</span><br><span class="line">        <span class="keyword">this</span>.days   = days;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice the totalDays() method starts by reading the value of days into the total variable. When reading the value of days, the values of months and years are also read into main memory. Therefore you are guaranteed to see the latest values of days, months and years with the above read sequence.</p><h2 id="Instruction-Reordering-Challenges"><a href="#Instruction-Reordering-Challenges" class="headerlink" title="Instruction Reordering Challenges"></a>Instruction Reordering Challenges</h2><p>The Java VM and the CPU are allowed to reorder instructions in the program for performance reasons, as long as the semantic meaning of the instructions remain the same. For instance, look at the following instructions: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">int</span> b = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">a++;</span><br><span class="line">b++;</span><br></pre></td></tr></table></figure><p>These instructions could be reordered to the following sequence without losing the semantic meaning of the program: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> a = <span class="number">1</span>;</span><br><span class="line">a++;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> b = <span class="number">2</span>;</span><br><span class="line">b++;</span><br></pre></td></tr></table></figure><p>However, instruction reordering present a challenge when one of the variables is a volatile variable. Let us look at the MyClass class from the example earlier in this Java volatile tutorial: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> years;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> months</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">int</span> days;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">int</span> years, <span class="keyword">int</span> months, <span class="keyword">int</span> days)</span></span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.years  = years;</span><br><span class="line">        <span class="keyword">this</span>.months = months;</span><br><span class="line">        <span class="keyword">this</span>.days   = days;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Once the update() method writes a value to days, the newly written values to years and months are also written to main memory. But, what if the Java VM reordered the instructions, like this: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">update</span><span class="params">(<span class="keyword">int</span> years, <span class="keyword">int</span> months, <span class="keyword">int</span> days)</span></span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.days   = days;</span><br><span class="line">    <span class="keyword">this</span>.months = months;</span><br><span class="line">    <span class="keyword">this</span>.years  = years;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The values of months and years are still written to main memory when the days variable is modified, but this time it happens before the new values have been written to months and years. The new values are thus not properly made visible to other threads. The semantic meaning of the reordered instructions has changed.</p><p>Java has a solution for this problem, as we will see in the next section.</p><h2 id="The-Java-volatile-Happens-Before-Guarantee"><a href="#The-Java-volatile-Happens-Before-Guarantee" class="headerlink" title="The Java volatile Happens-Before Guarantee"></a>The Java volatile Happens-Before Guarantee</h2><p>To address the instruction reordering challenge, the Java volatile keyword gives a “happens-before” guarantee, in addition to the visibility guarantee. The happens-before guarantee guarantees that:</p><ul><li>Reads from and writes to other variables cannot be reordered to occur after a write to a volatile variable, if the reads / writes originally occurred before the write to the volatile variable. The reads / writes before a write to a volatile variable are guaranteed to “happen before” the write to the volatile variable. Notice that it is still possible for e.g. reads / writes of other variables located after a write to a volatile to be reordered to occur before that write to the volatile. Just not the other way around. From after to before is allowed, but from before to after is not allowed.</li><li>Reads from and writes to other variables cannot be reordered to occur before a read of a volatile variable, if the reads / writes originally occurred after the read of the volatile variable. Notice that it is possible for reads of other variables that occur before the read of a volatile variable can be reordered to occur after the read of the volatile. Just not the other way around. From before to after is allowed, but from after to before is not allowed.</li></ul><p>The above happens-before guarantee assures that the visibility guarantee of the volatile keyword are being enforced.</p><h2 id="volatile-is-Not-Always-Enough"><a href="#volatile-is-Not-Always-Enough" class="headerlink" title="volatile is Not Always Enough"></a>volatile is Not Always Enough</h2><p>Even if the volatile keyword guarantees that all reads of a volatile variable are read directly from main memory, and all writes to a volatile variable are written directly to main memory, there are still situations where it is not enough to declare a variable volatile.</p><p>In the situation explained earlier where only Thread 1 writes to the shared counter variable, declaring the counter variable volatile is enough to make sure that Thread 2 always sees the latest written value.</p><p>In fact, multiple threads could even be writing to a shared volatile variable, and still have the correct value stored in main memory, if the new value written to the variable does not depend on its previous value. In other words, if a thread writing a value to the shared volatile variable does not first need to read its value to figure out its next value.</p><p>As soon as a thread needs to first read the value of a volatile variable, and based on that value generate a new value for the shared volatile variable, a volatile variable is no longer enough to guarantee correct visibility. The short time gap in between the reading of the volatile variable and the writing of its new value, creates an race condition where multiple threads might read the same value of the volatile variable, generate a new value for the variable, and when writing the value back to main memory - overwrite each other’s values.</p><p>The situation where multiple threads are incrementing the same counter is exactly such a situation where a volatile variable is not enough. The following sections explain this case in more detail.</p><p>Imagine if Thread 1 reads a shared counter variable with the value 0 into its CPU cache, increment it to 1 and not write the changed value back into main memory. Thread 2 could then read the same counter variable from main memory where the value of the variable is still 0, into its own CPU cache. Thread 2 could then also increment the counter to 1, and also not write it back to main memory. This situation is illustrated in the diagram below: </p><p><img src="/images/architect/concurrent-volatile-3.png" alt="Volatile"></p><p>Thread 1 and Thread 2 are now practically out of sync. The real value of the shared counter variable should have been 2, but each of the threads has the value 1 for the variable in their CPU caches, and in main memory the value is still 0. It is a mess! Even if the threads eventually write their value for the shared counter variable back to main memory, the value will be wrong.</p><h2 id="When-is-volatile-Enough"><a href="#When-is-volatile-Enough" class="headerlink" title="When is volatile Enough?"></a>When is volatile Enough?</h2><p>As I have mentioned earlier, <strong>if two threads are both reading and writing to a shared variable, then using the volatile keyword for that is not enough</strong>. You need to use a <a href="http://tutorials.jenkov.com/java-concurrency/synchronized.html" target="_blank" rel="noopener">synchronized</a> in that case to guarantee that the reading and writing of the variable is atomic. Reading or writing a volatile variable does not block threads reading or writing. For this to happen you must use the synchronized keyword around critical sections.</p><p>As an alternative to a synchronized block you could also use one of the many atomic data types found in the <a href="http://tutorials.jenkov.com/java-util-concurrent/index.html" target="_blank" rel="noopener">java.util.concurrent</a> package. For instance, the <a href="http://tutorials.jenkov.com/java-util-concurrent/atomiclong.html" target="_blank" rel="noopener">AtomicLong</a> or <a href="http://tutorials.jenkov.com/java-util-concurrent/atomicreference.html" target="_blank" rel="noopener">AtomicReference</a> or one of the others.</p><p><strong>In case only one thread reads and writes the value of a volatile variable and other threads only read the variable, then the reading threads are guaranteed to see the latest value written to the volatile variable</strong>. Without making the variable volatile, this would not be guaranteed.</p><p>The volatile keyword is guaranteed to work on 32 bit and 64 variables.</p><h2 id="Performance-Considerations-of-volatile"><a href="#Performance-Considerations-of-volatile" class="headerlink" title="Performance Considerations of volatile"></a>Performance Considerations of volatile</h2><p>Reading and writing of volatile variables causes the variable to be read or written to main memory. Reading from and writing to main memory is more expensive than accessing the CPU cache. <strong>Accessing volatile variables also prevent instruction reordering which is a normal performance enhancement technique</strong>. Thus, you should only use volatile variables when you really need to enforce visibility of variables.</p><h2 id="Links"><a href="#Links" class="headerlink" title="Links"></a>Links</h2><ul><li><a href="http://tutorials.jenkov.com/java-concurrency/volatile.html" target="_blank" rel="noopener">Java Concurrency and Multithreading Tutorial</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="concurrent" scheme="https://lugavin.github.io/tags/concurrent/"/>
    
  </entry>
  
  <entry>
    <title>Concurrent - JMM</title>
    <link href="https://lugavin.github.io/2020/05/18/architect/concurrent/jmm/"/>
    <id>https://lugavin.github.io/2020/05/18/architect/concurrent/jmm/</id>
    <published>2020-05-18T21:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.886Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>The Java memory model specifies how the Java virtual machine works with the computer’s memory (RAM). The Java virtual machine is a model of a whole computer so this model naturally includes a memory model - AKA the Java memory model.</p><p>It is very important to understand the Java memory model if you want to design correctly behaving concurrent programs. The Java memory model specifies how and when different threads can see values written to shared variables by other threads, and how to synchronize access to shared variables when necessary.</p><h2 id="Java-Memory-Model-JMM"><a href="#Java-Memory-Model-JMM" class="headerlink" title="Java Memory Model (JMM)"></a>Java Memory Model (JMM)</h2><p>The Java memory model used internally in the JVM divides memory between thread stacks and the heap. This diagram illustrates the Java memory model from a logic perspective: </p><p><img src="/images/architect/concurrent-jmm-1.png" alt="JMM"></p><p>Each thread running in the Java virtual machine has its own thread stack. The thread stack contains information about what methods the thread has called to reach the current point of execution. I will refer to this as the “call stack”. As the thread executes its code, the call stack changes.</p><p>The thread stack also contains all local variables for each method being executed (all methods on the call stack). A thread can only access it’s own thread stack. Local variables created by a thread are invisible to all other threads than the thread who created it. Even if two threads are executing the exact same code, the two threads will still create the local variables of that code in each their own thread stack. Thus, each thread has its own version of each local variable.</p><p>All local variables of primitive types ( boolean, byte, short, char, int, long, float, double) are fully stored on the thread stack and are thus not visible to other threads. One thread may pass a copy of a pritimive variable to another thread, but it cannot share the primitive local variable itself.</p><p>The heap contains all objects created in your Java application, regardless of what thread created the object. This includes the object versions of the primitive types (e.g. Byte, Integer, Long etc.). It does not matter if an object was created and assigned to a local variable, or created as a member variable of another object, the object is still stored on the heap.</p><p>Here is a diagram illustrating the call stack and local variables stored on the thread stacks, and objects stored on the heap: </p><p><img src="/images/architect/concurrent-jmm-2.png" alt="JMM"></p><p>A local variable may be of a primitive type, in which case it is totally kept on the thread stack.</p><p>A local variable may also be a reference to an object. In that case the reference (the local variable) is stored on the thread stack, but the object itself if stored on the heap.</p><p>An object may contain methods and these methods may contain local variables. These local variables are also stored on the thread stack, even if the object the method belongs to is stored on the heap.</p><p><strong>An object’s member variables are stored on the heap along with the object itself. That is true both when the member variable is of a primitive type, and if it is a reference to an object.</strong></p><p>Static class variables are also stored on the heap along with the class definition.</p><p>Objects on the heap can be accessed by all threads that have a reference to the object. When a thread has access to an object, it can also get access to that object’s member variables. If two threads call a method on the same object at the same time, they will both have access to the object’s member variables, but each thread will have its own copy of the local variables.</p><p>Here is a diagram illustrating the points above: </p><p><img src="/images/architect/concurrent-jmm-3.png" alt="JMM"></p><p>Two threads have a set of local variables. One of the local variables (Local Variable 2) point to a shared object on the heap (Object 3). The two threads each have a different reference to the same object. Their references are local variables and are thus stored in each thread’s thread stack (on each). The two different references point to the same object on the heap, though.</p><p>Notice how the shared object (Object 3) has a reference to Object 2 and Object 4 as member variables (illustrated by the arrows from Object 3 to Object 2 and Object 4). Via these member variable references in Object 3 the two threads can access Object 2 and Object 4.</p><p>The diagram also shows a local variable which point to two different objects on the heap. In this case the references point to two different objects (Object 1 and Object 5), not the same object. In theory both threads could access both Object 1 and Object 5, if both threads had references to both objects. But in the diagram above each thread only has a reference to one of the two objects.</p><p>So, what kind of Java code could lead to the above memory graph? Well, code as simple as the code below: </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyRunnable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        methodOne();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">methodOne</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> localVariable1 = <span class="number">45</span>;</span><br><span class="line">        MySharedObject localVariable2 = MySharedObject.sharedInstance;</span><br><span class="line">        <span class="comment">//... do more with local variables.</span></span><br><span class="line">        methodTwo();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">methodTwo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Integer localVariable1 = <span class="keyword">new</span> Integer(<span class="number">99</span>);</span><br><span class="line">        <span class="comment">//... do more with local variable.</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MySharedObject</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//static variable pointing to instance of MySharedObject</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> MySharedObject sharedInstance = <span class="keyword">new</span> MySharedObject();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//member variables pointing to two objects on the heap</span></span><br><span class="line">    <span class="keyword">public</span> Integer object2 = <span class="keyword">new</span> Integer(<span class="number">22</span>);</span><br><span class="line">    <span class="keyword">public</span> Integer object4 = <span class="keyword">new</span> Integer(<span class="number">44</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">long</span> member1 = <span class="number">12345</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">long</span> member2 = <span class="number">67890</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If two threads were executing the run() method then the diagram shown earlier would be the outcome. The run() method calls methodOne() and methodOne() calls methodTwo().</p><p>methodOne() declares a primitive local variable (localVariable1 of type int) and an local variable which is an object reference (localVariable2).</p><p>Each thread executing methodOne() will create its own copy of localVariable1 and localVariable2 on their respective thread stacks. The localVariable1 variables will be completely separated from each other, only living on each thread’s thread stack. One thread cannot see what changes another thread makes to its copy of localVariable1.</p><p>Each thread executing methodOne() will also create their own copy of localVariable2. However, the two different copies of localVariable2 both end up pointing to the same object on the heap. The code sets localVariable2 to point to an object referenced by a static variable. There is only one copy of a static variable and this copy is stored on the heap. Thus, both of the two copies of localVariable2 end up pointing to the same instance of MySharedObject which the static variable points to. The MySharedObject instance is also stored on the heap. It corresponds to Object 3 in the diagram above.</p><p>Notice how the MySharedObject class contains two member variables too. The member variables themselves are stored on the heap along with the object. The two member variables point to two other Integer objects. These Integer objects correspond to Object 2 and Object 4 in the diagram above.</p><p>Notice also how methodTwo() creates a local variable named localVariable1. This local variable is an object reference to an Integer object. The method sets the localVariable1 reference to point to a new Integer instance. The localVariable1 reference will be stored in one copy per thread executing methodTwo(). The two Integer objects instantiated will be stored on the heap, but since the method creates a new Integer object every time the method is executed, two threads executing this method will create separate Integer instances. The Integer objects created inside methodTwo() correspond to Object 1 and Object 5 in the diagram above.</p><p>Notice also the two member variables in the class MySharedObject of type long which is a primitive type. Since these variables are member variables, they are still stored on the heap along with the object. Only local variables are stored on the thread stack.</p><h2 id="Hardware-Memory-Architecture-HMA"><a href="#Hardware-Memory-Architecture-HMA" class="headerlink" title="Hardware Memory Architecture (HMA)"></a>Hardware Memory Architecture (HMA)</h2><p>Modern hardware memory architecture is somewhat different from the internal Java memory model. It is important to understand the hardware memory architecture too, to understand how the Java memory model works with it. This section describes the common hardware memory architecture, and a later section will describe how the Java memory model works with it.</p><p>Here is a simplified diagram of modern computer hardware architecture: </p><p><img src="/images/architect/concurrent-jmm-4.png" alt="JMM"></p><p>A modern computer often has 2 or more CPUs in it. Some of these CPUs may have multiple cores too. The point is, that on a modern computer with 2 or more CPUs it is possible to have more than one thread running simultaneously. Each CPU is capable of running one thread at any given time. That means that if your Java application is multithreaded, one thread per CPU may be running simultaneously (concurrently) inside your Java application.</p><p>Each CPU contains a set of registers which are essentially in-CPU memory. The CPU can perform operations much faster on these registers than it can perform on variables in main memory. That is because the CPU can access these registers much faster than it can access main memory.</p><p>Each CPU may also have a CPU cache memory layer. In fact, most modern CPUs have a cache memory layer of some size. The CPU can access its cache memory much faster than main memory, but typically not as fast as it can access its internal registers. So, the CPU cache memory is somewhere in between the speed of the internal registers and main memory. Some CPUs may have multiple cache layers (Level 1 and Level 2), but this is not so important to know to understand how the Java memory model interacts with memory. What matters is to know that CPUs can have a cache memory layer of some sort.</p><p>A computer also contains a main memory area (RAM). All CPUs can access the main memory. The main memory area is typically much bigger than the cache memories of the CPUs.</p><p>Typically, when a CPU needs to access main memory it will read part of main memory into its CPU cache. It may even read part of the cache into its internal registers and then perform operations on it. When the CPU needs to write the result back to main memory it will flush the value from its internal register to the cache memory, and at some point flush the value back to main memory.</p><p>The values stored in the cache memory is typically flushed back to main memory when the CPU needs to store something else in the cache memory. The CPU cache can have data written to part of its memory at a time, and flush part of its memory at a time. It does not have to read / write the full cache each time it is updated. Typically the cache is updated in smaller memory blocks called “cache lines”. One or more cache lines may be read into the cache memory, and one or mor cache lines may be flushed back to main memory again.</p><h2 id="Bridging-The-Gap-Between-JMM-And-HMA"><a href="#Bridging-The-Gap-Between-JMM-And-HMA" class="headerlink" title="Bridging The Gap Between JMM And HMA"></a>Bridging The Gap Between JMM And HMA</h2><p>As already mentioned, the Java memory model and the hardware memory architecture are different. The hardware memory architecture does not distinguish between thread stacks and heap. On the hardware, both the thread stack and the heap are located in main memory. Parts of the thread stacks and heap may sometimes be present in CPU caches and in internal CPU registers. This is illustrated in this diagram: </p><p><img src="/images/architect/concurrent-jmm-5.png" alt="JMM"></p><p>When objects and variables can be stored in various different memory areas in the computer, certain problems may occur. The two main problems are:</p><ul><li>Visibility of thread updates (writes) to shared variables.</li><li>Race conditions when reading, checking and writing shared variables.</li></ul><p>Both of these problems will be explained in the following sections.</p><h3 id="Visibility-of-Shared-Objects"><a href="#Visibility-of-Shared-Objects" class="headerlink" title="Visibility of Shared Objects"></a>Visibility of Shared Objects</h3><p>If two or more threads are sharing an object, without the proper use of either volatile declarations or synchronization, updates to the shared object made by one thread may not be visible to other threads.</p><p>Imagine that the shared object is initially stored in main memory. A thread running on CPU one then reads the shared object into its CPU cache. There it makes a change to the shared object. As long as the CPU cache has not been flushed back to main memory, the changed version of the shared object is not visible to threads running on other CPUs. This way each thread may end up with its own copy of the shared object, each copy sitting in a different CPU cache.</p><p>The following diagram illustrates the sketched situation. One thread running on the left CPU copies the shared object into its CPU cache, and changes its count variable to 2. This change is not visible to other threads running on the right CPU, because the update to count has not been flushed back to main memory yet.</p><p><img src="/images/architect/concurrent-jmm-6.png" alt="JMM"></p><p>To solve this problem you can use <a href="http://tutorials.jenkov.com/java-concurrency/volatile.html" target="_blank" rel="noopener">Java’s volatile keyword</a>. The volatile keyword can make sure that a given variable is read directly from main memory, and always written back to main memory when updated.</p><h3 id="Race-Conditions"><a href="#Race-Conditions" class="headerlink" title="Race Conditions"></a>Race Conditions</h3><p>If two or more threads share an object, and more than one thread updates variables in that shared object, race conditions may occur.</p><p>Imagine if thread A reads the variable count of a shared object into its CPU cache. Imagine too, that thread B does the same, but into a different CPU cache. Now thread A adds one to count, and thread B does the same. Now var1 has been incremented two times, once in each CPU cache.</p><p>If these increments had been carried out sequentially, the variable count would be been incremented twice and had the original value + 2 written back to main memory.</p><p>However, the two increments have been carried out concurrently without proper synchronization. Regardless of which of thread A and B that writes its updated version of count back to main memory, the updated value will only be 1 higher than the original value, despite the two increments.</p><p>This diagram illustrates an occurrence of the problem with race conditions as described above: </p><p><img src="/images/architect/concurrent-jmm-7.png" alt="JMM"></p><p>To solve this problem you can use a <a href="http://tutorials.jenkov.com/java-concurrency/synchronized.html" target="_blank" rel="noopener">Java synchronized block</a>. A synchronized block guarantees that only one thread can enter a given critical section of the code at any given time. Synchronized blocks also guarantee that all variables accessed inside the synchronized block will be read in from main memory, and when the thread exits the synchronized block, all updated variables will be flushed back to main memory again, regardless of whether the variable is declared volatile or not.</p><h2 id="Links"><a href="#Links" class="headerlink" title="Links"></a>Links</h2><ul><li><a href="http://tutorials.jenkov.com/java-concurrency/java-memory-model.html" target="_blank" rel="noopener">Java Concurrency and Multithreading Tutorial</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="concurrent" scheme="https://lugavin.github.io/tags/concurrent/"/>
    
  </entry>
  
  <entry>
    <title>Nest</title>
    <link href="https://lugavin.github.io/2020/05/01/web/nest/"/>
    <id>https://lugavin.github.io/2020/05/01/web/nest/</id>
    <published>2020-05-01T10:20:00.000Z</published>
    <updated>2026-05-09T06:54:58.893Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>近几年，由于 Node.js 的广泛流行，JavaScript 已经成为前端和后端应用程序的「通用语言」，从而产生了像<a href="https://angular.io/" target="_blank" rel="noopener">Angular</a>、<a href="https://zh-hans.reactjs.org/" target="_blank" rel="noopener">React</a>、<a href="https://cn.vuejs.org/" target="_blank" rel="noopener">Vue</a>等令人耳目一新的项目，这些项目提高了开发人员的生产力，使得可以快速构建可测试的且可扩展的前端应用程序。然而，在服务器端，虽然有很多优秀的库、helper 和 Node 工具，但是它们都没有有效地解决主要问题 - 架构。</p><p>Nest 旨在提供一个开箱即用的应用程序体系结构，允许轻松创建高度可测试、可扩展、松耦合且易于维护的应用程序。</p><p>Nest 是用于构建高效且可扩展的服务器端应用程序的渐进式 Node.js 框架，深受 Spring 和 Angular 的启发。它兼容 TypeScript 和纯 JavaScript，并结合了 OOP（面向对象编程）、FP（函数式编程）和 FRP（函数响应式编程），底层默认使用 Express，但也提供了与其他各种库的兼容（例如 Fastify），可以方便地使用各种可用的第三方插件。</p><h2 id="Controllers"><a href="#Controllers" class="headerlink" title="Controllers"></a><a href="https://docs.nestjs.com/controllers" target="_blank" rel="noopener">Controllers</a></h2><p><code>Controllers</code> 负责接收客户端的请求并返回响应。</p><p><code>Controller</code> 的目的是接收应用的特定请求，路由机制控制哪个控制器接收哪些请求。通常，每个控制器有多个路由，不同的路由可以执行不同的操作。为了创建一个基本的控制器，我们使用类和装饰器。装饰器将类与所需的元数据相关联，使得 <code>Nest</code> 可以创建路由映射（将请求绑定到相应的控制器）。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Controller(): Decorator that marks a class as a Nest controller that can receive inbound </span></span><br><span class="line"><span class="comment"> * requests and produce responses.</span></span><br><span class="line"><span class="comment"> * It defines a class that provides the context for one or more related route handlers that </span></span><br><span class="line"><span class="comment"> * correspond to HTTP request methods and associated routes.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Controller</span>(<span class="string">'dicts'</span>)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DictController &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> readonly dictService: DictService</span>) &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Post</span>()</span><br><span class="line">    <span class="keyword">async</span> createDicts(<span class="meta">@Body</span>() entity: DictEntity): <span class="built_in">Promise</span>&lt;DictEntity&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.dictService.createDict(entity);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Providers"><a href="#Providers" class="headerlink" title="Providers"></a><a href="https://docs.nestjs.com/providers" target="_blank" rel="noopener">Providers</a></h2><p><code>Provider</code> 只是一个用 <code>@Injectable()</code> 装饰器注释的纯粹的 <code>JavaScript</code> 类。</p><p><code>Provider</code> 的主要目的是它可以注入依赖项，这意味着对象可以彼此创建各种关系，并且“连接”对象实例的操作在很大程度上可以委托给 <code>Nest</code> 运行时系统。使用 <code>Nest</code> 内置的依赖注入（DI）技术，通过构造函数参数的方式，可以将 <code>Provider</code> 注入到其他类中。</p><p>当注入 <code>Provider</code> 时，<strong>它必须要在注入的类的模块范围内可见</strong>，这可以通过以下几种方式来完成：</p><ul><li>在同一模块范围内定义 <code>provider</code></li><li>从一个模块作用域导出 <code>provider</code> 并将该模块导入到要注入的类的模块作用域中</li><li>从标记了 <code>@Global()</code> 装饰器的全局模块中导出 <code>provider</code></li></ul><p>在前面的示例中，控制器接收客户端 HTTP 请求并将复杂的业务处理委托给 <code>Provider</code> 来完成：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Injectable(): Decorator that marks a class as a provider</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Injectable</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DictService &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@InjectRepository</span>(DictEntity)</span><br><span class="line">    <span class="keyword">private</span> dictRepository: Repository&lt;DictEntity&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">async</span> createDict(entity: DictEntity): <span class="built_in">Promise</span>&lt;DictEntity&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.dictRepository.save(entity);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Dependency-injection"><a href="#Dependency-injection" class="headerlink" title="Dependency injection"></a>Dependency injection</h3><p>依赖，是当类需要执行其功能时，所需要的服务或对象； DI 是一种编码模式，其中的类会从外部源中请求获取依赖，而不是自己创建它们。<strong>依赖注入（DI）是一种控制反转（IOC）技术，您可以将依赖的实例化操作委派给IOC容器（<code>Nest</code>运行时系统）来代替自己在代码中进行实例化操作。</strong></p><p>在 <code>Nest</code> 中，借助于 <strong>TypeScript</strong> 的特性，管理依赖项非常容易，因为仅需按类型进行解析即可。</p><h3 id="Scopes"><a href="#Scopes" class="headerlink" title="Scopes"></a>Scopes</h3><p><code>Providers</code> 通常具有与应用程序一致的生命周期。当应用程序启动时，必须处理解决每个依赖项，因此必须实例化每个 <code>Provider</code>；同样，当应用程序关闭时，每个 <code>Provider</code> 都将被销毁。</p><h3 id="Custom-providers"><a href="#Custom-providers" class="headerlink" title="Custom providers"></a>Custom providers</h3><p><code>Nest</code> 有一个内置的 IOC 容器（<code>Nest</code>运行时系统），可以解决 <code>Providers</code> 之间的依赖关系。此功能是上面描述的依赖注入功能的基础，但实际上要比我们到目前为止描述的功能强大得多。</p><p><code>@Injectable()</code> 装饰器并不是定义一个 Provider 的唯一方式，我们还可以使用<a href="https://docs.nestjs.com/fundamentals/custom-providers#value-providers-usevalue" target="_blank" rel="noopener">Value</a>、<a href="https://docs.nestjs.com/fundamentals/custom-providers#class-providers-useclass" target="_blank" rel="noopener">Class</a>、<a href="https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory" target="_blank" rel="noopener">Factory</a>来定义一个 Provider。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Module</span>(&#123;</span><br><span class="line">    providers: [&#123;</span><br><span class="line">        provide: APP_PIPE,</span><br><span class="line">        useClass: ValidationPipe</span><br><span class="line">    &#125;]</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> PipeModule &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!-- ### Optional providers--><h3 id="Property-based-injection"><a href="#Property-based-injection" class="headerlink" title="Property-based injection"></a>Property-based injection</h3><p>除了基于构造函数注入 <code>providers</code> 的方式之外，在某些非常特殊的情况下，基于属性的注入可能会很有用。例如，如果父类依赖于一个或多个 providers，那么通过从构造函数中调用子类中的 <code>super()</code> 来传递它们就会非常烦人了。因此，为了避免出现这种情况，可以在属性上使用 <code>@Inject()</code> 装饰器。</p><h3 id="Provider-registration"><a href="#Provider-registration" class="headerlink" title="Provider registration"></a>Provider registration</h3><p>现在我们已经定义了一个提供者（<code>DictService</code>），并且也已经有了该服务的使用者（<code>DictController</code>），此外，我们还需要将该服务注册到 Nest 中以便它可以进行注入。因此，我们需要编写模块文件（<code>DictModule</code>），并将该服务添加到 <code>@Module()</code> 装饰器的 <code>providers</code> 数组中：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Module</span>(&#123;</span><br><span class="line">    imports: [TypeOrmModule.forFeature([DictEntity])],</span><br><span class="line">    providers: [DictService],</span><br><span class="line">    controllers: [DictController],</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> DictModule &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的示例中，当 <code>Nest IOC</code> 容器实例化 <code>DictController</code> 时，它首先查找所有的依赖项；当找到 <code>DictService</code> 依赖项时，它将对 <code>DictService</code> 令牌执行查找，根据 <code>DictModule</code> 的配置，将返回 <code>DictService</code> 类；然后，Nest 创建 <code>DictService</code> 实例，将其缓存并返回，或者如果实例已经在缓存中存在，则返回现有实例。</p><h2 id="Modules"><a href="#Modules" class="headerlink" title="Modules"></a><a href="https://docs.nestjs.com/modules" target="_blank" rel="noopener">Modules</a></h2><p>模块是用 <code>@Module()</code> 装饰器注释的类。Nest 使用模块来组织应用程序结构，<code>@Module()</code> 装饰器提供了 Nest 用来组织应用程序结构的元数据。</p><table><thead><tr><th>Metadata</th><th>Describe</th></tr></thead><tbody><tr><td>providers</td><td>由 Nest 注入器实例化的<code>providers</code>，并且至少可以在此模块中共享</td></tr><tr><td>controllers</td><td>在此模块中定义的需实例化的一组<code>controllers</code></td></tr><tr><td>imports</td><td>导入模块的列表，这些模块道导出了此模块中所需的<code>providers</code></td></tr><tr><td>exports</td><td>由本模块提供供其他模块导入使用的<code>providers</code>子集</td></tr></tbody></table><p><strong>默认情况下，模块封装提供者，这意味着如果提供者既不是当前模块的一部分也不是从另外模块（已导入）导出的，那么它是无法注入的。</strong></p><h2 id="Middleware"><a href="#Middleware" class="headerlink" title="Middleware"></a><a href="https://docs.nestjs.com/middleware" target="_blank" rel="noopener">Middleware</a></h2><p><code>Middleware</code> 是在路由处理程序之前执行的函数，该函数可以访问 <a href="https://expressjs.com/en/4x/api.html#req" target="_blank" rel="noopener">request</a> 和 <a href="https://expressjs.com/en/4x/api.html#res" target="_blank" rel="noopener">response</a> 对象，以及应用程序请求-响应周期中 <code>next()</code> 中间件函数。Nest 中间件实际上等价于 <a href="http://expressjs.com/en/guide/using-middleware.html" target="_blank" rel="noopener">express</a> 中间件。</p><p>中间件无法在这 <code>@Module()</code> 装饰器中进行配置，我们可以通过使用模块类的 <code>configure()</code> 方法来进行设置，且包含中间件的模块必须实现 <code>NestModule</code> 接口。</p><!-- ## [Exception filters](https://docs.nestjs.com/exception-filters)## [Pipes](https://docs.nestjs.com/pipes)## [Guards](https://docs.nestjs.com/guards)## [Interceptors](https://docs.nestjs.com/interceptors)## [Decorators](https://docs.nestjs.com/custom-decorators)--><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://docs.nestjs.com/" target="_blank" rel="noopener">Nest</a></li><li><a href="https://expressjs.com/" target="_blank" rel="noopener">Express</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="web" scheme="https://lugavin.github.io/categories/web/"/>
    
    
      <category term="nest" scheme="https://lugavin.github.io/tags/nest/"/>
    
  </entry>
  
  <entry>
    <title>Software - MacOS</title>
    <link href="https://lugavin.github.io/2020/03/01/software/macos/"/>
    <id>https://lugavin.github.io/2020/03/01/software/macos/</id>
    <published>2020-03-01T09:30:00.000Z</published>
    <updated>2026-05-09T06:54:58.892Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="Browser"><a href="#Browser" class="headerlink" title="Browser"></a>Browser</h2><ul><li><a href="https://cloud.google.com/chrome-enterprise/browser/download/" target="_blank" rel="noopener">Chrome</a><br><a href="https://dl.google.com/dl/chrome/mac/universal/stable/gcea/googlechrome.dmg" target="_blank" rel="noopener">https://dl.google.com/dl/chrome/mac/universal/stable/gcea/googlechrome.dmg</a></li><li><a href="https://chrome.google.com/webstore/category/extensions" target="_blank" rel="noopener">Chrome Extensions</a><br><a href="https://chrome.google.com/webstore/detail/bitwarden-free-password/nngceckbapebfimnlniiiahkandclblb" target="_blank" rel="noopener">https://chrome.google.com/webstore/detail/bitwarden-free-password/nngceckbapebfimnlniiiahkandclblb</a><br><a href="https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm" target="_blank" rel="noopener">https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm</a><br><a href="https://chrome.google.com/webstore/detail/ai-translator-and-youtube/mjdbhokoopacimoekfgkcoogikbfgngb" target="_blank" rel="noopener">https://chrome.google.com/webstore/detail/ai-translator-and-youtube/mjdbhokoopacimoekfgkcoogikbfgngb</a></li></ul><h2 id="Downloader"><a href="#Downloader" class="headerlink" title="Downloader"></a>Downloader</h2><ul><li><a href="https://github.com/agalwood/Motrix/releases" target="_blank" rel="noopener">Motrix</a><br><a href="https://github.com/agalwood/Motrix/releases/download/v1.6.11/Motrix-1.6.11.dmg" target="_blank" rel="noopener">https://github.com/agalwood/Motrix/releases/download/v1.6.11/Motrix-1.6.11.dmg</a><br><a href="https://github.com/agalwood/Motrix/releases/download/v1.6.11/Motrix-1.6.11-arm64.dmg" target="_blank" rel="noopener">https://github.com/agalwood/Motrix/releases/download/v1.6.11/Motrix-1.6.11-arm64.dmg</a></li></ul><h2 id="Editor"><a href="#Editor" class="headerlink" title="Editor"></a>Editor</h2><ul><li><a href="https://www.sublimetext.com/" target="_blank" rel="noopener">SublimeText</a><br><a href="https://download.sublimetext.com/sublime_text_build_4126_mac.zip" target="_blank" rel="noopener">https://download.sublimetext.com/sublime_text_build_4126_mac.zip</a></li><li><a href="https://typora.io/releases/all" target="_blank" rel="noopener">Typora</a><br><a href="https://download.typora.io/mac/Typora-1.4.8.dmg" target="_blank" rel="noopener">https://download.typora.io/mac/Typora-1.4.8.dmg</a></li><li><a href="https://download.anytype.io/" target="_blank" rel="noopener">Anytype</a><br><a href="https://github.com/anyproto/anytype-ts/releases/download/v0.42.4/Anytype-0.42.4-mac-x64.dmg" target="_blank" rel="noopener">https://github.com/anyproto/anytype-ts/releases/download/v0.42.4/Anytype-0.42.4-mac-x64.dmg</a><br><a href="https://github.com/anyproto/anytype-ts/releases/download/v0.42.4/Anytype-0.42.4-mac-arm64.dmg" target="_blank" rel="noopener">https://github.com/anyproto/anytype-ts/releases/download/v0.42.4/Anytype-0.42.4-mac-arm64.dmg</a></li><li><a href="https://code.visualstudio.com/download" target="_blank" rel="noopener">VSCode</a><br><a href="https://vscode.cdn.azure.cn/stable/5235c6bb189b60b01b1f49062f4ffa42384f8c91/VSCode-darwin-arm64.zip" target="_blank" rel="noopener">https://vscode.cdn.azure.cn/stable/5235c6bb189b60b01b1f49062f4ffa42384f8c91/VSCode-darwin-arm64.zip</a></li></ul><h2 id="System"><a href="#System" class="headerlink" title="System"></a>System</h2><ul><li><a href="https://freemacsoft.net/appcleaner/" target="_blank" rel="noopener">AppCleaner</a><br><a href="https://freemacsoft.net/downloads/AppCleaner_3.6.7.zip" target="_blank" rel="noopener">https://freemacsoft.net/downloads/AppCleaner_3.6.7.zip</a></li><li><a href="https://macpaw.com/zh/support/cleanmymac" target="_blank" rel="noopener">CleanMyMac X</a><br><a href="https://dl.devmate.com/com.macpaw.zh.CleanMyMac4/CleanMyMacXChinese.dmg" target="_blank" rel="noopener">https://dl.devmate.com/com.macpaw.zh.CleanMyMac4/CleanMyMacXChinese.dmg</a></li><li><a href="https://theunarchiver.com/" target="_blank" rel="noopener">The Unarchiver</a><br><a href="https://dl.devmate.com/com.macpaw.site.theunarchiver/TheUnarchiver.dmg" target="_blank" rel="noopener">https://dl.devmate.com/com.macpaw.site.theunarchiver/TheUnarchiver.dmg</a></li><li><a href="https://macitbetter.com/" target="_blank" rel="noopener">BetterZip</a><br><a href="https://macitbetter.com/BetterZip.zip" target="_blank" rel="noopener">https://macitbetter.com/BetterZip.zip</a><br><a href="https://macitbetter.com/dl/BetterZip-4.2.5.zip" target="_blank" rel="noopener">https://macitbetter.com/dl/BetterZip-4.2.5.zip</a></li><li><a href="https://www.fatcatsoftware.com/plisteditpro_downloads" target="_blank" rel="noopener">PlistEdit</a><br><a href="https://www.fatcatsoftware.com/plisteditpro/PlistEditPro.zip" target="_blank" rel="noopener">https://www.fatcatsoftware.com/plisteditpro/PlistEditPro.zip</a><br><a href="https://www.fatcatsoftware.com/plisteditpro/PlistEditPro_187.zip" target="_blank" rel="noopener">https://www.fatcatsoftware.com/plisteditpro/PlistEditPro_187.zip</a></li><li><a href="https://www.alfredapp.com/" target="_blank" rel="noopener">Alfred</a><br><a href="https://cachefly.alfredapp.com/Alfred_3.8.6_972.dmg" target="_blank" rel="noopener">https://cachefly.alfredapp.com/Alfred_3.8.6_972.dmg</a><br><a href="https://cachefly.alfredapp.com/Alfred_4.0.8_1135.dmg" target="_blank" rel="noopener">https://cachefly.alfredapp.com/Alfred_4.0.8_1135.dmg</a></li><li><a href="https://topnotch.app/" target="_blank" rel="noopener">TopNotch</a><br><a href="https://updates.topnotch.app/TopNotch-latest.zip" target="_blank" rel="noopener">https://updates.topnotch.app/TopNotch-latest.zip</a></li><li><a href="https://media.codeweavers.com/pub/crossover/cxmac/demo/" target="_blank" rel="noopener">CrossOver</a><br><a href="https://media.codeweavers.com/pub/crossover/cxmac/demo/crossover-18.0.0.zip" target="_blank" rel="noopener">https://media.codeweavers.com/pub/crossover/cxmac/demo/crossover-18.0.0.zip</a><br><a href="https://media.codeweavers.com/pub/crossover/cxmac/demo/crossover-19.0.1.zip" target="_blank" rel="noopener">https://media.codeweavers.com/pub/crossover/cxmac/demo/crossover-19.0.1.zip</a></li><li><a href="https://www.mediaatelier.com/CheatSheet/" target="_blank" rel="noopener">CheatSheet</a><br><a href="https://www.mediaatelier.com/CheatSheet/CheatSheet_1.6.4.dmg" target="_blank" rel="noopener">https://www.mediaatelier.com/CheatSheet/CheatSheet_1.6.4.dmg</a></li><li><a href="https://kapeli.com/dash" target="_blank" rel="noopener">Dash</a><br><a href="https://london.kapeli.com/downloads/v5/Dash.zip" target="_blank" rel="noopener">https://london.kapeli.com/downloads/v5/Dash.zip</a></li></ul><h2 id="Media"><a href="#Media" class="headerlink" title="Media"></a>Media</h2><ul><li><a href="https://iina.io/" target="_blank" rel="noopener">IINA Player</a><br><a href="https://dl.iina.io/IINA.v1.3.1.dmg" target="_blank" rel="noopener">https://dl.iina.io/IINA.v1.3.1.dmg</a></li><li><a href="https://www.foobar2000.org/mac" target="_blank" rel="noopener">foobar2000</a><br><a href="https://www.foobar2000.org/files/foobar2000-v2.5.dmg" target="_blank" rel="noopener">https://www.foobar2000.org/files/foobar2000-v2.5.dmg</a></li><li><a href="https://www.everappz.com/" target="_blank" rel="noopener">Flacbox</a><br><a href="https://apps.apple.com/us/app/flacbox-flac-mp3-music-player-audio-streamer/id1097564256" target="_blank" rel="noopener">https://apps.apple.com/us/app/flacbox-flac-mp3-music-player-audio-streamer/id1097564256</a><br><a href="https://apps.apple.com/us/app/evermusic-offline-music-player-cloud-streamer/id885367198" target="_blank" rel="noopener">https://apps.apple.com/us/app/evermusic-offline-music-player-cloud-streamer/id885367198</a></li><li><a href="https://vox.rocks/mac-music-player/old-versions/" target="_blank" rel="noopener">VOX Music Player</a><br><a href="https://cloud.coppertino.com/vox/downloads/Vox_2.8.26.zip" target="_blank" rel="noopener">https://cloud.coppertino.com/vox/downloads/Vox_2.8.26.zip</a></li></ul><h2 id="Translator"><a href="#Translator" class="headerlink" title="Translator"></a>Translator</h2><ul><li><a href="https://www.eudic.net/v4/en/app/download" target="_blank" rel="noopener">Eudic</a><br><a href="https://www.eudic.net/download/eudic_win.zip?v=2022-11-10" target="_blank" rel="noopener">https://www.eudic.net/download/eudic_win.zip?v=2022-11-10</a><br><a href="https://static.frdic.com/pkg/eudicmac.dmg?v=2022-11-13" target="_blank" rel="noopener">https://static.frdic.com/pkg/eudicmac.dmg?v=2022-11-13</a></li><li><a href="https://fanyi.baidu.com/appdownload/download.html" target="_blank" rel="noopener">BaiduTranslate</a><br><a href="https://fanyiapp.cdn.bcebos.com/fanyi-client/pkg/mac/1.5.4/%E7%99%BE%E5%BA%A6%E7%BF%BB%E8%AF%91-1.5.4.pkg" target="_blank" rel="noopener">https://fanyiapp.cdn.bcebos.com/fanyi-client/pkg/mac/1.5.4/%E7%99%BE%E5%BA%A6%E7%BF%BB%E8%AF%91-1.5.4.pkg</a><br><a href="https://fanyiapp.cdn.bcebos.com/fanyi-client/pkg/mac/1.5.4/%E7%99%BE%E5%BA%A6%E7%BF%BB%E8%AF%91-1.5.4-arm64.pkg" target="_blank" rel="noopener">https://fanyiapp.cdn.bcebos.com/fanyi-client/pkg/mac/1.5.4/%E7%99%BE%E5%BA%A6%E7%BF%BB%E8%AF%91-1.5.4-arm64.pkg</a></li><li><a href="https://transmart.qq.com/zh-CN/download/" target="_blank" rel="noopener">TranSmart</a><br><a href="https://cdn.transmart.qq.com/installation_pro/TranSmart_Alpha0.8.12(20221214)_windows.exe" target="_blank" rel="noopener">https://cdn.transmart.qq.com/installation_pro/TranSmart_Alpha0.8.12(20221214)_windows.exe</a><br><a href="https://cdn.transmart.qq.com/installation_pro/TranSmart_Alpha0.8.12(20221214)_mac_x86.dmg" target="_blank" rel="noopener">https://cdn.transmart.qq.com/installation_pro/TranSmart_Alpha0.8.12(20221214)_mac_x86.dmg</a><br><a href="https://cdn.transmart.qq.com/installation_pro/TranSmart_Alpha0.8.12(20221214)_mac_arm64.dmg" target="_blank" rel="noopener">https://cdn.transmart.qq.com/installation_pro/TranSmart_Alpha0.8.12(20221214)_mac_arm64.dmg</a></li></ul><h2 id="Proxy"><a href="#Proxy" class="headerlink" title="Proxy"></a>Proxy</h2><ul><li><a href="https://apps.apple.com/us/app/karing/id6472431552" target="_blank" rel="noopener">Karing</a></li><li><a href="https://apps.apple.com/us/app/shadowrocket/id932747118" target="_blank" rel="noopener">Shadowrocket</a></li></ul><h2 id="Jetbrains"><a href="#Jetbrains" class="headerlink" title="Jetbrains"></a>Jetbrains</h2><ul><li><a href="https://www.jetbrains.com/idea/download/other.html" target="_blank" rel="noopener">IntelliJ IDEA</a><br><a href="https://download.jetbrains.com/idea/ideaIU-2021.1.3.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/idea/ideaIU-2021.1.3.dmg</a><br><a href="https://download.jetbrains.com/idea/ideaIU-2021.1.3-aarch64.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/idea/ideaIU-2021.1.3-aarch64.dmg</a></li><li><a href="https://www.jetbrains.com/go/download/other.html" target="_blank" rel="noopener">GoLand</a><br><a href="https://download.jetbrains.com/go/goland-2021.1.3.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/go/goland-2021.1.3.dmg</a><br><a href="https://download.jetbrains.com/go/goland-2021.1.3-aarch64.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/go/goland-2021.1.3-aarch64.dmg</a></li><li><a href="https://www.jetbrains.com/pycharm/download/other.html" target="_blank" rel="noopener">PyCharm</a><br><a href="https://download.jetbrains.com/python/pycharm-professional-2021.1.3.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/python/pycharm-professional-2021.1.3.dmg</a><br><a href="https://download.jetbrains.com/python/pycharm-professional-2021.1.3-aarch64.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/python/pycharm-professional-2021.1.3-aarch64.dmg</a></li><li><a href="https://www.jetbrains.com/webstorm/download/other.html" target="_blank" rel="noopener">WebStorm</a><br><a href="https://download.jetbrains.com/webstorm/WebStorm-2021.1.3.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/webstorm/WebStorm-2021.1.3.dmg</a><br><a href="https://download.jetbrains.com/webstorm/WebStorm-2021.1.3-aarch64.dmg" target="_blank" rel="noopener">https://download.jetbrains.com/webstorm/WebStorm-2021.1.3-aarch64.dmg</a></li></ul><h2 id="SDK"><a href="#SDK" class="headerlink" title="SDK"></a>SDK</h2><ul><li><a href="https://golang.google.cn/dl/" target="_blank" rel="noopener">Go</a><br><a href="https://dl.google.com/go/go1.17.8.darwin-amd64.tar.gz" target="_blank" rel="noopener">https://dl.google.com/go/go1.17.8.darwin-amd64.tar.gz</a><br><a href="https://dl.google.com/go/go1.17.8.darwin-arm64.tar.gz" target="_blank" rel="noopener">https://dl.google.com/go/go1.17.8.darwin-arm64.tar.gz</a></li><li><a href="https://nodejs.org/dist/" target="_blank" rel="noopener">Node</a><br><a href="https://nodejs.org/dist/v16.18.0/node-v16.18.0-darwin-x64.tar.gz" target="_blank" rel="noopener">https://nodejs.org/dist/v16.18.0/node-v16.18.0-darwin-x64.tar.gz</a><br><a href="https://nodejs.org/dist/v16.18.0/node-v16.18.0-darwin-arm64.tar.gz" target="_blank" rel="noopener">https://nodejs.org/dist/v16.18.0/node-v16.18.0-darwin-arm64.tar.gz</a></li><li><a href="https://jdk.java.net/archive/" target="_blank" rel="noopener">OpenJDK</a><br><a href="https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_windows-x64_bin.zip" target="_blank" rel="noopener">https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_windows-x64_bin.zip</a><br><a href="https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_osx-x64_bin.tar.gz" target="_blank" rel="noopener">https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_osx-x64_bin.tar.gz</a><br><a href="https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz" target="_blank" rel="noopener">https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz</a></li></ul><h2 id="SCM"><a href="#SCM" class="headerlink" title="SCM"></a>SCM</h2><ul><li><a href="https://www.sourcetreeapp.com/download-archives" target="_blank" rel="noopener">Sourcetree</a><br><a href="https://product-downloads.atlassian.com/software/sourcetree/ga/Sourcetree_4.2.3_252.zip" target="_blank" rel="noopener">https://product-downloads.atlassian.com/software/sourcetree/ga/Sourcetree_4.2.3_252.zip</a></li></ul><h2 id="Programming"><a href="#Programming" class="headerlink" title="Programming"></a>Programming</h2><ul><li><a href="https://tableplus.com/download/" target="_blank" rel="noopener">TablePlus</a><br><a href="https://download.tableplus.com/macos/468/TablePlus.dmg" target="_blank" rel="noopener">https://download.tableplus.com/macos/468/TablePlus.dmg</a></li><li><a href="https://github.com/beekeeper-studio/beekeeper-studio/releases" target="_blank" rel="noopener">Beekeeper Studio</a><br><a href="https://github.com/beekeeper-studio/beekeeper-studio/releases/download/v3.7.10/Beekeeper-Studio-3.7.10-mac.zip" target="_blank" rel="noopener">https://github.com/beekeeper-studio/beekeeper-studio/releases/download/v3.7.10/Beekeeper-Studio-3.7.10-mac.zip</a><br><a href="https://github.com/beekeeper-studio/beekeeper-studio/releases/download/v3.7.10/Beekeeper-Studio-3.7.10-arm64-mac.zip" target="_blank" rel="noopener">https://github.com/beekeeper-studio/beekeeper-studio/releases/download/v3.7.10/Beekeeper-Studio-3.7.10-arm64-mac.zip</a></li><li><a href="https://www.kafkatool.com/download.html" target="_blank" rel="noopener">Offset Explorer</a><br><a href="https://www.kafkatool.com/download2/offsetexplorer.dmg" target="_blank" rel="noopener">https://www.kafkatool.com/download2/offsetexplorer.dmg</a></li><li><a href="https://github.com/qishibo/AnotherRedisDesktopManager/releases/" target="_blank" rel="noopener">Redis Desktop Manager</a><br><a href="https://github.com/qishibo/AnotherRedisDesktopManager/releases/download/v1.5.9/Another-Redis-Desktop-Manager.1.5.9.dmg" target="_blank" rel="noopener">https://github.com/qishibo/AnotherRedisDesktopManager/releases/download/v1.5.9/Another-Redis-Desktop-Manager.1.5.9.dmg</a><br><a href="https://github.com/qishibo/AnotherRedisDesktopManager/releases/download/v1.5.9/Another-Redis-Desktop-Manager-M1-arm64-1.5.9.dmg" target="_blank" rel="noopener">https://github.com/qishibo/AnotherRedisDesktopManager/releases/download/v1.5.9/Another-Redis-Desktop-Manager-M1-arm64-1.5.9.dmg</a></li><li><a href="https://www.docker.com/products/docker-desktop" target="_blank" rel="noopener">Docker Desktop</a><br><a href="https://desktop.docker.com/mac/main/amd64/Docker.dmg" target="_blank" rel="noopener">https://desktop.docker.com/mac/main/amd64/Docker.dmg</a><br><a href="https://desktop.docker.com/mac/main/arm64/Docker.dmg" target="_blank" rel="noopener">https://desktop.docker.com/mac/main/arm64/Docker.dmg</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="software" scheme="https://lugavin.github.io/categories/software/"/>
    
    
      <category term="macos" scheme="https://lugavin.github.io/tags/macos/"/>
    
  </entry>
  
  <entry>
    <title>TypeScript</title>
    <link href="https://lugavin.github.io/2019/12/28/web/typescript/"/>
    <id>https://lugavin.github.io/2019/12/28/web/typescript/</id>
    <published>2019-12-28T08:55:00.000Z</published>
    <updated>2026-05-09T06:54:58.893Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Any application that can be written in JavaScript, will eventually be written in JavaScript.</p><h2 id="JavaScript"><a href="#JavaScript" class="headerlink" title="JavaScript"></a>JavaScript</h2><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>变量是对“值”的具名引用。变量就是为“值”起名，然后引用这个名字，就等同于引用这个值。变量的名字就是变量名。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br></pre></td></tr></table></figure></p><p>上面的代码先声明变量a，然后在变量a与数值1之间建立引用关系，称为将数值1“赋值”给变量a。以后，引用变量名a就会得到数值1。最前面的var，是变量声明命令。它表示通知解释引擎，要创建一个变量a。如果只是声明变量而没有赋值，则该变量的值是undefined。undefined是一个特殊的值，表示“无定义”。</p><h3 id="变量提升"><a href="#变量提升" class="headerlink" title="变量提升"></a>变量提升</h3><p>JavaScript 引擎的工作方式是，先解析代码，获取所有被声明的变量，然后再一行一行地运行。这造成的结果，就是所有的变量的声明语句，都会被提升到代码的头部，这就叫做变量提升。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(a);</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br></pre></td></tr></table></figure></p><p>上面代码首先使用console.log方法，在控制台（console）显示变量a的值。这时变量a还没有声明和赋值，所以这是一种错误的做法，但是实际上不会报错。因为存在变量提升，真正运行的是下面的代码。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a;</span><br><span class="line"><span class="built_in">console</span>.log(a);</span><br><span class="line">a = <span class="number">1</span>;</span><br></pre></td></tr></table></figure></p><p>最后的结果是显示undefined，表示变量a已声明，但还未赋值。</p><h3 id="区块"><a href="#区块" class="headerlink" title="区块"></a>区块</h3><p>JavaScript 使用大括号，将多个相关的语句组合在一起，称为“区块”（block）。对于var命令来说，JavaScript 的区块不构成单独的作用域（scope）。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line">a; <span class="comment">// 1</span></span><br></pre></td></tr></table></figure></p><p>上面代码在区块内部，使用var命令声明并赋值了变量a，然后在区块外部，变量a依然有效，区块对于var命令不构成单独的作用域，与不使用区块的情况没有任何区别。在 JavaScript 语言中，单独使用区块并不常见，区块往往用来构成其他更复杂的语法结构，比如for、if、while、function等。</p><blockquote><p>注意，对于var命令来说，局部变量只能在函数内部声明，在其他区块中声明，一律都是全局变量。</p></blockquote><h3 id="条件语句"><a href="#条件语句" class="headerlink" title="条件语句"></a>条件语句</h3><p>在 JavaScript 中，truthy（真值）指的是在布尔值上下文中，转换后的值为真的值。所有值都是真值，除非它们被定义为 假值（即除 false、0、””、null、undefined 和 NaN 以外皆为真值）。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="literal">false</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">undefined</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="number">0n</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">NaN</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="string">''</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="string">""</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="string">``</span>)</span><br></pre></td></tr></table></figure></p><blockquote><p>null 和 undefined 的区别：null是一个表示“空”的对象，转为数值时为0；undefined是一个表示”此处无定义”的原始值，转为数值时为NaN。</p></blockquote><h3 id="数值"><a href="#数值" class="headerlink" title="数值"></a>数值</h3><p>JavaScript 内部，所有数字都是以64位浮点数形式储存，即使整数也是如此。所以，1与1.0是相同的，是同一个数。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> === <span class="number">1.0</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure></p><p>这就是说，JavaScript 语言的底层根本没有整数，所有数字都是小数（64位浮点数）。容易造成混淆的是，某些运算只有整数才能完成，此时 JavaScript 会自动把64位浮点数，转成32位整数，然后再进行运算。由于浮点数不是精确的值，所以涉及小数的比较和运算要特别小心。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">0.1</span> + <span class="number">0.2</span> === <span class="number">0.3</span> <span class="comment">// false</span></span><br><span class="line"><span class="number">0.3</span> / <span class="number">0.1</span> <span class="comment">// 2.9999999999999996</span></span><br></pre></td></tr></table></figure></p><blockquote><p>NaN是 JavaScript 的特殊值，表示“非数字”（Not a Number），主要出现在将字符串解析成数字出错的场合。NaN不等于任何值，包括它本身。需要注意的是，NaN不是独立的数据类型，而是一个特殊数值，它的数据类型依然属于Number，使用typeof运算符可以看得很清楚。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="literal">NaN</span>  <span class="comment">// 'number'</span></span><br><span class="line"><span class="literal">NaN</span> === <span class="literal">NaN</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure></p></blockquote><h3 id="Base64编码"><a href="#Base64编码" class="headerlink" title="Base64编码"></a>Base64编码</h3><p>JavaScript 原生提供两个 Base64 相关的方法：</p><ul><li>btoa()：任意值转为 Base64 编码</li><li>atob()：Base64 编码转为原来的值<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">var string = &apos;Hello World!&apos;;</span><br><span class="line">btoa(string); // &quot;SGVsbG8gV29ybGQh&quot;</span><br><span class="line">atob(&apos;SGVsbG8gV29ybGQh&apos;); // &quot;Hello World!&quot;</span><br><span class="line">btoa(&apos;你好&apos;); // 报错</span><br><span class="line">btoa(encodeURIComponent(&apos;你好&apos;)); // &quot;JUU0JUJEJUEwJUU1JUE1JUJE&quot;</span><br><span class="line">decodeURIComponent(atob(&apos;JUU0JUJEJUEwJUU1JUE1JUJE&apos;)); // &quot;你好&quot;</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>注意，这两个方法不适合非 ASCII 码的字符，会报错。要将非 ASCII 码字符转为 Base64 编码，必须中间插入一个转码环节，再使用这两个方法。</p></blockquote><h3 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h3><p>什么是对象？简单说，对象就是一组“键值对”（key-value）的集合，是一种无序的复合数据集合。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  foo: <span class="string">'Hello'</span>,</span><br><span class="line">  bar: <span class="string">'World'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><p>如果不同的变量名指向同一个对象，那么它们都是这个对象的引用，也就是说指向同一个内存地址。修改其中一个变量，会影响到其他所有变量。如果取消某一个变量对于原对象的引用，不会影响到另一个变量。但是，这种引用只局限于对象，如果两个变量指向同一个原始类型的值。那么，变量这时都是值的拷贝。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> o1 = &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> o2 = o1;</span><br><span class="line"></span><br><span class="line">o1.a = <span class="number">1</span>;</span><br><span class="line">o2.a; <span class="comment">// 1</span></span><br><span class="line"></span><br><span class="line">o1 = <span class="number">1</span>;</span><br><span class="line">o2; <span class="comment">// &#123;a: 1&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> y = x;</span><br><span class="line"></span><br><span class="line">x = <span class="number">2</span>;</span><br><span class="line">y; <span class="comment">// 1</span></span><br></pre></td></tr></table></figure></p><p>对象采用大括号表示，这导致了一个问题：如果行首是一个大括号，它到底是表达式还是语句？<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;</span><br></pre></td></tr></table></figure></p><p>JavaScript 引擎读到上面这行代码，会发现可能有两种含义。第一种可能是，这是一个表达式，表示一个包含foo属性的对象；第二种可能是，这是一个语句，表示一个代码区块，里面有一个标签foo，指向表达式123。</p><p>为了避免这种歧义，JavaScript 引擎的做法是，如果遇到这种情况，无法确定是对象还是代码块，一律解释为代码块。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; <span class="built_in">console</span>.log(<span class="number">123</span>) &#125; <span class="comment">// 123</span></span><br></pre></td></tr></table></figure></p><p>上面的语句是一个代码块，而且只有解释为代码块，才能执行。如果要解释为对象，最好在大括号前加上圆括号。因为圆括号的里面，只能是表达式，所以确保大括号只能解释为对象。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(&#123; <span class="attr">foo</span>: <span class="number">123</span> &#125;) <span class="comment">// 正确</span></span><br><span class="line">(&#123; <span class="built_in">console</span>.log(<span class="number">123</span>) &#125;) <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure></p><p>for…in循环用来遍历一个对象的全部属性，使用时需要注意：</p><ul><li>它遍历的是对象所有可遍历（enumerable）的属性，会跳过不可遍历的属性。</li><li>它不仅遍历对象自身的属性，还遍历继承的属性。<br>如果继承的属性是可遍历的，那么就会被for…in循环遍历到。但是，一般情况下，都是只想遍历对象自身的属性，所以使用for…in的时候，应该结合使用hasOwnProperty方法，在循环内部判断一下，某个属性是否为对象自身的属性。<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123; <span class="attr">name</span>: <span class="string">'老张'</span> &#125;;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> person) &#123;</span><br><span class="line">  <span class="keyword">if</span> (person.hasOwnProperty(key)) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(key); <span class="comment">// name</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p>对象的继承：大部分面向对象的编程语言，都是通过“类”（class）实现对象的继承。传统上，JavaScript 语言的继承不通过 class，而是通过“原型对象”（prototype）实现（ES6 引入了基于 class 的继承语法）。JavaScript 通过构造函数生成新对象，因此构造函数可以视为对象的模板。实例对象的属性和方法，可以定义在构造函数内部。通过构造函数为实例对象定义属性，虽然很方便，但是有一个缺点。同一个构造函数的多个实例之间，无法共享属性，从而造成对系统资源的浪费。这个问题的解决方法，就是 JavaScript 的原型对象（prototype）。</p><p>prototype 属性的作用：JavaScript 继承机制的设计思想就是，原型对象的所有属性和方法，都能被实例对象共享。也就是说，如果属性和方法定义在原型上，那么所有实例对象就能共享，不仅节省了内存，还体现了实例对象之间的联系。JavaScript 规定，每个函数都有一个prototype属性，指向一个对象。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line"><span class="keyword">typeof</span> f.prototype <span class="comment">// "object"</span></span><br></pre></td></tr></table></figure></p><p>注意，原型对象的属性不是实例对象自身的属性。只要修改原型对象，变动就立刻会体现在所有实例对象上。原型对象的作用就是定义所有实例对象共享的属性和方法，这也是它被称为原型对象的原因，而实例对象可以视作从原型对象衍生出来的子对象。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Animal</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.name = name;</span><br><span class="line">&#125;</span><br><span class="line">Animal.prototype.color = <span class="string">'white'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> cat1 = <span class="keyword">new</span> Animal(<span class="string">'大毛'</span>);</span><br><span class="line"><span class="keyword">var</span> cat2 = <span class="keyword">new</span> Animal(<span class="string">'二毛'</span>);</span><br><span class="line"></span><br><span class="line">cat1.color <span class="comment">// 'white'</span></span><br><span class="line">cat2.color <span class="comment">// 'white'</span></span><br><span class="line"></span><br><span class="line">Animal.prototype.color = <span class="string">'yellow'</span>;</span><br><span class="line"></span><br><span class="line">cat1.color <span class="comment">// "yellow"</span></span><br><span class="line">cat2.color <span class="comment">// "yellow"</span></span><br></pre></td></tr></table></figure></p><p>constructor 属性：prototype对象有一个constructor属性，默认指向prototype对象所在的构造函数。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">P</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line">P.prototype.constructor === P <span class="comment">// true</span></span><br><span class="line"><span class="keyword">var</span> p = <span class="keyword">new</span> P();</span><br><span class="line">p.constructor === P <span class="comment">// true</span></span><br><span class="line">p.hasOwnProperty(<span class="string">'constructor'</span>) <span class="comment">// false</span></span><br><span class="line">p.constructor === P.prototype.constructor <span class="comment">// true</span></span><br></pre></td></tr></table></figure></p><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><p>调用函数时，要使用圆括号运算符。圆括号之中，可以加入函数的参数。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> add = <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> x + y;</span><br><span class="line">&#125;;</span><br><span class="line">add(<span class="number">1</span>, <span class="number">1</span>); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure></p><p>上面代码中，函数名后面紧跟一对圆括号，就会调用这个函数。</p><blockquote><p>JavaScript 语言将函数看作一种值，与其它值（数值、字符串、布尔值等等）地位相同。凡是可以使用值的地方，就能使用函数。比如，可以把函数赋值给变量和对象的属性，也可以当作参数传入其他函数，或者作为函数的结果返回。函数只是一个可以执行的值，此外并无特殊之处。由于函数与其他数据类型地位平等，所以在 JavaScript 语言中又称函数为第一等公民。</p></blockquote><p>JavaScript 引擎将函数名视同变量名，所以采用function命令声明函数时，整个函数会像变量声明一样，被提升到代码头部。所以，下面的代码不会报错。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">f();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;&#125;</span><br></pre></td></tr></table></figure></p><p>表面上，上面代码好像在声明之前就调用了函数f。但是实际上，由于“变量提升”，函数f被提升到了代码头部，也就是在调用之前已经声明了。但是，如果采用赋值语句定义函数，JavaScript 就会报错。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">f();</span><br><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> (<span class="params"></span>)</span>&#123;&#125;;</span><br><span class="line"><span class="comment">// 上面的代码等同于下面的形式</span></span><br><span class="line"><span class="keyword">var</span> f;</span><br><span class="line">f(); <span class="comment">// TypeError: undefined is not a function</span></span><br><span class="line">f = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;&#125;;</span><br></pre></td></tr></table></figure></p><p>函数作用域：作用域（scope）指的是变量存在的范围。在 ES5 的规范中，JavaScript 只有两种作用域：一种是全局作用域，变量在整个程序中一直存在，所有地方都可以读取；另一种是函数作用域，变量只在函数内部存在。ES6 又新增了块级作用域。</p><ul><li>函数外部声明的变量就是全局变量（global variable），它可以在函数内部读取；</li><li>在函数内部定义的变量称为局部变量（local variable），外部无法读取；</li><li>函数内部定义的变量，会在该作用域内覆盖同名全局变量；</li></ul><blockquote><p>注意，对于var命令来说，局部变量只能在函数内部声明，在其他区块中声明，一律都是全局变量。与全局作用域一样，函数作用域内部也会产生“变量提升”现象。var命令声明的变量，不管在什么位置，变量声明都会被提升到函数体的头部。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">x</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (x &gt; <span class="number">100</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> tmp = x - <span class="number">100</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">x</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> tmp;</span><br><span class="line">  <span class="keyword">if</span> (x &gt; <span class="number">100</span>) &#123;</span><br><span class="line">    tmp = x - <span class="number">100</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p></blockquote><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>本质上，数组属于一种特殊的对象。typeof运算符会返回数组的类型是object。</p><p>只要是数组，就一定有length属性。该属性是一个动态的值，等于键名中的最大整数加上1。清空数组的一个有效方法，就是将length属性设为0。如果一个对象的所有键名都是正整数或零，并且有length属性，那么这个对象就很像数组，语法上称为“类似数组的对象”（array-like object）。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="number">0</span>: <span class="string">'a'</span>,</span><br><span class="line">  <span class="number">1</span>: <span class="string">'b'</span>,</span><br><span class="line">  <span class="number">2</span>: <span class="string">'c'</span>,</span><br><span class="line">  length: <span class="number">3</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><p>典型的“类似数组的对象”是函数的arguments对象，以及大多数 DOM 元素集，还有字符串。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// arguments对象</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">func</span>(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> <span class="built_in">arguments</span> &#125;</span><br><span class="line"><span class="keyword">var</span> arrayLike = func(<span class="string">'a'</span>, <span class="string">'b'</span>);</span><br><span class="line"></span><br><span class="line">arrayLike[<span class="number">0</span>] <span class="comment">// 'a'</span></span><br><span class="line">arrayLike.length <span class="comment">// 2</span></span><br><span class="line">arrayLike <span class="keyword">instanceof</span> <span class="built_in">Array</span> <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// DOM元素集</span></span><br><span class="line"><span class="keyword">var</span> elts = <span class="built_in">document</span>.getElementsByTagName(<span class="string">'h3'</span>);</span><br><span class="line">elts.length <span class="comment">// 3</span></span><br><span class="line">elts <span class="keyword">instanceof</span> <span class="built_in">Array</span> <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 字符串</span></span><br><span class="line"><span class="string">'abc'</span>[<span class="number">1</span>] <span class="comment">// 'b'</span></span><br><span class="line"><span class="string">'abc'</span>.length <span class="comment">// 3</span></span><br><span class="line"><span class="string">'abc'</span> <span class="keyword">instanceof</span> <span class="built_in">Array</span> <span class="comment">// false</span></span><br></pre></td></tr></table></figure></p><p>数组的slice方法可以将“类似数组的对象”变成真正的数组。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arr = <span class="built_in">Array</span>.prototype.slice.call(arrayLike);</span><br></pre></td></tr></table></figure></p><p>除了转为真正的数组，“类似数组的对象”还有一个办法可以使用数组的方法，就是通过call()把数组的方法放到对象上面。注意，这种方法比直接使用数组原生的forEach要慢，所以最好还是先将“类似数组的对象”转为真正的数组，然后再直接调用数组的forEach方法。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.forEach.call(arrayLike, <span class="function"><span class="keyword">function</span>(<span class="params">value, index</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(index + <span class="string">' : '</span> + value);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p><h2 id="ECMAScript"><a href="#ECMAScript" class="headerlink" title="ECMAScript"></a>ECMAScript</h2><h3 id="let-amp-const"><a href="#let-amp-const" class="headerlink" title="let &amp; const"></a>let &amp; const</h3><p>var命令会发生“变量提升”现象，即变量可以在声明之前使用，值为undefined。这种现象多多少少是有些奇怪的，按照一般的逻辑，变量应该在声明语句之后才可以使用。为了纠正这种现象，let命令改变了语法行为，它所声明的变量一定要在声明后使用，否则报错。使用let命令所声明的变量，只在let命令所在的代码块内有效。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// var 的情况</span></span><br><span class="line"><span class="built_in">console</span>.log(foo); <span class="comment">// 输出undefined</span></span><br><span class="line"><span class="keyword">var</span> foo = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// let 的情况</span></span><br><span class="line"><span class="built_in">console</span>.log(bar); <span class="comment">// 报错ReferenceError</span></span><br><span class="line"><span class="keyword">let</span> bar = <span class="number">2</span>;</span><br></pre></td></tr></table></figure></p><p>const声明一个只读的常量。一旦声明，常量的值就不能改变。const实际上保证的，并不是变量的值不得改动，而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据（数值、字符串、布尔值），值就保存在变量指向的那个内存地址，因此等同于常量。但对于复合类型的数据（主要是对象和数组），变量指向的内存地址，保存的只是一个指向实际数据的指针，const只能保证这个指针是固定的（即总是指向另一个固定的地址），至于它指向的数据结构是不是可变的，就完全不能控制了。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> foo = &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为 foo 添加一个属性，可以成功</span></span><br><span class="line">foo.prop = <span class="number">123</span>;</span><br><span class="line">foo.prop <span class="comment">// 123</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 将 foo 指向另一个对象，就会报错</span></span><br><span class="line">foo = &#123;&#125;; <span class="comment">// TypeError: "foo" is read-only</span></span><br></pre></td></tr></table></figure></p><h3 id="Symbol"><a href="#Symbol" class="headerlink" title="Symbol"></a>Symbol</h3><p>ES5 的对象属性名都是字符串，这容易造成属性名的冲突。比如，你使用了一个他人提供的对象，但又想为这个对象添加新的方法（mixin 模式），新方法的名字就有可能与现有方法产生冲突。如果有一种机制，保证每个属性的名字都是独一无二的就好了，这样就从根本上防止属性名的冲突。这就是 ES6 引入 Symbol 的原因。</p><p>ES6 引入了一种新的原始数据类型Symbol，表示独一无二的值，它是 JavaScript 语言的第七种数据类型，前六种是：undefined、null、布尔值（Boolean）、字符串（String）、数值（Number）、对象（Object）。</p><p>数据类型 symbol 是一种基本数据类型，该类型的性质在于这个类型的值可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值（当你想让它是私有的时候）。例如，symbol 类型的键存在于各种内置的 JavaScript 对象中。同样，自定义类也可以这样创建私有成员。symbol 数据类型具有非常明确的目的，并且因为其功能性单一的优点而突出；一个 symbol 实例可以被赋值到一个左值变量，还可以通过标识符检查类型，这就是它的全部特性。</p><p>当一个 Symbol 包装器对象作为一个属性的键时，这个对象将被强制转换为它包装过的 symbol 值：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sym = <span class="built_in">Symbol</span>(<span class="string">"foo"</span>);</span><br><span class="line"><span class="keyword">var</span> obj = &#123;[sym]: <span class="number">1</span>&#125;;</span><br><span class="line">obj[sym];            <span class="comment">// 1</span></span><br><span class="line">obj[<span class="built_in">Object</span>(sym)];    <span class="comment">// still 1</span></span><br></pre></td></tr></table></figure><h3 id="Proxy"><a href="#Proxy" class="headerlink" title="Proxy"></a>Proxy</h3><p>Proxy 对象用于创建一个对象的代理，从而实现基本操作的拦截和自定义（如属性查找、赋值、枚举、函数调用等）。ES6 原生提供 Proxy 构造函数，用来生成 Proxy 实例：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param </span>target  要使用Proxy包装的目标对象(可以是任何类型的对象，包括原生数组，函数，甚至另一个代理)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param </span>handler 一个通常以函数作为属性的对象，各属性中的函数分别定义了在执行各种操作时代理 p 的行为</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns </span>A proxy instance.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="built_in">Proxy</span>(target, handler)</span><br></pre></td></tr></table></figure><p>Proxy 对象的所有用法，都是上面这种形式，不同的只是 handler 参数的写法。其中，new Proxy() 表示生成一个 Proxy 实例，target 参数表示所要拦截的目标对象，handler 参数也是一个对象，用来定制拦截行为。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p = <span class="keyword">new</span> <span class="built_in">Proxy</span>(&#123;&#125;, &#123;</span><br><span class="line">    <span class="keyword">get</span>: function(obj, prop) &#123;</span><br><span class="line">        <span class="keyword">return</span> prop <span class="keyword">in</span> obj ? obj[prop] : <span class="number">37</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">p.a = <span class="number">1</span>;</span><br><span class="line">p.b = <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(p.a, p.b);      <span class="comment">// 1, undefined</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'c'</span> <span class="keyword">in</span> p, p.c); <span class="comment">// false, 37</span></span><br></pre></td></tr></table></figure><blockquote><p>注意，要使得 Proxy 起作用，必须针对 Proxy 实例进行操作，而不是针对目标对象进行操作。</p></blockquote><p>下面是 Proxy 支持的拦截操作一览，一共 13 种：</p><ul><li>get(target, propKey, receiver)：拦截对象属性的读取，比如<code>proxy.foo</code>和<code>proxy[&#39;foo&#39;]</code>。</li><li>set(target, propKey, value, receiver)：拦截对象属性的设置，比如<code>proxy.foo = v</code>或<code>proxy[&#39;foo&#39;] = v</code>，返回一个布尔值。</li><li>has(target, propKey)：拦截<code>propKey in proxy</code>的操作，返回一个布尔值。</li><li>deleteProperty(target, propKey)：拦截<code>delete proxy[propKey]</code>的操作，返回一个布尔值。</li><li>ownKeys(target)：拦截<code>Object.getOwnPropertyNames(proxy)</code>、<code>Object.getOwnPropertySymbols(proxy)</code>、<code>Object.keys(proxy)</code>、<code>for...in</code>循环，返回一个数组。该方法返回目标对象所有自身的属性的属性名，而<code>Object.keys()</code>的返回结果仅包括目标对象自身的可遍历属性。</li><li>getOwnPropertyDescriptor(target, propKey)：拦截<code>Object.getOwnPropertyDescriptor(proxy, propKey)</code>，返回属性的描述对象。</li><li>defineProperty(target, propKey, propDesc)：拦截<code>Object.defineProperty(proxy, propKey, propDesc)</code>、<code>Object.defineProperties(proxy, propDescs)</code>，返回一个布尔值。</li><li>preventExtensions(target)：拦截<code>Object.preventExtensions(proxy)</code>，返回一个布尔值。</li><li>getPrototypeOf(target)：拦截<code>Object.getPrototypeOf(proxy)</code>，返回一个对象。</li><li>isExtensible(target)：拦截<code>Object.isExtensible(proxy)</code>，返回一个布尔值。</li><li>setPrototypeOf(target, proto)：拦截<code>Object.setPrototypeOf(proxy, proto)</code>，返回一个布尔值。如果目标对象是函数，那么还有两种额外操作可以拦截。</li><li>apply(target, object, args)：拦截 Proxy 实例作为函数调用的操作，比如<code>proxy(...args)</code>、<code>proxy.call(object, ...args)</code>、<code>proxy.apply(...)</code>。</li><li>construct(target, args)：拦截 Proxy 实例作为构造函数调用的操作，比如<code>new proxy(...args)</code>。</li></ul><h3 id="Reflect"><a href="#Reflect" class="headerlink" title="Reflect"></a>Reflect</h3><p><code>Reflect</code>对象与<code>Proxy</code>对象一样，也是 ES6 为了操作对象而提供的新 API。<code>Reflect</code>对象的设计目的有这样几个：</p><ol><li><p>将<code>Object</code>对象的一些明显属于语言内部的方法（比如<code>Object.defineProperty</code>），放到<code>Reflect</code>对象上。现阶段，某些方法同时在<code>Object</code>和<code>Reflect</code>对象上部署，未来的新方法将只部署在<code>Reflect</code>对象上。也就是说，从<code>Reflect</code>对象上可以拿到语言内部的方法。</p></li><li><p>修改某些<code>Object</code>方法的返回结果，让其变得更合理。比如，<code>Object.defineProperty(obj, name, desc)</code>在无法定义属性时，会抛出一个错误，而<code>Reflect.defineProperty(obj, name, desc)</code>则会返回<code>false</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 老写法</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="built_in">Object</span>.defineProperty(target, property, attributes);</span><br><span class="line">  <span class="comment">// success</span></span><br><span class="line">&#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">  <span class="comment">// failure</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新写法</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">Reflect</span>.defineProperty(target, property, attributes)) &#123;</span><br><span class="line">  <span class="comment">// success</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="comment">// failure</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>让<code>Object</code>操作都变成函数行为。某些<code>Object</code>操作是命令式，比如<code>name in obj</code>和<code>delete obj[name]</code>，而<code>Reflect.has(obj, name)</code>和<code>Reflect.deleteProperty(obj, name)</code>让它们变成了函数行为。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 老写法</span></span><br><span class="line"><span class="string">'assign'</span> <span class="keyword">in</span> <span class="built_in">Object</span> <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 新写法</span></span><br><span class="line"><span class="built_in">Reflect</span>.has(<span class="built_in">Object</span>, <span class="string">'assign'</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure></li><li><p><code>Reflect</code>对象的方法与<code>Proxy</code>对象的方法一一对应，只要是<code>Proxy</code>对象的方法，就能在<code>Reflect</code>对象上找到对应的方法。这就让<code>Proxy</code>对象可以方便地调用对应的<code>Reflect</code>方法，完成默认行为，作为修改行为的基础。也就是说，不管<code>Proxy</code>怎么修改默认行为，你总可以在<code>Reflect</code>上获取默认行为。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Proxy</span>(target, &#123;</span><br><span class="line">  <span class="keyword">set</span>: function(target, name, value, receiver) &#123;</span><br><span class="line">    <span class="comment">// 采用 Reflect.set 方法将值赋值给对象的属性，确保完成原有的行为，然后再添加额外的操作</span></span><br><span class="line">    <span class="keyword">var</span> success = <span class="built_in">Reflect</span>.set(target, name, value, receiver);</span><br><span class="line">    <span class="keyword">if</span> (success) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'property '</span> + name + <span class="string">' on '</span> + target + <span class="string">' set to '</span> + value);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> success;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></li></ol><h3 id="Class"><a href="#Class" class="headerlink" title="Class"></a>Class</h3><p>ES6 提供了更接近传统语言的写法，引入了 Class 这个概念，作为对象的模板。通过class关键字，可以定义类。ES6 的class可以看作只是一个语法糖，它的绝大部分功能，ES5 都可以做到，新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(x, y) &#123;</span><br><span class="line">    <span class="keyword">this</span>.x = x;</span><br><span class="line">    <span class="keyword">this</span>.y = y;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  toString() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">'('</span> + <span class="keyword">this</span>.x + <span class="string">', '</span> + <span class="keyword">this</span>.y + <span class="string">')'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">typeof</span> Point; <span class="comment">// "function"</span></span><br><span class="line">Point === Point.prototype.constructor; <span class="comment">// true</span></span><br></pre></td></tr></table></figure></p><p>构造函数的prototype属性，在 ES6 的“类”上面继续存在。事实上，类的所有方法都定义在类的prototype属性上面。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>() &#123;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  toString() &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line">Point.prototype = &#123;</span><br><span class="line">  <span class="keyword">constructor</span>() &#123;&#125;,</span><br><span class="line">  toString() &#123;&#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><p>由于类的方法都定义在prototype对象上面，所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>()&#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Object</span>.assign(Point.prototype, &#123;</span><br><span class="line">  toString()&#123;&#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></p><p>另外，类的内部所有定义的方法，都是不可枚举的（non-enumerable），这一点与 ES5 的行为不一致。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ES6 Class</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(x, y) &#123;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  toString() &#123;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">Object</span>.keys(Point.prototype); <span class="comment">// []</span></span><br><span class="line"><span class="built_in">Object</span>.getOwnPropertyNames(Point.prototype); <span class="comment">// ["constructor", "toString"]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ES5 Function</span></span><br><span class="line"><span class="keyword">var</span> Point = <span class="function"><span class="keyword">function</span> (<span class="params">x, y</span>) </span>&#123;</span><br><span class="line">&#125;;</span><br><span class="line">Point.prototype.toString = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="built_in">Object</span>.keys(Point.prototype); <span class="comment">// ["toString"]</span></span><br><span class="line"><span class="built_in">Object</span>.getOwnPropertyNames(Point.prototype); <span class="comment">// ["constructor", "toString"]</span></span><br></pre></td></tr></table></figure></p><h3 id="Generator-函数"><a href="#Generator-函数" class="headerlink" title="Generator 函数"></a>Generator 函数</h3><p>Generator 函数是 ES6 提供的一种异步编程解决方案，语法行为与传统函数完全不同。Generator 函数有多种理解角度。语法上，首先可以把它理解成，Generator 函数是一个状态机，封装了多个内部状态。执行 Generator 函数会返回一个 Iterator 对象，返回的 Iterator 对象可以依次遍历 Generator 函数内部的每一个状态。</p><p>形式上，Generator 函数就是一个普通函数，但是有两个特征。一是，function关键字与函数名之间有一个星号；二是，函数体内部使用yield表达式，定义不同的内部状态（yield在英语里的意思就是“产出”）。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">helloWorldGenerator</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">yield</span> <span class="string">'hello'</span>;</span><br><span class="line">  <span class="keyword">yield</span> <span class="string">'world'</span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'ending'</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> hw = helloWorldGenerator();</span><br></pre></td></tr></table></figure></p><p>上面代码定义了一个 Generator 函数helloWorldGenerator，它内部有两个yield表达式（hello和world），即该函数有三个状态：hello，world 和 return 语句（结束执行）。<br>然后，Generator 函数的调用方法与普通函数一样，也是在函数名后面加上一对圆括号。不同的是，调用 Generator 函数后，该函数并不执行，返回的也不是函数运行结果，而是一个指向内部状态的指针对象，也就是 Iterator 对象。</p><p>下一步，必须调用 Iterator 对象的next方法，使得指针移向下一个状态。也就是说，每次调用next方法，内部指针就从函数头部或上一次停下来的地方开始执行，直到遇到下一个yield表达式（或return语句）为止。换言之，Generator 函数是分段执行的，yield表达式是暂停执行的标记，而next方法可以恢复执行。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">hw.next(); <span class="comment">// &#123; value: 'hello', done: false &#125;</span></span><br><span class="line"></span><br><span class="line">hw.next(); <span class="comment">// &#123; value: 'world', done: false &#125;</span></span><br><span class="line"></span><br><span class="line">hw.next(); <span class="comment">// &#123; value: 'ending', done: true &#125;</span></span><br><span class="line"></span><br><span class="line">hw.next(); <span class="comment">// &#123; value: undefined, done: true &#125;</span></span><br></pre></td></tr></table></figure></p><h3 id="Async-函数"><a href="#Async-函数" class="headerlink" title="Async 函数"></a>Async 函数</h3><p>ES2017 标准引入了 async 函数，使得异步操作变得更加方便。async 函数是什么？一句话，它就是 Generator 函数的语法糖。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Generator 函数</span></span><br><span class="line"><span class="keyword">const</span> fun = <span class="function"><span class="keyword">function</span>* (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">yield</span> fetch(<span class="string">'https://api.github.com/orgs/nodejs'</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(res);</span><br><span class="line">  <span class="keyword">return</span> res;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// Async 函数</span></span><br><span class="line"><span class="keyword">const</span> fun = <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> fetch(<span class="string">'https://api.github.com/orgs/nodejs'</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(res);</span><br><span class="line">  <span class="keyword">return</span> res;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><p>一比较就会发现，async函数就是将Generator函数的星号替换成async，将yield替换成await，仅此而已（async表示函数里有异步操作，await表示紧跟在后面的表达式需要等待结果）；进一步说，async函数完全可以看作多个异步操作包装成的一个 Promise 对象，而await命令就是内部then命令的语法糖。</p><h3 id="Module"><a href="#Module" class="headerlink" title="Module"></a>Module</h3><p>在 ES6 之前，社区制定了一些模块加载方案，最主要的有 CommonJS 和 AMD 两种。前者用于服务器，后者用于浏览器。ES6 在语言标准的层面上，实现了模块功能，而且实现得相当简单，完全可以取代 CommonJS 和 AMD 规范，成为浏览器和服务器通用的模块解决方案。</p><p>ES6 模块的设计思想是尽量的静态化，使得编译时就能确定模块的依赖关系，以及输入和输出的变量。CommonJS 和 AMD 模块，都只能在运行时确定这些东西。比如，CommonJS 模块就是对象，输入时必须查找对象属性。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CommonJS模块</span></span><br><span class="line"><span class="keyword">let</span> &#123; stat, exists, readFile &#125; = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="keyword">let</span> _fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">let</span> stat = _fs.stat;</span><br><span class="line"><span class="keyword">let</span> exists = _fs.exists;</span><br><span class="line"><span class="keyword">let</span> readfile = _fs.readfile;</span><br></pre></td></tr></table></figure></p><p>上面代码的实质是整体加载fs模块（即加载fs的所有方法），生成一个对象（_fs），然后再从这个对象上面读取三个方法。这种加载称为运行时加载，因为只有运行时才能得到这个对象，导致完全没办法在编译时做静态优化。</p><p>ES6 模块不是对象，而是通过export命令显式指定输出的代码，再通过import命令输入。<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ES6模块</span></span><br><span class="line"><span class="keyword">import</span> &#123; stat, exists, readFile &#125; <span class="keyword">from</span> <span class="string">'fs'</span>;</span><br></pre></td></tr></table></figure></p><p>上面代码的实质是从fs模块加载三个方法，其他方法不加载。这种加载称为编译时加载或者静态加载，即 ES6 可以在编译时就完成模块加载，效率要比 CommonJS 模块的加载方式高。当然，这也导致了没法引用 ES6 模块本身，因为它不是对象。</p><p>模块功能主要由两个命令构成：export和import。export命令用于规定模块的对外接口，import命令用于输入其他模块提供的功能。一个模块就是一个独立的文件。该文件内部的所有变量，外部无法获取。如果你希望外部能够读取模块内部的某个变量，就必须使用export关键字输出该变量。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">msg</span>) </span>&#123;</span><br><span class="line"><span class="built_in">console</span>.info(msg);</span><br><span class="line">&#125;(<span class="string">'func run...'</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">    name: <span class="string">'AppModule'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>ES2015<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>ECMAScript modules in browsers<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"module"</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="keyword">import</span> AppModule <span class="keyword">from</span> <span class="string">'./AppModule.js'</span>; <span class="comment">// func run...</span></span></span><br><span class="line"><span class="javascript"><span class="built_in">console</span>.log(AppModule); <span class="comment">// &#123;name: "AppModule"&#125;</span></span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Decorator"><a href="#Decorator" class="headerlink" title="Decorator"></a>Decorator</h3><p>装饰器（Decorator）是一种与类（class）相关的语法，用来注释或修改类和类方法。装饰器是一种函数（写成<code>@+函数名</code>），它可以放在类和类方法的定义前面。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">@testable</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyTestableClass</span> </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">testable</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">  target.isTestable = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">MyTestableClass.isTestable; <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>上面代码中，<code>@testable</code> 就是一个装饰器。它修改了 MyTestableClass这 个类的行为，为它加上了静态属性isTestable。testable 函数的参数 target 是 MyTestableClass 类本身。</p><p>基本上，装饰器的行为就是下面这样：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">@decorator</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>&#123;&#125;</span><br><span class="line">A = decorator(A) || A;</span><br></pre></td></tr></table></figure><p>也就是说，<strong>装饰器是一个对类进行处理的函数。装饰器函数的第一个参数，就是所要装饰的目标类</strong>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">testable</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码中，<code>testable</code>函数的参数<code>target</code>，就是会被装饰的类。</p><p>如果觉得一个参数不够用，可以在装饰器外面再封装一层函数。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">testable</span>(<span class="params">isTestable</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">    target.isTestable = isTestable;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@testable(<span class="literal">true</span>)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyTestableClass</span> </span>&#123;&#125;</span><br><span class="line">MyTestableClass.isTestable <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">@testable(<span class="literal">false</span>)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> </span>&#123;&#125;</span><br><span class="line">MyClass.isTestable <span class="comment">// false</span></span><br></pre></td></tr></table></figure><p>上面代码中，装饰器 testable 可以接受参数，这就等于可以修改装饰器的行为。</p><p>注意，<strong>装饰器对类的行为的改变，是代码编译时发生的，而不是在运行时</strong>。这意味着，装饰器能在编译阶段运行代码。也就是说，<strong>装饰器本质就是编译时执行的函数</strong>。</p><p>前面的例子是为类添加一个静态属性，如果想添加实例属性，可以通过目标类的<code>prototype</code>对象操作。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">testable</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">  target.prototype.isTestable = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@testable</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyTestableClass</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = <span class="keyword">new</span> MyTestableClass();</span><br><span class="line">obj.isTestable <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>上面代码中，装饰器函数<code>testable</code>是在目标类的<code>prototype</code>对象上添加属性，因此就可以在实例上调用。</p><h2 id="TypeScript"><a href="#TypeScript" class="headerlink" title="TypeScript"></a>TypeScript</h2><h3 id="类"><a href="#类" class="headerlink" title="类"></a>类</h3><p>TypeScript 中的类除了实现了所有 ES6 中的类的功能以外，还添加了一些新的用法。</p><h4 id="修饰符"><a href="#修饰符" class="headerlink" title="修饰符"></a>修饰符</h4><p>TypeScript 可以使用三种访问修饰符：<code>public</code>、<code>private</code> 和 <code>protected</code></p><ul><li><code>public</code> 修饰的属性或方法是公有的，可以在任何地方被访问到，默认所有的属性和方法都是 <code>public</code> 的</li><li><code>private</code> 修饰的属性或方法是私有的，不能在声明它的类的外部访问</li><li><code>protected</code> 修饰的属性或方法是受保护的，它和 <code>private</code> 类似，区别是它在子类中也是允许被访问的</li></ul><h4 id="readonly修饰符"><a href="#readonly修饰符" class="headerlink" title="readonly修饰符"></a>readonly修饰符</h4><p>可以使用 readonly 关键字将属性设置为只读的，只读属性必须在声明时或构造函数里被初始化。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> AuthController &#123;</span><br><span class="line">    <span class="keyword">private</span> readonly authService: AuthService;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">constructor</span>(<span class="params">authService: AuthService</span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.authService = authService;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="参数属性"><a href="#参数属性" class="headerlink" title="参数属性"></a>参数属性</h4><p>修饰符和 <code>readonly</code> 可以使用在构造函数参数中，等同于在类中定义该属性同时给该属性赋值，使代码更简洁。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 使用参数属性可以方便地把声明和赋值合并至一处</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">class</span> AuthController &#123;</span><br><span class="line">    <span class="comment">// private readonly authService: AuthService;</span></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> readonly authService: AuthService</span>) &#123;</span><br><span class="line">        <span class="comment">// this.authService = authService;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="抽象类"><a href="#抽象类" class="headerlink" title="抽象类"></a>抽象类</h4><p>抽象类是供其他类继承的基类，抽象类不允许被实例化，抽象类中的抽象方法必须在子类中被实现。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> Animal &#123;</span><br><span class="line">  <span class="keyword">public</span> name;</span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">constructor</span>(<span class="params">name</span>) &#123;</span><br><span class="line">    <span class="keyword">this</span>.name = name;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">abstract</span> sayHi();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Cat <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">  <span class="keyword">public</span> sayHi() &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">`Meow, My name is <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cat = <span class="keyword">new</span> Cat(<span class="string">'Tom'</span>);</span><br></pre></td></tr></table></figure><h3 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h3><p>在TypeScript里，接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。在TypeScript里，我们只会去关注值的外形，只在两个类型内部的结构兼容那么这两个类型就是兼容的。这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以，而不必明确地使用 implements 语句。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Person &#123;</span><br><span class="line">    firstName: <span class="built_in">string</span>;</span><br><span class="line">    lastName: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greeter</span>(<span class="params">person: Person</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">"Hello, "</span> + person.firstName + <span class="string">" "</span> + person.lastName;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> user = &#123; firstName: <span class="string">"Jane"</span>, lastName: <span class="string">"User"</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.innerHTML = greeter(user);</span><br></pre></td></tr></table></figure><h3 id="装饰器"><a href="#装饰器" class="headerlink" title="装饰器"></a>装饰器</h3><p>随着TypeScript和ES6里引入了类，在一些场景下我们需要额外的特性来支持标注或修改类及其成员。装饰器（Decorators）为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。若要启用实验性的装饰器特性，你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项。</p><p>装饰器是一种特殊类型的声明，它能够被附加到类声明、方法、访问符、属性或参数上。装饰器使用 @expression 这种形式，expression求值后必须为一个函数，它会在运行时被调用，被装饰的声明信息做为参数传入。</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://wangdoc.com/javascript/" target="_blank" rel="noopener">JavaScript 教程</a></li><li><a href="https://es6.ruanyifeng.com/" target="_blank" rel="noopener">ECMAScript 入门</a></li><li><a href="https://babeljs.io/docs/en/learn" target="_blank" rel="noopener">Learn ES2015</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Truthy" target="_blank" rel="noopener">Truthy（真值）</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Falsy" target="_blank" rel="noopener">Falsy（虚值）</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="web" scheme="https://lugavin.github.io/categories/web/"/>
    
    
      <category term="typescript" scheme="https://lugavin.github.io/tags/typescript/"/>
    
  </entry>
  
  <entry>
    <title>Engineering - Docker</title>
    <link href="https://lugavin.github.io/2019/07/14/engineering/docker/"/>
    <id>https://lugavin.github.io/2019/07/14/engineering/docker/</id>
    <published>2019-07-14T12:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.889Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="Docker安装"><a href="#Docker安装" class="headerlink" title="Docker安装"></a><a href="https://docs.docker.com/install/linux/docker-ce/centos/" target="_blank" rel="noopener">Docker安装</a></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Download packages</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> wget https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> wget https://download.docker.com/linux/centos/7/x86_64/stable/Packages/docker-ce-cli-18.09.7-3.el7.x86_64.rpm</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> wget https://download.docker.com/linux/centos/7/x86_64/stable/Packages/docker-ce-18.09.0-3.el7.x86_64.rpm</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Install required packages</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> yum install container-selinux libseccomp</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> rpm -ivh containerd.io-1.2.6-3.3.el7.x86_64.rpm</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> rpm -ivh docker-ce-cli-18.09.7-3.el7.x86_64.rpm</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Install docker</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> rpm -ivh docker-ce-18.09.0-3.el7.x86_64.rpm</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Uninstall docker</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># $ yum remove docker-ce</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Start docker</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> systemctl start docker</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Verify docker</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run hello-world</span></span><br><span class="line"></span><br><span class="line">Hello from Docker!</span><br><span class="line">This message shows that your installation appears to be working correctly.</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># List images</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker images</span></span><br></pre></td></tr></table></figure><h2 id="Docker配置"><a href="#Docker配置" class="headerlink" title="Docker配置"></a><a href="https://docs.docker.com/engine/reference/commandline/dockerd/" target="_blank" rel="noopener">Docker配置</a></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 开启远程访问Docker</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment">## 方式一</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vi /usr/lib/systemd/system/docker.service</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">ExecStart=/usr/bin/dockerd -H unix://</span></span><br><span class="line">ExecStart=/usr/bin/dockerd</span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> vi /etc/docker/daemon.json</span></span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment">## 方式二</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vi /usr/lib/systemd/system/docker.service</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash">ExecStart=/usr/bin/dockerd -H unix://</span></span><br><span class="line">ExecStart=/usr/bin/dockerd -H unix:// -H tcp://0.0.0.0:2375</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment">## 方式三</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 重新加载配置</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> systemctl daemon-reload</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 重启docker</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> systemctl restart docker</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 测试</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> curl 127.0.0.1:2375/info</span></span><br></pre></td></tr></table></figure><h2 id="Docker部署服务"><a href="#Docker部署服务" class="headerlink" title="Docker部署服务"></a><a href="https://docs.docker.com/engine/reference/commandline/docker/" target="_blank" rel="noopener">Docker部署服务</a></h2><h3 id="构建Docker镜像"><a href="#构建Docker镜像" class="headerlink" title="构建Docker镜像"></a>构建Docker镜像</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- pom.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">finalName</span>&gt;</span>$&#123;project.artifactId&#125;<span class="tag">&lt;/<span class="name">finalName</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.spotify<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>docker-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">imageName</span>&gt;</span>$&#123;project.artifactId&#125;:$&#123;project.version&#125;<span class="tag">&lt;/<span class="name">imageName</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">dockerHost</span>&gt;</span>http://192.168.8.129:2375<span class="tag">&lt;/<span class="name">dockerHost</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">dockerDirectory</span>&gt;</span>$&#123;project.basedir&#125;/src/main/docker<span class="tag">&lt;/<span class="name">dockerDirectory</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">directory</span>&gt;</span>$&#123;project.build.directory&#125;<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">include</span>&gt;</span>$&#123;project.build.finalName&#125;.jar<span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- Package as an executable jar --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## src/main/docker/Dockerfile</span></span><br><span class="line"><span class="keyword">FROM</span> openjdk:<span class="number">8</span>-jdk-alpine</span><br><span class="line"><span class="keyword">VOLUME</span><span class="bash"> /tmp</span></span><br><span class="line"><span class="keyword">ADD</span><span class="bash"> *.jar app.jar</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="bash"> [<span class="string">"java"</span>,<span class="string">"-Djava.security.egd=file:/dev/./urandom"</span>,<span class="string">"-jar"</span>,<span class="string">"/app.jar"</span>]</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br></pre></td></tr></table></figure><h3 id="启动容器实例"><a href="#启动容器实例" class="headerlink" title="启动容器实例"></a>启动容器实例</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 将本机7070端口映射到容器的8080端口</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -p 7070:8080 reliablemq</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># -d|--detach: Run container in background and print container ID</span></span></span><br><span class="line">79fbc1e40a2aa3e5ef16823102a41218</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 查看容器日志</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker logs 79fbc1e40a2aa3e5ef16823102a41218</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 列出本地容器</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker ps</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 列出本地镜像</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker images</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 关闭容器</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker stop facf8533780a</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 删除容器</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rm facf8533780a</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 删除镜像</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker rmi 3d9019901c03</span></span><br></pre></td></tr></table></figure><h2 id="Docker可视化"><a href="#Docker可视化" class="headerlink" title="Docker可视化"></a><a href="https://www.portainer.io/" target="_blank" rel="noopener">Docker可视化</a></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 从DockerHub查找Portainer镜像</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker search portainer</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 下载Portainer镜像</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker pull portainer/portainer</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 单机运行Portainer</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker run -d -p 9000:9000 portainer/portainer</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 测试</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> curl http://127.0.0.1:9000</span></span><br></pre></td></tr></table></figure><h2 id="Docker私服"><a href="#Docker私服" class="headerlink" title="Docker私服"></a><a href="https://goharbor.io/" target="_blank" rel="noopener">Docker私服</a></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Install Docker Compose</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> wget https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mv docker-compose-Linux-x86_64 /usr/<span class="built_in">local</span>/bin/docker-compose</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> chmod +x /usr/<span class="built_in">local</span>/bin/docker-compose</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Test the installation</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker-compose --version</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Download the harbor installer</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> wget https://storage.googleapis.com/harbor-releases/release-1.8.0/harbor-offline-installer-v1.8.1.tgz</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> tar -zxvf harbor-offline-installer-v1.8.1.tgz</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> harbor</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Configure harbor.yml</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vi harbor.yml</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Configuration file of Harbor</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> The IP address or hostname to access admin UI and registry service.</span></span><br><span class="line"><span class="meta">#</span><span class="bash">hostname: reg.mydomain.com</span></span><br><span class="line">hostname: 192.168.8.129</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> http related config</span></span><br><span class="line">http:</span><br><span class="line">  port: 80</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> The initial password of Harbor admin</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> It only works <span class="keyword">in</span> first time to install harbor</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Remember Change the admin password from UI after launching Harbor.</span></span><br><span class="line">harbor_admin_password: Harbor12345</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Harbor DB configuration</span></span><br><span class="line">database:</span><br><span class="line"><span class="meta">  #</span><span class="bash"> The password <span class="keyword">for</span> the root user of Harbor DB. Change this before any production use.</span></span><br><span class="line">  password: root123</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> The default data volume</span></span><br><span class="line">data_volume: /data</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Install and start Harbor</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./install.sh</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Test the installation</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> curl http://192.168.8.129</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Deploy a plain HTTP registry</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vi /etc/docker/daemon.json</span></span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    "insecure-registries": ["192.168.8.129"]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Restart Docker for the changes to take effect</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> systemctl daemon-reload &amp;&amp; systemctl restart docker</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker-compose -f /opt/harbor/docker-compose.yml up -d</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker info</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Login to a self-hosted registry</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker login -u admin -p Harbor12345 http://192.168.8.129</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker tag hello-world:latest 192.168.8.129/library/hello-world:latest</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># To push an image to a private registry, Docker requires that the image tag being pushed is prefixed with the hostname and port of the registry.</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> docker push 192.168.8.129/library/hello-world:latest</span></span><br></pre></td></tr></table></figure><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- pom.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.spotify<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>docker-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- The credentials for the registry is set in the settings.xml --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">serverId</span>&gt;</span>docker-hub<span class="tag">&lt;/<span class="name">serverId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">pushImage</span>&gt;</span>true<span class="tag">&lt;/<span class="name">pushImage</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">imageName</span>&gt;</span></span><br><span class="line">            192.168.8.129/library/$&#123;project.artifactId&#125;:$&#123;project.version&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">imageName</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!--&lt;imageName&gt;$&#123;project.artifactId&#125;:$&#123;project.version&#125;&lt;/imageName&gt;--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dockerHost</span>&gt;</span>http://192.168.8.129:2375<span class="tag">&lt;/<span class="name">dockerHost</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dockerDirectory</span>&gt;</span>$&#123;project.basedir&#125;/src/main/docker<span class="tag">&lt;/<span class="name">dockerDirectory</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">directory</span>&gt;</span>$&#123;project.build.directory&#125;<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">include</span>&gt;</span>$&#123;project.build.finalName&#125;.jar<span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- $&#123;MAVEN_HOME&#125;/conf/settings.xml --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">servers</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">server</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">id</span>&gt;</span>docker-hub<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">username</span>&gt;</span>admin<span class="tag">&lt;/<span class="name">username</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">password</span>&gt;</span>Harbor12345<span class="tag">&lt;/<span class="name">password</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">email</span>&gt;</span>lugavin@outlook.com<span class="tag">&lt;/<span class="name">email</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">server</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">servers</span>&gt;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="engineering" scheme="https://lugavin.github.io/categories/engineering/"/>
    
    
      <category term="docker" scheme="https://lugavin.github.io/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>Engineering - Jenkins</title>
    <link href="https://lugavin.github.io/2019/07/10/engineering/jenkins/"/>
    <id>https://lugavin.github.io/2019/07/10/engineering/jenkins/</id>
    <published>2019-07-10T22:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.889Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="下载安装"><a href="#下载安装" class="headerlink" title="下载安装"></a><a href="http://mirrors.jenkins-ci.org/war-stable/" target="_blank" rel="noopener">下载安装</a></h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> wget http://mirrors.jenkins-ci.org/war-stable/latest/jenkins.war</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> java -jar jenkins.war --httpPort=8090 --prefix=/jenkins</span></span><br></pre></td></tr></table></figure><p>启动后如果遇到以下错误：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: </span><br><span class="line">sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target</span><br><span class="line">...</span><br></pre></td></tr></table></figure></p><p>则可以通过修改<code>${JENKINS_HOME}/hudson.model.UpdateCenter.xml</code>文件来解决：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version='1.1' encoding='UTF-8'?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">sites</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">site</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>default<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;url&gt;https://updates.jenkins.io/update-center.json&lt;/url&gt; --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://updates.jenkins.io/update-center.json<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">site</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">sites</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> wget http://mirrors.jenkins-ci.org/plugins/publish-over-ssh/latest/publish-over-ssh.hpi</span></span><br></pre></td></tr></table></figure><h2 id="配置-SSH-Server"><a href="#配置-SSH-Server" class="headerlink" title="配置 SSH Server"></a>配置 SSH Server</h2><p><em>Manage Jenkins &gt; Configure System &gt; Publish over SSH &gt; SSH Servers &gt; Add SSH Server</em></p><p><img src="/images/engineering/jenkins-add-ssh-server.png" alt="jenkins-add-ssh-server"></p><h2 id="创建-Freestyle-Job"><a href="#创建-Freestyle-Job" class="headerlink" title="创建 Freestyle Job"></a>创建 Freestyle Job</h2><p><img src="/images/engineering/jenkins-git-repo.png" alt="jenkins-git-repo"></p><p><img src="/images/engineering/jenkins-build-env.png" alt="jenkins-build-env"></p><p><img src="/images/engineering/jenkins-mvn-build.png" alt="jenkins-mvn-build"></p><blockquote><p><em>SSH Publishers</em> 中的 <em>Remote directory</em> 是相对于 <em>SSH Servers</em> 中设置的 <em>Remote Directory</em> 的；建议将 <em>SSH Servers</em> 中的 <em>Remote Directory</em> 设置为根目录，若不设置，那么打包后的文件可能无法上传到 <em>SSH Publishers</em> 中设置的 <em>Remote directory</em> 目录。</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="engineering" scheme="https://lugavin.github.io/categories/engineering/"/>
    
    
      <category term="jenkins" scheme="https://lugavin.github.io/tags/jenkins/"/>
    
  </entry>
  
  <entry>
    <title>Spring WebFlux</title>
    <link href="https://lugavin.github.io/2019/04/21/javaee/webflux/"/>
    <id>https://lugavin.github.io/2019/04/21/javaee/webflux/</id>
    <published>2019-04-21T20:50:00.000Z</published>
    <updated>2026-05-09T06:54:58.889Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>Spring 5 是继 Spring 4 之后将近四年内的一个重大的版本升级。Spring 5 中最重要改动是把反应式编程的思想应用到了框架的各个方面，Spring 5 的反应式编程以 Reactor 库为基础。</p><p>Spring WebFlux 是 Spring 5 的反应式核心，名称中的 Flux 来源于 Reactor 中的类 Flux。Spring WebFlux 模块中包含了对反应式 HTTP、服务器推送事件和 WebSocket 的客户端和服务器端的支持。WebFlux 需要底层提供运行时的支持，WebFlux 可以运行在支持 Servlet 3.1 非阻塞 IO API 的 Servlet 容器上，或者其他异步运行时环境，如 Netty 和 Undertow。</p><h2 id="同步Servlet和异步Servlet"><a href="#同步Servlet和异步Servlet" class="headerlink" title="同步Servlet和异步Servlet"></a>同步Servlet和异步Servlet</h2><p>在 Servlet 3.0 之前，Servlet采用<code>One-Request-Per-Thread</code>的方式处理请求，即每一个Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作，比如访问数据库、调用第三方服务接口等，那么其所对应的线程将同步地等待IO操作完成，而IO操作是非常慢的，所以此时的线程并不能及时地释放回线程池以供后续使用，在并发量越来越大的情况下，这将带来严重的性能问题。即便是像SpringMVC、Struts这样的框架也脱离不了这样的桎梏，因为它们都是建立在Servlet之上的。为了解决这样的问题，Servlet 3.0 引入了异步处理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@WebServlet</span>(<span class="string">"/blocking-sync"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BlockingSyncServlet</span> <span class="keyword">extends</span> <span class="title">HttpServlet</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doPost</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        doExecute();</span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        log.debug(<span class="string">"&#123;&#125;: Execute completed in &#123;&#125; ms"</span>, <span class="keyword">this</span>, endTime - startTime);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doExecute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 模拟业务处理耗时操作</span></span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">10</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 Servlet 3.0 中，我们可以从HttpServletRequest对象中获得一个AsyncContext对象，该对象构成了异步处理的上下文，Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程，并在新的线程中完成对请求的处理并返回结果给客户端，初始线程便可以还回给容器线程池以处理更多的请求。如此，通过将请求从一个线程传给另一个线程处理的过程便构成了 Servlet 3.0 中的异步处理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@WebServlet</span>(urlPatterns = <span class="string">"/blocking-async"</span>, asyncSupported = <span class="keyword">true</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BlockingAsyncServlet</span> <span class="keyword">extends</span> <span class="title">HttpServlet</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doPost</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        <span class="comment">// 开启异步模式: Servlet线程不再是一直处于阻塞状态以等待业务逻辑的处理, 而是启动异步线程之后线程本身返回至容器</span></span><br><span class="line">        AsyncContext ctx = request.startAsync();</span><br><span class="line">        <span class="comment">// 请求异步处理</span></span><br><span class="line">        doExecute(ctx);</span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        log.debug(<span class="string">"&#123;&#125;: Execute completed in &#123;&#125; ms"</span>, <span class="keyword">this</span>, endTime - startTime);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 直接调用AsyncContext的start()方法会向Servlet容器另外申请一个新的线程, 这种方式对性能的改进不大,</span></span><br><span class="line"><span class="comment">     * 因为如果新的线程和初始线程共享同一个线程池的话, 相当于闲置下了一个线程, 但同时又占用了另一个线程.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doExecute</span><span class="params">(AsyncContext ctx)</span> </span>&#123;</span><br><span class="line">        CompletableFuture.runAsync(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 模拟业务处理耗时操作</span></span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">10</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException ex) &#123;</span><br><span class="line">                <span class="comment">// Ignored</span></span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="comment">// 处理完毕后需要调用complete()方法告知Servlet容器</span></span><br><span class="line">                ctx.complete();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Servlet 3.0 对请求的处理虽然是异步的，但是对InputStream和OutputStream的IO操作却依然是阻塞的，对于数据量大的请求体或者返回体，阻塞IO也将导致不必要的等待。因此在 Servlet 3.1 中又引入了非阻塞IO来进一步增强异步处理的性能，通过在HttpServletRequest和HttpServletResponse中分别添加ReadListener和WriterListener方式，只有在IO数据满足一定条件时（比如数据准备好时）才进行后续的操作。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@WebServlet</span>(urlPatterns = <span class="string">"/non-blocking-async"</span>, asyncSupported = <span class="keyword">true</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NonBlockingAsyncServlet</span> <span class="keyword">extends</span> <span class="title">HttpServlet</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doPost</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        AsyncContext ctx = request.startAsync();</span><br><span class="line">        request.getInputStream()</span><br><span class="line">                .setReadListener(<span class="keyword">new</span> HttpRequestReadListener(ctx));</span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        log.debug(<span class="string">"&#123;&#125;: Execute completed in &#123;&#125; ms"</span>, <span class="keyword">this</span>, endTime - startTime);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">HttpRequestReadListener</span> <span class="keyword">implements</span> <span class="title">ReadListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsyncContext asyncContext;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="title">HttpRequestReadListener</span><span class="params">(AsyncContext asyncContext)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.asyncContext = asyncContext;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDataAvailable</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAllDataRead</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            CompletableFuture.runAsync(() -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    TimeUnit.SECONDS.sleep(<span class="number">10</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException ex) &#123;</span><br><span class="line">                    <span class="comment">// Ignored</span></span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    asyncContext.complete();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable t)</span> </span>&#123;</span><br><span class="line">            asyncContext.complete();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WebSocket和SSE"><a href="#WebSocket和SSE" class="headerlink" title="WebSocket和SSE"></a>WebSocket和SSE</h2><p>服务器端数据推送技术除了<code>WebSocket</code>外还有<code>Server-sent Events</code>(简称SSE)，WebSocket 规范和 Server-sent Events 都是 HTML5 标准的组成部分，在主流浏览器上都提供了原生的支持，都是推荐使用的。WebSocket适用于需要进行复杂双向数据通讯的场景，对于简单的服务器数据推送的场景，使用服务器推送事件就足够了。</p><p>正如名称所表示的一样，WebSocket使用的是套接字连接，基于<code>TCP</code>协议。使用WebSocket之后，实际上是在服务器端和浏览器之间建立一个套接字连接，可以进行双向的数据传输。WebSocket功能强大，使用起来也灵活，可以适用于不同的场景。除了WebSocket之外，其他的实现方式是基于<code>HTTP</code>协议来达到实时推送的效果。</p><p>严格地说，HTTP 协议无法做到服务器主动推送信息。但是，有一种变通方法，就是服务器向客户端声明，接下来要发送的是流信息。也就是说，发送的不是一次性的数据包，而是一个数据流，会连续不断地发送过来。这时，客户端不会关闭连接，会一直等着服务器发过来的新的数据流，视频播放就是这样的例子。本质上，这种通信就是以流信息的方式，完成一次用时很长的下载。SSE 就是利用这种机制，使用流信息向浏览器推送信息。它基于 HTTP 协议，目前除了 IE/Edge，其他浏览器都支持。</p><p>总体来说，WebSocket 更强大和灵活，可以双向通信；SSE 是单向通信，只能服务器向浏览器发送，因为流信息本质上就是下载。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@WebServlet</span>(<span class="string">"/sse"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SseServlet</span> <span class="keyword">extends</span> <span class="title">HttpServlet</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TEXT_EVENT_STREAM_VALUE = <span class="string">"text/event-stream"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">doGet</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        doExecute(response);</span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        System.out.println(String.format(<span class="string">"%s &gt;&gt; Execute completed in %s ms"</span>, <span class="keyword">this</span>, endTime - startTime));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">doExecute</span><span class="params">(HttpServletResponse response)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        response.setContentType(TEXT_EVENT_STREAM_VALUE);</span><br><span class="line">        response.setCharacterEncoding(StandardCharsets.UTF_8.name());</span><br><span class="line">        PrintWriter out = response.getWriter();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        out.write(<span class="string">"event: myEvent\n"</span>);</span><br><span class="line">        out.write(<span class="string">"retry: 10000\n"</span>);</span><br><span class="line">        out.write(<span class="string">"data: "</span> + LocalDateTime.now().format(DateTimeFormatter.ofPattern(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>)) + <span class="string">"\n\n"</span>);</span><br><span class="line">        out.flush();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sse = <span class="keyword">new</span> EventSource(<span class="string">'sse'</span>);</span><br><span class="line"><span class="comment">// 默认情况下, 服务器发来的数据, 总是触发浏览器EventSource实例的message事件</span></span><br><span class="line">sse.addEventListener(<span class="string">'message'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(e.data);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 可自定义 SSE 事件, 这种情况下, 发送回来的数据不会触发message事件</span></span><br><span class="line">sse.addEventListener(<span class="string">'myEvent'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(e.data);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Reactive-Stream"><a href="#Reactive-Stream" class="headerlink" title="Reactive Stream"></a>Reactive Stream</h2><p>流 Stream 是什么？流是序列，是生产者生产、一个或多个消费者消费的元素序列。这种具体的设计模式称为发布订阅模式。常见的流处理机制是 pull / push 模式。背压是一种常用策略，使得发布者拥有无限制的缓冲区存储元素，用于确保发布者发布元素太快时，不会去压制订阅者。</p><p><code>Reactive Streams</code>是提供处理非阻塞背压异步流的一种标准，<code>JDK9</code>提供了 <code>Flow</code> 和 <code>SubmissionPublisher</code> 两个主要的 API 来处理响应流。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReactiveStreamTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testReactiveStream</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        SubmissionPublisher&lt;Integer&gt; publisher = <span class="keyword">new</span> SubmissionPublisher&lt;&gt;();</span><br><span class="line">        Subscriber&lt;Integer&gt; subscriber = <span class="keyword">new</span> ConsumerSubscriber&lt;&gt;();</span><br><span class="line">        publisher.subscribe(subscriber);</span><br><span class="line">        <span class="comment">// submit是阻塞方法(当发布者发布数据时, 如果订阅者的数据缓冲数组已满, 则submit会被阻塞, 从而实现调节生产者发布数据的速度)</span></span><br><span class="line">        IntStream.rangeClosed(<span class="number">1</span>, <span class="number">1000</span>).forEach(i -&gt; &#123;</span><br><span class="line">            log.debug(<span class="string">"====== 生产数据 &#123;&#125; ======"</span>, i);</span><br><span class="line">            publisher.submit(i);</span><br><span class="line">        &#125;);</span><br><span class="line">        publisher.close();</span><br><span class="line">        Thread.currentThread().join(TimeUnit.SECONDS.toMillis(<span class="number">60</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ConsumerSubscriber</span>&lt;<span class="title">T</span>&gt; <span class="keyword">implements</span> <span class="title">Subscriber</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> Subscription subscription;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSubscribe</span><span class="params">(Subscription subscription)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.subscription = subscription;</span><br><span class="line">            <span class="keyword">this</span>.subscription.request(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onNext</span><span class="params">(T item)</span> </span>&#123;</span><br><span class="line">            log.debug(<span class="string">"====== 消费数据 &#123;&#125; ======"</span>, item);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 模拟业务处理耗时</span></span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">this</span>.subscription.request(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onError</span><span class="params">(Throwable throwable)</span> </span>&#123;</span><br><span class="line">            log.error(<span class="string">"出现异常啦"</span>, throwable);</span><br><span class="line">            <span class="keyword">this</span>.subscription.cancel();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onComplete</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            log.info(<span class="string">"数据全部处理完成"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="WebMVC-VS-WebFlux"><a href="#WebMVC-VS-WebFlux" class="headerlink" title="WebMVC VS WebFlux"></a>WebMVC VS WebFlux</h2><p>WebMVC是同步阻塞的IO模型，而WebFlux是异步非阻塞的IO模型。</p><blockquote><p>Reactor = JDK8 Stream + JDK9 Reactive Stream</p></blockquote><p><img src="/images/javaee/webflux-reactor.svg" alt="WebMVC VS WebFlux"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReactorResource</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"webmvc"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">webmvc</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        String currTime = <span class="keyword">this</span>.handle();</span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        log.info(<span class="string">"WebMVC &gt;&gt; Execute completed in &#123;&#125; ms"</span>, endTime - startTime);</span><br><span class="line">        <span class="keyword">return</span> currTime;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"webflux"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Mono&lt;String&gt; <span class="title">webflux</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        Mono&lt;String&gt; mono = Mono.fromSupplier(<span class="keyword">this</span>::handle); <span class="comment">// 中间操作, 没有执行最终操作, 不会阻塞</span></span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        log.info(<span class="string">"WebFlux &gt;&gt; Execute completed in &#123;&#125; ms"</span>, endTime - startTime);</span><br><span class="line">        <span class="keyword">return</span> mono;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(value = <span class="string">"stream"</span>, produces = TEXT_EVENT_STREAM_VALUE)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Flux&lt;Integer&gt; <span class="title">stream</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> startTime = System.currentTimeMillis();</span><br><span class="line">        Flux&lt;Integer&gt; flux = Flux.fromStream(IntStream.rangeClosed(<span class="number">1</span>, <span class="number">10</span>).mapToObj(i -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> i;</span><br><span class="line">        &#125;));</span><br><span class="line">        <span class="keyword">long</span> endTime = System.currentTimeMillis();</span><br><span class="line">        log.info(<span class="string">"Stream &gt;&gt; Execute completed in &#123;&#125; ms"</span>, endTime - startTime);</span><br><span class="line">        <span class="keyword">return</span> flux;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">handle</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">5</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> LocalDateTime.now().format(DateTimeFormatter.ofPattern(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="javaee" scheme="https://lugavin.github.io/categories/javaee/"/>
    
    
      <category term="webflux" scheme="https://lugavin.github.io/tags/webflux/"/>
    
  </entry>
  
  <entry>
    <title>数据库迁移</title>
    <link href="https://lugavin.github.io/2019/04/13/database/migration/"/>
    <id>https://lugavin.github.io/2019/04/13/database/migration/</id>
    <published>2019-04-13T11:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.889Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="Oracle-SQL-Developer"><a href="#Oracle-SQL-Developer" class="headerlink" title="Oracle SQL Developer"></a><a href="https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/sqldev-downloads-42-3802334.html" target="_blank" rel="noopener">Oracle SQL Developer</a></h2><p>Oracle SQL Developer 包含迁移支持，让用户可以将数据库对象和数据从 IBM DB2、MySQL、Microsoft SQL Server、Microsoft Access、Sybase 和 Teradata 迁移到 Oracle。</p><h2 id="DataX"><a href="#DataX" class="headerlink" title="DataX"></a><a href="https://github.com/alibaba/DataX/" target="_blank" rel="noopener">DataX</a></h2><p>DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台，实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。</p><p>DataX本身作为数据同步框架，将不同数据源的同步抽象为从源头数据源读取数据的Reader插件，以及向目标端写入数据的Writer插件，理论上DataX框架可以支持任意数据源类型的数据同步工作。同时DataX插件体系作为一套生态系统, 每接入一套新数据源该新加入的数据源即可实现和现有的数据源互通。</p><h3 id="运行环境"><a href="#运行环境" class="headerlink" title="运行环境"></a>运行环境</h3><p>DataX 需要 Python 运行环境的支持，官方推荐安装 <a href="https://www.python.org/ftp/python/" target="_blank" rel="noopener">Python2.6.X</a> 版本。</p><h3 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h3><h4 id="方式一"><a href="#方式一" class="headerlink" title="方式一"></a>方式一</h4><ol><li><p><a href="http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz" target="_blank" rel="noopener">下载DataX</a></p></li><li><p>启动DataX</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> &#123;DATAX_HOME&#125;</span><br><span class="line">$ python ./bin/datax.py ./job/job.json</span><br></pre></td></tr></table></figure></li></ol><h4 id="方式二"><a href="#方式二" class="headerlink" title="方式二"></a>方式二</h4><ol><li><p>下载DataX源码</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git clone git@github.com:alibaba/DataX.git</span><br></pre></td></tr></table></figure></li><li><p>通过maven打包</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> &#123;DATAX_HOME&#125;</span><br><span class="line">$ mvn -U clean package assembly:assembly -Dmaven.test.skip=true</span><br></pre></td></tr></table></figure></li><li><p>启动DataX</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> &#123;DATAX_HOME&#125;/target/datax/datax</span><br><span class="line">$ python ./bin/datax.py ./job/job.json</span><br></pre></td></tr></table></figure></li></ol><blockquote><p>说明：打包成功后的DataX包位于 {DATAX_HOME}/target/datax/datax/ 目录下</p></blockquote><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><ol><li>根据模板创建作业的配置文件(JSON格式)</li></ol><ul><li><a href="https://github.com/alibaba/DataX/blob/master/mysqlreader/doc/mysqlreader.md" target="_blank" rel="noopener">MysqlReader配置样例</a></li><li><a href="https://github.com/alibaba/DataX/blob/master/mysqlwriter/doc/mysqlwriter.md" target="_blank" rel="noopener">MysqlWriter配置样例</a></li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"job"</span>: &#123;</span><br><span class="line">        <span class="attr">"setting"</span>: &#123;</span><br><span class="line">            <span class="attr">"speed"</span>: &#123;</span><br><span class="line">                <span class="attr">"channel"</span>: <span class="number">8</span></span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="attr">"errorLimit"</span>: &#123;</span><br><span class="line">                <span class="attr">"record"</span>: <span class="number">0</span>,</span><br><span class="line">                <span class="attr">"percentage"</span>: <span class="number">0.02</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">"content"</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="attr">"reader"</span>: &#123;</span><br><span class="line">                    <span class="attr">"name"</span>: <span class="string">"mysqlreader"</span>,</span><br><span class="line">                    <span class="attr">"parameter"</span>: &#123;</span><br><span class="line">                        <span class="attr">"username"</span>: <span class="string">"$&#123;jdbc_username_r&#125;"</span>,</span><br><span class="line">                        <span class="attr">"password"</span>: <span class="string">"$&#123;jdbc_password_r&#125;"</span>,</span><br><span class="line">                        <span class="attr">"connection"</span>: [</span><br><span class="line">                            &#123;</span><br><span class="line">                                <span class="attr">"jdbcUrl"</span>: [<span class="string">"$&#123;jdbc_url_r&#125;"</span>],</span><br><span class="line">                                <span class="attr">"querySql"</span>: [</span><br><span class="line">                                    <span class="string">"SELECT id, username, password, nickname, salt, phone, email, avatar, lang_key, activated, activation_key, reset_key, reset_date, created_by, created_at, updated_by, updated_at FROM sys_user"</span></span><br><span class="line">                                ]</span><br><span class="line">                            &#125;</span><br><span class="line">                        ]</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="attr">"writer"</span>: &#123;</span><br><span class="line">                    <span class="attr">"name"</span>: <span class="string">"mysqlwriter"</span>,</span><br><span class="line">                    <span class="attr">"parameter"</span>: &#123;</span><br><span class="line">                        <span class="attr">"username"</span>: <span class="string">"$&#123;jdbc_username_w&#125;"</span>,</span><br><span class="line">                        <span class="attr">"password"</span>: <span class="string">"$&#123;jdbc_password_w&#125;"</span>,</span><br><span class="line">                        <span class="attr">"column"</span>: [</span><br><span class="line">                            <span class="string">"id"</span>, <span class="string">"username"</span>, <span class="string">"password"</span>, <span class="string">"nickname"</span>, <span class="string">"salt"</span>, <span class="string">"phone"</span>, <span class="string">"email"</span>, <span class="string">"avatar"</span>, <span class="string">"lang_key"</span>, <span class="string">"activated"</span>, <span class="string">"activation_key"</span>, <span class="string">"reset_key"</span>, <span class="string">"reset_date"</span>, <span class="string">"created_by"</span>, <span class="string">"created_at"</span>, <span class="string">"updated_by"</span>, <span class="string">"updated_at"</span></span><br><span class="line">                        ],</span><br><span class="line">                        <span class="attr">"connection"</span>: [</span><br><span class="line">                            &#123;</span><br><span class="line">                                <span class="attr">"jdbcUrl"</span>: <span class="string">"$&#123;jdbc_url_w&#125;"</span>,</span><br><span class="line">                                <span class="attr">"table"</span>: [<span class="string">"sys_user"</span>]</span><br><span class="line">                            &#125;</span><br><span class="line">                        ],</span><br><span class="line">                        <span class="attr">"preSql"</span>: [</span><br><span class="line">                            <span class="string">"TRUNCATE TABLE @table"</span></span><br><span class="line">                        ]</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>(1)可通过命令查看配置模板：<code>python ./bin/datax.py -r mysqlreader -w mysqlwriter</code><br>(2)<code>job.setting.speed.byte</code>配置全局的byte限速，<code>core.transport.channel.speed.byte</code>配置单个channel的byte限速（channel个数 = 全局的byte限速 / 单个channel的byte限速）</p></blockquote><ol start="2"><li>启动DataX<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> &#123;DATAX_HOME&#125;</span><br><span class="line">$ python ./bin/datax.py ./job/mysql2mysql.json -p"-Djdbc_username_r=root -Djdbc_password_r=root -Djdbc_url_r=jdbc:mysql://<span class="number">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">3306</span>/cloud -Djdbc_username_w=root -Djdbc_password_w=root -Djdbc_url_w=jdbc:mysql://<span class="number">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">3306</span>/test"</span><br></pre></td></tr></table></figure></li></ol><!-- <figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">REM for /r ./job %i in (*.json) do @echo %i</span></span><br><span class="line">$ <span class="built_in">cd</span> &#123;DATAX_HOME&#125;</span><br><span class="line">$ <span class="keyword">for</span> /r ./job %i <span class="keyword">in</span> (*.json) <span class="keyword">do</span> python ./bin/datax.py %i -p"-Djdbc_username_r=root -Djdbc_password_r=root -Djdbc_url_r=jdbc:mysql://<span class="number">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">3306</span>/cloud -Djdbc_username_w=root -Djdbc_password_w=root -Djdbc_url_w=jdbc:mysql://<span class="number">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">3306</span>/test"</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> &#123;DATAX_HOME&#125;</span><br><span class="line">$ find ./job -name <span class="string">"*.json"</span> -<span class="built_in">exec</span> python ./bin/datax.py &#123;&#125; -p<span class="string">"-Djdbc_username_r=root -Djdbc_password_r=root -Djdbc_url_r=jdbc:mysql://127.0.0.1:3306/cloud -Djdbc_username_w=root -Djdbc_password_w=root -Djdbc_url_w=jdbc:mysql://127.0.0.1:3306/test"</span></span><br></pre></td></tr></table></figure><p>–&gt;</p><blockquote><p>解决Windows平台下Python控制台中文乱码：在命令行窗口下输入<code>CHCP 65001</code>命令将代码页设置为UTF-8并将命令行窗口字体设为<code>Lucida Console</code>，若设置当前代码页为GBK则使用<code>CHCP 936</code></p></blockquote>-->]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="database" scheme="https://lugavin.github.io/categories/database/"/>
    
    
      <category term="database" scheme="https://lugavin.github.io/tags/database/"/>
    
  </entry>
  
  <entry>
    <title>分布式消息通信 - RabbitMQ</title>
    <link href="https://lugavin.github.io/2019/03/11/architect/distributed/rabbitmq/"/>
    <id>https://lugavin.github.io/2019/03/11/architect/distributed/rabbitmq/</id>
    <published>2019-03-11T09:15:00.000Z</published>
    <updated>2026-05-09T06:54:58.887Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="RabbitMQ"><a href="#RabbitMQ" class="headerlink" title="RabbitMQ"></a><a href="https://www.rabbitmq.com/getstarted.html" target="_blank" rel="noopener">RabbitMQ</a></h2><p>AMQP，即 Advanced Message Queuing Protocol，高级消息队列协议是应用层协议的一个开放标准，为面向消息的中间件设计。消息中间件主要用于组件之间的解耦，消息的发送者无需知道消息使用者的存在，反之亦然。AMQP 的主要特征是面向消息、队列和路由，可靠且安全。RabbitMQ 是一个开源的 AMQP 实现，服务器端用 Erlang 语言编写，支持多种客户端，如：Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等，支持 Ajax。用于在分布式系统中存储转发消息，在易用性、扩展性、高可用性等方面表现不俗。</p><h2 id="RabbitMQ相关概念"><a href="#RabbitMQ相关概念" class="headerlink" title="RabbitMQ相关概念"></a>RabbitMQ相关概念</h2><table><thead><tr><th>概念</th><th>描述</th></tr></thead><tbody><tr><td>VHost</td><td>Virtual Host只是起到一个命名空间的作用</td></tr><tr><td>Broker</td><td>消息队列服务器的实体，用于存储转发消息，可以把它看成MQ的Server端</td></tr><tr><td>Exchange</td><td>接收消息并将其路由到一个或多个队列，路由算法决定消息按什么规则、路由到哪个队列</td></tr><tr><td>Queue</td><td>用来存储消息，是消息的容器，它只受主机内存和磁盘的限制，是消息弹出之前的最终目的地</td></tr><tr><td>Binding</td><td>把 Exchange 和 Queue 按照路由算法绑定起来</td></tr><tr><td>RoutingKey</td><td>路由规则，Exchange 根据这个规则进行消息路由</td></tr><tr><td>Producter</td><td>消息生产者，产生消息的程序</td></tr><tr><td>Consumer</td><td>消息消费者，消费消息的程序</td></tr><tr><td>Channel</td><td>Channel 是进行消息读写的通道，在客户端的每个连接里可建立多个 Channel，每个 Channel 代表一个会话</td></tr></tbody></table><!--Message: 消息，消息队列中信息或数据的传递载体Producer: 消息生产者，即投递消息的程序Consumer: 消息的消费者，即接受消息的程序Broker: 简单来说就是消息队列服务器实体，即Rabbitmq ServerBinding: 绑定，它的作用就是把exchange和queue按照路由规则绑定起来Queue: 队列，负责保存消息和发放消息RoutingKey: 路由关键字，Exchange根据这个关键字进行消息投递Exchange: 接收消息，并转发到绑定的队列VHost: 虚拟主机，一个broker里可以开设多个vhost，用作不同用户的权限分离Channel: 消息通道，在客户端的每个连接里，可建立多个channel，每个channel代表一个会话任务--><blockquote><p><strong>Exchange:</strong> Takes a message and routes it to one or more queues. Routing algorithms decides where to send the message from the exchange. Routing algorithms depends on the exchange type and rules called “bindings”.</p></blockquote><table><thead><tr><th>Exchange Type</th><th>Routing Algorithms</th><th>Purpose</th></tr></thead><tbody><tr><td>Direct</td><td>It routes messages with a routing key equal to the routing key declared by the binding queue</td><td>This is a Default exchange type. It is used when a message needs to send to a queue</td></tr><tr><td>Fanout</td><td>It routes messages to all the queues from the bound exchange. If routing key is provided then it will be ignored</td><td>Useful for broadcast feature using publish subscribe pattern</td></tr><tr><td>Topic</td><td>It routes messages to queues based on either full or a portion of routing key matches</td><td>Useful for broadcast to specific queues based on some criteria</td></tr></tbody></table><p><img src="/images/architect/rabbitmq-message-flow.jpg" alt="RabbitMQ消息处理流程"></p><h2 id="RabbitMQ安装"><a href="#RabbitMQ安装" class="headerlink" title="RabbitMQ安装"></a><a href="https://github.com/rabbitmq/rabbitmq-server/releases/" target="_blank" rel="noopener">RabbitMQ安装</a></h2><p>RabbitMQ 是基于 Erlang 语言开发的，所以首先必须安装 Erlang 运行时环境。</p><ol><li><p>下载安装 <a href="http://erlang.org/download/" target="_blank" rel="noopener">Erlang</a></p></li><li><p>下载安装 <a href="https://www.rabbitmq.com/releases/rabbitmq-server/" target="_blank" rel="noopener">RabbitMQ</a></p></li><li><p>修改 RabbitMQ 配置文件</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> $&#123;RABBITMQ_HOME&#125;/etc</span><br><span class="line">$ <span class="built_in">copy</span> rabbitmq.config.example rabbitmq.config</span><br></pre></td></tr></table></figure></li><li><p>启用相关插件</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ rabbitmq-plugins enable rabbitmq_management rabbitmq_web_stomp rabbitmq_stomp</span><br></pre></td></tr></table></figure></li><li><p>启动 RabbitMQ 服务</p></li><li><p>通过访问 <a href="http://127.0.0.1:15672" target="_blank" rel="noopener">http://127.0.0.1:15672</a> 用 guest/guest 登录验证是否安装成功</p></li></ol><blockquote><p>默认情况下，RabbitMQ的默认的guest用户只允许本机访问，如果想让guest用户能够远程访问的话，只需要将配置文件中的<code>loopback_users</code>列表置为空即可<code>{loopback_users, []}</code>；另外关于新添加的用户，直接就可以从远程访问的，如果想让新添加的用户只能本地访问，可以将用户名添加到上面的列表，如只允许admin用户本机访问<code>{loopback_users, [&quot;admin&quot;]}</code></p></blockquote><h2 id="五种消息模型"><a href="#五种消息模型" class="headerlink" title="五种消息模型"></a>五种消息模型</h2><h3 id="基本消息模型"><a href="#基本消息模型" class="headerlink" title="基本消息模型"></a>基本消息模型</h3><p>一个生产者，一个消费者，生产者生产的消息直接被消费者消费。 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SimpleTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"simple_queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        <span class="comment">//factory.setHost(ConnectionFactory.DEFAULT_HOST);</span></span><br><span class="line">        <span class="comment">//factory.setPort(ConnectionFactory.DEFAULT_AMQP_PORT);</span></span><br><span class="line">        <span class="comment">//factory.setVirtualHost(ConnectionFactory.DEFAULT_VHOST);</span></span><br><span class="line">        <span class="comment">//factory.setUsername(ConnectionFactory.DEFAULT_USER);</span></span><br><span class="line">        <span class="comment">//factory.setPassword(ConnectionFactory.DEFAULT_PASS);</span></span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        String message = <span class="string">"Hello RabbitMQ!"</span>;</span><br><span class="line">        channel.basicPublish(<span class="string">""</span>, QUEUE_NAME, <span class="keyword">null</span>, message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRecv</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        System.out.println(<span class="string">" [*] Waiting for messages..."</span>);</span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123; <span class="comment">// autoAck为true时则消息一收到立马会确认</span></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                String message = <span class="keyword">new</span> String(body, StandardCharsets.UTF_8);</span><br><span class="line">                System.out.println(<span class="string">" [x] Received '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);  <span class="comment">// 手动ACK</span></span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Work消息模型"><a href="#Work消息模型" class="headerlink" title="Work消息模型"></a>Work消息模型</h3><p>在基本消息模型中，当消息处理比较耗时的时候，生产者生产消息的速度会远远快于消费者消费的速度，那就可能出现消息的堆积。此时就可以使用Work模型：<strong>让多个消费者绑定到一个队列，共同消费队列中的消息</strong>。队列中的消息一旦消费就会消失，因此任务是不会被重复执行的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WorkTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"work_queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">50</span>; i++) &#123;</span><br><span class="line">            String message = <span class="string">"task_"</span> + i;</span><br><span class="line">            channel.basicPublish(<span class="string">""</span>, QUEUE_NAME, <span class="keyword">null</span>, message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">            System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRecv</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.basicQos(<span class="number">1</span>);</span><br><span class="line">        System.out.println(<span class="string">" [*] Waiting for messages..."</span>);</span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                String message = <span class="keyword">new</span> String(body, StandardCharsets.UTF_8);</span><br><span class="line">                System.out.println(<span class="string">" [x] Received '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        Thread.currentThread().join();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="订阅模型-Fanout-广播"><a href="#订阅模型-Fanout-广播" class="headerlink" title="订阅模型 - Fanout(广播)"></a>订阅模型 - Fanout(广播)</h3><p>生产者发送的消息，没有直接发送到队列，而是发送到了交换机，交换机把消息发送给绑定过的所有队列，队列的消费者都能拿到消息，生产者发送的消息经过交换机到达队列，实现一条消息被多个消费者消费。需要注意的是，如果将消息发送到一个没有队列绑定的Exchange上面，那么该消息将会丢失，这是因为在RabbitMQ中Exchange只负责转发消息不具备存储消息的能力，只有队列具备存储消息的能力。</p><p><strong>Fanout 完全不关心key，直接采取<code>广播</code>的方式进行消息投递，任何发送到Fanout Exchange的消息都会被转发到与该Exchange绑定(Binding)的所有Queue上。</strong></p><blockquote><p>在基本消息模型和Work消息模型中，一条消息只能被一个消息者消费；在<code>Fanout</code>模型中，一条消息会被所有订阅的队列消费(一个生产者多个消费者)。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FanoutTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"fanout_exchange"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"fanout_exchange_queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);</span><br><span class="line">        String message = <span class="string">"Hello RabbitMQ!"</span>;</span><br><span class="line">        channel.basicPublish(EXCHANGE_NAME, <span class="string">""</span>, <span class="keyword">null</span>, message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRecv</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">""</span>); <span class="comment">// 绑定Queue到Exchange</span></span><br><span class="line">        System.out.println(<span class="string">" [*] Waiting for messages..."</span>);</span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                String message = <span class="keyword">new</span> String(body, StandardCharsets.UTF_8);</span><br><span class="line">                System.out.println(<span class="string">" [x] Received '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="订阅模型-Direct-定向"><a href="#订阅模型-Direct-定向" class="headerlink" title="订阅模型 - Direct(定向)"></a>订阅模型 - Direct(定向)</h3><p>在Fanout模型中，一条消息会被所有订阅的队列都消费，但是，在某些场景下，我们希望不同的消息被不同的队列消费，这时就要用到Direct Exchange模型：</p><ul><li>Queue与Exchange的绑定，不能是任意绑定了，而是要指定一个RoutingKey；</li><li>消息的发送方在向Exchange发送消息时，也必须指定消费的RoutingKey；</li><li>Exchange不再把消息投递给每一个绑定的队列，而是根据消息的RoutingKey来进行判断，只有队列的RoutingKey与消息的RoutingKey完全一致，才会接收到消息。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DirectTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"direct_exchange"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"direct_exchange_queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);</span><br><span class="line">        String message = <span class="string">"Hello RabbitMQ!"</span>;</span><br><span class="line">        channel.basicPublish(EXCHANGE_NAME, <span class="string">"order.create"</span>, <span class="keyword">null</span>, message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRecv</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"order.create"</span>); <span class="comment">// 使用routingKey绑定queue与exchange</span></span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"order.update"</span>);</span><br><span class="line">        System.out.println(<span class="string">" [*] Waiting for messages..."</span>);</span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                String message = <span class="keyword">new</span> String(body, StandardCharsets.UTF_8);</span><br><span class="line">                System.out.println(<span class="string">" [x] Received '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="订阅模型-Topic-通配符"><a href="#订阅模型-Topic-通配符" class="headerlink" title="订阅模型 - Topic(通配符)"></a>订阅模型 - Topic(通配符)</h3><p>Topic与Direct非常相似，只不过Topic Exchange可以让队列在绑定RoutingKey时使用通配符。符号 <code>#</code> 表示匹配一个或多个词，符号 <code>*</code> 表示仅匹配一个词。</p><blockquote><p>RoutingKey一般是由一个或多个单词组成，多个单词之间以<code>.</code>分割。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TopicTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"topic_exchange"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"topic_exchange_queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);</span><br><span class="line">        String message = <span class="string">"Hello RabbitMQ!"</span>;</span><br><span class="line">        channel.basicPublish(EXCHANGE_NAME, <span class="string">"order.create"</span>, <span class="keyword">null</span>, message.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        System.out.println(<span class="string">" [x] Sent '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRecv</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, <span class="string">"order.#"</span>);</span><br><span class="line">        System.out.println(<span class="string">" [*] Waiting for messages..."</span>);</span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                String message = <span class="keyword">new</span> String(body, StandardCharsets.UTF_8);</span><br><span class="line">                System.out.println(<span class="string">" [x] Received '"</span> + message + <span class="string">"'"</span>);</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h2><h3 id="交换机持久化"><a href="#交换机持久化" class="headerlink" title="交换机持久化"></a>交换机持久化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Channel channel = connection.createChannel();</span><br><span class="line"><span class="comment">// 第三个参数durable设置为true</span></span><br><span class="line">channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, <span class="keyword">true</span>);</span><br></pre></td></tr></table></figure><h3 id="队列持久化"><a href="#队列持久化" class="headerlink" title="队列持久化"></a>队列持久化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Channel channel = connection.createChannel();</span><br><span class="line"><span class="comment">// 第二个参数durable设置为true</span></span><br><span class="line">channel.queueDeclare(QUEUE_NAME, <span class="keyword">true</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br></pre></td></tr></table></figure><h3 id="消息持久化"><a href="#消息持久化" class="headerlink" title="消息持久化"></a>消息持久化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">String message = <span class="string">"Hello World!"</span>;</span><br><span class="line"><span class="comment">// 第三个参数BasicProperties设置为MessageProperties.PERSISTENT_TEXT_PLAIN</span></span><br><span class="line">channel.basicPublish(EXCHANGE_NAME, <span class="string">"order.create"</span>, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));</span><br></pre></td></tr></table></figure><h2 id="高级特性"><a href="#高级特性" class="headerlink" title="高级特性"></a>高级特性</h2><h3 id="生产者确认"><a href="#生产者确认" class="headerlink" title="生产者确认"></a>生产者确认</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PublisherCallbackTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Confirm模式只能保证消息到达Exchange却不能保证消息准确投递到目标Queue中</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testConfirmCallback</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.confirmSelect(); <span class="comment">// 启用生产者确认</span></span><br><span class="line">        channel.addConfirmListener(<span class="keyword">new</span> ConfirmListener() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleAck</span><span class="params">(<span class="keyword">long</span> deliveryTag, <span class="keyword">boolean</span> multiple)</span> </span>&#123;</span><br><span class="line">                log.debug(<span class="string">"=== Ack ==="</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleNack</span><span class="params">(<span class="keyword">long</span> deliveryTag, <span class="keyword">boolean</span> multiple)</span> </span>&#123;</span><br><span class="line">                log.debug(<span class="string">"=== Nack ==="</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        channel.exchangeDeclare(<span class="string">"order.exch"</span>, DIRECT);</span><br><span class="line">        String msg = <span class="string">"Test confirm callback"</span>;</span><br><span class="line">        channel.basicPublish(<span class="string">"order.exch"</span>, <span class="string">"order.create"</span>, <span class="keyword">null</span>, msg.getBytes());</span><br><span class="line">        latch.await();</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Return模式用于处理一些不可路由的消息, 配合mandatory使用(值为true表示接收路由不可达的消息, 为false表示broker自动删除不可路由的消息而不会触发ReturnCallback)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testReturnCallback</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.addReturnListener((replyCode, replyText, exchange, routingKey, properties, body) -&gt; &#123;</span><br><span class="line">            log.error(<span class="string">"消息无法从交换机路由到队列, 原因: &#123;&#125;"</span>, replyText);</span><br><span class="line">            latch.countDown();</span><br><span class="line">        &#125;);</span><br><span class="line">        channel.exchangeDeclare(<span class="string">"order.exch"</span>, DIRECT);</span><br><span class="line">        String msg = <span class="string">"Test confirm callback"</span>;</span><br><span class="line">        channel.basicPublish(<span class="string">"order.exch"</span>, <span class="string">"order.create.err"</span>, <span class="keyword">true</span>, <span class="keyword">null</span>, msg.getBytes());</span><br><span class="line">        latch.await();</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="消费端限流策略"><a href="#消费端限流策略" class="headerlink" title="消费端限流策略"></a>消费端限流策略</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">QosTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"order.exch"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROUTING_KEY = <span class="string">"order.create"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"order-create-queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testQos</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">5</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, DIRECT);</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">            String msg = <span class="string">"Hello RabbitMQ "</span> + i;</span><br><span class="line">            channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, <span class="keyword">null</span>, msg.getBytes());</span><br><span class="line">        &#125;</span><br><span class="line">        channel.basicQos(<span class="number">0</span>, <span class="number">1</span>, <span class="keyword">false</span>); <span class="comment">// 手动确认模式下限流才生效</span></span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                log.debug(<span class="string">" [x] Received '&#123;&#125;'"</span>, <span class="keyword">new</span> String(body));</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">                &#125;</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="消费端ACK与重回队列"><a href="#消费端ACK与重回队列" class="headerlink" title="消费端ACK与重回队列"></a>消费端ACK与重回队列</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RequeueTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"order.exch"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROUTING_KEY = <span class="string">"order.create"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"order-create-queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRequeue</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">5</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, DIRECT);</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">            String msg = <span class="string">"Hello RabbitMQ "</span> + i;</span><br><span class="line">            AMQP.BasicProperties prop = <span class="keyword">new</span> AMQP.BasicProperties().builder()</span><br><span class="line">                    .headers(Collections.singletonMap(<span class="string">"x-counter"</span>, i))</span><br><span class="line">                    .build();</span><br><span class="line">            channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, prop, msg.getBytes());</span><br><span class="line">        &#125;</span><br><span class="line">        channel.basicConsume(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                log.debug(<span class="string">" [x] Received '&#123;&#125;'"</span>, <span class="keyword">new</span> String(body));</span><br><span class="line">                Integer count = (Integer) properties.getHeaders().get(<span class="string">"x-counter"</span>);</span><br><span class="line">                <span class="keyword">if</span> (count == <span class="number">1</span>) &#123;</span><br><span class="line">                    channel.basicNack(envelope.getDeliveryTag(), <span class="keyword">false</span>, <span class="keyword">true</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">                    latch.countDown();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="TTL消息"><a href="#TTL消息" class="headerlink" title="TTL消息"></a>TTL消息</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TTLTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"order.exch"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROUTING_KEY = <span class="string">"order.create"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"order-create-queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testTTL</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, DIRECT);</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);</span><br><span class="line">        String msg = <span class="string">"Hello RabbitMQ"</span>;</span><br><span class="line">        AMQP.BasicProperties prop = <span class="keyword">new</span> AMQP.BasicProperties().builder()</span><br><span class="line">                .expiration(<span class="string">"5000"</span>)</span><br><span class="line">                .build();</span><br><span class="line">        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, prop, msg.getBytes());</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="死信队列"><a href="#死信队列" class="headerlink" title="死信队列"></a>死信队列</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DLXTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DLX_EXCHANGE_NAME = <span class="string">"dlx.exch"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DLX_ROUTING_KEY = <span class="string">"#"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DLX_QUEUE_NAME = <span class="string">"dlx-queue"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"order.exch"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROUTING_KEY = <span class="string">"order.create"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"order-create-queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDLX</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(EXCHANGE_NAME, DIRECT);</span><br><span class="line">        channel.queueDeclare(QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>,</span><br><span class="line">                Collections.singletonMap(<span class="string">"x-dead-letter-exchange"</span>, DLX_EXCHANGE_NAME)); </span><br><span class="line">        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);</span><br><span class="line">        <span class="comment">// 死信队列声明</span></span><br><span class="line">        channel.exchangeDeclare(DLX_EXCHANGE_NAME, TOPIC);</span><br><span class="line">        channel.queueDeclare(DLX_QUEUE_NAME, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(DLX_QUEUE_NAME, DLX_EXCHANGE_NAME, DLX_ROUTING_KEY);</span><br><span class="line">        <span class="comment">// 消息变成死信的情况: (1)TTL过期 (2)消息被拒绝(basicReject/basicNack)且requeue为false (3)队列达到最大值</span></span><br><span class="line">        String msg = <span class="string">"Hello RabbitMQ"</span>;</span><br><span class="line">        AMQP.BasicProperties prop = <span class="keyword">new</span> AMQP.BasicProperties().builder()</span><br><span class="line">                .expiration(<span class="string">"5000"</span>)</span><br><span class="line">                .build();</span><br><span class="line">        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, prop, msg.getBytes());</span><br><span class="line">        channel.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="延迟队列"><a href="#延迟队列" class="headerlink" title="延迟队列"></a>延迟队列</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DelayedPluginTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Connection connection;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ConnectionFactory factory = <span class="keyword">new</span> ConnectionFactory();</span><br><span class="line">        connection = factory.newConnection();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">tearDown</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (connection != <span class="keyword">null</span>) &#123;</span><br><span class="line">            connection.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDelayedMessage</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line">        Channel channel = connection.createChannel();</span><br><span class="line">        channel.exchangeDeclare(<span class="string">"order-exchange"</span>, <span class="string">"x-delayed-message"</span>, <span class="keyword">true</span>, <span class="keyword">false</span>,</span><br><span class="line">                Collections.singletonMap(<span class="string">"x-delayed-type"</span>, DIRECT.getType()));</span><br><span class="line">        channel.queueDeclare(<span class="string">"order-create-queue"</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>);</span><br><span class="line">        channel.queueBind(<span class="string">"order-create-queue"</span>, <span class="string">"order-exchange"</span>, <span class="string">"order.create"</span>);</span><br><span class="line">        AMQP.BasicProperties prop = <span class="keyword">new</span> AMQP.BasicProperties().builder()</span><br><span class="line">                .headers(Collections.singletonMap(<span class="string">"x-delay"</span>, <span class="number">5000</span>))</span><br><span class="line">                .build();</span><br><span class="line">        String msg = <span class="string">"Test delayed message"</span>;</span><br><span class="line">        log.debug(<span class="string">" [x] Send '&#123;&#125;'"</span>, msg);</span><br><span class="line">        channel.basicPublish(<span class="string">"order-exchange"</span>, <span class="string">"order.create"</span>, prop, msg.getBytes());</span><br><span class="line">        channel.basicConsume(<span class="string">"order-create-queue"</span>, <span class="keyword">false</span>, <span class="keyword">new</span> DefaultConsumer(channel) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleDelivery</span><span class="params">(String consumerTag, Envelope envelope,</span></span></span><br><span class="line"><span class="function"><span class="params">                                       AMQP.BasicProperties properties, <span class="keyword">byte</span>[] body)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">                log.debug(<span class="string">" [x] Received '&#123;&#125;'"</span>, <span class="keyword">new</span> String(body));</span><br><span class="line">                channel.basicAck(envelope.getDeliveryTag(), <span class="keyword">false</span>);</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="幂等性"><a href="#幂等性" class="headerlink" title="幂等性"></a>幂等性</h2><p>所谓幂等性，数学上定义为<code>f(n)=f(1)</code>，表示n被函数f作用一次和作用多次的结果是一样的。在软件系统中，表示某个接口使用相同参数调用一次或者多次其造成的后果是一样的。</p><h3 id="消息表"><a href="#消息表" class="headerlink" title="消息表"></a>消息表</h3><p>在数据库里面，添加一张消息消费记录表，表字段加上唯一约束条件，消费完之后就往表里写入一条数据，因为加了唯一约束条件，第二次保存时，数据库就会报错回滚事务，这样通过数据库唯一索引就可以防止重复消费。</p><h3 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h3><p>乐观锁，大多是基于数据版本（version）记录机制实现。何谓数据版本？即为数据增加一个版本标识，在基于数据库表的版本解决方案中，一般是通过为数据库表增加一个 <code>version</code> 字段来实现。读取出数据时，将此版本号一同读出，之后更新时，对此版本号加一。此时，将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对，如果提交的数据版本号大于数据库表当前版本号，则予以更新，否则认为是过期数据。</p><h3 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h3><p>如果你的数据库将来不会分库分表，那么可以在业务表字段加上唯一约束，这样相同的数据就不会保存多份；如果你的数据库做了分库分表，那么可以使用 Redis 或 Zookeeper 对消息id加锁来防止消息被重复消费。</p><h2 id="可靠性投递方案"><a href="#可靠性投递方案" class="headerlink" title="可靠性投递方案"></a>可靠性投递方案</h2><p>生产者：</p><ul><li>保证消息成功发出</li><li>保证Broker成功接收</li><li>生产者收到Broker确认应答</li><li>完善的消息补偿机制</li></ul><p>消费者：</p><ul><li>手动确认</li></ul><blockquote><p>生产者：生产者向Broker发送消息，但由于网络波动，Broker可能会没收到该条消息，所以当Broker收到消息后需要向生产者发送回执（如果是失败的回执，生产者需要进行重发）。需要注意的是，生产者确认只能保证消息到达Exchange却不能保证消息准确投递到目标Queue中，即如果RabbitMQ找不到任何需要投递的Queue队列，那么RabbitMQ依然后发ack给生产者，此时生产者可以认为消息已经正确投递，而不用关心消息没有Queue接收的问题（这是RabbitMQ和消息的接收方需要考虑的事情），生产者只需要保证消息能够发送到Exchange即可。在实际生产中，很难保障前三点的完全可靠，在某些极端的情况下，比如生产者向Broker发送消息过程中失败了，或者在Broker在返回确认应答过程中出现网络闪断等现象，所以要保障消息可靠性投递还需要有完善的消息补偿机制。</p><p>消费者：在RabbitMQ中，消息默认是自动ack的，即消息到达消费端立即ack而不管消费端业务是否处理成功，在自动ack模式下，如果业务处理失败或者出现突然宕机现象就会导致消息丢失，因此可以开启手动确认模式由消费端自行决定何时ack。</p></blockquote><p>可靠性投递方案：</p><ol><li>消息落库</li></ol><p><img src="/images/architect/mq-reliable-delivery.png" alt="消息可靠性投递方案之消息落库"></p><ol start="2"><li>延迟投递做二次确认</li></ol><p><img src="/images/architect/mq-reliable-delivery-delay.png" alt="消息可靠性投递方案之延迟投递"></p><h2 id="Spring整合RabbitMQ"><a href="#Spring整合RabbitMQ" class="headerlink" title="Spring整合RabbitMQ"></a>Spring整合RabbitMQ</h2><h3 id="整合配置"><a href="#整合配置" class="headerlink" title="整合配置"></a>整合配置</h3><h4 id="XML配置"><a href="#XML配置" class="headerlink" title="XML配置"></a>XML配置</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:task</span>=<span class="string">"http://www.springframework.org/schema/task"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:rabbit</span>=<span class="string">"http://www.springframework.org/schema/rabbit"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/context</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/context/spring-context.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/task</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/task/spring-task.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/rabbit</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">"classpath:application.properties"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- ==================== RabbitMQ Common Config ==================== --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">task:executor</span> <span class="attr">id</span>=<span class="string">"taskExecutor"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">pool-size</span>=<span class="string">"8"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">keep-alive</span>=<span class="string">"120"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">queue-capacity</span>=<span class="string">"100"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">rejection-policy</span>=<span class="string">"CALLER_RUNS"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">task:scheduler</span> <span class="attr">id</span>=<span class="string">"taskScheduler"</span> <span class="attr">pool-size</span>=<span class="string">"8"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:connection-factory</span> <span class="attr">id</span>=<span class="string">"connectionFactory"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">host</span>=<span class="string">"$&#123;rabbitmq.host:localhost&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">port</span>=<span class="string">"$&#123;rabbitmq.port:5672&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">virtual-host</span>=<span class="string">"$&#123;rabbitmq.vhost:/&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">username</span>=<span class="string">"$&#123;rabbitmq.username:guest&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">password</span>=<span class="string">"$&#123;rabbitmq.password:guest&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">publisher-confirms</span>=<span class="string">"true"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">executor</span>=<span class="string">"taskExecutor"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- The default exchange and routingKey are empty. --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:template</span> <span class="attr">id</span>=<span class="string">"amqpTemplate"</span> <span class="attr">connection-factory</span>=<span class="string">"connectionFactory"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:admin</span> <span class="attr">connection-factory</span>=<span class="string">"connectionFactory"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- ==================== Queues ==================== --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 声明Queue --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:queue</span> <span class="attr">id</span>=<span class="string">"directExchangeQueue"</span> <span class="attr">name</span>=<span class="string">"direct_exchange_queue"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- ==================== Bindings ==================== --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 绑定Queue到Exchange --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:direct-exchange</span> <span class="attr">name</span>=<span class="string">"direct_exchange"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">rabbit:bindings</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">rabbit:binding</span> <span class="attr">queue</span>=<span class="string">"directExchangeQueue"</span> <span class="attr">key</span>=<span class="string">"order.create"</span>/&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">rabbit:binding</span> <span class="attr">queue</span>=<span class="string">"directExchangeQueue"</span> <span class="attr">key</span>=<span class="string">"order.update"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">rabbit:bindings</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">rabbit:direct-exchange</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- ==================== Listener ==================== --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 绑定Queue和Listener --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:listener-container</span> <span class="attr">type</span>=<span class="string">"direct"</span> <span class="attr">connection-factory</span>=<span class="string">"connectionFactory"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">task-executor</span>=<span class="string">"taskExecutor"</span> <span class="attr">task-scheduler</span>=<span class="string">"taskScheduler"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">rabbit:listener</span> <span class="attr">ref</span>=<span class="string">"messageListener"</span> <span class="attr">queues</span>=<span class="string">"directExchangeQueue"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">rabbit:listener-container</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"messageListener"</span> <span class="attr">class</span>=<span class="string">"com.gavin.ssm.msg.core.mq.MessageHandler"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MessageHandler</span> <span class="keyword">implements</span> <span class="title">MessageListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(Message message)</span> </span>&#123;</span><br><span class="line">        System.err.println(<span class="keyword">new</span> String(message.getBody(), StandardCharsets.UTF_8));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="注解配置"><a href="#注解配置" class="headerlink" title="注解配置"></a>注解配置</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:task</span>=<span class="string">"http://www.springframework.org/schema/task"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:rabbit</span>=<span class="string">"http://www.springframework.org/schema/rabbit"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/context</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/context/spring-context.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/task</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/task/spring-task.xsd</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/rabbit</span></span></span><br><span class="line"><span class="tag"><span class="string">       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">"classpath:application.properties"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- ==================== RabbitMQ Common Config ==================== --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">task:executor</span> <span class="attr">id</span>=<span class="string">"taskExecutor"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">pool-size</span>=<span class="string">"8"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">keep-alive</span>=<span class="string">"120"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">queue-capacity</span>=<span class="string">"100"</span></span></span><br><span class="line"><span class="tag">                   <span class="attr">rejection-policy</span>=<span class="string">"CALLER_RUNS"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">task:scheduler</span> <span class="attr">id</span>=<span class="string">"taskScheduler"</span> <span class="attr">pool-size</span>=<span class="string">"8"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:connection-factory</span> <span class="attr">id</span>=<span class="string">"connectionFactory"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">host</span>=<span class="string">"$&#123;rabbitmq.host:localhost&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">port</span>=<span class="string">"$&#123;rabbitmq.port:5672&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">virtual-host</span>=<span class="string">"$&#123;rabbitmq.vhost:/&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">username</span>=<span class="string">"$&#123;rabbitmq.username:guest&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">password</span>=<span class="string">"$&#123;rabbitmq.password:guest&#125;"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">publisher-confirms</span>=<span class="string">"true"</span></span></span><br><span class="line"><span class="tag">                               <span class="attr">executor</span>=<span class="string">"taskExecutor"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- The default exchange and routingKey are empty. --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:template</span> <span class="attr">id</span>=<span class="string">"amqpTemplate"</span> <span class="attr">connection-factory</span>=<span class="string">"connectionFactory"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:admin</span> <span class="attr">connection-factory</span>=<span class="string">"connectionFactory"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- ==================== @RabbitListener Support ==================== --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 使用@RabbitListener注解方式若不指定containerFactory则默认为rabbitListenerContainerFactory --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">rabbit:annotation-driven</span> <span class="attr">container-factory</span>=<span class="string">"rabbitListenerContainerFactory"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"rabbitListenerContainerFactory"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">class</span>=<span class="string">"org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"connectionFactory"</span> <span class="attr">ref</span>=<span class="string">"connectionFactory"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"taskExecutor"</span> <span class="attr">ref</span>=<span class="string">"taskExecutor"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"taskScheduler"</span> <span class="attr">ref</span>=<span class="string">"taskScheduler"</span>/&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- The container acknowledge the message automatically unless MessageListener throws an exception --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"acknowledgeMode"</span> <span class="attr">value</span>=<span class="string">"AUTO"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"prefetchCount"</span> <span class="attr">value</span>=<span class="string">"1"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MessageListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RabbitListener</span>(bindings = <span class="meta">@QueueBinding</span>(</span><br><span class="line">            value = <span class="meta">@Queue</span>(value = <span class="string">"directExchangeQueue"</span>, durable = <span class="string">"true"</span>),</span><br><span class="line">            exchange = <span class="meta">@Exchange</span>(value = <span class="string">"direct_exchange"</span>, type = ExchangeTypes.DIRECT),</span><br><span class="line">            key = <span class="string">"order.create"</span></span><br><span class="line">    ))</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onMessage</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">"====== &#123;&#125; ======"</span>, message);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="整合测试"><a href="#整合测试" class="headerlink" title="整合测试"></a>整合测试</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith</span>(SpringJUnit4ClassRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">@ContextConfiguration("classpath:spring-rabbitmq.xml")</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringRabbitmqTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXCHANGE_NAME = <span class="string">"order.exch"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ROUTING_KEY = <span class="string">"order.create"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String QUEUE_NAME = <span class="string">"order-queue"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RabbitAdmin rabbitAdmin;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AmqpTemplate amqpTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setUp</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Assert.assertNotNull(rabbitAdmin);</span><br><span class="line">        Assert.assertNotNull(amqpTemplate);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testManage</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        rabbitAdmin.declareExchange(<span class="keyword">new</span> DirectExchange(EXCHANGE_NAME));</span><br><span class="line">        rabbitAdmin.declareQueue(<span class="keyword">new</span> Queue(QUEUE_NAME));</span><br><span class="line">        rabbitAdmin.declareBinding(<span class="keyword">new</span> Binding(QUEUE_NAME, DestinationType.QUEUE, EXCHANGE_NAME, ROUTING_KEY, <span class="keyword">null</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testSend</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        amqpTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, <span class="string">"订单创建"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://www.rabbitmq.com/getstarted.html" target="_blank" rel="noopener">RabbitMQ Tutorials</a></li><li><a href="https://www.ibm.com/developerworks/cn/opensource/os-cn-rabbit-mq/index.html" target="_blank" rel="noopener">俞 超. <em>基于 RabbitMQ 的实时消息推送</em></a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="architect" scheme="https://lugavin.github.io/tags/architect/"/>
    
  </entry>
  
  <entry>
    <title>JVM原理</title>
    <link href="https://lugavin.github.io/2019/01/11/javase/jvm/"/>
    <id>https://lugavin.github.io/2019/01/11/javase/jvm/</id>
    <published>2019-01-11T10:05:00.000Z</published>
    <updated>2026-05-09T06:54:58.890Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>为了使Java开发人员无需关心不同架构上内存模型之间的差异，Java提供了自己的内存模型，并且JVM通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。</p><p><img src="/images/javase/jvm-architecture.jpg" alt="JVM Memory Structure">  </p><ul><li>堆：堆是 Java 程序运行时动态分配内存的区域，用于存放对象、数组等数据结构，由垃圾回收器进行管理和回收。</li><li>栈：栈则是 Java 程序中<strong>方法的执行环境</strong>，用于存放局部变量、方法参数等信息。</li><li>方法区：用于存储类的信息、常量池等数据</li><li>本地方法栈：用于存储本地方法的执行环境</li><li>程序计数器：用于记录当前线程执行的字节码指令的位置。</li></ul><h2 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h2><p>JVM的类加载是通过 ClassLoader 及其子类来完成的，站在 Java 虚拟机的角度来讲，只存在两种不同的类加载器：</p><ul><li>启动类加载器：<em>Bootstrap ClassLoader</em>不继承自ClassLoader抽象类，因为它不是一个普通的Java类，底层由C++编写，是JVM自身的一部分，已嵌入到了JVM内核当中，当JVM启动后，Bootstrap ClassLoader也随着启动，负责加载完核心类库后，并构造<em>Extension ClassLoader</em>和<em>App ClassLoader</em>类加载器。启动类加载器是无法被 Java 程序直接引用的。</li><li>其他类加载器：这些类加载器都由 Java 语言实现，独立于虚拟机之外，并且全部继承自ClassLoader抽象类，这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。</li></ul><p><img src="/images/javase/jvm-classloader.png" alt="类的层次关系和加载顺序"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> Class&lt;?&gt; loadClass(String name, <span class="keyword">boolean</span> resolve) <span class="keyword">throws</span> ClassNotFoundException &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (getClassLoadingLock(name)) &#123;</span><br><span class="line">        <span class="comment">// First, check if the class has already been loaded</span></span><br><span class="line">        Class&lt;?&gt; c = findLoadedClass(name);</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">long</span> t0 = System.nanoTime();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (parent != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    c = parent.loadClass(name, <span class="keyword">false</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// BootstrapClassLoader has no parent ClassLoader</span></span><br><span class="line">                    c = findBootstrapClassOrNull(name);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">                <span class="comment">// ClassNotFoundException thrown if class not found</span></span><br><span class="line">                <span class="comment">// from the non-null parent class loader</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (c == <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="comment">// If still not found, then invoke findClass in order</span></span><br><span class="line">                <span class="comment">// to find the class.</span></span><br><span class="line">                <span class="keyword">long</span> t1 = System.nanoTime();</span><br><span class="line">                <span class="comment">// Subclass override</span></span><br><span class="line">                c = findClass(name);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// this is the defining class loader; record the stats</span></span><br><span class="line">                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);</span><br><span class="line">                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);</span><br><span class="line">                sun.misc.PerfCounter.getFindClasses().increment();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (resolve) &#123;</span><br><span class="line">            resolveClass(c);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="类加载机制"><a href="#类加载机制" class="headerlink" title="类加载机制"></a>类加载机制</h3><p>类从被加载到虚拟机内存中开始，到卸载出内存为止，它的整个生命周期包括：加载、验证、准备、解析、初始化、对象实例化、垃圾收集、对象终结、卸载类型。</p><p><img src="/images/javase/class-lifecycle.png" alt="类的生命周期"></p><p>其中类加载的过程包括了加载、连接、初始化三个阶段：</p><ol><li>加载：查找并且加载类的二进制数据</li><li>连接：<ul><li>验证：确保被加载类的正确性（安全性校验）</li><li>准备：为类的静态变量分配内存并将其初始化为默认值（默认初始化）</li><li>解析：把类中的符号引用转换为直接引用</li></ul></li><li>初始化：为类的静态变量赋予正确的初始值（显式初始化）</li></ol><h4 id="加载阶段"><a href="#加载阶段" class="headerlink" title="加载阶段"></a>加载阶段</h4><p>类的加载简单来说，就是将class文件中的二进制数据读取到内存中，将其放在方法区中，然后在堆内存中创建一个java.lang.Class对象，用来封装在方法区的数据结构。</p><p>加载是类加载过程的第一个阶段，在加载阶段，虚拟机需要完成以下三件事情：</p><ul><li>通过一个类的全限定名来获取其定义的二进制字节流。</li><li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。</li><li>在 Java 堆中生成一个代表这个类的 java.lang.Class 对象，作为对方法区中这些数据的访问入口。</li></ul><p>相对于类加载的其他阶段而言，加载阶段是可控性最强的阶段，因为开发人员既可以使用系统提供的类加载器来完成加载，也可以自定义自己的类加载器来完成加载。</p><p>加载阶段完成后，虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中，而且在 Java 堆中也创建一个 java.lang.Class 类的对象，这样便可以通过该对象访问方法区中的这些数据。</p><h4 id="连接阶段"><a href="#连接阶段" class="headerlink" title="连接阶段"></a>连接阶段</h4><h5 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h5><p>Java 是与平台无关的语言，这得益于 Java 源代码编译后生成的存储字节码的文件，即 Class 文件，以及Java虚拟机的实现。不仅使用 Java 编译器可以把 Java 代码编译成存储字节码的 Class 文件，使用 JRuby 等其他语言的编译器也可以把程序代码编译成 Class 文件，虚拟机并不关心 Class 的来源是什么语言，只要它符合一定的结构，就可以在 Java 中运行。Java 语言中的各种变量、关键字和运算符的语义最终都是由多条字节码命令组合而成的，因此字节码命令所能提供的语义描述能力肯定会比 Java 语言本身更强大，这便为其他语言实现一些有别于 Java 的语言特性提供了基础，而且这也正是在类加载时要进行安全验证的原因。</p><ol><li>文件格式验证<ul><li>魔数因子是否正确（0xCAFEBABE）</li><li>主从版本号是否符合当前虚拟机</li><li>常量池中的常量类型是不是不支持</li><li>etc</li></ul></li><li>元数据验证<ul><li>是否有父类</li><li>父类是不是允许继承</li><li>是否覆盖了父类的final字段</li><li>其他语义检查</li></ul></li><li>字节码验证</li><li>符号引用验证</li></ol><h5 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h5><p>准备阶段就是给类变量分配默认初始值。</p><table><thead><tr><th>数据类型</th><th>默认值</th></tr></thead><tbody><tr><td>byte</td><td>(byte)0</td></tr><tr><td>char</td><td>‘\u0000’</td></tr><tr><td>short</td><td>(short)0</td></tr><tr><td>int</td><td>0</td></tr><tr><td>long</td><td>0L</td></tr><tr><td>float</td><td>0.0f</td></tr><tr><td>double</td><td>0.0d</td></tr><tr><td>boolean</td><td>false</td></tr><tr><td>reference</td><td>null</td></tr></tbody></table><h5 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h5><ul><li>类或接口的解析</li><li>字段解析 </li><li>类方法解析</li><li>接口方法解析</li></ul><h4 id="类初始化阶段"><a href="#类初始化阶段" class="headerlink" title="类初始化阶段"></a>类初始化阶段</h4><p>初始化是类加载过程的最后一步，到了此阶段，才真正开始执行类中定义的 Java 程序代码。在准备阶段，类变量已经被赋过一次系统要求的初始值，而在初始化阶段，则是执行类构造器<code>&lt;clinit&gt;</code>方法的过程。换句话说，其实初始化阶段做的事情就是给static变量赋予用户指定的值以及执行静态代码块。</p><ul><li><code>&lt;clinit&gt;</code>方法是由编译器自动收集类中的所有<em>类变量</em>的赋值动作和<em>静态语句块</em>中的语句合并产生的，编译器收集的顺序是由语句在源文件中出现的顺序所决定的，静态语句块中只能访问到定义在静态语句块之前的变量，定义在它之后的变量，在前面的静态语句中可以赋值，但是不能访问。</li><li><code>&lt;clinit&gt;</code>方法与类的构造函数不同，它不需要显式地调用父类构造器，虚拟机会保证在子类的<code>&lt;clinit&gt;</code>方法执行之前，父类的<code>&lt;clinit&gt;</code>方法已经执行完毕。因此，在虚拟机中首先被执行的是Object的<code>&lt;clinit&gt;</code>方法。</li><li><code>&lt;clinit&gt;</code>方法对于类或接口来说并不是必须的，如果一个类中没有静态语句块，也没有对类变量的赋值操作，那么编译器可以不为这个类生成<code>&lt;clinit&gt;</code>方法。</li><li>接口中不能使用静态语句块，但仍然有类变量初始化的赋值操作，因此接口与类一样会生成<code>&lt;clinit&gt;</code>方法。但是接口与类不同的是：执行接口的<code>&lt;clinit&gt;</code>方法不需要先执行父接口的<code>&lt;clinit&gt;</code>方法，只有当父接口中定义的变量被使用时，父接口才会被初始化。另外，接口的实现类在初始化时也一样不会执行接口的<code>&lt;clinit&gt;</code>方法。</li><li>虚拟机会保证一个类的<code>&lt;clinit&gt;</code>方法的线程安全性，如果多个线程同时去初始化一个类，那么只会有一个线程去执行这个类的<code>&lt;clinit&gt;</code>方法，其他线程都需要阻塞等待，直到活动线程执行<code>&lt;clinit&gt;</code>方法完毕。如果在一个类的<code>&lt;clinit&gt;</code>方法中有耗时很长的操作，那就可能造成多个线程阻塞，在实际应用中这种阻塞往往是很隐蔽的。</li></ul><p>Java虚拟机规范为类的初始化时机做了严格定义：在首次主动使用时初始化。这个规则直接影响着类装载、连接和初始化类的机制，因为在类型被初始化之前它必须已经被连接，然而在连接之前又必须保证它已经被装载了。</p><p>说了这么多，类的初始化时机就是在首次主动使用时，那么，哪些情形下才符合首次主动使用的要求呢？</p><p>首次主动使用的情形：</p><ul><li>遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时，如果类还没有进行过初始化，则需要先触发其初始化。生成这四条指令最常见的 Java 代码场景是：使用 new 关键字实例化对象时、读取或设置一个类的静态字段时（被static修饰又被final修饰的，已在编译期把结果放入常量池的静态字段除外）、以及调用一个类的静态方法时。 </li><li>使用 Java.lang.refect 包的方法对类进行反射调用时，如果类还没有进行过初始化，则需要先触发其初始化。 </li><li>当初始化一个类的时候，如果发现其父类还没有进行初始化，则需要先触发其父类的初始化。 </li><li>当虚拟机启动时，用户需要指定一个要执行的主类，虚拟机会先执行该主类。</li></ul><p>除了以上几种情形以外，所有其它使用Java类型的方式都是被动使用的，它们不会导致类的初始化。</p><p>被动使用的几种情形：</p><ul><li>对于静态字段，只有直接定义这个字段的类才会被初始化，因此，通过其子类来引用父类中定义的静态字段，只会触发父类的初始化而不会触发子类的初始化。</li><li>常量在编译阶段会存入调用它的类的常量池中，本质上没有直接引用到定义该常量的类，因此不会触发定义常量的类的初始化。</li><li>通过数组定义来引用类，不会触发类的初始化。</li></ul><p><code>&lt;clinit&gt;</code>和<code>&lt;init&gt;</code>方法的区别：</p><ul><li><code>&lt;clinit&gt;</code>是类构造器方法。Java编译器把所有的类变量初始化语句和静态语句块中的语句通通收集到 <code>&lt;clinit&gt;</code> 方法内，该方法只能被 JVM 调用，专门承担初始化工作。</li><li><code>&lt;init&gt;</code>是对象构造器方法。一旦一个类被装载、连接和初始化，它就随时可以使用了。对象实例化和初始化时就是对象生命的起始阶段的活动，Java编译器在编译每个类时都会为该类至少生成一个实例初始化方法即 <code>&lt;init&gt;</code> 方法。</li></ul><p><code>final</code>、<code>static</code>、<code>static final</code>修饰的字段赋值的区别： </p><ul><li><code>static</code>修饰的字段在类加载过程中的准备阶段被初始化为 0 或 null 等默认值，而后在初始化阶段（触发类构造器）才会被赋予代码中设定的值，如果没有设定值，那么它的值就为默认值。</li><li><code>final</code>修饰的字段在运行时被初始化（可以直接赋值，也可以在对象构造器中赋值），一旦赋值便不可更改； </li><li><code>static final</code>修饰的字段在 Javac 时生成 ConstantValue 属性，在类加载的准备阶段根据ConstantValue的值为该字段赋值，它没有默认值，必须显式地赋值，否则 Javac 时会报错。可以理解为在编译期即把结果放入了常量池中。</li></ul><h2 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h2><p>方法区是各个线程共享的内存区域，它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据，垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。方法区域又被称为“永久代”。</p><h2 id="堆内存"><a href="#堆内存" class="headerlink" title="堆内存"></a>堆内存</h2><p>堆内存用来存放由new创建的对象和数组，在堆中分配的内存，由Java虚拟机的自动垃圾回收器来管理。</p><p>在堆中产生了一个数组或对象后，还可以在栈中定义一个特殊的变量，让栈中这个变量的取值等于数组或对象在堆内存中的首地址，栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称，以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。</p><p>引用变量是普通的变量，定义时在栈中分配，引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配，即使程序运行到使用new产生数组或者对象的语句所在的代码块之外，数组和对象本身占据的内存不会被释放，数组和对象在没有引用变量指向它的时候，才变为垃圾，不能再被使用，但仍然占据内存空间不放，在随后的一个不确定的时间被垃圾回收器收走释放掉，这也是Java比较占内存的原因。</p><h2 id="虚拟机栈"><a href="#虚拟机栈" class="headerlink" title="虚拟机栈"></a>虚拟机栈</h2><p>存放基本数据类型的数据和对象的引用，但对象本身不存在栈中，而是存放在堆中，实际上，栈中的引用变量指向堆内存中的数组和对象，这就是Java中的指针。</p><p>在函数中定义的一些基本类型的变量数据和对象的引用变量都是在函数的栈内存中分配的，当在一段代码块定义一个变量时，Java就会在栈中为这个变量分配内存空间，当该变量退出该作用域后，Java会自动释放掉为该变量所分配的内存空间，该内存空间可以立即被另作他用。</p><h2 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h2><p>本地方法栈与Java虚拟机栈作用非常类似，其区别是：Java虚拟机栈是为虚拟机执行Java方法服务，而本地方法栈是为虚拟机调用的操作系统本地方法服务。</p><h2 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h2><p>JVM是基于栈的体系结构来执行class字节码的。线程创建后，都会产生程序计数器(PC)和栈(Stack)，程序计数器存放下一条要执行的指令在方法内的偏移量，栈中存放一个个栈帧，每个栈帧对应着每个方法的每次调用。当线程在执行一个 Java 方法时，该计数器记录的是正在执行的虚拟机字节码指令的地址，当线程在执行的是Native方法时，该计数器的值为空。</p><h2 id="GC收集器"><a href="#GC收集器" class="headerlink" title="GC收集器"></a>GC收集器</h2><h3 id="垃圾对象的判定"><a href="#垃圾对象的判定" class="headerlink" title="垃圾对象的判定"></a>垃圾对象的判定</h3><p>Java中的垃圾回收一般是在堆中进行，因为堆中存放着几乎所有的对象实例，垃圾收集器对堆中的对象进行回收前，要先确定这些对象是否还有用，判定对象是否为垃圾对象有如下算法：引用计数算法、引用可达性分析算法。</p><blockquote><p>对象引用：如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址，就称这块内存代表着一个引用。</p></blockquote><h4 id="引用计数算法"><a href="#引用计数算法" class="headerlink" title="引用计数算法"></a>引用计数算法</h4><p>给对象添加一个引用计数器，每当有一个地方引用它时，计数器值就加 1，当引用失效时，计数器值就减1，任何时刻计数器都为 0 的对象就是不可能再被使用的。</p><p>引用计数算法的实现简单，判定效率也很高，在大部分情况下它都是一个不错的选择，但 Java 语言并没有选择这种算法来进行垃圾回收，主要原因是它很难解决对象之间的相互循环引用问题。</p><h4 id="引用可达性分析算法"><a href="#引用可达性分析算法" class="headerlink" title="引用可达性分析算法"></a>引用可达性分析算法</h4><p>Java是采用引用可达性分析算法来判定对象是否存活的。这种算法的基本思路是通过一系列名为<em>GC Roots</em>的对象作为起始点，从这些节点开始向下搜索，搜索所走过的路径称为引用链，当一个对象到<em>GC Roots</em>没有任何引用链相连时，就证明此对象是不可用的。GC会收集那些不是<em>GC Roots</em>且没有被<em>GC Roots</em>引用的对象。</p><p>在 Java 语言里，可作为<a href="https://help.eclipse.org/mars/topic/org.eclipse.mat.ui.help/concepts/gcroots.html" target="_blank" rel="noopener"><em>GC Roots</em></a>的对象包括下面几种：</p><ul><li>虚拟机栈（栈帧中的本地变量表）中引用的对象</li><li>方法区中的类静态属性引用的对象</li><li>方法区中的常量引用的对象</li><li>本地方法栈中 JNI（Native 方法）的引用对象</li></ul><h3 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h3><h4 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h4><p>首先标记出所需回收的对象，在标记完成后统一回收掉所有被标记的对象，它的标记过程其实就是前面的引用可达性分析算法中判定垃圾对象的标记过程。</p><ul><li>优点：不需要进行对象的移动，并且仅对不存活的对象进行处理，在存活对象比较多的情况下极为高效。</li><li>缺点：标记和清除过程的效率都不高，并且标记清除后会产生大量不连续的内存碎片。</li></ul><h4 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h4><p>该算法标记的过程与标记—清除算法中的标记过程一样，但对标记后出的垃圾对象的处理情况有所不同，它不是直接对可回收对象进行清理，而是让所有存活的对象都向一端移动，然后直接清理掉端边界以外的内存。</p><ul><li>优点：经过整理之后，新对象的分配只需要通过指针碰撞便能完成，相当简单；使用这种方法空闲区域的位置是始终可知的，也不会再有碎片的问题了。</li><li>缺点：GC暂停的时间会增长，因为需要将所有的对象都拷贝到一个新的地方，还得更新它们的引用地址。</li></ul><h4 id="标记-复制算法"><a href="#标记-复制算法" class="headerlink" title="标记-复制算法"></a>标记-复制算法</h4><p>将可用内存按容量划分为大小相等的两块，每次只是用其中一块。当这一块的内存用完了，就将还存活着的对象复制到另外一块上面，然后再把已使用过的内存空间一次清理掉。</p><ul><li>优点：内存分配时不用考虑内存碎片等复杂情况，只要移动堆顶指针，按顺序分配内存即可，实现简单，运行高效。</li><li>缺点：需要一块能容纳下所有存活对象的额外的内存空间，因此，可一次性分配的最大内存缩小了一半。</li></ul><h4 id="分代收集"><a href="#分代收集" class="headerlink" title="分代收集"></a>分代收集</h4><p>当前商业虚拟机的垃圾收集都采用分代收集，它根据对象的存活周期的不同将内存划分为几块，一般是把 Java 堆分为新生代和老年代。在新生代中，每次垃圾收集时都会发现有大量对象死去，只有少量存活，因此可选用标记-复制算法来完成收集，而老年代中因为对象存活率高、没有额外空间对它进行分配担保，就必须使用标记—清除算法或标记—整理算法来进行回收。</p><h3 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h3><p>垃圾收集器是内存回收算法的具体实现，Java 虚拟机规范中对垃圾收集器应该如何实现并没有任何规定，因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别。Sun HotSpot 虚拟机 1.6 版包含了如下收集器：Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old。这些收集器以不同的组合形式配合工作来完成不同分代区的垃圾收集工作。</p><p>Java 中的堆是 JVM 所管理的最大的一块内存空间，主要用于存放各种类的实例对象。在 Java 中，堆被划分成两个不同的区域：新生代(Young)、老年代(Old)。新生代(Young)又被划分为三个区域：Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象，包括内存的分配以及回收。</p><p><img src="/images/javase/jvm-heap-structure.png" alt="JVM Heap Structure">  </p><p>默认的，新生代(Young)与老年代(Old)的比例的值为1:2(该值可以通过参数 –XX:NewRatio 来指定)，新生代(Young)三个区域内存大小比例默认为8:1:1(可以通过参数 –XX:SurvivorRatio 来设定)。</p><ul><li>新生代：大多数情况下，对象在Eden中分配，当Eden没有足够空间时，会触发一次<em>Minor GC</em></li><li>老年代：用于存放经过几次<em>Minor GC</em>之后依旧存活的对象，当老年代的空间不足时，会触发<em>Major GC</em>或<em>Full GC</em></li></ul><p><strong>内存的分配策略</strong></p><ul><li>对象优先在 Eden 分配</li><li>大对象直接进入老年代</li><li>长期存活的对象将进入老年代</li></ul><p><strong>垃圾回收策略</strong></p><ul><li>新生代GC(Minor GC)：发生在新生代的垃圾收集动作，因为 Java 对象大多都具有朝生夕灭的特性，因此Minor GC 非常频繁，一般回收速度也比较快。</li><li>老年代GC(Major GC/Full GC)：发生在老年代的 GC，出现了 Major GC，经常会伴随至少一次 Minor GC。由于老年代中的对象生命周期比较长，因此 Major GC 并不频繁，一般都是等待老年代满了后才进行Full GC，而且其速度一般会比 Minor GC 慢 10 倍以上。另外，如果分配了 Direct Memory，在老年代中进行 Full GC时，会顺便清理掉 Direct Memory 中的废弃对象。</li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="http://tutorials.jenkov.com/java-concurrency/java-memory-model.html" target="_blank" rel="noopener">Java Concurrency</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="javase" scheme="https://lugavin.github.io/categories/javase/"/>
    
    
      <category term="jvm" scheme="https://lugavin.github.io/tags/jvm/"/>
    
  </entry>
  
  <entry>
    <title>Goodbye 2018 Hello 2019</title>
    <link href="https://lugavin.github.io/2018/12/31/life/hello-2019/"/>
    <id>https://lugavin.github.io/2018/12/31/life/hello-2019/</id>
    <published>2018-12-31T23:15:00.000Z</published>
    <updated>2026-05-09T06:54:58.890Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p><img src="/images/life/best-wishes-2019.gif" alt="Hello 2019"> </p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="life" scheme="https://lugavin.github.io/categories/life/"/>
    
    
      <category term="life" scheme="https://lugavin.github.io/tags/life/"/>
    
  </entry>
  
  <entry>
    <title>分布式事务</title>
    <link href="https://lugavin.github.io/2018/12/27/architect/distributed/transaction/"/>
    <id>https://lugavin.github.io/2018/12/27/architect/distributed/transaction/</id>
    <published>2018-12-27T09:00:00.000Z</published>
    <updated>2026-05-09T06:54:58.887Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在微服务架构中，随着服务的逐步拆分，数据库私有已经成为共识，这也导致所面临的分布式事务问题成为微服务落地过程中一个非常难以逾越的障碍，但是目前尚没有一个完整通用的解决方案。</p><p>其实不仅仅是在微服务架构中，随着用户访问量的逐渐上涨，数据库甚至是服务的分片、分区、水平拆分、垂直拆分已经逐渐成为较为常用的提升瓶颈的解决方案，因此越来越多的原子操作变成了<code>跨库</code>甚至是<code>跨服务</code>的事务操作，最终结果是在对高性能、高扩展性、高可用性的追求的道路上，我们开始逐渐放松对一致性的追求，但是在很多场景下，尤其是账务、电商等业务中，不可避免的存在着一致性问题，使得我们不得不去探寻一种机制，用以在分布式环境中保证事务的一致性。</p><p>微服务使得单体架构扩展为分布式架构，在扩展的过程中，逐渐丧失了单体架构中数据源单一、可以直接依赖于数据库进行事务操作的能力。而关系型数据库中，提供了强大的事务处理能力，可以满足<code>ACID</code>特性，这种特性保证了数据操作的强一致性，这也是分布式环境中弱一致性以及最终一致性能够得以实现的基础。</p><p>在讨论分布式事务之前，我们得先弄清楚本地事务和全局事务的概念以及它们的使用场景。</p><h3 id="本地事务"><a href="#本地事务" class="headerlink" title="本地事务"></a>本地事务</h3><blockquote><p>本地事务：在单个数据库的本地并且限制在单个进程内的事务（本地事务不涉及多个数据来源），事务由资源管理器（如DBMS）本地管理，而Spring为我们提供了非常方便的声明式事务管理，但是默认的Spring事务只支持单数据源，而实际上一个应用往往需要进行<code>跨库</code>甚至是<code>跨服务</code>的事务操作，这个时候就要依靠分布式事务。</p></blockquote><ul><li>优点<ol><li>支持严格的ACID属性</li><li>可靠</li><li>高效</li><li>状态可以只在资源管理器中维护</li><li>应用编程模型简单</li></ol></li><li>缺点<ol><li>不具备分布式事务处理能力</li><li>隔离的最小单位由资源管理器决定，如数据库中的一条记录</li></ol></li></ul><h3 id="全局事务"><a href="#全局事务" class="headerlink" title="全局事务"></a>全局事务</h3><blockquote><p>全局事务：由全局事务管理器管理和协调的事务，可以跨越多个资源（如数据库或JMS队列）和进程，全局事务管理器一般使用XA二阶段提交协议与数据库进行交互，事务由全局事务管理器全局管理。</p></blockquote><ul><li>优点：严格的ACID</li><li>缺点：效率非常低（微服务架构下已不太适用）<ol><li>全局事务方式下，全局事务管理器（TM）通过XA接口使用二阶段提交协议（2PC）与资源层（如数据库）进行交互，使用全局事务，数据被Lock的时间跨整个事务，直到全局事务结束。</li><li>2PC是反可伸缩模式，在事务处理过程中，参与者需要一直持有资源直到整个分布式事务结束。这样，当业务规模越来越大的情况下，2PC的局限性就越来越明显，系统可伸缩性会变得很差。</li><li>与本地事务相比，XA协议的系统开销相当大，因而应当慎重考虑是否确实需要分布式事务，而且只有支持XA协议的资源才能参与分布式事务。</li></ol></li></ul><p>全局事务，作为一种标准的分布式事务解决方案，它解决了本地事务无法满足分布式场景中数据的ACID的要求，但由于其存在效率底下的致命缺点，在微服务架构下已不太适用。</p><p>在业内，主要用来解决分布式事务的方案是使用柔性事务，柔性事务包括几种类型：两阶段型、补偿型、异步确保型和最大努力通知型。</p><h2 id="基于可靠消息最终一致性（异步确保型）"><a href="#基于可靠消息最终一致性（异步确保型）" class="headerlink" title="基于可靠消息最终一致性（异步确保型）"></a>基于可靠消息最终一致性（异步确保型）</h2><p>数据一致性分为三个种类型：强一致性、弱一致性以及最终一致性，数据库实现的就是强一致性，能够保证在写入一份新的数据库，立即使其可见。最终一致性是弱一致性的强化版，系统保证在没有后续更新的前提下，系统最终返回上一次更新操作的值。在最终一致性的实现过程中，最基本的操作就是保证事务参与者的幂等性，所谓的幂等性，就是业务方能够使用相关的手段，保证单个事务多次提交依然能够保证达到同样的目的。</p><p>在分布式系统中，服务之间通常会通过RPC来进行网络通讯，而远程调用最郁闷的地方就是结果有三种：成功、失败和超时（成功失败都有可能），从而导致了数据传输的不确定性，如何确保消息发送的一致性是可靠消息的前提保障。</p><ul><li>RPC同步调用：RPC的同步调用确保请求送达对方并收到对方响应，若没有收到响应，则抛出<code>Timeout</code>异常，这种情况下调用方是无法确定调用是成功还是失败的，需要根据业务场景（是否可重入、幂等）选择重试和补偿策略。</li><li>消息发送一致性：如果业务操作成功，那么由这个业务操作所产生的消息一定要成功投递出去，否则就会丢失消息。在设计范式里通常不允许消费下游业务失败，不然后面失败了前面也不好回滚。但消费端消费失败时该怎么办？消费失败了，重试，还一直失败怎么办？是不是要自动回滚整个流程？答案是人工介入。从工程实践角度讲，这种整个流程自动回滚的代价是非常巨大的，不但实现复杂，还会引入新的问题，比如自动回滚失败，又怎么处理？针对这种极低概率的情况，采取人工处理会比实现一个高复杂的自动化回滚系统更加可靠也更加简单。</li><li>最终一致性：主要是用<code>记录</code>和<code>补偿</code>的方式。在做所有的不确定的事情之前，先把事情记录下来，然后去做不确定的事情，结果可能是：成功、失败或是不确定，<code>不确定</code>（例如超时等）可以等价为失败。成功就可以把记录的东西清理掉了，对于失败和不确定，可以依靠<code>定时任务</code>等方式把所有失败的事情重新执行一遍，直到成功为止。</li></ul><p>以购物场景为例，张三购买物品，账户扣款 100 元的同时，需要在下游的会员服务给该账户增加 100 积分，如何保证原子性？</p><p>一般的思路都是通过消息中间件来实现最终一致性：资金账户服务扣钱，然后发消息给中间件，会员积分服务接收此消息，进行增加积分。</p><p>但这里面有个问题：在资金账户服务中，是先更新DB后发送消息呢？ 还是先发送消息后更新DB？</p><p>假设先更新DB成功，发送消息网络异常，重发又失败，怎么办？假设先发送消息成功，更新DB失败，消息已经发出去了，又不能撤回，怎么办？</p><p>当然，你可能已经想到了，我可以把发送消息这个网络调用和更新DB放在同一个本地事务中，如果发送消息失败，更新DB自动回滚，这样不就保证两个操作的原子性了吗？这个方案看似正确，但其实是错误的，原因有：</p><ol><li>把网络调用放在DB事务里面，可能会因为网络的延时，导致DB长事务，严重地会Block住整个DB，风险很大。</li><li>网络的两军问题：发送消息失败，发送方并不知道消息中间件是真的没有收到消息还是消息已经收到了只是返回的时候失败了，如果是已经收到消息了，而发送端认为没有收到而执行事务回滚操作，从而无法保证数据的一致性。</li></ol><p>所以，这里得出结论：只要发送消息和更新DB这两个操作不是原子的，无论谁先谁后都是有问题的。</p><p>事务消息的本质就是为了解决此类问题，即解决本地事务执行与消息发送的原子性问题。</p><p><img src="/images/architect/distributed-transaction.png" alt="基于可靠消息最终一致性的分布式事务解决方案"></p><p>消息发送基本流程如下：</p><ol><li>上游业务系统首先预发送（同步发送）消息到可靠消息服务</li><li>在预发送消息成功返回后执行本地事务（在返回过程中出现死机、超时等异常情况的消息将由消息状态确认子系统处理）</li><li>上游业务系统将本地事务执行结果发送（异步发送）到可靠消息服务（在发送过程中出现死机、超时等异常情况的消息将由消息状态确认子系统处理）</li><li>如果业务处理成功，可靠消息服务则更新消息状态并将消息投放到实时消息服务(消息中间件)；如果业务处理失败，则将该消息进行删除</li></ol><p>消息消费基本流程如下：</p><ol><li>投放到实时消息服务的消息会被消息业务消费端监听并调用（同步）下游业务系统进行消费</li><li>如果下游业务系统业务处理成功，消息业务消费端则和实时消息服务、可靠消息服务进行确认（确认结果就是将消息删除）；如果业务处理失败，则由消息恢复子系统进行处理</li></ol><ul><li>优点<ol><li>消息服务独立部署、独立维护、独立伸缩；</li><li>消息存储可以按需选择不同的数据库来集成实现；</li><li>消息服务可以被相同的使用场景共用，降低重复建设消息服务的成本；</li><li>从应用（分布式服务）设计开发的角度实现了消息数据的可靠性，消息数据的可靠性不依赖于MQ中间件，弱化了对MQ中间件特性的依赖；</li><li>降低了业务系统与消息系统间的耦合，有利于系统的扩展维护。</li></ol></li><li>弊端<ol><li>一次消息发送需要两次请求；</li><li>上游业务系统需要提供一个事务状态查询接口供可靠消息服务调用；</li><li>当下游业务处理成功时，下游业务系统需要调用可靠消息服务相关接口进行确认。</li></ol></li></ul><p><em>相关说明：</em></p><ul><li><p>上游业务系统预发送消息操作为何是同步的？</p><blockquote><p>因为我们需要获取消息预发送成功后返回的信息(消息ID)，否则后面就无法进行后续的消息更新(确认)或删除操作了。但消息预发送可能会有三种结果：成功、失败、超时(成功或失败都有可能)。然而，这个不要紧，因为消息必须要确认后才会进行投递。如果出现超时现象，我们尽可以把这个<strong>待确认</strong>的消息丢弃。此外，上游业务系统如果收到的不是<strong>消息预发送成功</strong>的反馈结果，就不会执行下一步业务处理操作，从而仍可以保证整个系统的一致性。</p></blockquote></li><li><p>上游业务系统发送业务处理结果为何是异步的？</p><blockquote><p>首先，上游系统和消息中间件之间采用异步通信是为了提高系统并发度。业务系统直接和用户打交道，用户体验尤为重要，因为这种异步通信方式能够极大程度地降低用户等待问题。此外，异步通信相对于同步通信，没有了长时间的阻塞等待，因此系统的并发性也大大增加。对于异步通信可能引起信息的丢失问题，可以由消息服务的超时询问机制来弥补。</p></blockquote></li><li><p>消息中间件投递消息失败后，为何是不断尝试重投而不是进行业务回滚？ </p><blockquote><p>这就涉及到分布式事务系统的实现成本问题。我们知道，当上游业务系统向消息中间件发送业务处理结果后，并不获取任何反馈结果便直接去做别的事情了。如果此时消息投递失败，我们就可以进行重试(下游业务系统需要保证幂等)。如果不断重试(超过了最大重试次数)还是失败了，那么就可以发通知，然后人工介入处理了。而如果是进行业务回滚的话，则需要让上游业务系统事先提供回滚接口，这无疑增加了额外的开发成本，业务系统的复杂度也将提高，为了这种小概率事件而设计这个复杂的流程反而得不偿失。对于一个业务系统的设计目标是：在保证性能的前提下，最大限度地降低系统的复杂度，从而降低系统的运维成本。</p></blockquote></li><li><p>消息中间件和下游业务系统之间为什么要采用同步通信？</p><blockquote><p>上游业务系统发送完业务处理结果后，并不获取任何反馈结果便直接去做别的事情，接下来提交或回滚操作就完全交给消息中间件来完成，并且完全信任消息中间件，认为它一定能正确地完成事务的提交或回滚。然而，消息中间件向下游系统投递消息的过程是同步的，也就是消息中间件将消息投递给下游系统后，它会阻塞等待，等下游系统成功处理完任务返回确认应答后才取消阻塞等待，为什么这两者在设计上是不一致的呢？</p><p>异步通信能提升系统性能，但随之会增加系统复杂度；而同步虽然降低系统并发度，但实现成本较低。因此，在对并发度要求不是很高或者服务器资源较为充裕的情况下，我们可以选择同步来降低系统的复杂度。我们知道，消息中间件是一个独立于业务系统的第三方中间件，它不和任何业务系统产生直接的耦合，它也不和用户产生直接的关联，它一般部署在独立的服务器集群上，具有良好的可扩展性，所以不必太过于担心它的性能，如果处理速度无法满足我们的要求，可以增加机器来解决。而且，即使消息中间件处理速度有一定的延迟也是可以接受的，因为我们追求的是最终一致性而非实时一致性，因此消息中间件产生的延时导致事务短暂的不一致是可以接受的。</p></blockquote></li></ul><h3 id="可靠消息服务"><a href="#可靠消息服务" class="headerlink" title="可靠消息服务"></a>可靠消息服务</h3><p>对消息进行预存储和转发以确保消息不丢失。</p><blockquote><p>尽管我们可以尽量地确保MQ可靠，让MQ可靠地持久化消息，但是网络是不可靠的，几乎没有办法确保网络可靠。所以，我们期望有一个可靠消息，能够避免任何问题，包括网络问题。如果消息不可靠，那么我们就需要采取其他的措施，比如本地消息表。</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> <span class="keyword">IF</span> <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> tx_message (</span><br><span class="line">  <span class="keyword">id</span> <span class="built_in">bigint</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'消息ID'</span>,</span><br><span class="line">  <span class="keyword">version</span> <span class="built_in">int</span>(<span class="number">8</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> <span class="keyword">COMMENT</span> <span class="string">'版本号'</span>,</span><br><span class="line">  msg_status <span class="built_in">varchar</span>(<span class="number">16</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'消息状态：待确认、发送中'</span>,</span><br><span class="line">  msg_body <span class="built_in">text</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'消息内容'</span>,</span><br><span class="line">  content_type <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="string">'JSON'</span> <span class="keyword">COMMENT</span> <span class="string">'消息数据类型'</span>,</span><br><span class="line">  dead_letter <span class="built_in">bit</span>(<span class="number">1</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> <span class="keyword">COMMENT</span> <span class="string">'死信'</span>,</span><br><span class="line">  retry_times <span class="built_in">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="number">0</span> <span class="keyword">COMMENT</span> <span class="string">'重试次数(超过最大重试次数将成为死信)'</span>,</span><br><span class="line">  biz_id <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'业务ID'</span>,</span><br><span class="line">  queue <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'消息队列'</span>,</span><br><span class="line">  callback <span class="built_in">varchar</span>(<span class="number">64</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'业务状态回查接口'</span>,</span><br><span class="line">  remark <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'备注'</span>,</span><br><span class="line">  created_at <span class="built_in">timestamp</span> <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'创建时间'</span>,</span><br><span class="line">  created_by <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">NOT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'创建者'</span>,</span><br><span class="line">  updated_at <span class="built_in">timestamp</span> <span class="literal">NULL</span> <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'更新时间'</span>,</span><br><span class="line">  updated_by <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">DEFAULT</span> <span class="literal">NULL</span> <span class="keyword">COMMENT</span> <span class="string">'更新者'</span>,</span><br><span class="line">  PRIMARY <span class="keyword">KEY</span> (<span class="keyword">id</span>)</span><br><span class="line">) <span class="keyword">ENGINE</span>=<span class="keyword">InnoDB</span> <span class="keyword">DEFAULT</span> <span class="keyword">CHARSET</span>=utf8 <span class="keyword">COMMENT</span>=<span class="string">'事务消息表'</span>;</span><br></pre></td></tr></table></figure><h3 id="消息状态确认子系统"><a href="#消息状态确认子系统" class="headerlink" title="消息状态确认子系统"></a>消息状态确认子系统</h3><p>用于定时从可靠消息服务中查询状态为<code>待确认</code>的消息，然后和上游业务系统进行确认，并根据确认结果进行处理：如果确认上游业务已处理成功，则更新消息状态并将消息投递到实时消息服务；如果确认上游业务处理失败，则将该消息删除；如果上游业务正在处理中，则继续等待(下次处理)。</p><h3 id="消息恢复子系统"><a href="#消息恢复子系统" class="headerlink" title="消息恢复子系统"></a>消息恢复子系统</h3><p>用于定时从可靠消息服务中查询状态为<code>发送中</code>的消息并进行重新投递到实时消息服务(消息中间件)。</p><blockquote><p>由于可靠消息服务存在重投机制，所以下游业务系统必须保证相关业务接口的幂等性（即<code>f(n)=f(1)</code>），以确保消息不会被重复消费。</p></blockquote><h3 id="消息管理子系统"><a href="#消息管理子系统" class="headerlink" title="消息管理子系统"></a>消息管理子系统</h3><p>主要用于对已死亡的消息进行人工干预。</p><h3 id="实时消息服务（消息中间件）"><a href="#实时消息服务（消息中间件）" class="headerlink" title="实时消息服务（消息中间件）"></a>实时消息服务（消息中间件）</h3><p>主要用于消息存储和转发。</p><h2 id="RocketMQ事务消息"><a href="#RocketMQ事务消息" class="headerlink" title="RocketMQ事务消息"></a><a href="http://rocketmq.apache.org/docs/transaction-example/" target="_blank" rel="noopener">RocketMQ事务消息</a></h2><p>在RocketMQ中实现了分布式事务，实际上其实是对本地消息表的一个封装，将本地消息表移动到了MQ内部，其基本流程如下：</p><ol><li>事务发起方首先发送prepare消息到MQ</li><li>在发送prepare消息成功后执行本地事务</li><li>根据本地事务执行结果返回commit或者rollback</li><li>如果是rollback消息，MQ将删除该prepare消息不进行投递；如果是commit消息，MQ将会把这个消息投递给consumer端</li><li>如果执行本地事务过程中，出现死机、超时等异常情况导致消息确认失败，那么MQ将会不停地询问其同组的其它producer来获取状态</li><li>consumer端的消费成功机制由MQ保证</li></ol><blockquote><p>RocketMQ的事务消息主要是通过消息的异步处理，可以保证 <code>本地事务</code> 和 <code>消息发送</code> 同时成功或失败，从而保证数据的最终一致性。</p></blockquote><p><img src="/images/architect/distributed-transaction-rocketmq.png" alt="RocketMQ事务消息"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Producer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        TransactionMQProducer producer = <span class="keyword">new</span> TransactionMQProducer(<span class="string">"TransactionGroup"</span>);</span><br><span class="line">        producer.setNamesrvAddr(<span class="string">"127.0.0.1:9876"</span>);</span><br><span class="line">        producer.setTransactionListener(<span class="keyword">new</span> TransactionListenerImpl());</span><br><span class="line">        producer.setExecutorService(ForkJoinPool.commonPool());</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            producer.start();</span><br><span class="line">            <span class="comment">// 发送事务消息, 此消息不可见</span></span><br><span class="line">            TransactionSendResult sendResult = producer.sendMessageInTransaction(<span class="keyword">new</span> Message(</span><br><span class="line">                    <span class="string">"TransactionTopic"</span>,</span><br><span class="line">                    <span class="string">"事务消息"</span>.getBytes(StandardCharsets.UTF_8)</span><br><span class="line">            ), <span class="string">"tx"</span>);</span><br><span class="line">            log.debug(<span class="string">"====== &#123;&#125; ======"</span>, sendResult);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (MQClientException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            producer.shutdown();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionListenerImpl</span> <span class="keyword">implements</span> <span class="title">TransactionListener</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 存储当前线程对应的事务状态</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, LocalTransactionState&gt; localTrans = <span class="keyword">new</span> ConcurrentHashMap&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 发送prepare消息成功后回调该方法用于执行本地事务</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> msg 回传的消息</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arg 调用send方法时传递的参数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> LocalTransactionState <span class="title">executeLocalTransaction</span><span class="params">(Message msg, Object arg)</span> </span>&#123;</span><br><span class="line">        String transactionId = msg.getTransactionId();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            localTrans.put(transactionId, UNKNOW);</span><br><span class="line">            log.debug(<span class="string">"====== 开始执行本地事务: transactionId=&#123;&#125; ======"</span>, transactionId);</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">60</span>);</span><br><span class="line">            log.debug(<span class="string">"====== 执行本地事务成功 ======"</span>);</span><br><span class="line">            localTrans.put(transactionId, COMMIT_MESSAGE);</span><br><span class="line">            <span class="keyword">return</span> COMMIT_MESSAGE;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            localTrans.put(transactionId, ROLLBACK_MESSAGE);</span><br><span class="line">            <span class="keyword">return</span> ROLLBACK_MESSAGE;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 消息回查, 当本地事务超时, broker会做消息回查操作</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> LocalTransactionState <span class="title">checkLocalTransaction</span><span class="params">(MessageExt msg)</span> </span>&#123;</span><br><span class="line">        String transactionId = msg.getTransactionId();</span><br><span class="line">        LocalTransactionState transactionState = localTrans.get(transactionId);</span><br><span class="line">        log.debug(<span class="string">"====== 执行消息回查: transactionId=&#123;&#125;, transactionState=&#123;&#125; ======"</span>, transactionId, transactionState);</span><br><span class="line">        <span class="keyword">return</span> Optional.ofNullable(transactionState).orElse(UNKNOW);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">====== 开始执行本地事务: transactionId=AC140A031F7C18B4AAC28A2C0D070000 ======</span><br><span class="line">====== 执行消息回查: transactionId=AC140A0323D018B4AAC28A25C2F40000, transactionState=null ======</span><br><span class="line">====== 执行消息回查: transactionId=AC140A031F7C18B4AAC28A2C0D070000, transactionState=UNKNOW ======</span><br><span class="line">====== 执行本地事务成功 ======</span><br><span class="line">====== SendResult [sendStatus=SEND_OK, msgId=AC140A031F7C18B4AAC28A2C0D070000, offsetMsgId=null, messageQueue=MessageQueue [topic=TransactionTopic, brokerName=Lenovo-PC, queueId=1], queueOffset=8] ======</span><br></pre></td></tr></table></figure><h2 id="Seata分布式事务解决方案"><a href="#Seata分布式事务解决方案" class="headerlink" title="Seata分布式事务解决方案"></a><a href="https://github.com/seata/seata/wiki" target="_blank" rel="noopener">Seata分布式事务解决方案</a></h2>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="distributed" scheme="https://lugavin.github.io/tags/distributed/"/>
    
  </entry>
  
  <entry>
    <title>性能优化 - Tomcat</title>
    <link href="https://lugavin.github.io/2018/12/08/architect/performance/tomcat/"/>
    <id>https://lugavin.github.io/2018/12/08/architect/performance/tomcat/</id>
    <published>2018-12-08T09:30:00.000Z</published>
    <updated>2026-05-09T06:54:58.888Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="REST应用减配"><a href="#REST应用减配" class="headerlink" title="REST应用减配"></a>REST应用减配</h2><p><img src="/images/architect/performance-jconsole.png" alt="performance-jconsole"><br>如上图所示，在默认情况下，Tomcat启用了<code>org.apache.catalina.servlets.DefaultServlet</code>和<code>org.apache.jasper.servlet.JspServlet</code>来分别对静态资源和JSP进行处理。而在时下流行的微服务架构中，应用通常采用前后端分离的设计，后端的功能通过<code>REST API</code>方式向前端提供服务。针对REST应用，我们可以将该两项配置禁用掉，即将<code>${TOMCAT_HOME}/conf/web.xml</code>配置文件中的以下内容注释掉：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">servlet</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>default<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-class</span>&gt;</span>org.apache.catalina.servlets.DefaultServlet<span class="tag">&lt;/<span class="name">servlet-class</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">init-param</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-name</span>&gt;</span>debug<span class="tag">&lt;/<span class="name">param-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-value</span>&gt;</span>0<span class="tag">&lt;/<span class="name">param-value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">init-param</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">init-param</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-name</span>&gt;</span>listings<span class="tag">&lt;/<span class="name">param-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-value</span>&gt;</span>false<span class="tag">&lt;/<span class="name">param-value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">init-param</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">load-on-startup</span>&gt;</span>1<span class="tag">&lt;/<span class="name">load-on-startup</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">servlet</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">servlet</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>jsp<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-class</span>&gt;</span>org.apache.jasper.servlet.JspServlet<span class="tag">&lt;/<span class="name">servlet-class</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">init-param</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-name</span>&gt;</span>fork<span class="tag">&lt;/<span class="name">param-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-value</span>&gt;</span>false<span class="tag">&lt;/<span class="name">param-value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">init-param</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">init-param</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-name</span>&gt;</span>xpoweredBy<span class="tag">&lt;/<span class="name">param-name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">param-value</span>&gt;</span>false<span class="tag">&lt;/<span class="name">param-value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">init-param</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">load-on-startup</span>&gt;</span>3<span class="tag">&lt;/<span class="name">load-on-startup</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">servlet</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">servlet-mapping</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>default<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>/<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">servlet-mapping</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">servlet-mapping</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>jsp<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>*.jsp<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>*.jspx<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">servlet-mapping</span>&gt;</span></span><br></pre></td></tr></table></figure></p><h2 id="禁用AccessLogValve"><a href="#禁用AccessLogValve" class="headerlink" title="禁用AccessLogValve"></a>禁用AccessLogValve</h2><p>在Nginx代理服务器中已经记录了AccessLog日志，所以在Tomcat应用服务器中没必要再记录一份，可以将<code>${TOMCAT_HOME}/conf/server.xml</code>配置文件中的以下内容注释掉：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">Valve</span> <span class="attr">className</span>=<span class="string">"org.apache.catalina.valves.AccessLogValve"</span> <span class="attr">directory</span>=<span class="string">"logs"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">prefix</span>=<span class="string">"localhost_access_log"</span> <span class="attr">suffix</span>=<span class="string">".txt"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">pattern</span>=<span class="string">"%h %l %u %t <span class="symbol">&amp;quot;</span>%r<span class="symbol">&amp;quot;</span> %s %b"</span> /&gt;</span></span><br></pre></td></tr></table></figure></p><h2 id="线程池配置"><a href="#线程池配置" class="headerlink" title="线程池配置"></a>线程池配置</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">Executor</span> <span class="attr">name</span>=<span class="string">"tomcatThreadPool"</span> <span class="attr">namePrefix</span>=<span class="string">"catalina-exec-"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">maxThreads</span>=<span class="string">"200"</span> <span class="attr">minSpareThreads</span>=<span class="string">"10"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">Connector</span> <span class="attr">executor</span>=<span class="string">"tomcatThreadPool"</span></span></span><br><span class="line"><span class="tag">           <span class="attr">port</span>=<span class="string">"8080"</span> <span class="attr">protocol</span>=<span class="string">"HTTP/1.1"</span></span></span><br><span class="line"><span class="tag">           <span class="attr">connectionTimeout</span>=<span class="string">"20000"</span></span></span><br><span class="line"><span class="tag">           <span class="attr">redirectPort</span>=<span class="string">"8443"</span> /&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>温馨提示：配置后可通过JDK自带的<code>jconsole</code>监视工具观察配置是否生效。</p></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="performance" scheme="https://lugavin.github.io/tags/performance/"/>
    
  </entry>
  
  <entry>
    <title>分布式存储 - MongoDB</title>
    <link href="https://lugavin.github.io/2018/11/30/architect/distributed/mongodb/"/>
    <id>https://lugavin.github.io/2018/11/30/architect/distributed/mongodb/</id>
    <published>2018-11-30T10:10:00.000Z</published>
    <updated>2026-05-09T06:54:58.887Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p><em>MongoDB是基于文档存储的，文档存储一般用类似 json 的格式存储，存储的内容是文档型的，这样也就有机会对某些字段建立索引，实现关系数据库的某些功能。</em></p><h2 id="MongoDB-安装"><a href="#MongoDB-安装" class="headerlink" title="MongoDB 安装"></a>MongoDB 安装</h2><h3 id="单机模式"><a href="#单机模式" class="headerlink" title="单机模式"></a>单机模式</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.11.tgz</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> tar -zxvf mongodb-linux-x86_64-4.0.11.tgz</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> mv mongodb-linux-x86_64-4.0.11 /usr/<span class="built_in">local</span>/mongodb</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">cd</span> /usr/<span class="built_in">local</span>/mongodb</span></span><br><span class="line"></span><br><span class="line"><span class="meta">$</span><span class="bash"> mkdir conf data logs</span></span><br><span class="line"><span class="meta">$</span><span class="bash"> vi conf/mongod.yaml</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> https://docs.mongodb.com/manual/reference/configuration-options/</span></span><br><span class="line">storage:</span><br><span class="line">  dbPath: /usr/local/mongodb/data</span><br><span class="line">systemLog:</span><br><span class="line">  destination: file</span><br><span class="line">  path: /usr/local/mongodb/logs/mongod.log</span><br><span class="line">net:</span><br><span class="line">  port: 27017</span><br><span class="line"><span class="meta">  #</span><span class="bash"> 允许远程访问(默认值为localhost即只能本地访问)</span></span><br><span class="line">  bindIp: 0.0.0.0</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 启动服务</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./bin/mongod --config conf/mongod.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 测试</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./bin/mongo --host 192.168.8.128 --port 27017</span></span><br></pre></td></tr></table></figure><h3 id="集群模式"><a href="#集群模式" class="headerlink" title="集群模式"></a>集群模式</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># MongoDB replica-set cluster</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 在集群的服务器上分别启动服务</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./bin/mongod --config /usr/<span class="built_in">local</span>/mongodb/conf/mongod.yaml --replSet rs0</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># 连接到其中一台服务器上创建replica-set集群</span></span></span><br><span class="line"><span class="meta">$</span><span class="bash"> ./bin/mongo --host 192.168.8.128 --port 27017</span></span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> rs.initiate(&#123;_id: <span class="string">'rs0'</span>, members: [</span></span><br><span class="line">      &#123;_id: 0, host: '192.168.8.128:27017'&#125;, </span><br><span class="line">      &#123;_id: 1, host: '192.168.8.129:27017'&#125;, </span><br><span class="line">      &#123;_id: 2, host: '192.168.8.130:27017'&#125;</span><br><span class="line">  ]&#125;)</span><br><span class="line"><span class="meta">&gt;</span><span class="bash"> rs.status()</span></span><br></pre></td></tr></table></figure><h2 id="MongoDB-相关概念"><a href="#MongoDB-相关概念" class="headerlink" title="MongoDB 相关概念"></a>MongoDB 相关概念</h2><table><thead><tr><th>SQL术语/概念</th><th>MongoDB术语/概念</th><th>解释/说明</th></tr></thead><tbody><tr><td>database</td><td>database</td><td>数据库</td></tr><tr><td>table</td><td>collection</td><td>数据库表 / 集合</td></tr><tr><td>row</td><td>document</td><td>数据记录行 / 文档</td></tr><tr><td>column</td><td>field</td><td>数据字段 / 域</td></tr><tr><td>index</td><td>index</td><td>索引</td></tr><tr><td>table joins</td><td></td><td>表连接 / MongoDB不支持</td></tr><tr><td>primary key</td><td>primary key</td><td>主键 / MongoDB自动将_id字段设置为主键</td></tr></tbody></table><blockquote><p>MongoDB集默认会创建admin、local、config数据库：admin数据库则主要存储MongoDB的用户、角色等信息；local数据库主要存储副本集的元数据；config 数据库在内部使用，当使用分片模式时，用于保存分片的信息。</p></blockquote><h2 id="MongoDB-事务支持"><a href="#MongoDB-事务支持" class="headerlink" title="MongoDB 事务支持"></a>MongoDB 事务支持</h2><p>在 MongoDB 4.0 之前的版本中，操作单个文档是原子性的，但操作多文档是非原子性的；从 MongoDB 4.0 版本开始支持多文档 ACID 事务，但多文档事务仅适用于副本集。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">data:</span></span><br><span class="line">    <span class="attr">mongodb:</span></span><br><span class="line">      <span class="comment"># Mongo supports transaction feature for a replica set</span></span><br><span class="line">      <span class="attr">uri:</span> <span class="string">mongodb://192.168.8.128:27017,192.168.8.129:27017,192.168.8.130:27017/test?replicaSet=rs0</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MongoConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Remove &#123;<span class="doctag">@link</span> DefaultMongoTypeMapper#DEFAULT_TYPE_KEY&#125; Field</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MongoConfig</span><span class="params">(MappingMongoConverter mappingMongoConverter)</span> </span>&#123;</span><br><span class="line">        mappingMongoConverter.setTypeMapper(<span class="keyword">new</span> DefaultMongoTypeMapper(<span class="keyword">null</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function">MongoTransactionManager <span class="title">transactionManager</span><span class="params">(MongoDbFactory dbFactory)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> MongoTransactionManager(dbFactory);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="MongoDB-常用命令"><a href="#MongoDB-常用命令" class="headerlink" title="MongoDB 常用命令"></a>MongoDB 常用命令</h2><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td>show dbs</td><td>显示所有数据库</td></tr><tr><td>use [database_name]</td><td>如果数据库不存在则创建，否则切换到指定数据库</td></tr><tr><td>db.dropDatabase()</td><td>删除当前使用的数据库（db代表当前使用的数据库）</td></tr><tr><td>db</td><td>查看当前使用的数据库</td></tr><tr><td>show collections</td><td>查看当前数据库中的所有集合</td></tr><tr><td>db.createCollection(name)</td><td>在当前数据库中创建集合</td></tr><tr><td>db.getCollection(name).drop()</td><td>在当前数据库中删除集合</td></tr></tbody></table><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">db.createCollection("distlock");</span><br><span class="line">db.getCollection("distlock").insert(&#123;</span><br><span class="line">    _id: NumberLong(<span class="string">"604797950328573952"</span>),</span><br><span class="line">    methodName: <span class="string">"createOrder"</span>,</span><br><span class="line">    updatedAt: <span class="keyword">new</span> <span class="built_in">Date</span>()</span><br><span class="line">&#125;);</span><br><span class="line">db.getCollection("distlock").update(&#123;</span><br><span class="line">    _id: NumberLong(<span class="string">"604797950328573952"</span>)</span><br><span class="line">&#125;, &#123;</span><br><span class="line">    methodName: <span class="string">"createPayOrder"</span>,</span><br><span class="line">    updatedAt: <span class="keyword">new</span> <span class="built_in">Date</span>()</span><br><span class="line">&#125;);</span><br><span class="line">db.getCollection("distlock").find(&#123;</span><br><span class="line">    methodName: "createPayOrder"</span><br><span class="line">&#125;);</span><br><span class="line">db.getCollection("distlock").drop();</span><br></pre></td></tr></table></figure><blockquote><p>注意： 在 MongoDB 中，集合只有在内容插入后才会创建，即创建集合（数据表）后要再插入一个文档（记录），集合才会真正创建；但 <em>spring-data-mongodb</em> 不支持自动创建Collection集合，故需要手动先创建Collection集合，否则会抛出 <em>Cannot create namespace xxx.xxx in multi-document transaction</em> 错误。</p></blockquote><h2 id="MongoDB-适用场景"><a href="#MongoDB-适用场景" class="headerlink" title="MongoDB 适用场景"></a>MongoDB 适用场景</h2><ul><li>应用不需要事务及复杂join支持</li><li>新应用，需求会变，数据模型无法确定，想快速迭代开发</li><li>应用需要2000~3000以上的读写QPS</li><li>应用需要TB甚至PB级别数据存储    </li><li>应用发展迅速，需要能快速水平扩展</li><li>应用要求存储的数据不丢失</li><li>应用需要99.999%高可用    </li><li>应用需要大量的地理位置查询、文本查询</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla
      
    
    </summary>
    
      <category term="architect" scheme="https://lugavin.github.io/categories/architect/"/>
    
    
      <category term="architect" scheme="https://lugavin.github.io/tags/architect/"/>
    
  </entry>
  
</feed>
