<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>董宗磊的博客</title>
  
  <subtitle>董宗磊的博客--靡不有初，鲜克有终</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://dongzl.github.io/"/>
  <updated>2023-12-31T06:59:02.281Z</updated>
  <id>https://dongzl.github.io/</id>
  
  <author>
    <name>[object Object]</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>异步 API 设计最佳实践：用于实时通信的服务端事件发送 (SSE)</title>
    <link href="https://dongzl.github.io/2023/12/19/26-Asynchronous-API-Design-Best-Practices-Server-Sent-Event/"/>
    <id>https://dongzl.github.io/2023/12/19/26-Asynchronous-API-Design-Best-Practices-Server-Sent-Event/</id>
    <published>2023-12-19T18:28:22.000Z</published>
    <updated>2023-12-31T06:59:02.281Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://blog.stackademic.com/asynchronous-api-design-best-practices-server-sent-event-sse-for-real-time-communication-a3a3e20233d2">https://blog.stackademic.com/asynchronous-api-design-best-practices-server-sent-event-sse-for-real-time-communication-a3a3e20233d2</a></p></blockquote><p>在当下应用程序开发领域，实时通信技术不再是一种奢侈品，而是必需品。异步 <code>API</code> 设计是实现这一目标的关键，异步 <code>API</code> 可以使应用程序提供实时的更新和通知能力，并且不受传统请求响应模型的限制。</p><p>在本文中，我们将探讨异步 <code>API</code> 设计的四种强大技术：<strong>Callbacks</strong>、<strong>WebSocket</strong>、<strong>消息队列</strong>和<strong>服务端事件发送（SSE）</strong>。所有的这些方法都具有独特的优势，对于创建响应式实时通知应用程序非常重要。</p><h2 id="为什么异步-API-设计很重要"><a href="#为什么异步-API-设计很重要" class="headerlink" title="为什么异步 API 设计很重要"></a>为什么异步 API 设计很重要</h2><p>传统的 <code>API</code> 设计中的请求-响应模型是有其局限性的。当客户端向服务器发送请求时，通常必须等待响应结果，这可能会导致响应延迟并降低用户体验，尤其在需要实时数据更新等至关重要的场景中。</p><p>异步 <code>API</code> 设计允许服务器异步处理耗时的任务并立即返回响应结果，从而摆脱了这些限制。这使得客户端无需等待响应结果即可继续其他操作，并在任务完成后立即接收更新信息。</p><h2 id="异步-API-工作流程"><a href="#异步-API-工作流程" class="headerlink" title="异步 API 工作流程"></a>异步 API 工作流程</h2><p>在异步 <code>API</code> 设计中</p><ol><li>客户端通过 <code>Rest Endpoint</code> 向服务器发送请求；</li><li>服务器异步处理耗时任务并立即向客户端返回响应结果–“我正在处理这个任务”；服务器可以在立即返回的响应结果中携带一个唯一的 <code>ID</code>，以便客户端可以使用该 <code>ID</code> 定期获取任务状态。</li></ol><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/26-Asynchronous-API-Design-Best-Practices-Server-Sent-Event/01.png"><ol start="3"><li><strong>任务完成后</strong>，服务器可以使用多种机制通过响应消息通知客户端；机制的选择通常取决于应用程序的使用场景和所使用的通信协议。</li></ol><h2 id="如何将数据推送到-API-客户端"><a href="#如何将数据推送到-API-客户端" class="headerlink" title="如何将数据推送到 API 客户端"></a>如何将数据推送到 API 客户端</h2><p>理想的情况是当有可用的新数据或新事件时，服务器能够通知客户端；但是我们无法使用传统的 <code>HTTP</code> 请求-响应模型的交互方式来达到这种效果。我们必须找到一种方法让服务器能够将数据推送到客户端–接入异步 <code>API</code>。</p><h2 id="如何实现服务端的实时通知"><a href="#如何实现服务端的实时通知" class="headerlink" title="如何实现服务端的实时通知"></a>如何实现服务端的实时通知</h2><h3 id="方法-1：轮询"><a href="#方法-1：轮询" class="headerlink" title="方法 1：轮询"></a>方法 1：轮询</h3><p>客户端反复向服务器发送请求，请求更新的数据；服务器在有新的信息或结果时给出响应。虽然这个方案实施起来很简单，但轮询可能会导致网络流量增加和网络延迟。</p><h3 id="方法-2：WebSocket"><a href="#方法-2：WebSocket" class="headerlink" title="方法 2：WebSocket"></a>方法 2：WebSocket</h3><p><code>WebSockets</code> 提供全双工通信能力，<strong>可以实现一旦服务端有数据更新，可以立即向客户端推送消息</strong>。<code>WebSocket</code> 非常适合需要低延迟、实时通信的应用程序。</p><h3 id="方法-3：服务器事件发送-SSE"><a href="#方法-3：服务器事件发送-SSE" class="headerlink" title="方法 3：服务器事件发送 (SSE)"></a>方法 3：服务器事件发送 (SSE)</h3><p><strong>服务器需要以单向的方式向客户端推送更新</strong>。它用单个 <code>HTTP</code> 连接，与打开多个连接相比减少了开销。</p><p><code>SSE</code> 是单向的，这意味着<strong>客户端只能从服务器接收更新数据，不能向服务端发送数据</strong>。<code>SSE</code> 适合仅需要单向通信的应用场景。</p><h3 id="方法-4：消息队列"><a href="#方法-4：消息队列" class="headerlink" title="方法 4：消息队列"></a>方法 4：消息队列</h3><p>服务器可以使用消息队列（例如：<code>RabbitMQ</code>、<code>Apache Kafka</code>）来发布消息；客户端订阅特定主题或队列，并在消息到达时异步接收消息。</p><h3 id="方法-5：回调-URL"><a href="#方法-5：回调-URL" class="headerlink" title="方法 5：回调 URL"></a>方法 5：回调 URL</h3><p>回调 <code>URL</code> 对于服务器长时间运行后通知客户端的场景非常有效，它们最大限度地减少了客户端轮询操作或者维护持久连接的需要。</p><p><strong>缺点</strong>：客户端必须开放可公开访问的回调 <code>URL</code>，这可能会带来安全隐患和隐私问题。此外管理回调 <code>URL</code> 和处理回调失败时的重试可能具有很大挑战性。</p><h2 id="使用服务器事件发送的异步-API"><a href="#使用服务器事件发送的异步-API" class="headerlink" title="使用服务器事件发送的异步 API"></a>使用服务器事件发送的异步 API</h2><p>服务器事件发送（<code>SSE</code>）提供一种强大的机制，用于在服务器和客户端之间实现异步通信，特别是在 <code>API</code> 上下文环境中。<code>SSE</code> 是基于浏览器的 <code>EventSource</code> 接口实现的，这个接口是由万维网联盟 (<code>W3C</code>) 制定的 <code>HTML5</code> 标准的一部分；它引入了一种使用 <code>HTTP</code> 建立长连接的方法，允许服务器主动将数据推送到客户端，该数据通常与关联的有效负载信息共同构造成一个事件。</p><p>最初，<code>SSE</code> 的构想是为了方便向 <code>Web</code> 应用程序传输数据，但它在 <code>API</code> 领域中的作用确越来越大。<code>SSE</code> 提供了引人注目的解决方案来替代传统轮询机制，解决了一些客户端-服务器通信相关的固有挑战问题。</p><h2 id="SSE-是如何工作的"><a href="#SSE-是如何工作的" class="headerlink" title="SSE 是如何工作的"></a>SSE 是如何工作的</h2><p><code>SSE</code> 使用标准的 <code>HTTP</code> 连接，但是会保持较长时间连接而不是立即断开。该连接允许服务器在数据可用时将数据推送到客户端：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/26-Asynchronous-API-Design-Best-Practices-Server-Sent-Event/02.png"><p>规范描述了所返回数据格式的一些内容，包括事件名称、注释、基于文本格式的单行或多行的数据以及事件标识符。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/26-Asynchronous-API-Design-Best-Practices-Server-Sent-Event/03.png"><h2 id="使用案例：电子商务系统中批量产品信息更新-API"><a href="#使用案例：电子商务系统中批量产品信息更新-API" class="headerlink" title="使用案例：电子商务系统中批量产品信息更新 API"></a>使用案例：电子商务系统中批量产品信息更新 API</h2><p>在此用例中，电子商务网站允许客户上传包含大量产品列表的 <code>CSV</code> 文件。</p><blockquote><p>服务器异步处理上传的 CSV 文件并立即发送响应结果以确认上传成功。CSV 文件的解析和验证完成后，服务器使用服务器事件发送（SSE）将处理后的产品数据发送到客户端。</p></blockquote><h2 id="客户端到服务器（CSV-上传和异步处理）："><a href="#客户端到服务器（CSV-上传和异步处理）：" class="headerlink" title="客户端到服务器（CSV 上传和异步处理）："></a>客户端到服务器（CSV 上传和异步处理）：</h2><h3 id="1-客户端初始化-CSV-上传："><a href="#1-客户端初始化-CSV-上传：" class="headerlink" title="1. 客户端初始化 CSV 上传："></a>1. 客户端初始化 CSV 上传：</h3><p>客户端访问电子商务网站，并通过用户界面发起 <code>CSV</code> 文件上传。</p><h3 id="2-客户端发送-CSV-文件："><a href="#2-客户端发送-CSV-文件：" class="headerlink" title="2. 客户端发送 CSV 文件："></a>2. 客户端发送 CSV 文件：</h3><p>客户端选择包含产品数据的 <code>CSV</code> 文件，并通过向 <code>/api/upload/csv</code> 发送 <code>POST</code> 请求将其上传到服务器。</p><h3 id="3-服务器验证文件并生成唯一-ID："><a href="#3-服务器验证文件并生成唯一-ID：" class="headerlink" title="3. 服务器验证文件并生成唯一 ID："></a>3. 服务器验证文件并生成唯一 ID：</h3><p>服务器接收 <code>CSV</code> 文件并对其进行验证，如果文件有效，服务器会立即响应 <code>HTTP 202</code>（<code>Accepted</code>）状态代码来确认上传成功。</p><ul><li>服务器会为本次上传生成一个<strong>唯一的事务 ID</strong>，用于跟踪进度处理过程。</li></ul><h3 id="4-异步处理开始："><a href="#4-异步处理开始：" class="headerlink" title="4. 异步处理开始："></a>4. 异步处理开始：</h3><p>服务器开始异步处理 <code>CSV</code> 文件。此处理包括 <code>CSV</code> 文件解析、数据验证和产品列表的创建。</p><ul><li>处理可能在后台进行，允许服务器在处理 <code>CSV</code> 文件同时能够处理其他请求。</li></ul><h3 id="5-通过-SSE-发送数据更新进度："><a href="#5-通过-SSE-发送数据更新进度：" class="headerlink" title="5. 通过 SSE 发送数据更新进度："></a>5. 通过 SSE 发送数据更新进度：</h3><p>处理 <code>CSV</code> 文件并生成产品数据时，服务器使用服务器事件发送（<code>SSE</code>）向客户端发送实时进度更新。<code>SSE Endpoint</code>（<code>/sse</code>）已经被创建，并通过生成的唯一 <code>ID</code> 与客户端进行连接。</p><h3 id="6-服务器到客户端（实时更新进度和完成操作）："><a href="#6-服务器到客户端（实时更新进度和完成操作）：" class="headerlink" title="6. 服务器到客户端（实时更新进度和完成操作）："></a>6. 服务器到客户端（实时更新进度和完成操作）：</h3><ol><li>客户端侦听 <code>SSE Endpoint</code>-实时更新进度：在客户端，<code>JavaScript</code> 脚本用来监听与唯一 <code>ID</code> 关联的 <code>SSE Endpoint</code>（<code>/sse</code>）。客户端通过 <code>SSE</code> 与服务器建立长连接，实现实时数据更新。</li><li><strong>服务器发送进度更新</strong>： <ul><li>在处理 <code>CSV</code> 文件时，<strong>服务器会将部分产品数据更新和进度消息发送到 <code>SSE Endpoint</code></strong>，客户端会监听该 <code>Endpoint</code> 返回的数据。</li><li>服务器会将这些更新数据实时推送到客户端，为用户提供关于 <code>CSV</code> 处理进度的结果000反馈。</li></ul></li></ol><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"><span class="comment">// Inside your CSV processing logic</span></span><br><span class="line"><span class="type">String</span> <span class="variable">transactionId</span> <span class="operator">=</span> <span class="string">&quot;TXN-123&quot;</span>; <span class="comment">// Replace with the actual transaction ID</span></span><br><span class="line"><span class="type">String</span> <span class="variable">progressMessage</span> <span class="operator">=</span> <span class="string">&quot;Processing 50% complete&quot;</span>; <span class="comment">// Replace with your progress message</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Send an SSE update to the client</span></span><br><span class="line">sseController.sendSseUpdate(transactionId, progressMessage);</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>通过 <code>SSE</code> 发送完成消息</strong>：</li></ol><ul><li>在成功处理整个 <code>CSV</code> 文件后，服务器通过 <code>SSE</code> 向客户端发送最终完成消息，并通知用户产品数据已可以检索。</li><li>用户端收到此消息后可以继续从服务器检索已处理的产品数据。</li></ul><ol start="4"><li><strong>错误处理</strong>：</li></ol><ul><li>如果上传的 <code>CSV</code> 文件存在问题（例如：格式错误或数据无效）或者处理过程中发生错误，服务器会向客户端发送错误响应结果。</li><li>客户会收到报错通知，修复问题后可以继续操作。</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><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><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.http.HttpStatus;</span><br><span class="line"><span class="keyword">import</span> org.springframework.http.ResponseEntity;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.*;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.multipart.MultipartFile;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.servlet.mvc.method.annotation.SseEmitter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ConcurrentHashMap;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/api/upload&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CsvUploadController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, SseEmitter&gt; sseEmitters = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/csv&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;String&gt; <span class="title function_">uploadCsv</span><span class="params">(<span class="meta">@RequestParam(&quot;file&quot;)</span> MultipartFile file)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (file.isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">return</span> ResponseEntity.badRequest().body(<span class="string">&quot;Please select a CSV file to upload.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!isCsvValid(file)) &#123;</span><br><span class="line">            <span class="keyword">return</span> ResponseEntity.badRequest().body(<span class="string">&quot;Invalid CSV file format or data.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">transactionId</span> <span class="operator">=</span> <span class="string">&quot;TXN-&quot;</span> + System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1. Start asynchronous processing and return a CompletableFuture</span></span><br><span class="line">        CompletableFuture&lt;Void&gt; processingFuture = CompletableFuture.runAsync(() -&gt; &#123;</span><br><span class="line">            asyncProcessCsv(file, transactionId);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//2. Return a 202 (Accepted) response with the transaction ID</span></span><br><span class="line">        <span class="keyword">return</span> ResponseEntity.status(HttpStatus.ACCEPTED).body(transactionId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isCsvValid</span><span class="params">(MultipartFile file)</span> &#123;</span><br><span class="line">        <span class="comment">// Add your CSV validation logic here</span></span><br><span class="line">        <span class="comment">// Return true if the CSV is valid; otherwise, return false</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">asyncProcessCsv</span><span class="params">(MultipartFile file, String transactionId)</span> &#123;</span><br><span class="line">        CompletableFuture&lt;Void&gt; processingFuture = CompletableFuture.runAsync(() -&gt; &#123;</span><br><span class="line">            <span class="comment">// Your CSV processing logic here</span></span><br><span class="line">            <span class="keyword">try</span> (<span class="type">CSVReader</span> <span class="variable">csvReader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CSVReader</span>(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(file.getInputStream()))) &#123;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// Process CSV rows here</span></span><br><span class="line">                <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// Send progress updates via SSE</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= totalRows; i++) &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">progressMessage</span> <span class="operator">=</span> <span class="string">&quot;Processing row &quot;</span> + i;</span><br><span class="line">                    sendProgressUpdate(transactionId, progressMessage);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// Send completion message via SSE</span></span><br><span class="line">                sendCompletionMessage(transactionId, <span class="string">&quot;CSV processing completed.&quot;</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                <span class="comment">// Handle exceptions during processing</span></span><br><span class="line">                sendErrorMessage(transactionId, <span class="string">&quot;Error during processing: &quot;</span> + e.getMessage());</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                sseEmitters.remove(transactionId);</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">// Handle any exceptions that occur during processing</span></span><br><span class="line">        processingFuture.exceptionally(ex -&gt; &#123;</span><br><span class="line">            sendErrorMessage(transactionId, <span class="string">&quot;Error during processing: &quot;</span> + ex.getMessage());</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/sse/&#123;transactionId&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> SseEmitter <span class="title function_">getSseEmitter</span><span class="params">(<span class="meta">@PathVariable</span> String transactionId)</span> &#123;</span><br><span class="line">        <span class="type">SseEmitter</span> <span class="variable">sseEmitter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SseEmitter</span>();</span><br><span class="line">        sseEmitters.put(transactionId, sseEmitter);</span><br><span class="line">        <span class="keyword">return</span> sseEmitter;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendProgressUpdate</span><span class="params">(String transactionId, String message)</span> &#123;</span><br><span class="line">        <span class="type">SseEmitter</span> <span class="variable">sseEmitter</span> <span class="operator">=</span> sseEmitters.get(transactionId);</span><br><span class="line">        <span class="keyword">if</span> (sseEmitter != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                sseEmitter.send(SseEmitter.event().name(<span class="string">&quot;progress&quot;</span>).data(message));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="comment">// Handle exceptions when sending SSE updates</span></span><br><span class="line">                e.printStackTrace();</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><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendCompletionMessage</span><span class="params">(String transactionId, String message)</span> &#123;</span><br><span class="line">        <span class="type">SseEmitter</span> <span class="variable">sseEmitter</span> <span class="operator">=</span> sseEmitters.get(transactionId);</span><br><span class="line">        <span class="keyword">if</span> (sseEmitter != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                sseEmitter.send(SseEmitter.event().name(<span class="string">&quot;complete&quot;</span>).data(message));</span><br><span class="line">                sseEmitter.complete(); <span class="comment">// Close the SSE connection</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="comment">// Handle exceptions when sending SSE updates</span></span><br><span class="line">                e.printStackTrace();</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><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">sendErrorMessage</span><span class="params">(String transactionId, String message)</span> &#123;</span><br><span class="line">        <span class="type">SseEmitter</span> <span class="variable">sseEmitter</span> <span class="operator">=</span> sseEmitters.get(transactionId);</span><br><span class="line">        <span class="keyword">if</span> (sseEmitter != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                sseEmitter.send(SseEmitter.event().name(<span class="string">&quot;error&quot;</span>).data(message));</span><br><span class="line">                sseEmitter.completeWithError(<span class="keyword">new</span> <span class="title class_">RuntimeException</span>(message)); <span class="comment">// Complete with an error</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="comment">// Handle exceptions when sending SSE updates</span></span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>关键点</strong>：</p><ul><li>接收到请求后，服务器立即返回响应结果，并携带 <code>TXN ID</code> 信息；</li><li>客户端接收到 <code>TXN ID</code> 后，会注册到 <code>SSE</code> 来获取实时进度更新、错误处理消息和接收任务完成通知；</li><li>服务器异步处理任务，在处理完成后会通过 <code>SSE</code> 向客户端发送通知。</li></ul><p><strong>客户端实现</strong>：</p><p>在客户端（通常是网页端），我们需要使用 <code>JavaScript</code> 侦听 <code>SSE Endpoint</code>（<code>/sse/stream</code>）并处理返回的更新数据。下面这段代码是如何在 <code>JavaScript</code> 中执行操作的简化示例：</p><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><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;!DOCTYPE <span class="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>Asynchronous Order Processing<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>Asynchronous Order Processing<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;processOrder()&quot;</span>&gt;</span>Process Order<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;result&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">let</span> eventSource = <span class="literal">null</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">processOrder</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">const</span> orderRequest = &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="attr">csvFilePath</span>: <span class="string">&quot;Path&quot;</span></span></span><br><span class="line"><span class="language-javascript">            &#125;;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">try</span> &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&#x27;/api/upload/csv&#x27;</span>, &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">headers</span>: &#123;</span></span><br><span class="line"><span class="language-javascript">                        <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span></span><br><span class="line"><span class="language-javascript">                    &#125;,</span></span><br><span class="line"><span class="language-javascript">                    <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(orderRequest)</span></span><br><span class="line"><span class="language-javascript">                &#125;);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">if</span> (response.<span class="property">status</span> === <span class="number">202</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;result&#x27;</span>).<span class="property">textContent</span> = <span class="string">&#x27;Order processing initiated. Waiting for completion...&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">                     <span class="keyword">const</span> transactionId =response.<span class="property">result</span>.<span class="property">transactionId</span>;</span></span><br><span class="line"><span class="language-javascript">                    <span class="comment">// Connect to the SSE endpoint for this order</span></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> eventSource = <span class="keyword">new</span> <span class="title class_">EventSource</span>(<span class="string">`/sse/stream?transactionId=<span class="subst">$&#123;transactionId&#125;</span>`</span>);                    </span></span><br><span class="line"><span class="language-javascript">                    eventSource.<span class="property">onmessage</span> = <span class="function">(<span class="params">event</span>) =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">                        <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;result&#x27;</span>).<span class="property">textContent</span> = event.<span class="property">data</span>;</span></span><br><span class="line"><span class="language-javascript">                    &#125;;</span></span><br><span class="line"><span class="language-javascript">                    </span></span><br><span class="line"><span class="language-javascript">                    eventSource.<span class="property">onerror</span> = <span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">                        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;SSE Error:&#x27;</span>, error);</span></span><br><span class="line"><span class="language-javascript">                    &#125;;</span></span><br><span class="line"><span class="language-javascript">                &#125;</span></span><br><span class="line"><span class="language-javascript">            &#125; <span class="keyword">catch</span> (error) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;Error:&#x27;</span>, error);</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">        &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">function</span> <span class="title function_">closeEventSource</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">if</span> (eventSource) &#123;</span></span><br><span class="line"><span class="language-javascript">                eventSource.<span class="title function_">close</span>();</span></span><br><span class="line"><span class="language-javascript">                eventSource = <span class="literal">null</span>;</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">        &#125;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// Close the SSE connection when leaving the page</span></span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;beforeunload&#x27;</span>, closeEventSource);</span></span><br><span class="line"><span class="language-javascript">    </span><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><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>在当下应用程序开发领域，快速响应能力和实时通信是至关重要的。异步 <code>API</code> 设计及其一系列技术实现（例如：<code>Callbacks</code>、<code>WebSocket</code>、消息队列和服务端事件发送（<code>SSE</code>））使开发人员能够快速构建面向用户提供实时数据更新和通知的应用程序。在我们现实生活中的电子商务案例中，<code>SSE</code> 被证明是一个颠覆性技术实现，能够为客户提供实时进度更新和数据通知，同时能够优化性能和用户体验。</p><p>当我们再思考异步 <code>API</code> 领域设计时，我们需要考虑应用程序的需求场景，并选择最符合我们需要的目标方法。无论是让用户了解产品更新还是在协作平台中实时协作，掌握这些异步技术将使我们的应用程序在当今瞬息万变的数字世界中脱颖而出。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://blog.stackademic.com/asynchronous-api-design-best-practices-server-sent-event-sse-for-real-time-commun
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="SSE" scheme="https://dongzl.github.io/tags/SSE/"/>
    
  </entry>
  
  <entry>
    <title>系统设计中正向代理和反向代理的差异</title>
    <link href="https://dongzl.github.io/2023/11/18/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/"/>
    <id>https://dongzl.github.io/2023/11/18/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/</id>
    <published>2023-11-18T20:03:22.000Z</published>
    <updated>2023-12-31T06:59:02.237Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://medium.com/javarevisited/difference-between-forward-proxy-and-reverse-proxy-in-system-design-da05c1f5f6ad">https://medium.com/javarevisited/difference-between-forward-proxy-and-reverse-proxy-in-system-design-da05c1f5f6ad</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/01.webp"><p>朋友们大家好，如果大家正在准备系统设计的面试，那么了解正向代理和反向代理之间的差异是非常重要，这是关于系统设计最常见的面试题之一，<a href="https://dongzl.github.io/2023/07/30/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/">API网关和负载均衡器之间的差异</a>，我们之前的文章中已经了解过。</p><p>我们在设计复杂系统时，通常会使用代理服务器来提升系统性能、安全性和可靠性。代理服务器位于客户端和服务器之间，用于管理流经客户端和服务器之间的流量。</p><p>我们经常使用到的两种代理服务的类型是正向代理和反向代理。虽然两者的设计目的都是<strong>提高系统的性能和安全性</strong>，但它们的工作方式却有很大的不同，并且是在不同的场景中使用。在本文中，我们将探讨正向代理和反向代理在<a href="https://medium.com/javarevisited/top-10-system-design-concepts-every-programmer-should-learn-54375d8557a6">系统设计</a>方面的差异。</p><p>顺便说一句，如果大家正在准备高级开发人员岗位的面试，那么除了系统设计之外，我们还应该熟悉像微服务等不同架构，以及各种微服务设计模式，例如<a href="https://medium.com/javarevisited/what-is-event-sourcing-design-pattern-in-microservices-architecture-how-does-it-work-b38c996d445a">事件溯源</a>、<a href="https://medium.com/javarevisited/what-is-cqrs-command-and-query-responsibility-segregation-pattern-7b1b38514edd">CQRS</a>、<a href="https://dongzl.github.io/2023/08/20/22-What-SAGA-Pattern-Microservice-Architecture/">SAGA</a>、<a href="https://medium.com/javarevisited/what-is-database-per-microservices-pattern-what-problem-does-it-solve-60b8c5478825">微服务独立数据库模式</a>、<a href="https://dongzl.github.io/2023/07/30/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/">API 网关</a>、<a href="https://medium.com/javarevisited/what-is-circuit-breaker-design-pattern-in-microservices-java-spring-cloud-netflix-hystrix-example-f285929d7f68">熔断器</a>，这些文章内容将会在面试中为大家提供很大帮助，因为这些内容通常用于衡量一名开发人员的资历水平。</p><p>现在让我们回到本文的主题，详细了解什么是正向代理和反向代理、它们的优缺点、如何去使用它们以及最重要的一点–正向代理和反向代理之间的差异。</p><h2 id="什么是正向代理？在什么场景中使用它？"><a href="#什么是正向代理？在什么场景中使用它？" class="headerlink" title="什么是正向代理？在什么场景中使用它？"></a>什么是正向代理？在什么场景中使用它？</h2><p>正向代理是位于客户端和互联网之间的代理服务器。客户端通过正向代理从互联网请求资源或服务，正向代理充当请求代理中介，将请求转发到互联网，然后将响应结果返回给客户端。</p><p>正向代理通常用于<strong>互联网访问控制</strong>、<strong>内容过滤</strong>或<strong>为客户端提供匿名访问</strong>。正向代理还可以通过缓存频繁被请求的内容来加速对资源的访问。</p><p>这是一个正向代理的示例：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/02.webp"><p>我们可以看到客户端连接到正向代理，然后正向代理将请求路由到互联网服务。</p><h3 id="正向代理架构的优缺点"><a href="#正向代理架构的优缺点" class="headerlink" title="正向代理架构的优缺点"></a>正向代理架构的优缺点</h3><p>现在大家已经熟悉了代理服务在正向代理中的作用，那么我们就可以很容易地总结出它的优缺点。以下是使用正向代理的一些优缺点总结：</p><p><strong>优点：</strong></p><ol><li><strong>增强安全性</strong>：正向代理可以通过隐藏访问互联网服务的客户端的原始 <code>IP</code> 地址来提供额外的安全性保障；</li><li><strong>提升速度和性能</strong>：缓存被频繁请求的资源可以缩短客户端访问互联网服务的响应时间；</li><li><strong>访问控制</strong>：通过限制对某些资源的访问，服务提供方可以使用正向代理来阻止对敏感数据进行未经授权访问；</li><li><strong>匿名</strong>：用户在浏览互联网服务时可以保持匿名状态，因为他们的 <code>IP</code> 地址是被隐藏的。</li></ol><p><strong>缺点：</strong></p><ol><li><strong>复杂配置</strong>：正向代理需要更复杂的配置，因为它们必须在每个设备上配置才能生效；</li><li><strong>单点故障</strong>：如果正向代理出现故障，所有依赖它的设备也将无法访问互联网服务；</li><li><strong>延迟增加</strong>：正向代理会增加响应延迟并降低对互联网服务访问的整体性能；</li><li><strong>限制控制</strong>：正向代理可能会限制用户访问某些资源的能力，从而给用户带来困扰并降低工作效率。</li></ol><p>除了这些限制之外，它提供的访问控制能力和安全能力是在各种系统架构上使用正向代理的主要原因。下面让我们继续学习一下什么是反向代理以及它是如何工作的。</p><hr><h2 id="什么是反向代理？什么时候使用它？"><a href="#什么是反向代理？什么时候使用它？" class="headerlink" title="什么是反向代理？什么时候使用它？"></a>什么是反向代理？什么时候使用它？</h2><p><strong>反向代理是位于客户端和源服务器之间的服务器</strong>，接收来自客户端的请求并将其转发到适当的服务器。</p><p>然后来自服务器的响应返回给代理服务并转发到客户端。从本质上讲，它有助于保护源服务器不被客户端直接访问。</p><p>反向代理通常<strong>用作负载平衡器，用来平衡多个服务器之间的请求负载</strong>，通过隐藏服务器基础架构的详细信息来提高安全性，并提供缓存、<code>SSL</code>终端等其它增值服务。</p><p>反向代理常用于以下场景：</p><ul><li><strong>负载平衡</strong>：在多个服务器之间分配传入流量，以提高性能和可用性；</li><li><strong>安全性</strong>：保护后端服务器免于直接暴露在互联网环境并防止未经授权的访问；</li><li><strong>可扩展性</strong>：实现在客户端无感的情况下水平扩展服务器基础架构。</li></ul><p>反向代理<strong>为客户端提供单一入口点</strong>，使管理和监控后端服务器的流量变得更加容易。</p><p>反向代理还在客户端和服务器之间提供一定程度的抽象，使得在客户端无感的情况下修改或升级服务器基础结构。</p><p>以下是 <code>NGINX</code> 反向代理设置的示例：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/03.webp"><p>我们可以看到客户端直接连接到互联网，但服务器位于代理后面，因此客户端永远不会知道其发出的请求被处理的服务器，从而实现从外部保护我们的基础设施服务。</p><h3 id="反向代理架构的优缺点"><a href="#反向代理架构的优缺点" class="headerlink" title="反向代理架构的优缺点"></a>反向代理架构的优缺点</h3><p>以下是使用反向代理架构的一些优点和缺点：</p><p><strong>优点：</strong></p><ol><li><strong>提高安全性</strong>：反向代理可以通过屏蔽后端服务器的身份和位置来提供额外的安全层保障，防止外部客户端直接访问内部服务器；</li><li><strong>更好的可扩展性</strong>：反向代理可以在多个后端服务器之间均匀分配请求流量，确保不会有某一台服务器过载从而导致应用程序崩溃；</li><li><strong>提高性能</strong>：通过缓存和压缩数据，反向代理可以减少客户端和服务器之间传输的数据量，从而缩短响应时间；</li><li><strong>简化架构</strong>：反向代理可用于将多个后端服务器整合到单个端点，从而简化应用程序的整体架构。</li></ol><p><strong>缺点：</strong></p><ol><li><strong>单点故障</strong>：如果反向代理发生故障，整个服务可能会变得不可用，这是它最大的缺点；</li><li><strong>复杂性增加</strong>：实施和维护反向代理可能比维护简单的 <code>client-server</code> 架构更复杂。</li><li><strong>能力限制</strong>：反向代理可能无法提供与客户端和服务器之间直接访问所能提供相同级别服务能力，这可能会限制应用程序的某些功能；</li><li><strong>额外成本</strong>：实施反向代理可能需要额外的硬件和软件，这可能会增加整个系统架构的成本。</li></ol><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/04.png"><hr><h2 id="正向代理和反向代理之间的差异"><a href="#正向代理和反向代理之间的差异" class="headerlink" title="正向代理和反向代理之间的差异"></a>正向代理和反向代理之间的差异</h2><p>现在我们已经基本了解了什么是正向代理和反向代理，它们的所处的位置以及它们的功能，现在需要研究一下这二者之间的差异以便我们更好地理解它们：</p><p><strong>1. 方向</strong><br>正向代理和反向代理之间的主要差异在于流量的方向。正向代理用于将流量从客户端转发到 <code>Internet</code>，而反向代理用于将流量从 <code>Internet</code> 转发到应用服务器。</p><p><strong>2. 客户端访问</strong><br>使用正向代理时，必须显式配置客户端才能使用代理服务器。相比之下，反向代理对客户端来说是透明的，客户端可以直接访问应用服务器，而不需要知道代理服务器的地址。</p><p><strong>3.负载均衡</strong><br>反向代理可以将客户端的请求分发到多个服务器以平衡负载，而正向代理则不具备该能力。</p><p><strong>4. 缓存</strong><br>正向代理可以缓存被频繁访问的资源，从而降低应用服务器的负载并可以缩短请求的响应时间；反向代理同样可以缓存被访问的资源，但缓存通常在更靠近客户端的地方进行，以提高性能。</p><p><strong>5. 安全</strong><br>正向代理可以通过隐藏因特网上的 <code>IP</code> 地址来保护客户端用户；反向代理可以通过隐藏服务器身份并向互联网公开指定 <code>IP</code> 地址来保护应用服务器。</p><p><strong>6. SSL/TLS 终端</strong><br>反向代理可以代理应用服务器终止 <code>SSL/TLS</code> 连接，从而减少服务器的负载并简化证书管理；正向代理通常不会终止 <code>SSL/TLS</code> 的连接。</p><p><strong>7. 内容过滤</strong><br>正向代理可用于内容过滤、限制对特定资源的访问以及强制执行特定访问策略；反向代理也可以进行内容过滤，但这通常是在靠近客户端的地方完成的，从而降低应用服务器上的负载。</p><p><strong>8. 路由</strong><br>正向代理可用于根据预定义的规则将流量路由到不同的服务器；反向代理也可以执行请求路由，但这通常是根据请求的 <code>URL</code> 和其他标识来完成的。</p><p><strong>9. 可扩展性</strong><br>反向代理可以通过在多个服务器之间分配流量来水平扩展应用程序；正向代理不提供这种可扩展性能力。</p><p><strong>10. 网络复杂性</strong><br>正向代理的设置和管理相对简单；而反向代理由于其负载均衡、<code>SSL/TLS</code> 终端和缓存功能可能导致架构更加复杂。</p><p>这是一张来自 <code>ByteByteGo</code> 网站的精美图表，这个网站是学习系统设计的更好地方之一，这张图片重点强调了正向代理和反向代理之间的差异：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/25-Difference-Between-Forward-Proxy-Reverse-Proxy-System-Design/05.jpeg">]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-forward-proxy-and-reverse-proxy-in-system-design-da05c1f5f
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="正向代理" scheme="https://dongzl.github.io/tags/%E6%AD%A3%E5%90%91%E4%BB%A3%E7%90%86/"/>
    
      <category term="反向代理" scheme="https://dongzl.github.io/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>微服务扩展：以 99% 的处理效率来应对不断增长的需求</title>
    <link href="https://dongzl.github.io/2023/09/23/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/"/>
    <id>https://dongzl.github.io/2023/09/23/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/</id>
    <published>2023-09-23T20:03:22.000Z</published>
    <updated>2023-12-31T06:59:02.217Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://blog.stackademic.com/scaling-microservices-strategies-for-handling-increased-demand-with-99-efficiency-1ce47dd02490">https://blog.stackademic.com/scaling-microservices-strategies-for-handling-increased-demand-with-99-efficiency-1ce47dd02490</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/04.webp"><p><font color="DarkGray" size="2">image_credit — uber.com</font></p><p>朋友们大家好，很多朋友都曾建议我写一篇如何<strong>在实际工作中实现微服务可扩展性</strong>的文章？这就是我写这篇文章的初衷，在这篇文章中我将会分享我对实现微服务可扩展性的一些理解，并为大家提供一个将微服务扩展至百万服务的案例，同时能够保障 <code>99%</code> 的时间正常提供服务，而这正是 <code>Uber.com</code>（我最喜欢的出租车叫车服务）的应用程序。</p><p><a href="https://www.java67.com/2023/06/how-to-create-microservice-application.html">创建</a>一个能够正常运行的微服务是一回事，而创建一个每天与数百万用户完成数十亿次交互的微服务则完全不同。</p><p>近年来，<a href="https://javarevisited.blogspot.com/2021/09/microservices-design-patterns-principles.html#axzz8EaNxWt4z">微服务架构</a>因其模块化、独立且高度可扩展的软件架构能力而受到广泛欢迎。然而，随着应用系统用户数量和访问量的增长，如何实现微服务的高效扩展也成为一项极具挑战的任务。</p><p>在前面的几篇文章中，我一直在分享我在微服务和 <code>Spring Cloud</code> 方面的经验，例如<a href="https://medium.com/javarevisited/10-microservices-design-principles-every-developer-should-know-44f2f69e960f">微服务最佳实践</a>、<a href="https://medium.com/javarevisited/top-10-microservice-design-patterns-for-experienced-developers-f4f5f782810e">微服务设计模式</a>、<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务常见面试题</a>和 <a href="https://medium.com/javarevisited/10-spring-cloud-features-which-makes-microservice-development-easier-in-java-e061885422fe">Spring Cloud 所具备的 10 个微服务特性</a>，我之前还分享过关于<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA 设计模式</a>和<a href="https://medium.com/javarevisited/difference-between-microservices-and-monolithic-architecture-for-java-interviews-af525908c2d5">单体与微服务架构</a>等文章。</p><p>在本文中，我们将深入探讨如何应对微服务架构中不断增长的流量和不断变化的需求，重点关注负载平衡、弹性扩展和性能优化，同时我们还将通过一个真实的案例来理解如何在实际工作中运用这些概念。</p><hr><h3 id="1-微服务扩展方面的挑战"><a href="#1-微服务扩展方面的挑战" class="headerlink" title="1. 微服务扩展方面的挑战"></a>1. 微服务扩展方面的挑战</h3><p>微服务架构将单一应用程序拆分成多个较小的、互连的服务，这些服务通过网络进行通信。</p><p>这种设计非常地灵活，可以更充分的实现资源利用以及服务的独立开发和部署。然而随着用户流量的增长，个别的服务可能会出现瓶颈，遇到性能问题。</p><p>如何实现<a href="https://www.java67.com/2023/01/what-is-cqrs-command-query.html">微服务</a>扩展，软件开发团队会面临几个挑战，来应对微服务架构中不断增长的流量和不断变化的需求。主要挑战包括：</p><ol><li><strong>服务依赖</strong>：微服务通常依赖其他服务来实现特定功能；随着流量的增加，服务之间的互相依赖可能会导致瓶颈和性能问题，从而影响整个系统的可扩展性；</li><li><strong>数据管理</strong>：跨分布式微服务场景中协调和管理数据可能会变得复杂，尤其是在处理海量数据时，确保跨多个服务的数据一致性、可用性和完整性是一个需要面对的挑战；</li><li><strong>网络通信开销</strong>：微服务通过网络进行通信，随着服务数量的增长，网络通信可能会产生巨大的开销，这可能会导致系统延迟和响应时间变长，从而影响用户体验；</li><li><strong>维护复杂性</strong>：维护和监控大量微服务需要强大的基础设施、部署和监控工具，随着系统的扩展，管理工作会变得越来越复杂；</li><li><strong>动态可扩展性</strong>：虽然自动扩展是一项关键策略，但根据实际需要动态添加或删除实例可能会导致资源争用，尤其是在操作不当的情况下；</li><li><strong>一致性和事务</strong>：确保分布式微服务之间的事务一致性是一项具有挑战性的任务，多个服务的变更操作全部完成或在发生故障时全部回滚是很复杂的工作；</li><li><strong>状态管理</strong>：管理微服务的状态，尤其是在需要状态的应用场景中，可能会很困难，处理有状态服务的故障转移和主从复制增加了整个扩展过程的复杂性；</li><li><strong>安全和授权</strong>：在处理增长的流量的同时确保微服务的安全性需要强大的身份验证和授权机制，随着请求数量的增长，安全性管理变得非常重要。</li></ol><p>应对这些挑战需要仔细进行系统规划、架构设计以及采用一些在微服务开发、部署和运维方面的<strong>最佳实践</strong>。微服务的自动扩展需要专业技术知识、监控工具和持续优化工作相结合。</p><p>但是不用担心，我们将会学习一些可以遵循的最佳实践，将微服务扩展到数百万级用户量，并保证 <code>99%</code> 的效率。</p><hr><h3 id="2-理解负载均衡"><a href="#2-理解负载均衡" class="headerlink" title="2. 理解负载均衡"></a>2. 理解负载均衡</h3><p>负载均衡是在微服务的多个实例之间分配传入请求流量的关键策略，来确保资源被充分利用并防止某个实例流量过载。</p><p>有多种负载均衡算法，每种算法都有其优缺点和使用场景：</p><ol><li><strong>轮询算法</strong>：请求按顺序循环均匀分布在多个实例之间；</li><li><strong>最少连接算法</strong>：流量会被路由到活动连接最少的服务器；</li><li><strong>加权轮询算法</strong>：根据容量为服务器分配不同的权重。</li></ol><p>我们还可以使用 <a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API 网关</a> 等模式作为微服务架构中的负载均衡器，以在微服务的多个实例之间分配请求流量。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/01.webp"><p><a href="https://medium.com/javarevisited/what-is-api-gateway-pattern-in-microservices-architecture-what-problem-does-it-solve-ebf75ae84698">API 网关</a>充当客户端访问各种微服务的单一入口点，提供负载均衡、流量路由、安全性等能力，我在之前的文章中也深入探讨了 <a href="https://medium.com/javarevisited/what-is-api-gateway-pattern-in-microservices-architecture-what-problem-does-it-solve-ebf75ae84698">API 网关的工作原理</a>。</p><hr><h3 id="3-弹性扩容：适应需求变化"><a href="#3-弹性扩容：适应需求变化" class="headerlink" title="3. 弹性扩容：适应需求变化"></a>3. 弹性扩容：适应需求变化</h3><p>这是我们用来将微服务扩展到百万级的另一种策略，弹性扩容允许微服务应用根据流量波动动态调整资源数量。</p><p>弹性扩容允许系统无需手动增加或者删除实例，而是持续监控流量负载并根据实际需要自动添加或删除实例。</p><p>这种策略确保了最佳性能和成本效率。</p><p>触发弹性扩容的关键指标包括<strong>CPU利用率</strong>、<strong>内存使用率</strong>和<strong>系统响应时间</strong>。主流的云平台服务商，像 <a href="https://medium.com/javarevisited/10-best-aws-certified-cloud-practitioner-clf-c01-online-courses-and-practice-test-to-crack-ecc0f913091e">AWS</a>、<a href="https://medium.com/javarevisited/10-best-aws-google-cloud-and-azure-courses-and-certification-from-coursera-to-join-in-2021-5c5e2029a8e7">Google Cloud</a> 和 <a href="https://medium.com/javarevisited/6-best-ai-900-practice-tests-to-crack-azure-ai-fundamentals-certification-exam-c2a7124c8415">Azure</a> 都提供与微服务部署集成的弹性扩容服务。</p><p>下图是在 <code>AWS</code> 云服务中设置自动扩容的示例：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/02.webp"><hr><h3 id="4-性能优化：确保高效执行"><a href="#4-性能优化：确保高效执行" class="headerlink" title="4. 性能优化：确保高效执行"></a>4. 性能优化：确保高效执行</h3><p>性能优化是实现应用程序扩展并保障其响应能力的非常传统的策略。性能优化涉及对微服务功能进行微调以最大限度地提高效率并最大限度地缩短请求响应时间。性能优化的技术包括：</p><ol><li><strong>缓存</strong>：将经常访问的数据存储在内存中，来减少数据库查询操作；</li><li><strong>异步处理</strong>：将资源密集型任务放到后台任务或队列中执行；</li><li><strong>数据库分片</strong>：将数据分布到多个数据库实例以提高读写性能。</li></ol><p>下图是 <code>AWS</code> <a href="https://medium.com/javarevisited/serverless-architecture-with-spring-cloud-function-an-introduction-and-practical-implementation-84ff5a17fab9">无服务器架构</a>中使用缓存的另一个非常好的示例：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/03.webp"><p><font color="DarkGray" size="2">image — <a href="https://aws.amazon.com/blogs/architecture/data-caching-across-microservices-in-a-serverless-architecture/">https://aws.amazon.com/blogs/architecture/data-caching-across-microservices-in-a-serverless-architecture/</a></font></p><hr><h3 id="5-现实中真实案例：Uber-系统的可扩展性之旅"><a href="#5-现实中真实案例：Uber-系统的可扩展性之旅" class="headerlink" title="5. 现实中真实案例：Uber 系统的可扩展性之旅"></a>5. 现实中真实案例：Uber 系统的可扩展性之旅</h3><p>没有什么是比研究一个真实的案例更好的方法来理解某个事情了，让我们看一个现实世界的真实案例来理解这些扩展策略的实际应用：<strong>约车服务巨头 Uber</strong>。</p><p><code>Uber</code> 的微服务架构使其能够在全球众多城市中高效运营，每天为数百万用户提供服务。随着 <code>Uber</code> 的受欢迎程度不断飙升，有效扩展系统微服务变得至关重要。</p><p>这里还要顺便说一句，对于软件开发人员我还有另外一个建议是阅读 <code>Uber</code>、<code>NetFlix</code> 等科技巨头的工程博客，学习他们如何应对软件开发中的挑战，这能够提高大家的知识和理解力。</p><p><code>Uber</code> 公司的微服务架构如下所示：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/04.webp"><p>通过这幅图大家可以了解到他们正在使用 <a href="https://www.java67.com/2023/04/3-what-is-api-gateway-design-pattern-in.html">API 网关</a>、缓存代理以及更多大家可以学习借鉴的东西，方便将我们的微服务扩展到数百万级。</p><h3 id="6-Uber-系统架构中的负载均衡"><a href="#6-Uber-系统架构中的负载均衡" class="headerlink" title="6. Uber 系统架构中的负载均衡"></a>6. Uber 系统架构中的负载均衡</h3><p>为了应对大流量，<code>Uber</code> 在系统中使用了强大的负载均衡机制；当用户打开 <code>Uber</code> 应用程序时，他们的请求将被重定向到用户附近的数据中心。</p><p>在数据中心内部，负载均衡器使用最少连接算法将请求分发到负载最低的微服务实例。</p><p>这种机制确保了用户请求的均匀分布，防止某个微服务实例变得不堪重负。我们可以在<a href="https://www.uber.com/en-SG/blog/better-load-balancing-real-time-dynamic-subsetting/">此处</a>学习有关 <code>Uber</code> 实时负载均衡机制的更多信息。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/05.webp"><hr><h3 id="7-弹性扩容以满足系统需要"><a href="#7-弹性扩容以满足系统需要" class="headerlink" title="7. 弹性扩容以满足系统需要"></a>7. 弹性扩容以满足系统需要</h3><p>在流量高峰时段或特殊活动期间，<code>Uber</code> 系统的用户请求会激增；为了应对这些高峰，<code>Uber</code> 广泛使用了弹性扩容机制。</p><p>例如，负责匹配乘客和司机的“乘车约车”微服务会根据传入请求数量和平均响应时间等指标动态扩容服务实例数量。</p><p><code>Uber</code> 的弹性扩容系统会监控这些指标并自动调整实例数量，以确保系统保持较短的响应时间和快速的完成乘车匹配。当流量减少时，微服务的实例就会减少，从而节约资源使用和成本。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/24-Scaling-Microservices-Strategies-Handling-Increased-Demand-Efficiency/06.webp"><hr><h3 id="8-性能优化，打造无缝体验"><a href="#8-性能优化，打造无缝体验" class="headerlink" title="8. 性能优化，打造无缝体验"></a>8. 性能优化，打造无缝体验</h3><p><code>Uber</code> 的应用程序提供实时位置跟踪，这对微服务应用的性能要求非常高。为了优化性能，<code>Uber</code> 系统广泛使用缓存。</p><p>驾驶员和乘客数据以及乘车状态变更都缓存在内存中，从而减少了频繁的数据库查询操作。</p><p>此外 <code>Uber</code> 采用异步机制计算乘客乘车费用、发送通知等任务。</p><p>通过将这些任务转移到后台任务或队列中执行，微服务系统可以快速处理用户乘车数据，减少对用户体验的影响。</p><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>这就是将一个微服务扩展到数百万级用户所需要做的重要工作，虽然这些工作看起来并不难，但是当我们真正去做时，我们还是需要对系统进行仔细的计划。</p><p>随着用户数量不断增加，实现微服务的高效扩展是现代应用程序开发中一个很关键方面。负载均衡、弹性扩容和性能优化是确保基于微服务的应用程序实现最佳性能、可靠性和成本效益的根本策略。</p><p><code>Uber</code> 等现实中真实的案例展示如何在实际工作实施这些策略，也展示了它们在处理大流量和多样化需求方面的高效性。</p><p><strong>通过使用负载均衡算法、弹性扩容机制和性能优化技术</strong>，我们可以创建具备弹性且响应迅速的微服务生态系统，即使在高峰使用期间也能提供无缝的用户体验。</p><p>总之，面对不断增长的用户期望和流量，掌握扩展微服务的艺术对于旨在提供高质量数字服务的企业来说是至关重要。</p><p>顺便说一句，如果大家正在准备微服务面试，那么还应该准备一些其它问题，比如<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API网关和负载均衡之间的差异</a>、<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA模式</a>、<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">如何管理微服务中的事务</a>、<a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">SAGA 和 CQRS模式之间的差异</a>，这些问题在面试中也很常见。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://blog.stackademic.com/scaling-microservices-strategies-for-handling-increased-demand-with-99-eff
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="微服务" scheme="https://dongzl.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
      <category term="弹性" scheme="https://dongzl.github.io/tags/%E5%BC%B9%E6%80%A7/"/>
    
  </entry>
  
  <entry>
    <title>微服务中 SAGA 和 CQRS 设计模式的差异？</title>
    <link href="https://dongzl.github.io/2023/08/27/23-Difference-Between-SAGA-CQRS-Design-Patterns-Microservices/"/>
    <id>https://dongzl.github.io/2023/08/27/23-Difference-Between-SAGA-CQRS-Design-Patterns-Microservices/</id>
    <published>2023-08-27T15:57:22.000Z</published>
    <updated>2023-12-31T06:59:02.201Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/23-Difference-Between-SAGA-CQRS-Design-Patterns-Microservices/01.webp"><p>朋友们大家好，如果大家不只是听说过微服务架构中的 <code>SAGA</code> 模式和 <code>CQRS</code> 模式，还想知道它们到底是什么以及何时使用它们，那么大家看这篇文章就对了。<code>SAGA</code> 和 <code>CQRS</code> 模式之间的差异也是 <a href="https://medium.com/javarevisited/top-10-java-interview-questions-for-3-to-4-years-experienced-programmers-c4bf6d8b5e7b">Java 面试</a>中常见的问题之一。</p><p><code>SAGA</code> 模式和 <code>CQRS</code> 模式是微服务架构中使用广泛的设计模式，但是它们有不同的使用场景。</p><p><code>SAGA</code> 模式和 <code>CQRS</code> 模式之间的主要区别在于，<code>SAGA</code> 模式侧重于保证系统的<strong>事务一致性</strong>，而 <code>CQRS</code> 模式侧重于系统的<strong>可扩展性和性能</strong>，我们将在本文中分析更多有关它们的差异。</p><p>在前面的文章中，我在面向初学者和经验丰富的开发人员所分享的 <a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务面试问题</a>和<a href="https://medium.com/javarevisited/10-microservices-design-principles-every-developer-should-know-44f2f69e960f">10 个基本的微服务设计原则</a>等文章中都提到过 <code>SAGA</code> 设计模式；在这篇文章中，我将分享两者之间的差异以及何时在微服务架构中使用 <code>SAGA</code> 模式和 <code>CQRS</code> 模式。</p><hr><h2 id="什么是微服务架构中的-SAGA-设计模式？"><a href="#什么是微服务架构中的-SAGA-设计模式？" class="headerlink" title="什么是微服务架构中的 SAGA 设计模式？"></a>什么是微服务架构中的 SAGA 设计模式？</h2><p><code>SAGA</code> 设计模式常用于管理跨多个服务的事务，它能够确保事务中被涉及到的多个服务都正确地提交或回滚其所负责的事务部分，即使其它服务出现失败也是如此。</p><p>例如，在电子商务应用系统中，当用户发起在线下单时，这涉及支付处理、订单管理和商品配送等多种服务。</p><blockquote><p>SAGA 模式可用于确保如果这些服务中的任何一个出现失败，事务都会被回滚，用户的钱会被退还，不会受到损失。</p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/23-Difference-Between-SAGA-CQRS-Design-Patterns-Microservices/02.webp"><hr><h2 id="什么是微服务中的-CQRS-模式？它能够解决什么问题？"><a href="#什么是微服务中的-CQRS-模式？它能够解决什么问题？" class="headerlink" title="什么是微服务中的 CQRS 模式？它能够解决什么问题？"></a>什么是微服务中的 CQRS 模式？它能够解决什么问题？</h2><p><code>CQRS</code> （<code>Command Query Responsibility Segregation</code>）设计模式常用于将应用程序的读写操作分离到不同的模型中，它允许不同的模型针对各自的功能进行优化。</p><p>例如，考虑一个银行应用系统，用户可以查看账户余额、交易历史记录并进行转账。读取操作（例如查看账户余额）可以针对高可用性和低延迟进行优化，而写入操作（例如进行转账操作）可以针对数据一致性和准确性进行优化。</p><blockquote><p>通过分离读写操作，我们可以为每个操作选择适当的存储模型和处理方式。</p></blockquote><p>下面是微服务架构中 <code>CQRS</code> 模式的架构：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/23-Difference-Between-SAGA-CQRS-Design-Patterns-Microservices/03.webp"><p>综上所述，<code>SAGA</code> 模式和 <code>CQRS</code> 模式是微服务架构中两种适用于不同场景的设计模式。</p><p><code>SAGA</code> 模式适用于管理跨多个服务的事务，而 <code>CQRS</code> 模式适用于将应用程序的读写操作分离到不同的模型中。</p><hr><h2 id="微服务架构中-SAGA-和-CQRS-设计模式的区别？"><a href="#微服务架构中-SAGA-和-CQRS-设计模式的区别？" class="headerlink" title="微服务架构中 SAGA 和 CQRS 设计模式的区别？"></a>微服务架构中 SAGA 和 CQRS 设计模式的区别？</h2><p>现在我们已经熟悉了 <code>SAGA</code> 和 <code>CQRS</code> 模式之间的基本区别，下面让我们继续分析它们之间的其它差异。下面是微服务架构中 <code>SAGA</code> 模式和 <code>CQRS</code> 模式之间的对比分析：</p><h3 id="1-它们能够解决什么问题？"><a href="#1-它们能够解决什么问题？" class="headerlink" title="1. 它们能够解决什么问题？"></a>1. 它们能够解决什么问题？</h3><p><code>SAGA</code> 模式是一种用于管理分布式事务顺序的设计模式，每个事务可能需要更新多个服务中的数据；而 <code>CQRS</code> 模式是一种分离系统中的读写操作的设计模式，每个操作使用独立的模型。</p><h3 id="2-功能"><a href="#2-功能" class="headerlink" title="2. 功能"></a>2. 功能</h3><p><code>SAGA</code> 模式用于在某个事务需要更新多个微服务数据时，确保跨多个微服务的数据一致性；而 <code>CQRS</code> 模式通过分离读写操作模型并分别优化来提高系统的可扩展性、性能和可用性。</p><h3 id="3-工作机制"><a href="#3-工作机制" class="headerlink" title="3. 工作机制"></a>3. 工作机制</h3><p><code>SAGA</code> 模式使用一系列补偿机制来协调事务，如果发生故障，这些补偿机制将回滚前一个事务所做的变更；另一方面，<code>CQRS</code> 模式使用不同的模型分离读写操作，写入模型针对数据更新进行优化，而读取模型针对查询和数据检索进行优化。</p><h3 id="4-一致性与可扩展性"><a href="#4-一致性与可扩展性" class="headerlink" title="4. 一致性与可扩展性"></a>4. 一致性与可扩展性</h3><p><code>SAGA</code> 模式是一种在分布式系统中维护数据一致性的解决方案，数据会分布在多个微服务中；另一方面，<code>CQRS</code> 模式是一种通过分离读写操作并为每个操作使用不同模型来提升系统扩展性的解决方案。</p><h3 id="5-复杂性"><a href="#5-复杂性" class="headerlink" title="5. 复杂性"></a>5. 复杂性</h3><p><code>SAGA</code> 模式的实现和管理可能很复杂，因为它需要协调跨多个服务的多个事务；而 <code>CQRS</code> 模式可以通过分离读写操作，并为每个操作模型进行单独优化来简化复杂系统的管理工作。</p><h3 id="6-使用场景"><a href="#6-使用场景" class="headerlink" title="6.使用场景"></a>6.使用场景</h3><p><code>SAGA</code> 模式最适合于在某个业务事务中，需要更新多个微服务数据的场景；而 <code>CQRS</code> 模式最适合于使用大量读写操作并且需要单独进行优化的场景。</p><p><code>SAGA</code> 模式通常用于<strong>需要强事务一致性</strong>的系统，例如金融系统；而 <code>CQRS</code> 模式则用于需要高可扩展性和高性能的系统，例如社交媒体平台。</p><h3 id="7-实施"><a href="#7-实施" class="headerlink" title="7. 实施"></a>7. 实施</h3><p><code>SAGA</code> 模式可以使用 <code>choreography</code> 或者 <code>orchestration</code> 方法来实现，而 <code>CQRS</code> 模式通常使用 <code>event sourcing</code> 来实现。</p><p>这就是<strong>微服务架构中 <code>SAGA</code> 模式和 <code>CQRS</code> 设计模式的区别</strong>。<code>SAGA</code> 模式和 <code>CQRS</code> 模式之间的主要区别在于 <code>SAGA</code> 模式侧重于系统的<strong>事务一致性</strong>，而 <code>CQRS</code> 模式侧重于系统的<em>可扩展性</em>和<em>高性能</em>。</p><p>同样重要并且要注意的是，这两种模式可以在微服务架构中一起使用来实现不同的目的。</p><p>事实上，<code>SAGA</code> 模式和 <code>CQRS</code> 模式可以在微服务架构中一起使用，来实现强大的事务一致性以及可扩展性和高性能。但是，这可能会增加系统的额外复杂性，因此在使用 <code>SAGA</code> 模式或 <code>CQRS</code> 模式之前需要分析使用的场景和它们各自的优缺点。</p><p>顺便说一下，<code>SAGA</code> 模式和 <code>CQRS</code> 模式只是<a href="https://medium.com/javarevisited/top-10-microservice-design-patterns-for-experienced-developers-f4f5f782810e">众多常用的微服务设计模式</a>中的两个，大家可以自己学习，我将来还会分享更多设计模式的文章。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b0
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="微服务" scheme="https://dongzl.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
      <category term="SAGA" scheme="https://dongzl.github.io/tags/SAGA/"/>
    
      <category term="CQRS" scheme="https://dongzl.github.io/tags/CQRS/"/>
    
  </entry>
  
  <entry>
    <title>微服务架构中的SAGA模式是什么？它能够解决哪些问题？</title>
    <link href="https://dongzl.github.io/2023/08/20/22-What-SAGA-Pattern-Microservice-Architecture/"/>
    <id>https://dongzl.github.io/2023/08/20/22-What-SAGA-Pattern-Microservice-Architecture/</id>
    <published>2023-08-20T11:04:22.000Z</published>
    <updated>2023-12-31T06:59:02.193Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/22-What-SAGA-Pattern-Microservice-Architecture/01.webp"><p>朋友们大家好，如果大家正在从事 <code>Java</code> 微服务相关岗位的工作，或者正在准备 <code>Java</code> 微服务开发岗位的面试，那么我们必须要准备 <code>SAGA</code> 模式的相关知识。<code>SAGA</code> 是一种重要的微服务模式，它的目的是解决微服务架构中长事务问题。这也是流行的<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">微服务面试问题</a>之一，经常被用来考察经验丰富的开发人员。</p><p>由于微服务架构将我们的应用程序分解为多个小的应用程序，单个请求也被分解为多个请求，所以有可能会出现部分请求成功，部分请求失败的现象，在这种情况下，保证数据的一致性是很困难的。</p><p>如果我们正在处理实际业务场景数据，例如亚马逊上的订单服务，那么我们必须妥善处理这种情况，保证如果出现付款失败，那么库存将恢复到原始状态并且订单不会被发货。</p><p>在这篇文章中，我们将解释什么是 <code>SAGA</code> 模式？它的作用是什么、能够解决什么问题以及微服务架构中 <code>SAGA</code> 模式的优缺点。</p><p>顺便说一下，如果大家正在准备 <code>Java</code> 开发岗位的面试，那么也可以看看我之前发布的文章，比如 <a href="https://medium.com/javarevisited/200-coursera-plus-discount-and-best-new-year-deals-for-developers-in-2023-eb2b682575">25 个 Java 高级问题</a>、<a href="https://medium.com/javarevisited/25-spring-framework-interview-questions-for-1-to-3-years-experienced-java-programmers-567f268ed897">25 个 Spring 框架问题</a>、<a href="https://medium.com/javarevisited/20-sql-queries-for-programming-interviews-a7b5a7ea8144">20 个面试中常见的 SQL 查询</a>、<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务问题</a>、<a href="https://medium.com/javarevisited/top-60-tree-data-structure-coding-interview-questions-every-programmer-should-solve-89c4dbda7c5a">60 个关于树的数据结构问题</a>、<a href="https://medium.com/javarevisited/7-system-design-problems-to-crack-software-engineering-interviews-in-2023-13a518467c3e">15 个系统设计问题</a>，以及 <a href="https://medium.com/javarevisited/top-10-java-interview-questions-for-3-to-4-years-experienced-programmers-c4bf6d8b5e7b">35 个核心 Java 问题</a>。</p><h2 id="什么是SAGA设计模式？它解决什么问题？"><a href="#什么是SAGA设计模式？它解决什么问题？" class="headerlink" title="什么是SAGA设计模式？它解决什么问题？"></a>什么是SAGA设计模式？它解决什么问题？</h2><p><code>SAGA</code>（或 <code>Saga</code>）模式是一种微服务设计模式，它常用于处理分布式系统中的数据一致性问题。它提供了一种处理由多个步骤组成的长事务的解决方案，其中每个步骤都是一个独立的数据库操作。</p><p><code>SAGA</code> 模式的主要思路是将事务的所有步骤捕获在数据库中，以便在发生故障时系统可以将事务回滚到其初始状态。</p><p><code>SAGA</code> 模式解决了分布式系统中保证数据一致性的问题，在分布式系统中很难保证事务中的所有操作都以原子方式执行，尤其是在出现故障的情况下。</p><p><code>SAGA</code> 模式的一个经典案例是电子商务交易，例如在 <code>Amazon</code> 或 <code>FlipKart</code> 下单，下单后从客户的账户中扣除货款，并将商品在库存中扣除。</p><p>如果其中某个步骤出现失败，则需要回滚之前的步骤以确保数据一致性。例如，如果付款失败，则需要取消扣除商品的库存。<code>SAGA</code> 模式解决在涉及多个步骤的事务中，出现部分成功部分失败可能会导致的数据一致性问题。</p><p>这是某个微服务架构图，用来演示 <code>SAGA</code> 模式的工作原理：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/22-What-SAGA-Pattern-Microservice-Architecture/02.jpeg"><hr><h2 id="微服务架构中-SAGA-设计模式的优缺点"><a href="#微服务架构中-SAGA-设计模式的优缺点" class="headerlink" title="微服务架构中 SAGA 设计模式的优缺点"></a>微服务架构中 SAGA 设计模式的优缺点</h2><p>每当我们学习一种设计模式时，我们都应该了解它的优缺点，因为它可以帮助我们更好地理解这种模式，并帮助我们决定何时在应用程序中使用它们：</p><p>以下是微服务中 <code>SAGA</code> 模式的一些优缺点：</p><p><strong>优点</strong>：</p><p>以下是在微服务架构中使用 <code>SAGA</code> 设计模式的一些优点：</p><ol><li>比较方便处理跨多个微服务的复杂事务；</li><li>可以优雅地处理系统故障并确保数据一致性；</li><li>提高系统的弹性和稳定性；</li><li>避免数据不一致和丢失更新问题；</li><li>提供清晰明确的交易补偿流程。</li></ol><p><strong>缺点</strong>：</p><p>以下是在微服务架构中使用 <code>SAGA</code> 设计模式的一些缺点：</p><ol><li>实施和维护困难，程序监控和调试也比较困难；</li><li>我们需要存储和管理 <code>Saga</code> 状态，这会产生额外的开销；</li><li>由于需要管理跨多个微服务的事务，它还会产生额外的性能开销；</li><li>由于微服务之间需要进行多次交互，应用程序响应延迟会增加；</li><li>跨不同微服务的 <code>Saga</code> 模式并没有标准化实现，未来如果像 <code>Spring Cloud</code> 或 <code>Quarks</code> 这样的框架原生支持 <code>Saga</code> 模式，那就完美了。</li></ol><h2 id="如何在微服务架构中实现-SAGA-模式？"><a href="#如何在微服务架构中实现-SAGA-模式？" class="headerlink" title="如何在微服务架构中实现 SAGA 模式？"></a>如何在微服务架构中实现 SAGA 模式？</h2><p><code>SAGA</code> 模式可以通过在微服务架构中将复杂的业务事务拆分为多个更小的独立步骤或服务来实现。</p><p>每个步骤都会与其所对应的微服务进行通信以完成事务的一部分，如果某个步骤失败，系统将会启动补偿机制以撤消之前的步骤。</p><p><strong>协调这些步骤可以使用数据库、消息队列或其它协调服务</strong>来存储事务的状态并触发补偿机制来实现，这样系统可以确保最终一致性并优雅地处理系统故障。</p><p>如果我们想知道是否有某个 <code>Java</code> 微服务框架可以提供对 <code>SAGA</code> 模式的支持？很遗憾的是并没有特定的微服务框架为 <code>SAGA</code> 模式提供直接支持。</p><p>不过我们可以使用 <code>Apache Camel</code> 等框架或者将 <code>Apache Kafka</code>、事件溯源和消息驱动框架与 <code>Spring</code> 集成来实现 <code>SAGA</code> 模式。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/22-What-SAGA-Pattern-Microservice-Architecture/03.webp"><hr><p>感谢大家阅读这篇的文章，这就是<strong>微服务架构中的 <code>SAGA</code> 设计模式</strong>。它是一种复杂但重要的设计模式，从面试的角度来说学习这种模式非常重要。</p><p>即使大家并没有在项目中实现 <code>SAGA</code> 模式，它同样值得关注，因为管理分布式事务和数据一致性问题是一个真实存在的问题，作为经验丰富的开发人员，我们应该知道如何在微服务架构中处理这个问题。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="微服务" scheme="https://dongzl.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
      <category term="SAGA" scheme="https://dongzl.github.io/tags/SAGA/"/>
    
  </entry>
  
  <entry>
    <title>如何实现分布式系统和微服务中的事务管理？</title>
    <link href="https://dongzl.github.io/2023/08/13/21-How-Manage-Transactions-Distributed-Systems-Microservices/"/>
    <id>https://dongzl.github.io/2023/08/13/21-How-Manage-Transactions-Distributed-Systems-Microservices/</id>
    <published>2023-08-13T21:23:22.000Z</published>
    <updated>2023-12-31T06:59:02.169Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/01.webp"><p>朋友们大家好，如果大家正在准备微服务相关的面试题，那么我们可能同样需要知道<em>如何管理分布式系统或微服务中的事务</em>？这个问题是<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">流行的微服务面试问题</a>之一，难道不是吗，它也是重要的问题之一。</p><p>在前面的文章中，我分享了<a href="https://medium.com/javarevisited/10-microservices-design-principles-every-developer-should-know-44f2f69e960f">基本的微服务设计原则和最佳实践</a>以及流行的<a href="https://medium.com/javarevisited/top-10-microservice-design-patterns-for-experienced-developers-f4f5f782810e">微服务设计模式</a>，在本文中，我将回答这个问题，并告诉大家在分布式系统中管理事务的不同方法。</p><p>如果我们曾经在<a href="https://medium.com/javarevisited/difference-between-microservices-and-monolithic-architecture-for-java-interviews-af525908c2d5">微服务架构</a>的系统场景中工作过，那么我们就应该知道某个业务场景的事务通常会跨越多个微服务，这就是为什么确保数据一致性和管理事务是非常具有挑战性的工作。</p><blockquote><p>事务处理不当可能会导致数据不一致、丢失数据更新或数据重复，这可能会给业务带来严重后果。</p></blockquote><p>因此，拥有强大的事务管理系统以确保系统的可靠性是至关重要。</p><p>在本文中，我们将<strong>探讨管理分布式系统和微服务中事务的各种策略和最佳实践</strong>。我们将讨论与分布式事务相关的挑战以及如何克服这些挑战，包括两阶段提交和 <code>SAGA</code> 模式。</p><p>此外，我们还将介绍流行的微服务相关的框架（例如 <code>Spring Cloud</code> 和 <code>Apache Kafka</code>）所提供的不同事务管理机制。</p><p>阅读完本文后，我们将能够清楚地了解如何管理分布式系统中的事务并确保数据的可靠性和一致性。</p><h2 id="管理分布式系统和微服务中事务的三种方法？"><a href="#管理分布式系统和微服务中事务的三种方法？" class="headerlink" title="管理分布式系统和微服务中事务的三种方法？"></a>管理分布式系统和微服务中事务的三种方法？</h2><p>正如我所说，由于系统的分布式特性，管理分布式系统和微服务中的事务可能是一项复杂的工作。在现实世界中，对于多个服务需要协作和共享数据的分布式系统中，事务对于确保数据一致性和完整性是至关重要的。</p><p>管理分布式系统中事务的最流行方案之一是使用<strong>两阶段提交协议（<code>2PC</code>）</strong>。在该协议中，<strong>事务协调者</strong>负责确保事务中的所有参与者都同意提交或回滚事务。</p><p>协调者首先向所有参与者发送“准备提交”消息，如果所有参与者都做出正确响应，则协调者向所有参与者发送“提交”消息。</p><p>如果任何参与者做出否定响应，协调者将向所有参与者发送“中止”消息，并进行事务回滚。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/02.gif"><p>但是，<strong>两阶段提交可能会成为性能瓶颈</strong>，并可能导致系统可用性降低。另一种方法是使用<strong>基于补偿的事务协议</strong>。在该协议中，事务的每个参与者都有责任在事务失败时补偿事务所产生的任何影响。</p><p>这种方法比 <code>2PC</code> 协议更灵活，速度更快，可扩展性更强，但需要仔细设计补偿逻辑。</p><p>用于管理分布式系统中事务的其它技术还包括<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">Saga 模式</a>和事件溯源机制。<a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">Saga</a>是一种事务模式，它使用一系列本地事务来实现全局事务。</p><p>另一方面，<strong>事件溯源</strong>会将系统状态的所有更改存储为事件序列，而不仅仅是当前状态，这种方式可以简化事务管理流程。</p><p>在微服务架构中，必须确保正确定义每个微服务的事务边界，来避免出现多个服务之间的数据更新不一致问题。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/03.webp"><p>使用事务管理器也是很有必要的，它可以跨多个微服务协调分布式事务。</p><p>简而言之，管理分布式系统和微服务中的事务可能很具有挑战性，但是使用适当的事务管理技术，例如 <code>2PC</code>、基于补偿的协议、<code>Saga</code> 和事件溯源机制，可以帮助我们确保分布式系统中的数据一致性和完整性。</p><p>现在我们已经了解了一些基础知识，下面让我们更深入地学习这些技术。</p><h3 id="什么是两阶段提交？它是如何管理分布式事务的？"><a href="#什么是两阶段提交？它是如何管理分布式事务的？" class="headerlink" title="什么是两阶段提交？它是如何管理分布式事务的？"></a>什么是两阶段提交？它是如何管理分布式事务的？</h3><p>两阶段提交（<code>2PC</code>）是一种分布式事务协议，用于管理跨多个分布式系统的事务。它确保参与分布式事务的所有节点都达成一致，提交或回滚事务。</p><p>在分布式事务中，协调者节点负责管理事务，并负责协调所有参与事务的节点。</p><p><strong>协调者向所有参与者发送准备消息，以确认他们做好准备提交事务</strong>。如果所有参与者都已做好准备，协调者会发送提交消息，所有参与者都会提交事务。</p><p>另一方面，<strong>如果某个参与者没有做好准备或者做出了否定响应，协调者会向所有参与者发送回滚消息</strong>，所有参与者都会回滚事务。</p><p>两阶段提交协议确保即使在出现通信故障或系统崩溃时，事务也可以在分布式环境中正常提交或者回滚。两阶段提交协议被广泛应用于分布式数据库、分布式消息系统和其他分布式应用程序。</p><p>例如，考虑一个具有多个分支机构的银行系统，每个分支机构都有自己的数据库。当客户在一个分支机构执行交易时，需要与其他分支机构的数据库同步以保持数据一致性。</p><p>在这种情况下，我们就可以考虑使用两阶段提交协议来确保事务在所有分支机构的数据库上一致地进行提交或者回滚。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/04.webp"><br><h3 id="什么是微服务中的-SAGA-设计模式？它是如何管理分布式事务的？"><a href="#什么是微服务中的-SAGA-设计模式？它是如何管理分布式事务的？" class="headerlink" title="什么是微服务中的 SAGA 设计模式？它是如何管理分布式事务的？"></a>什么是微服务中的 SAGA 设计模式？它是如何管理分布式事务的？</h3><p><code>SAGA</code> 是 “<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">Saga Pattern</a>” 的缩写，是一种协助管理微服务架构中的分布式事务的设计模式。在微服务环境中，不同的服务可以拥有自己的数据库和独立的事务，<code>SAGA</code> 模式提供了一种确保跨多个服务的事务一致性的方法。</p><p><a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA</a> 将一个长时间运行的事务分解为多个较小的事务，每个事务都可以独立提交或者回滚。这些较小的事务由 <code>SAGA</code> 协调器进行编排，该协调器负责以正确的顺序执行程序并在发生故障时进行补偿操作。</p><p><code>SAGA</code> 模式可以通过两种方式实现：<code>choreography-based</code> 和 <code>orchestration-based</code>。在 <code>choreography-based</code> 实现的 <code>SAGA</code> 系统中，每个服务负责自己的事务，并且通过服务之间的事件驱动通信来完成协调。</p><p>而在 <code>orchestration-based</code> 实现的 <code>SAGA</code> 系统中，是由 <code>SAGA</code> 协调器负责协调事务并控制 <code>SAGA</code> 的整个流程。</p><p>下图很好地解释了分布式微服务中的 <code>Saga</code> 模式：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/05.webp"><br><h3 id="什么是微服务中的事件溯源模式？它是如何管理分布式事务的？"><a href="#什么是微服务中的事件溯源模式？它是如何管理分布式事务的？" class="headerlink" title="什么是微服务中的事件溯源模式？它是如何管理分布式事务的？"></a>什么是微服务中的事件溯源模式？它是如何管理分布式事务的？</h3><p>事件溯源是微服务架构中使用的一种设计模式，它会将系统状态持久化为一系列事件，而不仅仅是系统的当前状态。</p><p><em>它通过提供可靠且可扩展的方式来处理复杂的业务流程，帮助管理分布式事务。</em></p><p>在事件溯源中，对系统状态所做的所有变更都将作为事件被捕获并保存到事件日志中。事件日志作为系统状态的真实来源，允许在需要时轻松回滚或重放事件。<strong>这种方法确保对系统的所有变更都是可跟踪、可审计的，并且可以在必要时进行回滚</strong>。</p><p>事件溯源的主要优点之一是它可以创建松散耦合的服务，这些服务可以彼此独立运行。每个服务都可以根据需要消费事件或者是生成事件，而不必关心其它服务的实现细节。</p><p>在管理分布式事务方面，<strong>事件源提供了一种方法来确保事务中涉及的每个服务都可以可靠地提交或回滚变更内容</strong>。</p><p>当事务涉及多个服务时，<strong>每个服务可以发布一个事件来表明它所负责的事务是否已成功完成</strong>。</p><p>然后，其它服务可以使用这些事件并执行相应的动作，确保在所有涉及的服务中一致地提交或回滚事务。</p><p>总的来说，事件溯源是一种在管理微服务架构中的分布式事务的强大模式，提供了一种可扩展且可靠的方式来处理复杂的业务流程。</p><p>下图很好地解释了事件溯源模式：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/06.webp"><br><h3 id="Spring-Cloud-如何管理分布式事务"><a href="#Spring-Cloud-如何管理分布式事务" class="headerlink" title="Spring Cloud 如何管理分布式事务"></a>Spring Cloud 如何管理分布式事务</h3><p><code>Spring Cloud</code> 提供了一些用于微服务架构中分布式事务管理的能力：</p><ol><li><strong>服务注册和发现</strong><br><code>Spring Cloud</code> 所提供的服务注册和发现功能允许多个服务之间轻松地发现彼此的存在并进行通信；它通过提供服务注册和发现的中心化组件来帮助管理分布式事务。</li><li><strong>熔断</strong><br><code>Spring Cloud</code> 的熔断器模式用于防止分布式系统中出现级联故障；熔断器模式通过隔离特定服务中的故障，防止故障影响系统中的其它服务从而保障整个系统的可用性。</li><li><strong>分布式追踪</strong><br><code>Spring Cloud</code> 的分布式跟踪功能用于跟踪多个微服务之间的事务；这有助于识别事务中问题出现的根源从而轻松进行功能调试。</li><li><strong>配置管理</strong><br><code>Spring Cloud</code> 提供了一个集中的配置管理系统，用于管理跨多个微服务的配置；这使得系统可以轻松地实现更新和扩展，这有助于分布式系统中的事务管理。</li></ol><p>总体而言，<code>Spring Cloud</code> 提供了一系列工具和功能，有助于管理微服务架构中的分布式事务，从而更轻松地维护系统的一致性和可靠性。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/21-How-Manage-Transactions-Distributed-Systems-Microservices/07.webp"><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上内容就是如何在分布式系统中管理事务。总之，微服务和分布式系统中的事务管理是一项具有挑战性的任务，处理起来并不容易，但是对于确保整个系统的数据完整性和一致性却是至关重要的。</p><p>了解各种事务管理模式（例如两阶段提交、<code>SAGA</code> 和事件溯源）并为系统的特定场景选择适当的方法是至关重要的。每种模式都有其优点和缺点，选择某种模式应基于系统的特定要求和规范。</p><p>虽然<strong>两阶段提交确保了一致性</strong>，但是它<strong>存在单点故障，并且性能可能会受到影响</strong>；另一方面，<strong>SAGA 和事件溯源提供了更好的可扩展性和可用性</strong>，但需要额外的实现和维护工作。</p><p>因此，<em>对于开发人员和架构师来说，仔细分析和设计微服务和分布式系统中的事务管理功能是至关重要的，以确保系统性能最佳并保证数据完整性</em>。</p><p>伴随着微服务和分布式系统的不断普及，事务管理面临的挑战还会不断出现，新的模式和技术也将会不断涌现；紧跟该领域的最新发展对于开发高效、健壮的微服务和分布式系统是非常重要的。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-i
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="两阶段提交" scheme="https://dongzl.github.io/tags/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4/"/>
    
      <category term="SAGA" scheme="https://dongzl.github.io/tags/SAGA/"/>
    
      <category term="事件溯源" scheme="https://dongzl.github.io/tags/%E4%BA%8B%E4%BB%B6%E6%BA%AF%E6%BA%90/"/>
    
  </entry>
  
  <entry>
    <title>微服务中 API 网关和负载均衡技术的差异？</title>
    <link href="https://dongzl.github.io/2023/07/30/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/"/>
    <id>https://dongzl.github.io/2023/07/30/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/</id>
    <published>2023-07-30T16:52:22.000Z</published>
    <updated>2023-12-31T06:59:02.169Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/01.webp"><p>朋友们大家好，<code>API</code> 网关和负载均衡技术之间的差异对比是非常<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">流行的微服务面试问题</a>之一，面试官经常会在电话或现场面试中用这个问题来考察经验丰富的 <code>Java</code> 开发人员。过去，我分享了<a href="https://medium.com/javarevisited/10-microservices-design-principles-every-developer-should-know-44f2f69e960f">基本的微服务设计原则和最佳实践</a>以及流行的<a href="https://medium.com/javarevisited/top-10-microservice-design-patterns-for-experienced-developers-f4f5f782810e">微服务设计模式</a>等文章；在本文中，我将回答这个问题，并告诉大家它们之间的关键区别。</p><p>虽然 <code>API</code> 网关和负载均衡都是微服务架构中的重要组件，但它们的用途却不相同。</p><p><strong>API 网关充当所有 API 请求的单一入口点</strong>，提供<em>请求路由</em>、<em>请求限流</em>、<em>身份认证</em>和 <em><code>API</code> 版本控制</em>等功能，并且还向客户端应用程序隐藏底层微服务的复杂性。</p><p>另一方面，<strong>负载均衡负责在微服务的多个实例之间分发传入请求</strong>，以提升服务可用性、性能和可扩展性。它有助于<em>在多个实例之间均匀分配工作负载</em>，并确保每个服务实例都得到充分利用。</p><p>换句话说，<code>API</code> 网关提供与 <code>API</code> 管理相关的<strong>更高层级的能力</strong>，而负载均衡提供与跨微服务的多个实例的流量分发相关的<strong>较低层级的能力</strong>。</p><h2 id="微服务中的-API-网关模式是什么？"><a href="#微服务中的-API-网关模式是什么？" class="headerlink" title="微服务中的 API 网关模式是什么？"></a>微服务中的 API 网关模式是什么？</h2><p><a href="https://medium.com/javarevisited/what-is-api-gateway-pattern-in-microservices-architecture-what-problem-does-it-solve-ebf75ae84698">API 网关</a>是微服务架构中使用的基本模式之一，它在整个系统中充当<strong>反向代理</strong>角色，将客户端的请求路由到多个内部服务。它还为所有客户端提供与系统交互的单一入口点，从而实现更好的<a href="https://medium.com/javarevisited/difference-between-horizontal-scalability-vs-vertical-scalability-67455efc91c">可扩展性</a>、安全性和对 <code>API</code> 的控制。</p><p><code>API</code> 网关通常用于实现身份认证、请求限流和缓存等常见功能，同时还屏蔽了底层服务的复杂性。当我们体验过使用 <code>API</code> 网关的实际场景时，系统整体架构会变得更加清晰，所以我们需要这样做。</p><p>假设我们有一个基于微服务的电子商务应用程序，允许用户浏览和购买产品，并且该应用程序由<strong>多个微服务</strong>组成，包括商品分类服务、购物车服务、订单服务和用户服务。</p><p><strong>为了简化客户端与这些微服务之间的交互，我们可以使用 API 网关</strong>。<code>API</code> 网关充当所有客户端请求的单一入口点，并将它们路由到正确的微服务系统。</p><p>例如，<strong>当用户想要查看产品分类时，他们向 API 网关发出请求，API 网关将请求转发到商品分类服务</strong>。同样，当用户想要购买下单时，他们同样会向 <code>API</code> 网关发出请求，<code>API</code> 网关会将请求转发到订单服务。</p><p><em>通过使用 API 网关，我们可以简化客户端代码，减少需要发出的请求数量</em>，并为客户端与微服务交互提供统一的入口。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/02.webp"><h2 id="什么时候使用-API-网关？"><a href="#什么时候使用-API-网关？" class="headerlink" title="什么时候使用 API 网关？"></a>什么时候使用 API 网关？</h2><p>现在我们知道什么是 <code>API</code> 网关以及它提供哪些能力，就很容易理解何时使用它了。<code>API</code> 网关可用于多种场景，例如：</p><ol><li><strong>为多个微服务提供统一的入口</strong>：<code>API</code> 网关可以作为多个微服务的单一入口点，为客户端提供统一的入口；</li><li><strong>建立安全机制</strong>：<code>API</code> 网关可以为所有微服务实施安全策略，例如身份认证和授权；</li><li><strong>提高性能</strong>：<code>API</code> 网关可以缓存微服务的响应并减少访问后端服务的请求数量；</li><li><strong>简化客户端</strong>：<code>API</code> 网关可以抽象底层微服务的复杂性，为客户端交互提供更简单的接口；</li><li><strong>版本控制和路由</strong>：<code>API</code> 网关可以根据请求参数将请求路由到同一微服务的不同版本或者是不同的微服务。</li></ol><p>因此，我们可以看到 <code>API</code> 网关所做的不仅仅是将请求转发到微服务实例，这可能是 <code>API</code> 网关和负载均衡技术之间的主要区别。</p><h2 id="什么是负载均衡？它能够解决什么问题？"><a href="#什么是负载均衡？它能够解决什么问题？" class="headerlink" title="什么是负载均衡？它能够解决什么问题？"></a>什么是负载均衡？它能够解决什么问题？</h2><p>负载均衡是一个在服务器集群中的多个服务器或节点之间分发网络流量的组件，不仅在微服务架构中，在其它任何系统架构中都有相似的作用。</p><p>它通过在服务器之间均匀分配工作负载，有助于提高应用程序和服务的性能、可扩展性和可用性。</p><p>通过这种方式，<strong>负载均衡可以确保不会出现某一台服务器流量过载</strong>，而其他服务器还处于空闲状态，通过这种方式可以提高资源利用率并提升整个系统的可靠性。</p><p>这里有一张很好的图表，显示了负载均衡的工作原理以及如何在硬件和软件中使用负载均衡：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/20-Difference-Between-API-Gateway-Load-Balancer-Microservices/03.webp"><h2 id="何时需要在微服务中使用负载均衡？"><a href="#何时需要在微服务中使用负载均衡？" class="headerlink" title="何时需要在微服务中使用负载均衡？"></a>何时需要在微服务中使用负载均衡？</h2><p>正如前面所说，负载均衡主要用于微服务架构中，以便在同一服务的多个实例之间分发请求的网络流量。</p><p>负载均衡可以帮助提高应用程序的可用性和可扩展性，这就是为什么在微服务架构出现之前负载均衡技术就已经存在很久了。</p><p>当应用程序的流量过高并且单个服务实例无法处理负载时，也会使用负载均衡技术。当应用程序跨多个服务器或数据中心部署时，也会使用到负载均衡技术。</p><hr><h2 id="微服务中负载均衡的优缺点"><a href="#微服务中负载均衡的优缺点" class="headerlink" title="微服务中负载均衡的优缺点"></a>微服务中负载均衡的优缺点</h2><p>现在我们来分析一下微服务架构中使用负载均衡的优缺点：</p><p><strong>优点</strong>：</p><ul><li><strong>提高性能和可扩展性</strong>：负载均衡在多个服务器之间均匀分配流量，减少每个服务器上的负载并确保服务器不会出现流量过载；</li><li><strong>提高可用性和容错能力</strong>：如果一台服务器出现故障，负载均衡可以自动将流量重定向到其它健康的服务器节点，确保即使某些服务器出现故障，整个服务仍然可用；</li><li><strong>灵活性和可定制化</strong>：负载均衡可以使用不同的算法和规则进行定制，以最适合系统需要的方式处理流量。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>复杂性和成本</strong>：负载均衡会增加系统的复杂性和成本，因为需要额外的硬件或软件来管理和维护负载均衡；</li><li><strong>单点故障</strong>：如果负载均衡自身发生故障，则整个系统可能会出现不可用；</li><li><strong>延迟增加</strong>：根据配置需要，负载均衡需要在客户端和服务器之间添加额外的节点，这会导致系统延迟增加。</li></ul><hr><h2 id="微服务架构中负载均衡和-API-网关的区别"><a href="#微服务架构中负载均衡和-API-网关的区别" class="headerlink" title="微服务架构中负载均衡和 API 网关的区别"></a>微服务架构中负载均衡和 API 网关的区别</h2><p>以下是 <code>API</code> 网关和负载均衡之间的 <code>10</code> 个差异点：</p><h3 id="1-目标"><a href="#1-目标" class="headerlink" title="1. 目标"></a>1. 目标</h3><p><code>API</code> 网关的主要目的是为微服务提供统一的请求访问入口，而负载均衡的主要目的是在多个服务器之间均匀分发流量。</p><h3 id="2-功能性"><a href="#2-功能性" class="headerlink" title="2. 功能性"></a>2. 功能性</h3><p><code>API</code> 网关可以提供多种能力，例如路由、安全、负载均衡和 <code>API</code> 管理，而负载均衡仅处理流量的分发。</p><h3 id="3-路由"><a href="#3-路由" class="headerlink" title="3. 路由"></a>3. 路由</h3><p><code>API</code> 网关根据一组预定义的规则路由请求，而负载均衡根据预定义的算法（例如<strong>轮询算法</strong>或<strong>最少连接算法</strong>）路由请求。</p><h3 id="4-协议支持"><a href="#4-协议支持" class="headerlink" title="4. 协议支持"></a>4. 协议支持</h3><p><code>API</code> 网关通常支持多种协议，如 <code>HTTP</code>、<code>WebSocket</code>、<code>MQTT</code> 等，而负载均衡仅支持传输层协议，如 <code>TCP</code>、<code>UDP</code> 等。</p><h3 id="5-安全性"><a href="#5-安全性" class="headerlink" title="5. 安全性"></a>5. 安全性</h3><p><code>API</code> 网关能够提供身份认证、授权和 <code>SSL</code> 认证等功能，而负载均衡仅提供 <code>SSL</code> 认证等基本安全功能。</p><h3 id="6-缓存"><a href="#6-缓存" class="headerlink" title="6. 缓存"></a>6. 缓存</h3><p><code>API</code> 网关可以缓存微服务的响应以提高性能，而负载均衡一般不提供缓存功能。</p><h3 id="7-可移植性"><a href="#7-可移植性" class="headerlink" title="7. 可移植性"></a>7. 可移植性</h3><p><code>API</code> 网关可以在不同格式之间转换数据，例如 <code>JSON</code> 转换成 <code>XML</code>，而负载均衡不提供数据格式转换能力。</p><h3 id="8-服务发现"><a href="#8-服务发现" class="headerlink" title="8. 服务发现"></a>8. 服务发现</h3><p><code>API</code> 网关可以与服务发现机制集成来实现微服务动态发现，而负载均衡则依赖于静态配置。</p><h3 id="9-粒度"><a href="#9-粒度" class="headerlink" title="9. 粒度"></a>9. 粒度</h3><p><code>API</code> 网关可以提供对 <code>API</code> 节点的细粒度控制，而负载均衡器仅控制服务器级别的流量。</p><h3 id="10-可扩展性"><a href="#10-可扩展性" class="headerlink" title="10. 可扩展性"></a>10. 可扩展性</h3><p><code>API</code> 网关可以处理大量 <code>API</code> 请求并提供微服务垂直扩展能力，而负载均衡仅提供水平扩展能力。</p><hr><p>这就是<strong>微服务中 API 网关和负载均衡之间的差异</strong>。正如我前面所说，<code>API</code> 网关提供与 <code>API</code> 管理相关的<strong>更高层级的能力</strong>，例如安全性、版本控制、单一流量入口；而负载均衡提供与跨微服务的多个实例的流量分发相关的<strong>较低层级的能力</strong>。</p><p>因此，如果我们需要的不仅仅是将请求转发到多个实例，我们应该选择使用 <code>API</code> 网关而不是负载均衡，但是如果我们的需求只是将传入流量分发到多个应用程序实例，则只需要使用负载均衡技术。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="微服务" scheme="https://dongzl.github.io/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
      <category term="网关" scheme="https://dongzl.github.io/tags/%E7%BD%91%E5%85%B3/"/>
    
      <category term="负载均衡" scheme="https://dongzl.github.io/tags/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/"/>
    
  </entry>
  
  <entry>
    <title>REST、GraphQL 和 gRPC 之间的差异</title>
    <link href="https://dongzl.github.io/2023/07/24/19-Difference-Between-REST-GraphQL-gRPC/"/>
    <id>https://dongzl.github.io/2023/07/24/19-Difference-Between-REST-GraphQL-gRPC/</id>
    <published>2023-07-24T20:37:51.000Z</published>
    <updated>2023-12-31T06:59:02.165Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/difference-between-rest-graphql-and-grpc-10ac365462b8">https://medium.com/javarevisited/difference-between-rest-graphql-and-grpc-10ac365462b8</a></p></blockquote><p>朋友们大家好，如果大家正在通过学习 <code>Spring Boot</code> 和微服务知识来准备 <code>Java</code> 开发岗位的面试，我建议还应该准备一下 <code>REST</code>、<code>GraphQL</code> 和 <code>gRPC</code> 等知识的内容，例如 <strong><code>REST</code>、<code>GraphQL</code> 和 <code>gRPC</code> 之间的差异</strong>，这也是 <code>Java</code> 面试中的热门问题之一。</p><p>在上一篇文章中，我分享了 <a href="https://medium.com/javarevisited/difference-between-jwt-oauth-and-saml-for-authentication-and-authorization-in-web-apps-75b412754127">JWT、OAuth 和 SAML 之间的差异</a>；在本文中我将分享我对 <code>REST</code>、<code>GraphQL</code> 和 <code>gRPC</code> 这三种流行的用于构建 <code>Web</code> <code>API</code> 的通信协议的一些思考。</p><p>它们通常应用于通过网络进行相互通信的不同系统组件之间，例如<a href="https://medium.com/javarevisited/how-microservices-communicates-with-each-other-synchronous-vs-asynchronous-communication-pattern-31ca01027c53">使用 REST 可以在微服务之间进行同步通信</a>。这些协议都有各自的优缺点，了解它们之间的差异不仅对于面试很重要，而且对于实际的项目中选择正确的协议也很重要。</p><p>在本文中，我们将学习 <code>REST</code>、<code>GraphQL</code> 和 <code>gRPC</code> 之间的差异。我将解释每个协议背后的核心概念、它们的优缺点，并提供每种协议的一些实际使用场景。在本文结束时，我们应该能够更好地理解每种协议，最终能够满足我们的项目要求。</p><p>我们首先从一些简单的介绍开始，然后将会深入研究每一种协议，然后再总结它们之间的差异，以便大家清晰地了解它们的优缺点以及何时使用它们。</p><p><a href="https://en.wikipedia.org/wiki/Representational_state_transfer">REST</a> 即表述性状态转移，它是一种流行的协议，通常用于构建 <code>Web</code> 服务，这些服务通过 <code>HTTP</code> 公开数据和开放功能。它是基于 <code>HTTP</code> 的协议和一组约束，用来定义如何识别和定位某个资源以及如何对这些资源执行相关操作。</p><p>另一方面，<a href="https://graphql.org/">GraphQL</a> 是 <code>Facebook</code> 开发的 <code>API</code> 查询语言。它允许客户端准确指定它们所需要的数据，并且服务器仅响应指定的该数据内容。</p><p><code>GraphQL</code> 的出现是为了解决 <code>REST</code> 的缺点和限制，因此它提供了一种更灵活、更高效的从服务器获取数据的方式，因为客户端可以在单个请求中请求多个资源。</p><p><a href="https://grpc.io/">gRPC</a> 是一种用于创建 <code>API</code> 的高性能开源协议。它使用 <strong><code>Google</code> 的 <code>Protocol Buffers</code></strong> 作为数据格式，并提供对数据流和双向通信的支持。<code>gRPC</code> 因其性能和对多种编程语言的支持，经常用于微服务架构。</p><p>现在我们知道这些协议是什么了，接下来让我们深入研究它们。</p><p>顺便说一下，如果大家正在准备 <code>Java</code> 开发岗位的面试，还可以查看我之前发布的关于 <a href="https://medium.com/javarevisited/21-software-design-pattern-interview-questions-and-answers-b7d1774b5dd2">21 个软件设计模式问题</a>、<a href="https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-experienced-developers-3391e4f6b591">10 个基于微服务场景的问题</a>、<a href="https://medium.com/javarevisited/20-sql-queries-for-programming-interviews-a7b5a7ea8144">20 个 SQL 查询面试题</a>、<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务问题</a>、<a href="https://medium.com/javarevisited/top-60-tree-data-structure-coding-interview-questions-every-programmer-should-solve-89c4dbda7c5a">60 个关于树的数据结构问题</a>、<a href="https://medium.com/javarevisited/7-system-design-problems-to-crack-software-engineering-interviews-in-2023-13a518467c3e">15 个系统设计问题</a>、<a href="https://medium.com/javarevisited/top-10-java-interview-questions-for-3-to-4-years-experienced-programmers-c4bf6d8b5e7b">35 个 Java 核心问题</a> 和 <a href="https://medium.com/javarevisited/21-lambda-and-stream-interview-questions-for-java-programmers-38d7e83b5cac">21 个 Lambda 和 Stream 问题</a> 等文章，这些文章包含大量常见问题，可以帮助大家更好地准备面试。</p><h2 id="什么是-REST？什么时候使用它？"><a href="#什么是-REST？什么时候使用它？" class="headerlink" title="什么是 REST？什么时候使用它？"></a>什么是 REST？什么时候使用它？</h2><p>正如我前面所说，<code>REST</code>（表述性状态转移）是一种用于设计分布式应用程序的架构风格，尤其是基于 <code>Web</code> 的 <code>API</code> 的设计。 <code>RESTful</code> <code>API</code> 使用 <code>HTTP</code> 方式（例如 <code>GET</code>、<code>POST</code>、<code>PUT</code>、<code>DELETE</code>）对 <code>URL</code>（统一资源定位器）标识的资源执行 <code>CRUD</code>（创建、读取、更新、删除）操作。</p><p>如果我们了解 <code>HTTP</code>，那么我们就可以很容易了解 <code>REST</code>。</p><p><code>REST</code> 还依赖于无状态的客户端-服务器架构，其中来自客户端的每个请求都包含服务器完成请求所需要的所有信息，从而无需维护会话状态。</p><p>以下是一些不错的使用 <code>REST</code> 的场景：</p><ol><li>当我们需要通过 <code>API</code> 公开数据和服务时，因为 <code>REST</code> 是一种流行且完善的协议，用于创建可供其他应用程序和服务轻松使用的 <code>API</code>；</li><li>当我们依赖于标准的 <code>HTTP</code> 方法和数据格式而需要支持多种平台和编程语言时，<code>REST</code> 可以被多种编程语言和平台使用；</li><li>当我们需要缓存支持时，因为 <code>REST</code> 支持缓存，这可以提高性能并减少网络流量；</li><li>当我们需要构建简单、轻量级的 <code>API</code> 时；</li><li>当我们需要支持大量资源时。</li></ol><p>此外，了解 <code>HTTP</code> 方法对于设计 <code>REST</code> <code>API</code> 非常重要。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/19-Difference-Between-REST-GraphQL-gRPC/01.png"><p>总体而言，<code>REST</code> 是一种灵活且被广泛采用的协议，对于许多类型的 <code>API</code> 来说都是不错的选择。但是它可能不是所有场景的最佳选择，尤其是那些需要实时更新或者是更复杂的查询和数据操作的场景，在这些情况下，其他协议（例如 <code>GraphQL</code> 或 <code>gRPC</code>）可能更合适。</p><hr><h2 id="什么是-GraphQL？什么时候使用它？"><a href="#什么是-GraphQL？什么时候使用它？" class="headerlink" title="什么是 GraphQL？什么时候使用它？"></a>什么是 GraphQL？什么时候使用它？</h2><p><code>GraphQL</code> 是一种 <code>API</code> 查询语言，由 <code>Facebook</code> 于 <code>2012</code> 年开发，并于 <code>2015</code> 年作为开源项目发布。它被创建的初衷是为了解决 <code>REST</code> 的限制和缺点。</p><p><code>GraphQL</code> 允许客户端定义他们需要的数据的结构，并且服务器可以准确地响应该数据，同时没有任何不必要的数据。它通常用作 <code>RESTful</code> <code>API</code> 的替代方案，特别是在客户端需要对返回的数据进行细粒度控制的情况下。</p><p>以下是一些不错的使用 <code>GraphQL</code> 的场景：</p><ol><li>当我们想要减少网络流量时，因为 <code>GraphQL</code> 允许客户端准确指定他们需要的数据，这可以减少通过网络传输的不必要的数据量；</li><li>当我们需要支持各种客户端时，因为 <code>GraphQL</code> 支持强类型查询，这可用于确保客户端以它们可以理解的格式接收正确的数据；</li><li><strong>当我们需要支持实时更新</strong>时，因为 <code>GraphQL</code> 支持通过订阅进行实时更新，这允许客户端在更新可用时立即接收更新数据；</li><li>当我们需要支持复杂的查询和数据操作时：因为 <code>GraphQL</code> 允许客户端使用简单的语法执行复杂的查询和数据操作操作，例如过滤、排序和聚合；</li><li>当我们需要支持版本控制时，因为 <code>GraphQL</code> 允许客户端指定它们在请求中指定 <code>schema</code> 的版本来支持版本控制，这可以让我们在 <code>schema</code> 随着时间的推移而发生变化时更轻松地保持向后兼容性。</li></ol><p>总的来说，<code>GraphQL</code> 是一个强大而灵活的协议，对于数据细粒度控制和实时更新很重要的场景来说，它是一个不错的选择。然而，<strong>它可能比 <code>RESTful</code> <code>API</code> 需要更多的设置和配置</strong>，特别是当我们使用多种编程语言或平台时。</p><p>这里有一个很好的图表，突出<strong>显示了 <code>REST</code> 和 <code>GraphQL</code> 查询</strong>之间的区别：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/19-Difference-Between-REST-GraphQL-gRPC/02.png"><hr><h2 id="什么是-gRPC？什么时候使用它？"><a href="#什么是-gRPC？什么时候使用它？" class="headerlink" title="什么是 gRPC？什么时候使用它？"></a>什么是 gRPC？什么时候使用它？</h2><p>现在让我们来学习什么是 <code>gRPC</code> 以及它能够提供什么？ <code>gRPC</code> 是 <code>Google</code> 开发的一个高性能、开源的远程过程调用 (<code>RPC</code>) 框架。它使用 <code>Protocol Buffers</code> 作为接口描述语言，支持多种编程语言，可以轻松构建跨不同平台和环境的分布式系统。</p><p>以下是一些不错的使用 <code>gRPC</code> 的场景：</p><ol><li><strong>当我们需要高性能和高效率时</strong>，因为 <code>gRPC</code> 使用二进制协议并支持流式传输，这可以使其比其他协议更快、更高效，特别是在高延迟或低带宽连接上；</li><li>当我们需要支持多种编程语言时，因为 <code>gRPC</code> 支持多种编程语言，包括 <code>Java</code>、<code>C</code>、<code>Python</code> 和 <code>Go</code>，可以轻松构建跨不同平台和环境的分布式系统；</li><li>当我们需要支持实时更新时，因为 <code>gRPC</code> 支持双向流，这允许服务器实时向客户端发送更新；</li><li><strong>当我们需要处理大量数据时</strong>，因为 <code>gRPC</code> 使用 <code>Protocol Buffers</code>，它比 <code>JSON</code> 或 <code>XML</code> 等其他数据格式更高效、更紧凑，使其成为处理大量数据的不错选择；</li><li>当我们需要构建微服务或分布式系统时，因为 <code>gRPC</code> 提供了强大而灵活的框架，用于构建可以水平扩展并处理大量流量的微服务和分布式系统。</li></ol><p>总体而言，<code>gRPC</code> 是一个强大且高效的协议，对于性能、效率和实时更新很重要的场景来说，它是一个不错的选择。但是，与 <code>RESTful</code> <code>API</code> 等其他协议相比，它可能需要更多的设置和配置，特别是当您使用多种编程语言或平台时。</p><p>这里有一个很好的图表，突出显示了 <code>REST</code>、<code>gRPC</code> 和 <code>GraphQL</code> 协议之间的区别：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/19-Difference-Between-REST-GraphQL-gRPC/03.png"><p><font color="DarkGray" size="2">image_credit — <a href="https://medium.com/@LadyNoBug/grpc-v-s-rest-v-s-others-5d8b6eaa61df">https://medium.com/@LadyNoBug/grpc-v-s-rest-v-s-others-5d8b6eaa61df</a></font></p><hr><h2 id="REST、gRPC-和-GraphQL-之间的区别"><a href="#REST、gRPC-和-GraphQL-之间的区别" class="headerlink" title="REST、gRPC 和 GraphQL 之间的区别"></a>REST、gRPC 和 GraphQL 之间的区别</h2><p>现在我们已经了解什么是 <code>REST</code>、<code>gRPC</code> 和 <code>GraphQL</code> 以及它们的工作原理，以下是 <code>REST</code>、<code>GraphQL</code> 和 <code>gRPC</code> 之间的主要区别，请记住它们的主要特性以及何时在项目中使用它们：</p><h3 id="REST："><a href="#REST：" class="headerlink" title="REST："></a>REST：</h3><ul><li>表述性状态转移；</li><li>使用 <code>HTTP</code> 方法（<code>GET</code>、<code>POST</code>、<code>PUT</code>、<code>DELETE</code>）执行 <code>CRUD</code> 操作；</li><li>以结构化格式发送数据，通常是 <code>JSON</code> 或 <code>XML</code> 格式；</li><li>支持不同资源可以有多个端点；</li><li>客户端收到响应中的所有数据，即使它们并不需要全部数据；</li><li>支持缓存，但缓存管理可能很复杂；</li><li>完善且广泛采用，提供大量工具和文档。</li></ul><h3 id="GraphQL："><a href="#GraphQL：" class="headerlink" title="GraphQL："></a>GraphQL：</h3><ul><li>允许客户端准确指定它们需要什么数据，并且仅接收该数据；</li><li>使用单个端点访问多个资源；</li><li>拥有自己的查询语言，允许复杂的数据读取和其它操作；</li><li>可以支持通过订阅实时更新数据；</li><li>在某些情况下比 <code>REST</code> 更高效，特别是对于带宽有限的移动设备；</li><li>与 <code>REST</code> 相比，缓存可以更细粒度且更易于管理；</li><li>比 <code>REST</code> 需要更多的设置和配置，并且可能需要更多的专业知识才能高效地使用。</li></ul><h3 id="gRPC："><a href="#gRPC：" class="headerlink" title="gRPC："></a>gRPC：</h3><ul><li>表示带有 <code>Google</code> 协议缓冲区的远程过程调用 (<code>RPC</code>)；</li><li>使用二进制数据代替 <code>HTTP</code> 协议进行通信；</li><li>支持流数据实时更新；</li><li>使用协议缓冲区进行序列化，这比 <code>JSON</code> 或 <code>XML</code> 更高效；</li><li>可以跨不同的编程语言使用；</li><li>专为微服务之间的高性能、低延迟通信而设计；</li><li>比 <code>REST</code> 需要更多的设置和配置，并且可能需要更多的专业知识才能高效地使用；</li><li>可操作性可能不如 <code>REST</code> 或 <code>GraphQL</code> 方便，因为它不是基于 <code>HTTP</code></li></ul><p>这里总结了一个很好的表格，突出显示了 <code>REST</code>、<code>GraphQL</code> 和 <code>REST</code> 之间的差异，我们可以使用它来复习前面的知识：</p><table><thead><tr><th>特性</th><th>REST</th><th>GraphQL</th><th>gRPC</th></tr></thead><tbody><tr><td>定义</td><td>表述性状态转移</td><td><code>API</code> 查询语言</td><td>使用 <code>Protocol Buffers</code> 的远程过程调用</td></tr><tr><td>支持的 HTTP 方法</td><td><code>GET</code>、<code>POST</code>、<code>PUT</code>、<code>DELETE</code></td><td><code>POST</code></td><td>自定义方法</td></tr><tr><td>数据格式</td><td><code>JSON</code> 或者 <code>XML</code></td><td><code>GraphQL</code> 规范格式</td><td>二进制格式</td></tr><tr><td><code>endpoint</code> 风格</td><td>支持多 <code>endpoint</code></td><td>单个 <code>endpoint</code></td><td>无</td></tr><tr><td>查询语言</td><td>无</td><td><code>GraphQL</code></td><td>无</td></tr><tr><td>实时更新</td><td>不支持（只能轮询）</td><td>支持（通过订阅）</td><td>支持（通过流机制）</td></tr><tr><td>效率</td><td>中等</td><td>高</td><td>高</td></tr><tr><td>缓存能力</td><td>支持，但是管理比较复杂</td><td>支持细粒度缓存，管理比较方便</td><td>不常用，需要做很多设置</td></tr><tr><td>互操作性</td><td>高</td><td>中等</td><td>低</td></tr></tbody></table><p>还值得注意的是，<strong>这些协议并不互斥，完全可以组合使用它们，充分利用它们之间的不同优势</strong>。</p><p>例如，我们可能对大多数 <code>API</code> 使用 <code>REST</code>，但对某些资源密集型查询使用 <code>GraphQL</code>，或者使用 <code>gRPC</code> 在微服务之间进行通信，同时对外部 <code>API</code> 客户端使用 <code>REST</code> 或 <code>GraphQL</code>。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这就是 <code>REST</code>、<code>GraphQL</code> 和 <code>gRPC</code> 技术之间的差异。简而言之，<code>REST</code> 是一种用于创建 <code>Web</code> 服务的流行协议，受到 <code>HTTP</code> 的启发并充分利用 <code>HTTP</code> 提供的能力；而 <code>GraphQL</code> 是一种查询语言，允许客户端准确指定他们需要从服务器获取哪些数据，它的出现是为了解决 <code>REST</code> 协议的缺点，因此如果我们正在努力维护 <code>REST</code> <code>API</code>，那么它绝对是一个可行的选择。</p><p>另一方面，<code>gRPC</code> 是一个高性能、开源的协议，常用于微服务架构中。</p><p>这些协议中的每一种都有不同的用途，并且它们都可以一起使用，为 <code>Web</code> 应用程序提供全面且高效的通信系统。</p><p>这是我认为每个 <code>Java</code> 开发人员都应该准备的面试问题，但如果大家想要了解更多，还可以准备微服务面试问题，例如<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API Gateway 和 Load Balancer 的区别</a>、<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA 模式</a>、<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">如何在微服务中管理事务</a> 以及 <a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">SAGA 和 CQRS 模式的区别</a>，它们在面试中很受欢迎。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-rest-graphql-and-grpc-10ac365462b8&quot;&gt;https://medium.
      
    
    </summary>
    
    
      <category term="web开发" scheme="https://dongzl.github.io/categories/web%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="gRPC" scheme="https://dongzl.github.io/tags/gRPC/"/>
    
      <category term="REST" scheme="https://dongzl.github.io/tags/REST/"/>
    
      <category term="GraphQL" scheme="https://dongzl.github.io/tags/GraphQL/"/>
    
  </entry>
  
  <entry>
    <title>RabbitMQ、Apache Kafka 和 ActiveMQ 之间的差异</title>
    <link href="https://dongzl.github.io/2023/06/16/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/"/>
    <id>https://dongzl.github.io/2023/06/16/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/</id>
    <published>2023-06-16T15:14:51.000Z</published>
    <updated>2023-12-31T06:59:02.161Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/difference-between-rabbitmq-apache-kafka-and-activemq-65e26b923114">https://medium.com/javarevisited/difference-between-rabbitmq-apache-kafka-and-activemq-65e26b923114</a></p></blockquote><p>朋友们大家好，如果大家正在通过学习 <code>Spring Boot</code> 和微服务知识来准备 <code>Java</code> 开发岗位的面试，我建议还应该准备一下消息代理、<code>Kafka</code>、<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 等框架知识，比如 <strong>Kafka、RabbitMQ 和 ActiveMQ 之间有什么差异</strong>？这也是 <code>Java</code> 面试的热门问题之一。</p><p>在我的上一篇文章中，我分享了<a href="https://medium.com/javarevisited/difference-between-jwt-oauth-and-saml-for-authentication-and-authorization-in-web-apps-75b412754127">JWT、OAuth 和 SAML 之间的差异</a> 和 <a href="https://medium.com/javarevisited/difference-between-rest-graphql-and-grpc-10ac365462b8">REST、GraphQL 和 gRPC 之间的差异</a> ；在这篇文章中，我将分享我对 <code>Kafka</code>、<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 这三个流行的<a href="https://medium.com/javarevisited/how-microservices-communicates-with-each-other-synchronous-vs-asynchronous-communication-pattern-31ca01027c53">异步通信</a>代理框架的一些理解。</p><p>消息系统在现代分布式架构中，在通过网络相互通信的分布式应用程序和服务中扮演着至关重要的角色。消息系统可以使消息发送方和消息接收方解耦，从而实现<a href="https://medium.com/javarevisited/how-microservices-communicates-with-each-other-synchronous-vs-asynchronous-communication-pattern-31ca01027c53">异步通信</a> 。<code>RabbitMQ</code>、<code>Apache Kafka</code> 和 <code>ActiveMQ</code> 是业界使用的三个流行消息队列框架。在本文中，我们将讨论 <code>RabbitMQ</code>、<code>Apache Kafka</code> 和 <code>ActiveMQ</code> 之间的差异。</p><p>顺便说一下，如果大家正在准备 <code>Java</code> 开发岗位的面试，还可以查看我之前发布的关于 <a href="https://medium.com/javarevisited/21-software-design-pattern-interview-questions-and-answers-b7d1774b5dd2">21 个软件设计模式问题</a>、<a href="https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-experienced-developers-3391e4f6b591">10 个基于微服务场景的问题</a>、<a href="https://medium.com/javarevisited/20-sql-queries-for-programming-interviews-a7b5a7ea8144">20 个 SQL 查询面试题</a>、<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务问题</a>、<a href="https://medium.com/javarevisited/top-60-tree-data-structure-coding-interview-questions-every-programmer-should-solve-89c4dbda7c5a">60 个关于树的数据结构问题</a>、<a href="https://medium.com/javarevisited/7-system-design-problems-to-crack-software-engineering-interviews-in-2023-13a518467c3e">15 个系统设计问题</a>、<a href="https://medium.com/javarevisited/top-10-java-interview-questions-for-3-to-4-years-experienced-programmers-c4bf6d8b5e7b">35 个 Java 核心问题</a> 和 <a href="https://medium.com/javarevisited/21-lambda-and-stream-interview-questions-for-java-programmers-38d7e83b5cac">21 个 Lambda 和 Stream 问题</a> 等文章，这些文章包含大量常见问题，可以帮助大家更好地准备面试。</p><hr><h2 id="什么是-RabbitMQ，可以在什么地方使用它？"><a href="#什么是-RabbitMQ，可以在什么地方使用它？" class="headerlink" title="什么是 RabbitMQ，可以在什么地方使用它？"></a>什么是 RabbitMQ，可以在什么地方使用它？</h2><p><code>RabbitMQ</code> 是一个实现高级消息队列协议 (<code>AMQP</code>) 标准的开源消息代理框架。它是用 <code>Erlang</code> 语言编写的，具备可插拔的体系结构，可以轻松进行扩展。</p><p><code>RabbitMQ</code> 支持多种消息传输模式，例如<strong>发布/订阅</strong>、<strong>请求/应答</strong>和<strong>点对点</strong>模式，并且它具有一组强大的功能，例如<strong>消息确认机制</strong>、<strong>消息路由</strong>和<strong>消息队列</strong>。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/01.png"><hr><h2 id="什么是-Apache-Kafka，可以在什么地方使用它？"><a href="#什么是-Apache-Kafka，可以在什么地方使用它？" class="headerlink" title="什么是 Apache Kafka，可以在什么地方使用它？"></a>什么是 Apache Kafka，可以在什么地方使用它？</h2><p><code>Apache Kafka</code> 是一个开源分布式事件流平台，最初由 <code>LinkedIn</code> 公司开发。<code>Kafka</code> 是用 <code>Scala</code> 和 <code>Java</code> 语言编写的，旨在处理大规模流式数据。</p><p><code>Kafka</code> 使用<strong>发布/订阅消息传输模型</strong>，并针对高吞吐量、低延迟和容错方面进行了优化。</p><p><code>Kafka</code> 支持持久化的消息传递模型，这意味着消息可以存储在磁盘上并且可以进行多次重放。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/02.png"><hr><h2 id="什么是-ActiveMQ，可以在什么地方使用它？"><a href="#什么是-ActiveMQ，可以在什么地方使用它？" class="headerlink" title="什么是 ActiveMQ，可以在什么地方使用它？"></a>什么是 ActiveMQ，可以在什么地方使用它？</h2><p><code>Apache ActiveMQ</code> 是一个开源消息代理框架，它实现了 <code>Java</code> 消息服务（<code>JMS</code>）的 <code>API</code>。<code>ActiveMQ</code> 是用 <code>Java</code> 语言编写的，具有可插拔的体系结构，可以轻松实现扩展。</p><p><code>ActiveMQ</code> 支持多种消息传输模型，例如<strong>点对点</strong>、<strong>发布/订阅</strong>和<strong>请求/应答</strong>模式，并且它具备一组强大的功能，例如<strong>消息确认机制</strong>、<strong>消息路由</strong>和<strong>消息队列</strong>。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/03.gif"><hr><h2 id="RabbitMQ、Apache-Kafka-和-ActiveMQ-之间的差异"><a href="#RabbitMQ、Apache-Kafka-和-ActiveMQ-之间的差异" class="headerlink" title="RabbitMQ、Apache Kafka 和 ActiveMQ 之间的差异"></a>RabbitMQ、Apache Kafka 和 ActiveMQ 之间的差异</h2><p>现在大家已经对 <code>RabbitMQ</code>、<code>ActiveMQ</code> 和 <code>Apache Kafka</code> 有了清晰地认识，是时候找出它们之间从消息传输模型到性能之间的差异了。以下是 <code>Apache Kafka</code>、<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 之间的主要差异：</p><h3 id="1-消息模型"><a href="#1-消息模型" class="headerlink" title="1. 消息模型"></a>1. 消息模型</h3><p><code>RabbitMQ</code> 和 <code>ActiveMQ</code> 都支持 <code>JMS</code> 的 <code>API</code>，这意味着它们遵循传统的消息传输模型，消息被发送到队列或主题并由一个或多个消费者消费。</p><p>另一方面，<em><code>Kafka</code> 使用发布/订阅消息传输模型</em>，将消息发布到主题并由一个或多个订阅者消费。</p><p><code>RabbitMQ</code> 和 <code>ActiveMQ</code> 使用的是传统消息传输模型，非常适合需要严格排序和可靠消息传递的应用程序。</p><p>另一方面，<code>Kafka</code> 使用的发布/订阅消息模型更适合流式数据，需要实时处理数据的场景。</p><p>下图是一张很好的对比图，它突出展示了 <code>Kafka</code> 和 <code>RabbitMQ</code> 之间的架构差异：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/04.png"><hr><h2 id="2-可扩展性"><a href="#2-可扩展性" class="headerlink" title="2. 可扩展性"></a>2. 可扩展性</h2><p>可扩展性是消息传输系统的基本要求，尤其是在处理海量数据时。<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 都具备可扩展性，但它们实现可扩展性的方法并不不同。</p><p><code>RabbitMQ</code> 使用集群的方式来实现可扩展性，其中多个 <code>RabbitMQ</code> <code>broker</code> 彼此连接形成一个集群。消息分布在整个集群中，消费者可以连接到集群中的任意一个代理来消费消息。<code>RabbitMQ</code> 还支持联邦，允许多个 <code>RabbitMQ</code> 集群连接在一起。</p><p><code>ActiveMQ</code> 使用网络代理方法来实现可扩展性，其中多个 <code>ActiveMQ</code> 代理连接形成一个网络。消息分布在整个网络中，消费者可以连接到网络中的任何代理节点来消费消息。<code>ActiveMQ</code> 还支持<strong>主/从</strong>复制，为消息代理节点提供高可用性。</p><p>另一方面，<code>Kafka</code> 具备开箱即用的高可扩展性。<code>Kafka</code> 使用分区方法来实现可扩展性，其中消息在多个 <code>Kafka</code> 代理之间进行分区，每个分区都被复制到多个代理节点中实现容错，这种方法允许 <code>Kafka</code> 处理海量数据，同时保证低延迟和高吞吐量性。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/05.png"><hr><h3 id="3-性能"><a href="#3-性能" class="headerlink" title="3. 性能"></a>3. 性能</h3><p>性能是选择消息队列时需要考虑的另外一个关键因素，<code>RabbitMQ</code>、<code>Kafka</code> 和 <code>ActiveMQ</code> 都具备不同的性能特征。</p><p><code>RabbitMQ</code> 被设计成一个可靠的消息传输系统，这意味着它优先考虑消息传输而不是性能。<code>RabbitMQ</code> 处理的消息速率适中，适用于需要严格排序和可靠传输消息的应用程序场景。</p><p>另一方面，<code>Kafka</code> 专为高性能而设计，可以在低延迟下处理海量数据。<code>Kafka</code> 使用分布式架构和优化顺序 <code>I/O</code> 来提高性能。</p><p><code>ActiveMQ</code> 也是为高性能而设计的，可以以较高速率处理消息。<code>ActiveMQ</code> 通过使用异步架构和批处理方案来实现高性能。</p><p>这是来自 <a href="https://www.confluent.io/blog/kafka-fastest-messaging-system/">confluent 的图表</a>，比较了 <code>Apache Kafka</code>、<code>Pulsar</code> 和 <code>RabbitMQ</code> 的性能：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/06.png"><hr><h3 id="4-数据持久化"><a href="#4-数据持久化" class="headerlink" title="4. 数据持久化"></a>4. 数据持久化</h3><p>数据持久化是消息系统的一个重要特性，因为它允许消息系统在出现故障时存储并再次处理消息。<code>RabbitMQ</code>、<code>Kafka</code> 和 <code>ActiveMQ</code> 都有不同的数据持久化方法。</p><p><code>RabbitMQ</code> 默认情况下将消息存储在磁盘上，这使得即使代理节点关闭也可以持久化保存消息。<code>RabbitMQ</code> 还支持不同类型的存储后端，包括内存存储，它可以在支持数据持久化同时还能提供良好的性能。</p><p><code>Kafka</code> 默认将消息存储在磁盘上，并使用基于日志的架构来实现持久性和可靠性。<code>Kafka</code> 将消息保留一段时间，这个时间是可配置的，这使得在必要时可以重放消息。</p><p><code>ActiveMQ</code> 也是默认将消息存储在磁盘上，并支持不同的存储后端，包括 <code>JDBC</code> 和基于文件的存储。<code>ActiveMQ</code> 可以将消息存储在数据库中，以牺牲一部分性能为代价提供更好的数据持久性。</p><p>这是来自 <code>IBM</code> 网站的一张图片，它很好地展示了 <code>Kafka</code> 的架构：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/18-Difference-Between-RabbitMQ-Kafka-ActiveMQ/07.png"><p><font color="DarkGray" size="2">image — <a href="https://ibm-cloud-architecture.github.io/refarch-eda/technology/kafka-overview/">https://ibm-cloud-architecture.github.io/refarch-eda/technology/kafka-overview/</a></font></p><hr><h3 id="5-与其他系统集成"><a href="#5-与其他系统集成" class="headerlink" title="5. 与其他系统集成"></a>5. 与其他系统集成</h3><p>与其他系统的集成是选择消息系统时需要考虑的一个重要因素。<code>RabbitMQ</code>、<code>Kafka</code> 和 <code>ActiveMQ</code> 都具有不同的集成能力。</p><p><code>RabbitMQ</code> 能够很好地支持不同的编程语言，包括 <code>Java</code>、<code>Python</code>、<code>Ruby</code> 和 <code>.NET</code>。<code>RabbitMQ</code> 同时支持插件功能，允许使用插件与不同的系统集成，包括<strong>数据库</strong>、<strong>Web 服务器</strong>和<strong>消息代理</strong>。</p><p><code>Kafka</code> 能够与不同的数据处理系统很好地集成到一起，包括 <code>Apache Spark</code>、<code>Apache Storm</code> 和 <code>Apache Flink</code>，<code>Kafka</code> 还支持连接器框架，允许它与不同的数据库和数据源集成。</p><p><code>ActiveMQ</code> 能够与不同的 <code>JMS</code> 客户端很好地集成，包括 <code>Java</code>、<code>.NET</code> 和 <code>C</code>。<code>ActiveMQ</code> 还支持与不同系统集成的插件，包括 <code>Apache Camel</code> 和 <code>Apache CXF</code>。</p><p>这里整理了一个表格，这个表格很好地展示了 <code>Kafka</code>、<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 之间的差异：</p><table><thead><tr><th>特性</th><th>RabbitMQ</th><th>Apache Kafka</th><th>ActiveMQ</th></tr></thead><tbody><tr><td>消息模型</td><td>传统消息模型</td><td>发布/订阅模型</td><td>传统消息模型</td></tr><tr><td>可扩展性</td><td>集群/网络代理节点</td><td>分区模型</td><td>集群/网络代理节点</td></tr><tr><td>性能</td><td>中等</td><td>高</td><td>高</td></tr><tr><td>数据持久化</td><td>磁盘模式（默认设置）、内存模式</td><td>磁盘模式</td><td>磁盘模式（默认设置）、数据库模式</td></tr><tr><td>集成能力</td><td>支持各种编程语言、数据库、<code>Web</code> 服务器</td><td>数据处理系统、数据库、其他数据源</td><td><code>JMS</code> 客户端、<code>Apache Camel</code> 和 <code>Apache CXF</code></td></tr><tr><td>适用场景</td><td>消息严格有序、可靠传输、消息处理速率适中场景</td><td>流式数据、消息处理速率要求较高场景</td><td>数据持久性要求比较高、高性能场景</td></tr></tbody></table><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这就是 <code>Apache Kafka</code>、<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 之间的差异。<code>RabbitMQ</code>、<code>Apache Kafka</code> 和 <code>ActiveMQ</code> 是三种流行的消息系统框架，它们具备不同的功能和特性。</p><p><code>RabbitMQ</code> 和 <code>ActiveMQ</code> 遵循传统的消息传输模型，而 <code>Kafka</code> 使用的是发布/订阅消息传输模型。<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 使用集群和网络代理方案来实现可扩展性，而 <code>Kafka</code> 使用分区方式实现可扩展性。</p><p><code>RabbitMQ</code> 优先考虑消息传递可靠性而不是性能，而 <code>Kafka</code> 和 <code>ActiveMQ</code> 优先考虑性能。<code>RabbitMQ</code>、<code>Kafka</code>、<code>ActiveMQ</code> 都具备不同的数据持久化和与其他框架的集成能力。</p><p>选择消息系统时，必须考虑应用程序或系统的具体要求。<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 适用于要求消息严格有序和消息可靠传输的应用场景，而 <code>Kafka</code> 适用于流式数据场景。</p><p><code>RabbitMQ</code> 和 <code>ActiveMQ</code> 适用于对消息处理速率要求不高的应用场景，而 <code>Kafka</code> 适用于对消息处理速率要求较高的应用场景。</p><p>同样，<code>RabbitMQ</code> 和 <code>ActiveMQ</code> 适用于对数据持久性要求比较高的应用场景，而 <code>Kafka</code> 适用于对性能要求比较高的应用场景。</p><p>这是我认为每个 <code>Java</code> 开发人员都应该准备的面试问题，但如果大家想要了解更多，还可以准备微服务面试问题，例如<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API Gateway 和 Load Balancer 的区别</a>、<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA 模式</a>、<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">如何在微服务中管理事务</a> 以及 <a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">SAGA 和 CQRS 模式的区别</a>，它们在面试中很受欢迎。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-rabbitmq-apache-kafka-and-activemq-65e26b923114&quot;&gt;ht
      
    
    </summary>
    
    
      <category term="web开发" scheme="https://dongzl.github.io/categories/web%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="Kafka" scheme="https://dongzl.github.io/tags/Kafka/"/>
    
      <category term="RabbitMQ" scheme="https://dongzl.github.io/tags/RabbitMQ/"/>
    
      <category term="ActiveMQ" scheme="https://dongzl.github.io/tags/ActiveMQ/"/>
    
  </entry>
  
  <entry>
    <title>Web 应用程序中用于身份认证和授权的 JWT、OAuth 和 SAML 之间的差异对比？</title>
    <link href="https://dongzl.github.io/2023/06/10/17-Difference-JWT-OAuth-SAML-Authentication-Authorization/"/>
    <id>https://dongzl.github.io/2023/06/10/17-Difference-JWT-OAuth-SAML-Authentication-Authorization/</id>
    <published>2023-06-10T14:34:53.000Z</published>
    <updated>2023-12-31T06:59:02.157Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/difference-between-jwt-oauth-and-saml-for-authentication-and-authorization-in-web-apps-75b412754127">https://medium.com/javarevisited/difference-between-jwt-oauth-and-saml-for-authentication-and-authorization-in-web-apps-75b412754127</a></p></blockquote><p>朋友们大家好，许多 <code>Java</code> 开发人员在面试中会被问到 <code>JWT</code>、<code>OAuth2.0</code> 和 <code>SAML</code> 之间的差异，以及何时使用它们？这是最常见的问题之一，如果大家正在准备 <code>Java</code> 开发人员岗位面试并恰巧被问到这个问题，那么正好可以来这篇文章中寻找答案。</p><p>在前面的文章中，我分享了一些 <code>Java</code> 面试内容，例如 <a href="https://medium.com/javarevisited/21-software-design-pattern-interview-questions-and-answers-b7d1774b5dd2">21 个软件设计模式问题</a>、<a href="https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-experienced-developers-3391e4f6b591">10 个基于微服务场景的问题</a>、<a href="https://medium.com/javarevisited/20-sql-queries-for-programming-interviews-a7b5a7ea8144">20 个 SQL 查询面试题</a>、<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务问题</a>、<a href="https://medium.com/javarevisited/top-60-tree-data-structure-coding-interview-questions-every-programmer-should-solve-89c4dbda7c5a">60 个关于树的数据结构问题</a>、<a href="https://medium.com/javarevisited/7-system-design-problems-to-crack-software-engineering-interviews-in-2023-13a518467c3e">15 个系统设计问题</a>、<a href="https://medium.com/javarevisited/top-10-java-interview-questions-for-3-to-4-years-experienced-programmers-c4bf6d8b5e7b">35 个 Java 核心问题</a> 和 <a href="https://medium.com/javarevisited/21-lambda-and-stream-interview-questions-for-java-programmers-38d7e83b5cac">21 个 Lambda 和 Stream 问题</a>，在这篇文章中我将会系统地回答这个面试中常见问题。</p><p>虽然 <code>JWT</code>、<code>OAuth</code> 和 <code>SAML</code> 都是众所周知的用于 <code>Web</code> 应用程序中的身份认证和授权的标准，但是它们之间还是存在很多差异。</p><p>比如，<code>JWT</code> 表示 <code>JSON Web Token</code>，它是<em>使用 <code>JSON</code> 对象在各方之间安全传输信息的标准</em>。它用于对用户身份进行认证和授权，通常用于 <code>Web</code> 应用程序，因为 <code>JWT</code> 是经过数字签名的，所以它们可以被验证和信任。</p><p>另一方面，<code>OAuth</code>（开放授权）是一种<em>开放的授权协议标准，允许第三方应用程序访问用户数据而无需共享用户登录凭证</em>。它通常用于外部服务需要访问数据的应用程序，例如社交媒体平台或 <code>API</code>。</p><p>同样，<code>SAML</code>（安全断言标记语言）是另一种<em>用于在各方之间交换身份认证和授权数据的标准，特别是在身份提供者 (<code>IdP</code>) 和服务提供者 (<code>SP</code>) 之间</em>。它通常用于企业应用程序中以提供单点登录 (<code>SSO</code>) 功能。</p><p><code>SAML</code> 最著名的案例之一是被新加坡政府网站使用的 <code>SingPass</code> 身份认证服务，用来访问<code>疫苗证书</code>、<code>CPF</code>、<code>IRAS</code> 等政府网站。</p><p>现在我们对这些知识有了一些基本了解，是时候深入了解并细致地学习这些内容，以便于我们可以回答面试中关于这些技术的任何问题。</p><hr><h2 id="什么是-JWT（JSON-Web-Token）？何时需要使用它？"><a href="#什么是-JWT（JSON-Web-Token）？何时需要使用它？" class="headerlink" title="什么是 JWT（JSON Web Token）？何时需要使用它？"></a>什么是 JWT（JSON Web Token）？何时需要使用它？</h2><p>正如前面所说，<code>JWT</code> 表示 <code>JSON Web Token</code>，这是一种用于在各方之间安全传输信息的令牌。<code>JWT</code> 通常用于 <code>Web</code> 应用程序中的身份认证和授权。</p><p><code>JWT</code> 由三部分组成：<strong>标头</strong>、<strong>有效负载</strong>和<strong>签名</strong>。标头指定令牌的类型和使用的签名算法，而有效负载包含正在传输的实际数据。</p><blockquote><p>签名是将标头和有效负载与只有服务器所信任的密钥组合在一起所生成的。</p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/17-Difference-JWT-OAuth-SAML-Authentication-Authorization/01.png"><p><code>JWT</code> 通常用于 <code>Web</code> 应用程序中，作为在客户端和服务器之间传输用户身份认证数据的一种方式。当用户登录时，服务器会生成一个包含用户 <code>ID</code> 和相关权限或角色信息的 <code>JWT</code>，然后将此令牌返回给客户端，客户端将其存储后并应用到对服务器的后续请求中。</p><blockquote><p>服务器会验证 JWT 的真实性并使用其中包含的信息来确认用户是否被授权执行请求的操作。</p></blockquote><p><code>JWT</code> 在多个服务需要共享用户身份认证信息的<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">分布式系统</a>中也很有用，每个服务不需要都维护自己的身份认证系统，而是可以使用某个 <code>JWT</code> 对所有服务中的用户进行身份认证。</p><p>总的来说，当我们需要以安全高效的方式在各方之间传输敏感信息时，<code>JWT</code> 非常有用，尤其是在传统的基于会话的身份验证不可行的情况下。</p><p>下图很好地<strong>解释了 <code>JWT</code>（<code>JSON Web Token</code>）如何在 <code>Web</code> 应用程序中工作</strong>：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/17-Difference-JWT-OAuth-SAML-Authentication-Authorization/02.png"><hr><h2 id="什么是-OAuth2-0？何时需要使用它？"><a href="#什么是-OAuth2-0？何时需要使用它？" class="headerlink" title="什么是 OAuth2.0？何时需要使用它？"></a>什么是 OAuth2.0？何时需要使用它？</h2><p><a href="https://oauth.net/2/">OAuth2.0</a> 是一种用于 <code>Web</code> 应用和移动应用程序中的授权和身份认证的协议，它允许用户授权第三方应用程序访问他们的资源，例如用户的社交媒体账户或其它在线服务，但是不会泄露他们的凭据或密码。</p><p><code>OAuth2.0</code> 通过在用户、第三方应用程序和资源服务器之间建立信任关系来工作。该过程涉及几个步骤：</p><ol><li>用户通过尝试访问资源服务器上的受保护资源来启动该过程，例如登录社交媒体账户；</li><li>资源服务器通过将用户重定向到授权服务器进行响应，它们可以在授权服务器上授予第三方应用程序访问其资源的权限；</li><li>然后用户登录到授权服务器并授予第三方应用程序访问其资源的权限；</li><li>授权服务器生成访问令牌并将其发送给第三方应用程序；</li><li>第三方应用程序使用访问令牌来请求和访问资源服务器上用户的资源。</li></ol><p><strong><code>OAuth2.0</code> 在用户想要授予第三方应用程序访问其资源但又不想泄露其凭据或密码的情况下非常有用</strong>。它通常用于 <code>Web</code> 和移动应用程序，尤其是那些依赖外部 <code>API</code> 或服务来获取数据和使用某些功能的应用程序。</p><p>下图很好地解释了 <code>OAuth2.0</code> 的工作流程和工作方式：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/17-Difference-JWT-OAuth-SAML-Authentication-Authorization/03.jpg"><p><code>OAuth2.0</code> 还有助于确保用户保留对其资源的控制权并可以随时撤销访问权限。它为用户提供了一种安全和标准化的方式来与第三方应用程序共享他们的数据和资源，同时也能够维护用户的隐私和安全。</p><p>例如现在大多数网站都允许用户使用 <code>Twitter</code>、<code>Facebook</code> 或 <code>Google</code> 账户登录，这种场景下就可以使用 <code>OAuth</code> 授权访问的方式，用户无需创建新的用户名或密码，也无需共享个人的 <code>Google</code>、<code>Twitter</code> 或 <code>Facebook</code> 密码即可访问任何第三方网站上的资源。</p><hr><h2 id="什么是-SAML？什么时候使用它？"><a href="#什么是-SAML？什么时候使用它？" class="headerlink" title="什么是 SAML？什么时候使用它？"></a>什么是 SAML？什么时候使用它？</h2><p><code>SAML</code> 表示安全断言标记语言，是一种基于 <code>XML</code> 的协议，用于在<strong>身份提供者</strong> (<code>IdP</code>) 和<strong>服务提供者</strong> (<code>SP</code>) 等各方之间交换身份认证和授权数据。</p><p><code>SAML</code> 通过在 <code>IdP</code> 和 <code>SP</code> 之间建立信任关系来工作。<code>IdP</code> 负责对用户进行身份认证并生成 <code>SAML</code> 断言，其中包含有关用户身份和身份认证状态的信息。<code>SP</code> 依靠 <code>SAML</code> 断言做出授权决定并授予对受保护资源的访问权限。</p><p><code>SAML</code> 可用于多种场景，例如单点登录 (<code>SSO</code>) 和联合身份管理。</p><p>在 <code>SSO</code> 中，<code>SAML</code> 用于允许用户使用一组凭据访问多个应用程序或服务。当用户登录到一个应用程序或服务时，<code>IdP</code> 会生成一个 <code>SAML</code> 断言，该断言可用于对其他应用程序或服务的用户进行身份认证，并且无需用户再次登录。</p><p>在联合身份管理中，<code>SAML</code> 用于启用组织之间的信任关系，允许用户使用一组凭据访问多个组织的资源。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/17-Difference-JWT-OAuth-SAML-Authentication-Authorization/04.png"><p><code>SAML</code> 通常用于企业环境，用户需要跨不同域或组织访问多个应用程序和服务。它提供了一种标准化的方式来传输身份认证和授权数据，从而更容易做到访问控制管理并确保敏感资源的安全。</p><p><code>SAML</code> 应用的一个著名案例是新加坡政府的 <code>SingPass</code> 身份验证系统，它允许用户使用 <code>Singapss</code> 系统登录访问所有政府网站，如 <code>CPF</code> 和 <code>IRAS</code>。</p><p>总的来说，<code>SAML</code> 是一个强大的工具，可以保护跨不同的组织和应用程序的安全、无缝地访问资源，使其成为许多企业和组织的热门选择。</p><h2 id="Web-应用程序中用于身份认证和授权的-JWT、OAuth-和-SAML-之间的差异"><a href="#Web-应用程序中用于身份认证和授权的-JWT、OAuth-和-SAML-之间的差异" class="headerlink" title="Web 应用程序中用于身份认证和授权的 JWT、OAuth 和 SAML 之间的差异"></a>Web 应用程序中用于身份认证和授权的 JWT、OAuth 和 SAML 之间的差异</h2><p>现在我们知道了这些技术都是什么、它们是如何工作的以及它们的使用场景，是时候对比一下它们之间的主要差异了。以下是 <code>JWT</code>、<code>OAuth</code> 和 <code>SAML</code> 在几个关键点上的主要区别：</p><h3 id="JWT"><a href="#JWT" class="headerlink" title="JWT"></a>JWT</h3><ul><li><code>JWT</code> 是一种基于令牌的身份验证机制；</li><li>它用于在各方之间传输信息（用户身份、权限等）；</li><li>它不依赖于集中式身份认证服务器或会话状态；</li><li>它通常用于单页应用程序 (<code>SPA</code>) 或移动应用程序；</li><li>它可用于身份认证和授权。</li></ul><h3 id="OAuth"><a href="#OAuth" class="headerlink" title="OAuth"></a>OAuth</h3><ul><li><code>OAuth</code> 是一种用于 <code>Web</code> 和移动端应用程序中的授权和身份认证的协议；</li><li>它通常用于用户授权第三方应用程序对资源的访问权限；</li><li>它依赖于集中式授权服务器；</li><li>它通常用于依赖外部 <code>API</code> 或服务获取数据和功能的应用程序；</li><li>它常用于授权，而不是进行身份认证。</li></ul><h3 id="SAML"><a href="#SAML" class="headerlink" title="SAML"></a>SAML</h3><ul><li><code>SAML</code> 是一种用于在身份提供者 (<code>IdP</code>) 和服务提供者 (<code>SP</code>) 等各方之间交换身份认证和授权数据的协议；</li><li>它用于在组织之间建立信任关系并启用单点登录 (<code>SSO</code>) 和联合身份管理；</li><li>它依赖于集中式身份提供者 (<code>IdP</code>)；</li><li>它通常用于企业环境；</li><li>它用于身份认证和授权。</li></ul><p>下面的表格内容总结地很好，大家可以打印出来，帮助记忆 <code>JWT</code>、<code>SAML</code> 和 <code>OAuth2.0</code> 之间的差异。</p><table><thead><tr><th></th><th>JWT</th><th>OAuth</th><th>SAML</th></tr></thead><tbody><tr><td>目标</td><td>用基于令牌的身份验证机制来传输身份认证信息</td><td><code>Web</code> 应用和移动端应用中认证和授权协议</td><td>在多个模块之间交换认证和授权协议的协议</td></tr><tr><td>中心化服务</td><td>无中心化服务</td><td>中心化认证服务</td><td>中心化身份提供者</td></tr><tr><td>应用场景</td><td>认证和授权</td><td>只做授权，不做认证</td><td>认证和授权</td></tr><tr><td>应用程序</td><td>单页应用程序，移动端应用</td><td>需要依赖于外部 <code>API</code> 或服务的应用程序</td><td>企业级应用环境</td></tr><tr><td>关联关系</td><td>各方之间直接交互</td><td>在第三方应用程序和资源拥有者之间交互</td><td>在身份提供者和服务提供者之间交互</td></tr><tr><td>认证能力</td><td>有</td><td>无</td><td>有</td></tr><tr><td>授权能力</td><td>有</td><td>有</td><td>有</td></tr></tbody></table><p>综上所述，<code>JWT</code> 是一种基于令牌的身份认证机制，通常用于传递认证信息，<code>OAuth</code> 是一种用于用户授予第三方应用程序访问资源的协议，而 <code>SAML</code> 是一种用于在各方之间交换身份认证和授权数据的协议建立组织间的信任关系。</p><hr><p>这就是 <code>JWT</code>、<code>OAuth</code> 和 <code>SAML</code> 在身份认证和授权方面的区别。简而言之，<code>JWT</code> 是一种在各方之间安全传输数据的标准，而 <code>OAuth</code> 是一种允许第三方应用程序访问用户数据的授权标准。</p><p><code>SAML</code> 是用于在 <code>IdP</code> 和 <code>SP</code> 之间交换身份认证和授权数据的标准，通常用于企业应用程序。这些标准中的每一个都有不同的用途，它们可以一起使用，为 <code>Web</code> 应用程序提供安全高效的身份认证和授权过程。</p><p>此外，你还可以准备一些微服务问题，比如<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API Gateway 和 Load Balancer 的区别</a>、<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA 模式</a>、<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">如何在微服务中管理事务</a> 以及 <a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">SAGA 和 CQRS 模式的区别</a>，它们在面试中很受欢迎。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-jwt-oauth-and-saml-for-authentication-and-authoriza
      
    
    </summary>
    
    
      <category term="web开发" scheme="https://dongzl.github.io/categories/web%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="JWT" scheme="https://dongzl.github.io/tags/JWT/"/>
    
      <category term="OAuth" scheme="https://dongzl.github.io/tags/OAuth/"/>
    
      <category term="SAML" scheme="https://dongzl.github.io/tags/SAML/"/>
    
  </entry>
  
  <entry>
    <title>Hibernate、JPA 和 Spring Data JPA 之间对比</title>
    <link href="https://dongzl.github.io/2023/06/03/16-Difference-Between-Hibernate-JPA-Spring-Data-JPA/"/>
    <id>https://dongzl.github.io/2023/06/03/16-Difference-Between-Hibernate-JPA-Spring-Data-JPA/</id>
    <published>2023-06-03T12:33:45.000Z</published>
    <updated>2023-12-31T06:59:02.157Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/difference-between-hibernate-jpa-and-spring-data-jpa-7df55717692f">https://medium.com/javarevisited/difference-between-hibernate-jpa-and-spring-data-jpa-7df55717692f</a></p></blockquote><p>朋友们大家好，如果大家正在准备 <code>Java</code> 开发岗位面试，那么除了准备 <code>Core Java</code>、<code>Spring Boot</code> 和微服务之外，我们还应该准备一些其他内容，比如 <code>ORM</code> 内容，<code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 这些框架知识，例如 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间的区别？这也是 <code>Java</code> 面试的热门问题之一。</p><p>在前面的文章中，我分享了<a href="https://medium.com/javarevisited/difference-between-jwt-oauth-and-saml-for-authentication-and-authorization-in-web-apps-75b412754127">JWT、OAuth 和 SAML 之间的区别</a>、<a href="https://medium.com/javarevisited/difference-between-rabbitmq-apache-kafka-and-activemq-65e26b923114">Kafka 与 RabbitMQ之间的区别</a> 以及 <a href="https://medium.com/javarevisited/difference-between-rest-graphql-and-grpc-10ac365462b8">REST、GraphQL 与 gRPC 之间的区别</a> 这几篇文章；在本文中我将分享我对 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 的理解，学习 <code>Java</code> 应用程序访问数据库的三个流行框架。</p><p>在开发与数据库交互的 <code>Java</code> 应用程序时，开发人员通常依赖某些框架和 <code>API</code> 来简化数据持久化的过程；虽然 <code>JDBC</code> 来自 <code>Java</code>，但是由于 <code>API</code> 的过度设计或者设计不足，<code>JDBC</code> 并不一定是最佳选择。</p><p>在 <code>Java</code> 中处理对象关系映射 (<code>ORM</code>) 的三个主流框架是 <code>Hibernate</code>、<code>JPA</code>（<code>Java Persistence API</code>）和 <code>Spring Data JPA</code>。虽然这些框架有一些相似之处，但是了解它们的特性差异、实现原理和优缺点是非常重要的。</p><p>在本文中，我们将学习 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间的区别，将会探讨它们的定义、实现、持久性 <code>API</code>、数据库支持、事务管理、查询语言、缓存机制、配置选项、与 <code>Spring</code> 框架的集成以及其他功能特性。</p><p>通过了解这些区别，我们可以为项目选择最合适的框架，并利用这些框架提供的特性做出最佳的选择。</p><p>无论我们是开始使用 <code>ORM</code> 框架的 <code>Java</code> 开发人员，还是希望加强对 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 的理解，本文旨在提供深入的研究和功能比较，来帮助大家应对 <code>Java</code> 应用程序开发中数据持久化的复杂性。</p><p>首先我们深入研究每个框架并揭示出使它们与众不同的独特功能。</p><p>顺便说一下，如果大家正在准备 <code>Java</code> 开发岗位的面试，还可以查看我之前发布的关于 <a href="https://medium.com/javarevisited/21-software-design-pattern-interview-questions-and-answers-b7d1774b5dd2">21 个软件设计模式问题</a>、<a href="https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-experienced-developers-3391e4f6b591">10 个基于微服务场景的问题</a>、<a href="https://medium.com/javarevisited/20-sql-queries-for-programming-interviews-a7b5a7ea8144">20 个面试中常见的 SQL 查询</a>、<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务问题</a>、<a href="https://medium.com/javarevisited/top-60-tree-data-structure-coding-interview-questions-every-programmer-should-solve-89c4dbda7c5a">60 个关于树的数据结构问题</a>、<a href="https://medium.com/javarevisited/7-system-design-problems-to-crack-software-engineering-interviews-in-2023-13a518467c3e">15 个系统问题</a>、<a href="https://medium.com/javarevisited/top-10-java-interview-questions-for-3-to-4-years-experienced-programmers-c4bf6d8b5e7b">35 个 Java 核心问题</a> 和 <a href="https://medium.com/javarevisited/21-lambda-and-stream-interview-questions-for-java-programmers-38d7e83b5cac">21 个 Lambda 和 Stream 问题</a>等文章，其中包含大量常见问题，可以帮助大家更好地准备面试。</p><h2 id="1-Hibernate：强大的-ORM-框架"><a href="#1-Hibernate：强大的-ORM-框架" class="headerlink" title="1. Hibernate：强大的 ORM 框架"></a>1. Hibernate：强大的 ORM 框架</h2><p>和 <code>Spring</code> 框架一样，<code>Hibernate</code> 可能是最受 <code>Java</code> 开发人员欢迎的框架。<code>Hibernate</code> 是一个功能强大且广泛使用的 <code>ORM</code> 框架，它提供了一组广泛的功能，用于将 <code>Java</code> 对象映射到关系数据库。</p><p>它内部提供了 <code>JPA</code> 规范实现，使其成为管理 <code>Java</code> 应用程序数据持久化的综合解决方案。<code>Hibernate</code> 通过数据库方言支持各种数据库，允许开发人员无缝地迁移到各种不同的数据库系统。</p><p>借助 <code>Hibernate</code>，<em><code>Java</code> 开发人员可以使用 <code>XML</code> 配置文件、注释或者基于 <code>Java</code> 的配置来定义对象的持久化映射</em>。它提供了自己的查询语言，称为 <strong>Hibernate 查询语言（HQL）</strong>，允许开发人员使用面向对象的语法编写数据库查询，从而更轻松地处理复杂的数据关系并执行高效的数据检索。</p><p><code>Hibernate</code> 的主要优势之一是它对缓存机制的支持，它同时支持<a href="https://javarevisited.blogspot.com/2017/03/difference-between-first-and-second-level-cache-in-Hibernate.html">一级和二级缓存</a>，可以通过减少数据库交互次数来显着提高操作数据库的性能。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/16-Difference-Between-Hibernate-JPA-Spring-Data-JPA/01.png"><hr><h2 id="2-JPA：Java-持久化-API"><a href="#2-JPA：Java-持久化-API" class="headerlink" title="2. JPA：Java 持久化 API"></a>2. JPA：Java 持久化 API</h2><p>另一方面，<code>JPA</code> 其实是一种<strong>规范</strong>，它为 <code>Java</code> 中的 <code>ORM</code> 定义了一组标准 <code>API</code>；它旨在为对象关系映射定义一套统一的且与具体框架无关的接口方法，但是 <code>JPA</code> 本身不提供实现，它需要依赖具体底层实现。</p><p>开发人员可以从多种 <code>JPA</code> 框架实现中进行选择，例如 <code>Hibernate</code>、<code>EclipseLink</code> 或 <code>Apache OpenJPA</code> 等，这些框架的实现遵循 <code>JPA</code> 规范并提供各自独特的功能，并进行内部优化。</p><p><code>JPA</code> 的查询语言 <code>JPQL</code>（<code>Java Persistence Query Language</code>）类似于 <code>Hibernate</code> 的 <code>HQL</code>，允许开发人员使用基于实体的对象模型编写数据库查询，这种抽象简化了查询过程并促进了不同 <code>JPA</code> 框架实现之间的可移植性。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/16-Difference-Between-Hibernate-JPA-Spring-Data-JPA/02.png"><h2 id="3-Spring-Data-JPA：简化-JPA-开发"><a href="#3-Spring-Data-JPA：简化-JPA-开发" class="headerlink" title="3. Spring Data JPA：简化 JPA 开发"></a>3. Spring Data JPA：简化 JPA 开发</h2><p><code>Spring Data JPA</code> 建立在 <code>JPA</code> 规范之上，并提供了一种简化且直观的方法来处理 <code>Spring</code> 应用程序中的数据持久化操作；它提供了<strong>比 <code>JPA</code> 更高级别的抽象</strong>，减少了样板代码并提供了便捷的功能，例如抽象存储和查询支持。</p><p>借助 <code>Spring Data JPA</code>，<code>Java</code> 开发人员可以将存储库定义为接口，利用 <code>Spring</code> 强大的依赖注入能力自动生成必要的 <code>CRUD</code>（创建、读取、更新、删除）操作，它还支持根据特定命名约定定义的方法名称来创建自定义查询，从而无需手工编写 <code>JPQL</code> 或 <code>SQL</code> 查询。</p><p><code>Spring Data JPA</code> 与 <code>Spring</code> 生态系统无缝集成，允许开发人员利用其他 <code>Spring</code> 能力，例如事务管理、依赖项注入和声明式缓存；它还提供了一套内聚且高效的解决方案，用于在利用标准化 <code>JPA</code> <code>API</code> 的同时管理 <code>Spring</code> 应用程序中的数据持久化。</p><p>下图是一张很好的图表，它显示了 <code>Spring Data JPA</code> 与使用 <code>EntityManager</code> 的 <code>Raw JPA</code> 相比是如何工作的：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/16-Difference-Between-Hibernate-JPA-Spring-Data-JPA/03.png"><hr><h2 id="何时使用-JPA、Hibernate-或-Spring-Data-JPA？"><a href="#何时使用-JPA、Hibernate-或-Spring-Data-JPA？" class="headerlink" title="何时使用 JPA、Hibernate 或 Spring Data JPA？"></a>何时使用 JPA、Hibernate 或 Spring Data JPA？</h2><p>在 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间做出选择时，我们需要考虑多种因素。<code>Hibernate</code> 提供了一套全面的功能，包括它定制的查询语言和缓存机制。 </p><p><code>JPA</code> 提供了一种具有多种实现选择的<strong>标准化方法</strong>，提供了跨不同 <code>ORM</code> 框架的可移植性。另一方面，<code>Spring Data JPA</code> 简化了 <code>Spring</code> 生态系统中的 <code>JPA</code> 实现，提供了抽象存储和查询支持。</p><p>框架的选择取决于项目的具体要求、所需控制的层级和框架的灵活性，以及开发团队的熟悉程度和专业知识。<strong>性能评估</strong>、<strong>数据库兼容性</strong>、<strong>易用性</strong>以及与<strong>现有 Spring 应用程序的集成</strong>等因素有助于做出最佳的选择。</p><p>如果我们真正了解这些框架之间的差异，我们可以为项目选择正确的技术框架，这就是了解 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间差异的原因，对于寻求高效解决方案来管理其应用程序数据持久化的 <code>Java</code> 开发人员来说这是至关重要。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/16-Difference-Between-Hibernate-JPA-Spring-Data-JPA/04.png"><hr><h2 id="Hibernate、JPA-和-Spring-Data-JPA-之间有什么区别？"><a href="#Hibernate、JPA-和-Spring-Data-JPA-之间有什么区别？" class="headerlink" title="Hibernate、JPA 和 Spring Data JPA 之间有什么区别？"></a>Hibernate、JPA 和 Spring Data JPA 之间有什么区别？</h2><p>现在我们已经对什么是 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 以及各个框架的功能有了清晰的认识，是时候研究一下它们之间的具体区别了。以下是 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 功能列表比较：</p><h3 id="1-定义"><a href="#1-定义" class="headerlink" title="1. 定义"></a>1. 定义</h3><p><code>Hibernate</code> 是具备对象关系映射能力的强大的 <code>ORM</code> 框架，虽然 <code>JPA</code> 是 <code>Java</code> 中的 <code>ORM</code> 规范，但是只定义了一组用于持久化的标准化 <code>API</code>，而 <code>Spring Data JPA</code> 提供了对 <code>JPA</code> 的简化抽象，提供了额外的能力和抽象存储。</p><h3 id="2-实现"><a href="#2-实现" class="headerlink" title="2. 实现"></a>2. 实现</h3><p><code>Hibernate</code> 提供自己的 <code>JPA</code> 规范实现，而使用 <code>JPA</code> 需要依赖具体实现，例如 <code>Hibernate</code>、<code>EclipseLink</code> 或 <code>OpenJPA</code>；<code>Spring Data JPA</code> 构建在 <code>JPA</code> 之上，需要符合 <code>JPA</code> 的实现，例如 <code>Hibernate</code> 或 <code>EclipseLink</code>。</p><h3 id="3-持久化-API"><a href="#3-持久化-API" class="headerlink" title="3. 持久化 API"></a>3. 持久化 API</h3><p>此外，<code>JPA</code> 为 <code>Java</code> 中的 <code>ORM</code> 定义了一组标准 <code>API</code>，而 <code>Hibernate</code> 为数据持久化操作提供了自己的 <code>API</code>，而 <code>Spring Data JPA</code> 是构建在 <code>JPA</code> <code>API</code> 之上并添加了额外的特性，例如抽象存储和查询支持。</p><h3 id="4-数据库支持"><a href="#4-数据库支持" class="headerlink" title="4. 数据库支持"></a>4. 数据库支持</h3><p><code>JPA</code> 对数据库支持取决于所使用的 <code>JPA</code> 具体实现框架，框架可能具有特定的数据库支持；<code>Hibernate</code> 是通过数据库方言支持各种数据库，而 <code>Spring Data JPA</code> 也取决于所使用的 <code>JPA</code> 实现，提供与不同数据库的兼容性。</p><h3 id="5-事务管理"><a href="#5-事务管理" class="headerlink" title="5. 事务管理"></a>5. 事务管理</h3><p><code>Hibernate</code> 带有自己的事务管理功能，而 <code>JPA</code> 和 <code>Spring Data JPA</code> 都依赖于 <code>JPA</code> 具体实现框架的事务管理能力。</p><h3 id="6-查询语言"><a href="#6-查询语言" class="headerlink" title="6. 查询语言"></a>6. 查询语言</h3><p><code>Hibernate</code> 提供了用于编写面向对象查询的 <code>Hibernate</code> 查询语言（<code>HQL</code>），<code>JPA</code> 为数据库查询定义了 <code>Java</code> 持久化查询语言（<code>JPQL</code>），而 <code>Spring Data JPA</code> 使用 <code>JPQL</code> 作为查询语言，类似于 <code>JPA</code>。</p><h3 id="7-缓存"><a href="#7-缓存" class="headerlink" title="7. 缓存"></a>7. 缓存</h3><p><code>Hibernate</code> 提供一级和二级缓存机制，<code>JPA</code> 同样依赖于所使用的 <code>JPA</code> 具体实现框架，它可能提供缓存功能，而 <code>Spring Data JPA</code> 同样依赖于 <code>JPA</code> 具体实现来完成对缓存的支持。</p><h3 id="8-配置"><a href="#8-配置" class="headerlink" title="8. 配置"></a>8. 配置</h3><p><code>Hibernate</code> 支持使用 <code>XML</code>、注释或基于 <code>Java</code> 的方法进行配置，<code>JPA</code> 同样支持使用 <code>XML</code>、注释或基于 <code>Java</code> 的方法进行配置，而 <code>Spring Data JPA</code> 也支持使用 <code>XML</code>、注释或基于 <code>Java</code> 的方法进行配置。</p><h3 id="9-集成能力"><a href="#9-集成能力" class="headerlink" title="9. 集成能力"></a>9. 集成能力</h3><p><code>Hibernate</code> 可以独立使用，也可以与 <code>Spring</code> 框架集成使用；<code>JPA</code> 可以与任何符合 <code>JPA</code> 规范的框架一起使用，包括与 <code>Spring</code> 和 <code>Spring Data JPA</code> 的集成构成了 <code>Spring Data</code> 系列的一部分，旨在便于与 <code>Spring</code> 应用程序集成。</p><h3 id="10-其他特性"><a href="#10-其他特性" class="headerlink" title="10. 其他特性"></a>10. 其他特性</h3><p><code>Hibernate</code> 提供超出 <code>JPA</code> 规范的额外特性，而 <code>JPA</code> 仅提供一组标准化的 <code>API</code>；另一方面，<code>Spring Data JPA</code> 还在 <code>JPA</code> 之上提供额外的抽象存储和查询支持。</p><p>这个清单清晰地总结了 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间每个功能的区别；但是如果我们想通过表格形式查看各个框架的区别，这里还有一个非常不错的总结表格，它突出显示了 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间的关键技术差异：</p><table><thead><tr><th>特性</th><th>Hibernate</th><th>JPA</th><th>Spring Data JPA</th></tr></thead><tbody><tr><td>定义</td><td><code>ORM</code>（对象关系映射）框架</td><td><code>Java</code> 中 <code>ORM</code> 规范</td><td>对 <code>JPA</code> 的简化和抽象</td></tr><tr><td>实现</td><td>自身提供实现</td><td>仅定义规范，依赖具体实现</td><td>构建在 <code>JPA</code> 之上，提供额外的特性</td></tr><tr><td>持久化 <code>API</code></td><td><code>Hibernate</code> 提供内置 <code>API</code></td><td>为 <code>ORM</code> 定义一系列标准化 <code>API</code></td><td>构建在 <code>JPA</code> 的 <code>API</code> 之上，提供更多功能支持</td></tr><tr><td>数据库支持</td><td>通过数据库方言支持不同数据库</td><td>依赖于使用的具体 <code>JPA</code> 框架实现</td><td>依赖于使用的具体 <code>JPA</code> 框架实现</td></tr><tr><td>事务管理</td><td>提供内置的事务管理</td><td>依赖于使用的具体 <code>JPA</code> 框架实现</td><td>依赖于使用的具体 <code>JPA</code> 框架实现</td></tr><tr><td>查询语言</td><td><code>Hibernate</code> 查询语言（<code>HQL</code>）</td><td><code>JPQL</code>（<code>Java</code> 持久化查询语言）</td><td><code>JPQL</code>（<code>Java</code> 持久化查询语言）</td></tr><tr><td>缓存</td><td>提供一级和二级缓存</td><td>依赖于使用的具体 <code>JPA</code> 框架实现</td><td>依赖于使用的具体 <code>JPA</code> 框架实现</td></tr><tr><td>配置</td><td><code>XML</code>、注解或者是基于 <code>Java</code> 配置</td><td><code>XML</code>、注解或者是基于 <code>Java</code> 配置</td><td><code>XML</code>、注解或者是基于 <code>Java</code> 配置</td></tr><tr><td>集成能力</td><td>可以独立使用或者与 <code>JPA</code> 一起使用</td><td>与任何符合 <code>JPA</code> 规范实现一起工作</td><td>与任何符合 <code>JPA</code> 规范实现一起工作</td></tr><tr><td>与 <code>Spring</code> 集成能力</td><td>可以与 <code>Spring</code> 集成一起使用</td><td>可以与 <code>Spring</code> 集成一起使用</td><td><code>Spring Data</code> 家族的一款产品，可以与 <code>Spring</code> 集成一起使用</td></tr><tr><td>其他特性</td><td>提供额外功能特性，超过 <code>JPA</code> 规范</td><td>无</td><td>提供额外的抽象存储和查询支持</td></tr></tbody></table><hr><h2 id="如何处理面试中问题？"><a href="#如何处理面试中问题？" class="headerlink" title="如何处理面试中问题？"></a>如何处理面试中问题？</h2><p>当在 <code>Java</code> 开发岗位面试中遇到有关 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间差异对比的问题时，提供全面准确的答案至关重要。我们可以考虑如下思路：</p><ol><li>首先解释 <code>Hibernate</code> 和 <code>JPA</code> 之间的关系，强调 <code>Hibernate</code> 不仅是 <code>JPA</code> 规范的实现，而且提供额外的特性和功能增强；</li><li>阐明 <code>Hibernate</code> 是一个强大的 <code>ORM</code> 框架，它提供了 <code>JPA</code> 定义之外的高级特性；</li><li>强调当使用 <code>Hibernate</code> 时，本质上是在使用 <code>JPA</code>，因为 <code>Hibernate</code> 完全支持 <code>JPA</code> 规范；</li><li>继续探讨 <code>Spring Data JPA</code> 作为 <code>Spring Data</code> 项目的一部分，它简化了使用 <code>JPA</code> 对数据库地访问；</li><li>突出 <code>Spring Data JPA</code> 的优点，例如抽象存储、查询方法和动态查询处理；</li><li>解释 <code>Spring Data JPA</code> 是如何减少样板代码并简化数据库操作交互；</li><li>讨论 <code>Hibernate</code> 提供的附加功能，包括缓存、延迟加载、<code>HQL</code> 和标准化 <code>API</code>；</li><li>强调 <code>Spring Data JPA</code> 构建在 <code>JPA</code> 和 <code>Hibernate</code> 之上，提供更高级别的抽象和易用性；</li><li>最后总结要点并重点强调理解这些差异对于高效开发应用程序的重要性。</li></ol><p>在面试中，不仅要<strong>展示这些技术的理论知识，还要展示实际工作经验和对如何在现实场景中使用这些框架给出一些理解</strong>，这一点至关重要。根据特定的项目要求，举例说明何时以及为何选择使用其中某一个框架，可以进一步证明我们的理解。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这就是 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 技术之间的区别。了解 <code>Hibernate</code>、<code>JPA</code> 和 <code>Spring Data JPA</code> 之间的差异对于面试准备和构建具有高效数据库交互的健壮 <code>Java</code> 应用程序至关重要。 </p><p><code>Hibernate</code> 作为一个强大的 <code>ORM</code> 框架，实现了 <code>JPA</code> 规范并提供了额外的特性。<code>Spring Data JPA</code> 通过在 <code>JPA</code> 和 <code>Hibernate</code> 之上提供抽象存储和生成动态查询来简化数据库访问。</p><p>通过展示这些区别并突出实际用例，候选人可以用他们在使用这些技术方面的知识和专长给面试官留下深刻印象。</p><p>这是我认为每个 <code>Java</code> 开发人员都应该准备的一个问题，但如果你想要更多，你还可以准备微服务问题，例如 <a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API 网关和负载均衡器之间的区别</a>、<a href="https://medium.com/javarevisited/what-is-saga-pattern-in-microservice-architecture-which-problem-does-it-solve-de45d7d01d2b">SAGA 模式</a>、<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">如何在微服务中管理事务</a>以及 <a href="https://medium.com/javarevisited/difference-between-saga-and-cqrs-design-patterns-in-microservices-acd1729a6b02">SAGA 和 CQRS 模式之间的区别</a>, 这些问题在面试中很受欢迎。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/difference-between-hibernate-jpa-and-spring-data-jpa-7df55717692f&quot;&gt;htt
      
    
    </summary>
    
    
      <category term="web开发" scheme="https://dongzl.github.io/categories/web%E5%BC%80%E5%8F%91/"/>
    
    
      <category term="Hibernate" scheme="https://dongzl.github.io/tags/Hibernate/"/>
    
      <category term="JPA" scheme="https://dongzl.github.io/tags/JPA/"/>
    
      <category term="Spring Data JPA" scheme="https://dongzl.github.io/tags/Spring-Data-JPA/"/>
    
  </entry>
  
  <entry>
    <title>使用连接控制插件实现 MySQL 安全连接</title>
    <link href="https://dongzl.github.io/2023/05/25/15-MySQL-Connection-Security-With-Connection-Control-Plugins/"/>
    <id>https://dongzl.github.io/2023/05/25/15-MySQL-Connection-Security-With-Connection-Control-Plugins/</id>
    <published>2023-05-25T20:03:22.000Z</published>
    <updated>2023-12-31T06:59:02.157Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://www.percona.com/blog/mysql-connection-security-with-connection-control-plugins/">https://www.percona.com/blog/mysql-connection-security-with-connection-control-plugins/</a></p></blockquote><p>作为数据库管理员，您是否遇到过数据库被暴力破解的情况？恶意用户对 <code>MySQL</code> 中的用户账户发起暴力攻击。<code>MySQL</code> 根据验证结果返回登录成功或者失败，这两种验证结果所需要花费的验证时间几乎是相同的。因此，攻击者可以快速对 <code>MySQL</code> 用户账户发起暴力攻击，并且可以尝试使用许多不同的密码进行攻击。</p><p>根据密码学，暴力攻击是指攻击者尝试许多密码或密码短语，希望最终能够猜对密码；攻击者系统地检查所有可能的密码和密码短语，直到找到正确的密码。</p><p>不仅仅是暴力攻击一直会出现，<code>IT</code> 行业最近发现分布式拒绝服务（<code>DDoS</code>）攻击也在逐步增加。您的数据库是否也成为 <code>3306</code> 端口上此类恶意连接的攻击目标？今天，今天我们想带您了解一个特殊的插件，即 <a href="https://dev.mysql.com/doc/refman/8.0/en/connection-control.html">connection_control 插件</a>！它在 <code>MySQL 8.0</code> 中引入，并向后移植到 <code>MySQL 5.7</code> 和 <code>MySQL 5.6</code>。</p><h2 id="什么是连接控制插件"><a href="#什么是连接控制插件" class="headerlink" title="什么是连接控制插件"></a>什么是连接控制插件</h2><p>连接控制插件库允许管理员设置在指定次数的连续登录尝试失败后，增加服务器对错误连接的响应延迟。</p><p>使用连接控制插件的想法是配置 <code>MySQL</code> 服务器，以便服务器延迟响应。因为除了服务器返回响应结果，否则未经授权的用户或客户端无法判断密码是否正确；如果攻击者通过生成多个连接请求来攻击服务器，那么此类连接必须处于活动状态，直到服务器返回响应结果为止。引入延迟响应会提高攻击者的攻击难度，因为现在资源正在被占用，需要确保连接请求处于活动状态，这种方式可以减缓攻击者对 <code>MySQL</code> 用户账户的攻击速度。</p><h3 id="插件库包含两个插件："><a href="#插件库包含两个插件：" class="headerlink" title="插件库包含两个插件："></a>插件库包含两个插件：</h3><ul><li><a href="https://dev.mysql.com/doc/refman/5.7/en/connection-control.html">CONNECTION_CONTROL</a>：<code>CONNECTION_CONTROL</code> 尝试用来检查传入的连接，并根据需要对服务器响应添加一定延迟；该插件还公开了一些配置：配置是否开启该操作的系统变量和配置基本监控信息的状态变量；</li><li><a href="https://dev.mysql.com/doc/refman/5.7/en/information-schema-connection-control-failed-login-attempts-table.html">CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS</a>：<code>CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS</code> 实现了一个 <code>INFORMATION_SCHEMA</code> 数据表，该数据表公开了有关失败连接尝试的更多详细的监视信息。</li></ul><h2 id="如何安装连接控制插件"><a href="#如何安装连接控制插件" class="headerlink" title="如何安装连接控制插件"></a>如何安装连接控制插件</h2><p>为了在运行时加载插件，请使用如下这些 <code>SQL</code> 语句，并且根据平台需要调整 <code>.so</code> 文件后缀。在这里，我将使用 <code>Percona Server for MySQL 5.7.36</code> 对插件进行测试：</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> INSTALL PLUGIN CONNECTION_CONTROL SONAME <span class="string">&#x27;connection_control.so&#x27;</span>;</span><br><span class="line">INSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS SONAME <span class="string">&#x27;connection_control.so&#x27;</span>;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.01</span> sec)</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>或者，我们可以在 <code>my.cnf</code> 中安装插件，在 <code>MySQL</code> 配置文件（<code>/etc/my.cnf</code>）的 <code>[mysqld]</code> 选项组下添加如下配置选项：</p><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></pre></td><td class="code"><pre><span class="line">[mysqld]</span><br><span class="line"></span><br><span class="line">plugin-load-add=connection_control.so</span><br><span class="line">connection-control=FORCE_PLUS_PERMANENT</span><br><span class="line">connection-control-failed-login-attempts=FORCE_PLUS_PERMANENT</span><br></pre></td></tr></table></figure><p>现在让我们更深入地了解其中的每一个配置选项：</p><ul><li><code>plugin-load-add=connection_control.so</code> – 每次启动服务器时加载 <code>connection_control.so</code> 库；</li><li><code>connection_control=FORCE_PLUS_PERMANENT</code> – 防止服务器在没有 <code>CONNECTION_CONTROL</code> 插件的情况下运行，如果插件未成功初始化，则服务器启动失败；</li><li><code>connection-control-failed-login-attempts=FORCE_PLUS_PERMANENT</code> – 防止服务器在没有 <code>CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS</code> 插件的情况下运行，如果插件未成功初始化，则服务器启动失败。</li></ul><p>要验证插件安装情况，需要重新启动服务器并检查 <code>INFORMATION_SCHEMA.PLUGINS</code> 表或使用 <code>SHOW PLUGINS</code> 语句：</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">SELECT</span> PLUGIN_NAME, PLUGIN_STATUS</span><br><span class="line">      <span class="keyword">FROM</span> INFORMATION_SCHEMA.PLUGINS</span><br><span class="line">      <span class="keyword">WHERE</span> PLUGIN_NAME <span class="keyword">LIKE</span> <span class="string">&#x27;connection%&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------------+---------------+</span></span><br><span class="line"><span class="operator">|</span> PLUGIN_NAME                              <span class="operator">|</span> PLUGIN_STATUS <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------------+---------------+</span></span><br><span class="line"><span class="operator">|</span> CONNECTION_CONTROL                       <span class="operator">|</span> ACTIVE        <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS <span class="operator">|</span> ACTIVE        <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------------+---------------+</span></span><br></pre></td></tr></table></figure><h3 id="配置连接控制阈值"><a href="#配置连接控制阈值" class="headerlink" title="配置连接控制阈值"></a>配置连接控制阈值</h3><p>现在，我们尝试使用这些服务器配置参数为失败的连接尝试配置服务器响应延迟。我们将连续失败的连接阈值暂定为 <code>3</code> 个，并添加至少 <code>1</code> 秒的连接延迟。</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">SET</span> <span class="keyword">GLOBAL</span> connection_control_failed_connections_threshold <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> connection_control_min_connection_delay <span class="operator">=</span> <span class="number">1000</span>;  </span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> connection_control_min_connection_delay <span class="operator">=</span> <span class="number">90000</span>;</span><br></pre></td></tr></table></figure><p>或者，要在运行时设置和保留变量，可以使用如下语句：</p><figure class="highlight sql"><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">mysql<span class="operator">&gt;</span> <span class="keyword">SET</span> PERSIST connection_control_failed_connections_threshold <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="keyword">SET</span> PERSIST connection_control_min_connection_delay <span class="operator">=</span> <span class="number">1000</span>;</span><br></pre></td></tr></table></figure><p>此外，我们还可以将这些选项添加到 <code>MySQL</code> 配置文件（<code>/etc/my.cnf</code>）的 <code>[mysqld]</code> 选项组下，以便稍后根据需要进行调整。</p><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></pre></td><td class="code"><pre><span class="line">Shell</span><br><span class="line">[mysqld]</span><br><span class="line"></span><br><span class="line">connection_control_failed_connections_threshold=3</span><br><span class="line">connection_control_min_connection_delay=1000 </span><br><span class="line">connection_control_max_connection_delay=2147483647</span><br></pre></td></tr></table></figure><p>让我们详细地讨论其中的每一个变量：</p><ul><li><a href="https://dev.mysql.com/doc/refman/8.0/en/connection-control-variables.html#sysvar_connection_control_failed_connections_threshold">connection_control_failed_connections_threshold</a>：在服务器配置为后续连接尝试添加延迟之前，允许账户连续失败的连接尝试次数；</li><li><a href="https://dev.mysql.com/doc/refman/8.0/en/connection-control-variables.html#sysvar_connection_control_min_connection_delay">connection_control_min_connection_delay</a>：超过阈值后的连接失败的最小延迟（以毫秒为单位）；</li><li><a href="https://dev.mysql.com/doc/refman/8.0/en/connection-control-variables.html#sysvar_connection_control_max_connection_delay">connection_control_max_connection_delay</a>：超过阈值后的连接失败的最大延迟（以毫秒为单位）。</li></ul><h2 id="测试和监控连接"><a href="#测试和监控连接" class="headerlink" title="测试和监控连接"></a>测试和监控连接</h2><h3 id="第一个窗口："><a href="#第一个窗口：" class="headerlink" title="第一个窗口："></a>第一个窗口：</h3><p>请注意，这里我们将失败的连接阈值设置为 <code>3</code>，配置最小 <code>1</code> 秒的连接延迟，最大连接延迟设置为 <code>90</code> 秒。</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">SET</span> <span class="keyword">GLOBAL</span> connection_control_failed_connections_threshold <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> connection_control_min_connection_delay <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line"><span class="keyword">set</span> <span class="keyword">global</span> connection_control_max_connection_delay<span class="operator">=</span><span class="number">90000</span>;</span><br><span class="line"></span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.00</span> sec)</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.00</span> sec)</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> variables <span class="keyword">like</span> <span class="string">&#x27;%connection_control%&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">-------------------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Variable_name                                   <span class="operator">|</span> <span class="keyword">Value</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-------------------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> connection_control_failed_connections_threshold <span class="operator">|</span> <span class="number">3</span>     <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> connection_control_max_connection_delay         <span class="operator">|</span> <span class="number">90000</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> connection_control_min_connection_delay         <span class="operator">|</span> <span class="number">1000</span>  <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-------------------------------------------------+-------+</span></span><br><span class="line"><span class="number">3</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>尝试获取这些变量的值：</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> information_schema.connection_control_failed_login_attempts;</span><br><span class="line"><span class="keyword">Empty</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> <span class="keyword">global</span> status <span class="keyword">like</span> <span class="string">&#x27;connection_control_%&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Variable_name                      <span class="operator">|</span> <span class="keyword">Value</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Connection_control_delay_generated <span class="operator">|</span> <span class="number">0</span>     <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>因为我们在这里重新配置了参数，所以没有失败的连接尝试，也没有产生连接延迟；我们可以查询 <code>information_schema.connection_control_failed_login_attempts</code> 为空，<code>Connection_control_delay_generated</code> 值为 <code>0</code>。</p><h3 id="第二个窗口："><a href="#第二个窗口：" class="headerlink" title="第二个窗口："></a>第二个窗口：</h3><p>通过以上设置，我测试了 <code>53</code> 个假连接的暴力破解。</p><p>打开另一个终端并以 <code>root</code> 用户身份执行这些错误的连接，并且每次都指定错误的密码。</p><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></pre></td><td class="code"><pre><span class="line">[root@ip-xxx-xx-xx-xx ~]# for i in `seq 1 53`;   do time mysql mysql  -uroot -p”try_an_incorrect_password” </span><br><span class="line">-h xxx.xx.x.x 2&gt;&amp;1 &gt;/dev/null | grep meh ;   done</span><br><span class="line">0.093</span><br><span class="line">0.092</span><br><span class="line">0.093</span><br><span class="line">1.093</span><br><span class="line">2.093</span><br><span class="line">3.105</span><br><span class="line">4.093</span><br><span class="line">5.093</span><br><span class="line">…</span><br><span class="line">…</span><br><span class="line">45.092</span><br><span class="line">46.093</span><br><span class="line">47.093</span><br><span class="line">48.093</span><br><span class="line">49.092</span><br><span class="line">50.093</span><br></pre></td></tr></table></figure><h2 id="这些连接中发生了什么？"><a href="#这些连接中发生了什么？" class="headerlink" title="这些连接中发生了什么？"></a>这些连接中发生了什么？</h2><ul><li>在 <code>MySQL</code> 进程列表中，每个连接都会处于<strong>Waiting in connection_control plugin</strong>状态；</li><li>在第三次连接尝试之后，每个连接都会添加小而明显的延迟，并且延迟会持续增加，直到我们进行最后一次尝试；对于随后的每次失败尝试，延迟都会增加一秒，直到达到最大限制阈值，这意味着如果在 <code>3</code> 次登录失败后建立第 <code>50</code> 个连接，则第 <code>51</code> 个连接将花费 <code>51</code> 秒，第 <code>52</code> 个连接将再次花费 <code>52</code> 秒，依此类推；这意味着延迟将持续增加，直到达到 <code>connection_control_max_connection_delay</code> 设置的阈值。至此，自动暴力攻击工具将会失效，因为它们将面对不断增加的延迟。</li></ul><h3 id="第一个窗口"><a href="#第一个窗口" class="headerlink" title="第一个窗口"></a>第一个窗口</h3><p>现在切换回第一个终端并重新检查变量的值。</p><p><code>connection_control</code> 开始监视所有失败的连接尝试，并跟踪每个用户连续失败的连接尝试。</p><p>直到连续失败的尝试次数小于阈值，即在我们的例子中为 <code>3</code> 次，用户不会遇到任何延迟，添加尝试次数的阈值可以避免用户在真实情况下错误输入密码而产生延迟响应。</p><p>在这里我们可以注意到 <code>Connection_control_delay_generated</code> 的状态现在是 <code>50</code>，<code>CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS</code> 是 <code>53</code>。</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> <span class="keyword">global</span> status <span class="keyword">like</span> <span class="string">&#x27;connection_control_%&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Variable_name                      <span class="operator">|</span> <span class="keyword">Value</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Connection_control_delay_generated <span class="operator">|</span> <span class="number">50</span>    <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">SELECT</span> FAILED_ATTEMPTS <span class="keyword">FROM</span> INFORMATION_SCHEMA.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="operator">|</span> FAILED_ATTEMPTS <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="operator">|</span>              <span class="number">53</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h2 id="当我们想成功-真正登录时会发生什么？"><a href="#当我们想成功-真正登录时会发生什么？" class="headerlink" title="当我们想成功/真正登录时会发生什么？"></a>当我们想成功/真正登录时会发生什么？</h2><p>请注意，服务器将会继续为所有后续失败连接和第一次成功连接引入此类延迟。因此，在第 <code>53</code> 次登录失败后第一次成功登录尝试产生的延迟是 <code>53</code> 秒。假设 <code>MySQL</code> 在第一次成功连接后没有添加任何延迟，在这种情况下，攻击者将得到一个提示，延迟意味着密码错误，因此可以在等待特定时间后释放被挂起的连接；因此，如果您在 <code>N</code> 次不成功尝试后尝试建立一个成功连接，我们肯定会在第一次成功登录尝试时遇到 <code>N</code> 秒的延迟。</p><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></pre></td><td class="code"><pre><span class="line">[root@ip-xxx-xx-xx-xx ~]# date; mysql -uroot -p’correct_password’ -hxxx.xx.x.x -e &quot;select now();&quot;;date</span><br><span class="line">Tue Apr 18 06:27:36 PM UTC 2023</span><br><span class="line">mysql: [Warning] Using a password on the command line interface can be insecure.</span><br><span class="line">+---------------------+</span><br><span class="line">| now()               |</span><br><span class="line">+---------------------+</span><br><span class="line">| 2023-04-18 18:28:29 |</span><br><span class="line">+---------------------+</span><br><span class="line">Tue Apr 18 06:28:29 PM UTC 2023</span><br></pre></td></tr></table></figure><h2 id="哪个用户正在进行暴力攻击？"><a href="#哪个用户正在进行暴力攻击？" class="headerlink" title="哪个用户正在进行暴力攻击？"></a>哪个用户正在进行暴力攻击？</h2><p>我们还可以确定这些失败的连接尝试来自哪个用户或主机。</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> information_schema.connection_control_failed_login_attempts;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------+-----------------+</span></span><br><span class="line"><span class="operator">|</span> USERHOST              <span class="operator">|</span> FAILED_ATTEMPTS <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------+-----------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="string">&#x27;root&#x27;</span>@<span class="string">&#x27;xxx-xx-xx-xx&#x27;</span> <span class="operator">|</span>              <span class="number">53</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------+-----------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h2 id="如何重置失败的连接阈值"><a href="#如何重置失败的连接阈值" class="headerlink" title="如何重置失败的连接阈值"></a>如何重置失败的连接阈值</h2><p>如果我们想重置这些计数器，只需再次为变量 <code>connection_control_failed_connections_threshold</code> 赋值：</p><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></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">SET</span> <span class="keyword">GLOBAL</span> connection_control_failed_connections_threshold <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"># Now you can see the <span class="keyword">values</span> <span class="keyword">are</span> reset<span class="operator">!</span></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> information_schema.connection_control_failed_login_attempts;</span><br><span class="line"><span class="keyword">Empty</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> <span class="keyword">global</span> status <span class="keyword">like</span> <span class="string">&#x27;connection_control_%&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Variable_name                      <span class="operator">|</span> <span class="keyword">Value</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="operator">|</span> Connection_control_delay_generated <span class="operator">|</span> <span class="number">0</span>     <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+-------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p><code>MySQL</code> 连接控制对于限制暴力攻击或拦截不合法的 <code>TCP</code> 连接非常有用。 <code>Percona Server for MySQL</code> 也支持这些插件，默认情况下它未启用，我们可以执行相同的步骤来启用该插件并为我们的环境设置安全连接。</p><p>总而言之，该插件提供以下功能：</p><ul><li>添加阈值配置，在该阈值之后触发连接增加延迟；</li><li>使用最小值和最大值配置服务器回复的延迟时间；</li><li><code>information_schema</code> 视图，用于查看与失败的连接尝试相关的监控数据信息。</li></ul><p>关于如何确保数据库安全，可以查看我们的最新博客文章：</p><ul><li><a href="https://www.percona.com/blog/keep-your-database-secure-with-percona-advisors/">Keep Your Database Secure With Percona Advisors</a></li><li><a href="https://www.percona.com/blog/improving-mysql-password-security-with-validation-plugin/">Improving MySQL Password Security with Validation Plugin</a></li><li><a href="https://www.percona.com/blog/mysql-8-account-locking/">MySQL 8: Account Locking</a></li><li><a href="https://www.percona.com/blog/brute-force-mysql-password-from-a-hash/">Brute-Force MySQL Password From a Hash</a></li><li><a href="https://docs.percona.com/percona-monitoring-and-management/details/develop-checks/checks-v2.html?_gl=1*x9v1q5*_gcl_au*NDUzNDMxODk0LjE2ODQ5MzEzMzE.">Version 2 Advisor Checks for PMM 2.28 and Newer</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://www.percona.com/blog/mysql-connection-security-with-connection-control-plugins/&quot;&gt;https://www.percona.c
      
    
    </summary>
    
    
      <category term="数据库" scheme="https://dongzl.github.io/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
      <category term="MySQL" scheme="https://dongzl.github.io/tags/MySQL/"/>
    
      <category term="Security" scheme="https://dongzl.github.io/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>解决大规模问题的 10 个系统设计算法、协议和分布式数据结构</title>
    <link href="https://dongzl.github.io/2023/05/20/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/"/>
    <id>https://dongzl.github.io/2023/05/20/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/</id>
    <published>2023-05-20T20:03:22.000Z</published>
    <updated>2023-12-31T06:59:02.149Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/10-system-design-algorithms-protocols-and-distributed-data-structure-to-solve-large-scales-40bd24d9a57f">https://medium.com/javarevisited/10-system-design-algorithms-protocols-and-distributed-data-structure-to-solve-large-scales-40bd24d9a57f</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/02.png" style="width:100%"><p>朋友们大家好，如果我们正在准备系统设计面试，那么我们应该关注的一件事情：学习不同的系统设计算法以及研究这些算法能够在分布式系统和微服务中解决的问题。</p><p>在过去的文章中，我分享了<a href="https://medium.com/javarevisited/7-system-design-problems-to-crack-software-engineering-interviews-in-2023-13a518467c3e">7 个系统设计问题</a>和<a href="https://medium.com/javarevisited/top-10-system-design-concepts-every-programmer-should-learn-54375d8557a6">10 个系统设计基本概念</a>两篇文章；在本文中，我将继续分享每个开发人员都应该学习的 <code>10</code> 个系统设计算法。</p><p>事不宜迟，以下是可用于解决大规模分布式系统问题的 <code>10</code> 种系统设计算法和分布式数据结构：</p><ol><li>一致性哈希（<code>Consistent Hashing</code>）</li><li><code>MapReduce</code></li><li>分布式哈希表（<code>Distributed Hash Tables</code> (<code>DHT</code>)）</li><li>布隆过滤器（<code>Bloom Filters</code>）</li><li>两阶段提交（<code>Two-phase commit</code> (<code>2PC</code>)）</li><li><code>Paxos</code></li><li><code>Raft</code></li><li><code>Gossip</code> 协议（<code>Gossip protocol</code>）</li><li><code>Chord</code></li><li><code>CAP</code> 理论（<code>CAP theorem</code>）</li></ol><p>这些算法和分布式数据结构只是用于解决大规模分布式系统问题的众多技术中的几个例子。</p><h2 id="面向程序开发人员的-10-大分布式数据结构和系统设计算法"><a href="#面向程序开发人员的-10-大分布式数据结构和系统设计算法" class="headerlink" title="面向程序开发人员的 10 大分布式数据结构和系统设计算法"></a>面向程序开发人员的 10 大分布式数据结构和系统设计算法</h2><p>深入地理解这些算法，以及理解在不同的场景中如何有效地应用这些算法，对一名开发人员很重要。</p><p>因此，让我们来深入研究其中的每一个算法，研究它们是什么、它们是如何工作的以及何时使用它们。</p><h2 id="1-一致性哈希"><a href="#1-一致性哈希" class="headerlink" title="1. 一致性哈希"></a>1. 一致性哈希</h2><p><a href="https://medium.com/javarevisited/what-is-consistent-hashing-what-problem-does-it-solve-9c161fc6147d">一致性哈希</a>是分布式系统中用于在多个节点之间有效分发数据的技术。当在系统中添加或删除节点时，它可以使系统最小化在节点之间迁移的数据量。</p><p>一致性哈希背后的基本原理是使用哈希函数将每条数据映射到系统中的某个节点。每个节点都分配了一段哈希值范围，哈希值映射到该范围内的的任何数据都会被分配到该节点。</p><p>当从系统中添加或删除节点时，需要将分配给该节点的数据迁移到另一个节点，这个过程是通过使用虚拟节点的概念来实现的，并不是直接为每个物理节点分配一系列哈希值，而是为每个物理节点分配了多个虚拟节点，每个虚拟节点都被分配了一段唯一的哈希值范围，任何哈希值映射到该范围内的数据都被分配给虚拟节点相对应的物理节点上。</p><p>当从系统中添加或删除节点时，只有受影响的虚拟节点上的数据需要重新分配，并且分配给这些虚拟节点的所有数据都将被迁移到另外一个节点，这就允许系统动态高效地进行扩展，从而避免在每次添加或删除节点时需要重新分配数据。</p><p>总的来说，<strong>一致性哈希提供了一种在分布式系统中的多个节点之间分发数据的简单而有效的方法</strong>。它通常用于大型分布式系统，例如内容分发网络（<code>CDN</code>）和分布式数据库，用来提供高可用性和可扩展性。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/01.png" style="width:100%"><hr><h2 id="2-Map-reduce"><a href="#2-Map-reduce" class="headerlink" title="2. Map reduce"></a>2. Map reduce</h2><p><code>MapReduce</code> 是一种用于在分布式系统中处理海量数据集的编程模型和框架。它的思想最初由谷歌提出，目前广泛应用于许多大数据处理系统，例如 <code>Apache Hadoop</code>。</p><p><code>MapReduce</code> 背后的基本思想是将海量数据集切割成较小的数据块，并将这些数据块分布在集群中的多个节点上，多个节点之间并行处理数据，处理过程分为两个阶段：<strong>Map阶段</strong>和<strong>Reduce阶段</strong>。</p><p>在 <code>Map</code> 阶段，输入数据集由一组 <code>Map</code> 函数并行处理，每个 <code>Map</code> 函数都将一组键值对作为输入，并生成一组中间键值对作为输出，然后将这些中间键值对按键排序和分区，并发送到 <code>Reduce</code> 阶段进行处理。</p><p>在 <code>Reduce</code> 阶段，<code>Map</code> 阶段生成的中间键值对被一组 <code>Reduce</code> 函数并行处理，每个 <code>Reduce</code> 函数都将一个键和一组值作为输入，并生成输出一组键值对。</p><p>下面是一个示例，说明如何使用 <code>MapReduce</code> 计算大型文本文件中单词的出现频率：</p><ol><li><strong>Map阶段</strong>：每个 <code>Map</code> 函数读取输入文件的一个数据块并输出一组中间键值对，其中键是某个单词，值是该单词在数据块中出现的次数；</li><li><strong>Shuffle阶段</strong>：对生成的中间键值对按键排序和分区，以便将相同单词分组到一起；</li><li><strong>Reduce阶段</strong>：每个 <code>Reduce</code> 函数都将某个单词和该单词出现次数作为输入，并输出一个键值对，其中键是该单词，值是输入文件中该单词出现的总次数。</li></ol><p><code>MapReduce</code> 框架负责计算的并行处理、数据分布处理和机器故障容错，这使它能够高效可靠地处理海量数据集，即使是在普通的商用硬件集群上也是如此。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/02.png" style="width:100%"><hr><h2 id="3-分布式哈希表（DHT）"><a href="#3-分布式哈希表（DHT）" class="headerlink" title="3. 分布式哈希表（DHT）"></a>3. 分布式哈希表（DHT）</h2><p>分布式哈希表（<code>DHT</code>）是一种分布式数据结构，可提供散列的键值存储，它常用于 <code>P2P</code> 网络场景，通过可扩展和容错的方式存储和检索信息。</p><p>在 <code>DHT</code> 中，每个参与计算的节点存储的是所有键值对的子集，并使用映射函数将键分配到某个节点，这使得某个节点仅通过查询一小部分节点就可以定位到与给定键关联的值，而且查询的通常是那些映射空间中接近给定键的所在节点的键。</p><p><code>DHT</code> 提供了几个优秀的特性，例如自组织、容错、负载均衡和高效路由。<code>DHT</code> 通常用于 <code>P2P</code> 文件共享系统、内容分发网络（<code>CDN</code>）和分布式数据库等场景。</p><p>一种非常流行的 <code>DHT</code> 算法是 <code>Chord</code> 协议算法，它使用基于环的拓扑结构和一致性的哈希函数将数据键分配给某个节点；另一个广泛使用的 <code>DHT</code> 算法是 <code>Kademlia</code> 协议算法，它使用二叉树树状结构来定位存储给定键的节点。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/03.png" style="width:100%"><hr><h2 id="4-布隆过滤器"><a href="#4-布隆过滤器" class="headerlink" title="4. 布隆过滤器"></a>4. 布隆过滤器</h2><p>布隆过滤器是一种概率数据结构，通常用于高效的测试集合中是否存在某个元素，它是由 <code>Burton Howard Bloom</code> 于 <code>1970</code> 年提出的。布隆过滤器是一种节省存储空间的概率数据结构，用于测试元素在集合中是否存在，它使用位数组和一组哈希函数来存储和检查集合中是否存在某个元素。</p><p>将元素添加到布隆过滤器的过程首先将元素传入给一组散列函数，这些哈希函数返回位数组中的一组索引位置，然后将位数组中对应索引位置的值设置为 <code>1</code>。</p><p>为了检查集合中是否存在某个元素，首先对该元素应用相同的哈希函数，并在位数组中检查哈希函数结果对应的索引位置的值，如果对应索引位置的所有值都为 <code>1</code>，则认为该元素可能存在于集合中；但是如果对应索引位置的某个值 <code>0</code>，则认为该元素不存在于集合中。</p><p>由于布隆过滤器使用哈希函数来索引位数组，因此存在误报的可能性，即过滤器可能判断认为某个元素存在于集合中，而实际上它不存在；但是可以通过调整位数组的大小和使用的哈希函数的数量来控制误报出现的概率；假阴性率（即布隆过滤器无法识别集合中实际存在的元素的概率）可以做到为零。</p><p>布隆过滤器广泛应用于网络、数据库和网络缓存等各个领域，用来高效地测试集合中是否存在某个成员。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/04.png" style="width:100%"><hr><h2 id="5-两阶段提交"><a href="#5-两阶段提交" class="headerlink" title="5. 两阶段提交"></a>5. 两阶段提交</h2><p><a href="https://medium.com/javarevisited/difference-between-saga-pattern-and-2-phase-commit-in-microservices-e1d814e12a5a">两阶段提交（2PC）</a>是一种用于保证分布式系统中事务的原子性和一致性的协议，它能够保证参与事务的所有节点一起提交或回滚的思想。</p><p>两阶段提交协议分两个步骤：</p><ol><li><strong>Prepare阶段</strong>：在 <code>prepare</code> 阶段，协调者向所有参与者发送消息，要求它们准备提交事务；每个参与者都会回复一条响应消息，表明它是否做好提交事务准备，如果某个参与者还未准备就绪，它会回复一条消息，表明它无法参与事务；</li><li><strong>Commit阶段</strong>：如果所有参与者都已经做好提交准备，协调者会向所有节点发送一条消息，要求他们提交事务，每个参与者提交事务后会向协调者发送确认消息，如果某个参与者无法提交事务，它会回滚事务并向协调者发送一条消息，表明它已回滚事务。</li></ol><p>如果协调者收到所有参与者的确认消息，它会向所有节点发送一条消息，表明事务已提交；如果协调器收到某个参与者的回滚消息，它会向所有节点发送一条消息，指示事务已回滚。</p><p><a href="https://medium.com/javarevisited/difference-between-saga-pattern-and-2-phase-commit-in-microservices-e1d814e12a5a">两阶段提交协议</a>能够确保分布式系统中的所有节点事务结果是一致的，即使出现故障也是如此；然而它也有一些缺点，包括<strong>延迟较高</strong>和<strong>死锁</strong>的可能性，此外它需要一个协调者节点，这可能引起单点故障。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/05.png" style="width:100%"><hr><h2 id="6-Paxos"><a href="#6-Paxos" class="headerlink" title="6. Paxos"></a>6. Paxos</h2><p><code>Paxos</code> 是一种分布式共识算法，即使在某个节点出现故障的情况下，也允许一组节点就某个结果值达成一致。它是由 <code>Leslie Lamport</code> 于 <code>1998</code> 年提出，现已成为分布式系统的基本算法。</p><p><code>Paxos</code> 算法旨在处理各种故障场景，包括消息丢失、重复、重排序和节点故障等等。该算法分两个阶段进行：<strong>prepare阶段</strong>和<strong>accept阶段</strong>。在<strong>prepare阶段</strong>，一个节点向所有其他节点发送一个 <code>prepare</code> 消息，要求他们确保不接受任何数值小于一定值的提议。</p><p>一旦大多数节点响应提案消息，节点就可以进入<strong>accept阶段</strong>，在<strong>accept阶段</strong>，节点向所有其他节点发送 <code>accept</code> 消息，提出某个值，如果大多数节点响应消息内容，则该值被视为已接受。</p><p><code>Paxos</code> 是一种<strong>复杂的算法</strong>，它有多种变体和优化，例如 <code>Multi-Paxos</code>、<code>Fast Paxos</code> 等；这些变体的目的是减少交换的消息数量，优化算法的延迟，并减少需要参与共识的节点数量。<code>Paxos</code> 被广泛应用于分布式数据库、分布式文件系统等需要高度容错性的分布式系统中。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/06.png" style="width:100%"><hr><h2 id="7-Raft"><a href="#7-Raft" class="headerlink" title="7. Raft"></a>7. Raft</h2><p><code>Raft</code> 是一种共识算法，旨在确保分布式系统的容错性。它负责维护一个复制的日志，该日志存储跨集群中多个节点的一系列变更状态。<code>Raft</code> 通过选举一个领导者来达成共识，领导者协调各个节点之间的通信，并确保日志在整个集群中的状态是一致的。</p><p><code>Raft</code> 算法由三个主要部分组成：<strong>领导者选举</strong>、<strong>日志复制</strong>和<strong>安全性</strong>。在领导者选举阶段，集群中的节点使用随机超时机制选举领导者。</p><p>领导者通过接收从客户端发送的数据变更并将它们复制到集群中的其他从节点来协调日志复制，从节点还可以向领导者请求日志条目以确保整个集群的一致性。</p><p><code>Raft</code> 的安全组件确保共识算法对系统故障具备容错性，并确保日志在整个集群中保持一致；<code>Raft</code> 算法确保在任何给定时间只有一个领导者节点并通过在集群中强制执行严格的日志条目排序来实现安全性。</p><p><code>Raft</code> 广泛用于分布式系统，以提供容错性和高可用性。它通常用于需要保证强一致性的系统，例如分布式数据库和分布式键值存储。</p><hr><h2 id="8-Gossip"><a href="#8-Gossip" class="headerlink" title="8. Gossip"></a>8. Gossip</h2><p><code>Gossip</code> 协议是分布式系统中快速有效地传播信息的点对点通信协议。它是一种概率协议，允许节点以去中心化的方式与相邻节点交换状态信息数据。该协议的名称来源于传播谣言或八卦等信息的方式。</p><p>在 <code>Gossip</code> 协议中，某个节点随机选择一组其他节点来与之交换信息。当一个节点从另一个节点接收到信息时，它将将该信息转发给其相邻的其他节点，然后该过程会继续进行。随着时间的推移，当信息从一个节点传播到另一个节点时，整个网络中节点就全部能够识别到这些信息。</p><p><code>Gossip</code> 协议的主要优点之一是它的容错性。由于该协议依赖于概率通信而不是中心化节点，因此即使某些节点出现故障或退出网络，它也可以继续运行。这使它成为分布式系统中的一个有用工具，能够解决分布式系统中可靠性这个关键问题。</p><p><code>Gossip</code> 协议已用于各种应用程序，包括分布式数据库、<code>P2P</code> 文件共享网络和大规模传感器网络。它们特别适合需要在大量节点之间快速有效地传播信息的应用程序。</p><hr><h2 id="9-Chrod"><a href="#9-Chrod" class="headerlink" title="9. Chrod"></a>9. Chrod</h2><p><code>Chord</code> 是一种分布式哈希表 (<code>DHT</code>) 协议，用于去中心化 (<code>P2P</code>) 系统。它提供了一种在给定标识符的情况下在 <code>P2P</code> 网络中快速定位某个节点（或一组节点）的方法。<code>Chord</code> 允许 <code>P2P</code> 系统扩展到大量节点，同时能够保持较低系统开销。</p><p>在 <code>Chord</code> 网络中，每个节点都分配有一个唯一的标识符，可以是任意 <code>m</code> 位数字。所有节点排列成一个环，节点之间根据它们的标识符按顺时针方向排序，每个节点负责存储一组键，键可以是 <code>0</code> 到 <code>2^m-1</code> 范围内的任意值。</p><p>为了在网络中找到一个键，某个节点首先计算这个键的哈希值，然后定位其标识符是该哈希值的顺时针第一个后继者节点；如果后继节点上没有所需的键，它将请求继续转发给它的后继节点，依此类推，直到找到这个键。这个过程被称为 <code>finger lookup</code>，它通常需要发送对数级别的消息才能找到所需的节点。</p><p>为了保持网络的一致性，<code>Chord</code> 使用一种称为 <code>finger table</code> 的协议，它负责存储网络中其他节点的信息。每个节点都维护一个 <code>finger table</code>，其中包含环中距离越来越远的后继者节点的标识符；这使得某个节点有效地定位网络中的其他节点，而无需维护所有节点的完整列表。</p><p><code>Chord</code> 还能够确保在网络中添加或删除节点时保持一致性。当网络中添加一个节点时，它会通知它的直接后继者，后者相应地更新它的 <code>finger table</code>；当在网络中删除某个节点时，这个节点上的键被迁移到它的后继节点，后继节点更新它的 <code>finger table</code> 以记录节点被删除。</p><p>总的来说，<code>Chord</code> 提供了一种高效且可扩展的方式，使用简单且去中心化协议在 <code>P2P</code> 网络中定位某个节点。</p><hr><h2 id="10-CAP-理论"><a href="#10-CAP-理论" class="headerlink" title="10. CAP 理论"></a>10. CAP 理论</h2><p><code>CAP</code> 定理，也称为布鲁尔定理，是分布式系统中的一个基本概念，它指出分布式系统不可能同时保证以下三个特性：</p><ol><li><strong>一致性</strong>：每次读取都会返回最新写入的新数据或错误；</li><li><strong>可用性</strong>：每次请求都会接收到一个响应结果，但不保证它返回的是最新版本的信息；</li><li><strong>分区容错性</strong>：即使发生网络分区，系统也能继续运行并提供一致且可用的服务。</li></ol><p>换句话说，<a href="https://medium.com/javarevisited/how-to-manage-transactions-in-distributed-systems-and-microservices-d66ff26b405e">分布式系统</a>只能满足上述三个特性中的两个；这个定理意味着在出现网络分区的情况下，分布式系统必须在<strong>一致性</strong>和<strong>可用性</strong>之间做出选择。</p><p>例如在分区系统中，如果一个节点无法与另一个节点通信，则它必须返回错误或返回可能已经过时的响应结果。</p><p><code>CAP</code> 定理对设计分布式系统具有重要意义，因为它要求开发人员在一致性、可用性和分区容错性之间进行权衡。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/14-10-System-Design-Algorithms-Protocols-Distributed-Data-Structure-Large-Scales-Problems/07.png" style="width:100%"><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这就是我们可以在 <code>2023</code> 年学习的基本系统设计数据结构、算法和协议。总而言之，系统设计是软件工程师的一项基本技能，尤其是那些从事大规模分布式系统的工程师。</p><p>这十种算法、数据结构和协议为解决复杂问题而生，并为构建可扩展、可靠的系统提供了坚实的基础。通过了解这些算法并在使用中做出权衡取舍，我们可以在设计和构建系统时做出明智的选择。</p><p>此外，学习这些算法可以帮助我们准备系统设计面试并提高大家解决问题的能力。但是请务必注意，这些算法只是一个起点，我们应该随着技术的发展不断学习和适应新的技术。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/10-system-design-algorithms-protocols-and-distributed-data-structure-t
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="数据结构" scheme="https://dongzl.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
      <category term="算法" scheme="https://dongzl.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
      <category term="分布式系统" scheme="https://dongzl.github.io/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>使用 ClickHouse 作为 MySQL 的数据分析扩展</title>
    <link href="https://dongzl.github.io/2023/05/11/13-Using-Clickhouse-Analytic-Extension-MySQL/"/>
    <id>https://dongzl.github.io/2023/05/11/13-Using-Clickhouse-Analytic-Extension-MySQL/</id>
    <published>2023-05-11T10:07:22.000Z</published>
    <updated>2023-12-31T06:59:02.149Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://www.percona.com/blog/using-clickhouse-as-an-analytic-extension-for-mysql/">https://www.percona.com/blog/using-clickhouse-as-an-analytic-extension-for-mysql/</a></p></blockquote><p><code>MySQL</code> 是一款出色的用于在线事务处理的数据库。搭配合适的硬件，<code>MySQL</code> 可以轻松的<a href="https://www.percona.com/blog/2017/01/06/millions-queries-per-second-postgresql-and-mysql-peaceful-battle-at-modern-demanding-workloads/">每秒处理超过百万查询</a>并<a href="https://www.percona.com/blog/mysql-challenge-100k-connections/">处理数万个并发连接</a>。在我们这个星球上许多要求非常苛刻的 <code>Web</code> 应用程序都是基于 <code>MySQL</code> 构建的。有了这样的能力，为什么使用 MySQL 的用户还需要其他工具？</p><p>那好，首先考虑的是分析查询，分析查询可以回答很多重要的业务问题，例如查找一段时间内访问过网站的唯一身份用户数量，或者弄清楚如何增加在线购买量。分析查询会扫描大量数据并进行聚合运算，包括求和、求平均值以及其他更复杂的计算，计算的结果有非常大的价值，但分析查询过程可能会影响在 <code>MySQL</code> 上运行的联机事务的性能。</p><p>幸运的是，还有 <code>ClickHouse</code>：一个功能强大的分析型数据库，可以与 <code>MySQL</code> 完美搭配。<a href="https://www.percona.com/">Percona</a> 与我们的合作伙伴 <a href="https://altinity.com/">Altinity</a> 密切合作，帮助用户轻松地将 <code>ClickHouse</code> 嵌入到现有的 <code>MySQL</code> 应用程序中。您可以在我们最近的新闻稿中阅读更多关于我们合作伙伴关系的信息，以及我们实现的 <code>MySQL-to-ClickHouse</code> 解决方案。</p><p>这篇文章给出了一些提示，如何识别 <code>MySQL</code> 何时分析任务过重以及如何使用 <code>ClickHouse</code> 的独特功能并从中受益。然后，我们展示了将 <code>MySQL</code> 和 <code>ClickHouse</code> 集成到一起的三种重要方式。充分利用这两种数据库的优势的结果是可以创建更强大、更具成本效益的应用程序。</p><h2 id="MySQL-需要辅助分析工具的迹象"><a href="#MySQL-需要辅助分析工具的迹象" class="headerlink" title="MySQL 需要辅助分析工具的迹象"></a>MySQL 需要辅助分析工具的迹象</h2><p>让我们首先深入研究一些明显的迹象，这些迹象表明我们的 <code>MySQL</code> 数据库因处理分析工作而负担过重。</p><h3 id="数据量巨大的不变数据表与在线事务表混合在一起"><a href="#数据量巨大的不变数据表与在线事务表混合在一起" class="headerlink" title="数据量巨大的不变数据表与在线事务表混合在一起"></a>数据量巨大的不变数据表与在线事务表混合在一起</h3><p>数据分析的驱动表往往数据量非常大，很少有数据更新，并且表可能还会有很多列。典型示例是 <code>Web</code> 访问日志数据、营销活动事件数据和监控数据。如果我们观察到一些异常大的不可变数据表与较小的、频繁更新的事务处理表混合在一起，此时添加一个单独的分析型数据库会给用户带来非常大的益处。</p><h3 id="复杂的聚合管道"><a href="#复杂的聚合管道" class="headerlink" title="复杂的聚合管道"></a>复杂的聚合管道</h3><p>分析处理产生聚合结果，这些聚合操作能够汇总大型数据集生成统计数据以帮助用户进行模式识别。这些示例包括每周站点唯一访问者、平均页面用户流失率或网络流量来源。<code>MySQL</code> 可能需要几分钟甚至几小时来计算这些数值结果，为了提高性能，通常会预先添加聚合计算的复杂批处理流程。如果我们看到这样的聚合管道，通常表明添加单独的分析数据库可以减少操作应用程序的工作量，并可以更快、更及时地为用户提供结果。</p><h3 id="MySQL-太慢或不够灵活，无法解答重要的业务问题"><a href="#MySQL-太慢或不够灵活，无法解答重要的业务问题" class="headerlink" title="MySQL 太慢或不够灵活，无法解答重要的业务问题"></a>MySQL 太慢或不够灵活，无法解答重要的业务问题</h3><p>最后一条线索是有关基于 <code>MySQL</code> 的应用程序的深入问题，我们不要问，因为很难得到答案。为什么用户不在电子商务网站上完成购买？哪种游戏内促销策略在多人游戏中收益最大？直接从 <code>MySQL</code> 事务数据回答这些问题通常需要大量时间，并需要额外的应用程序。这些问题其实很难回答，大多数用户也根本不会关心，不过将 <code>MySQL</code> 与功能强大的分析数据库相结合可能是一种可行的解决方案。</p><h2 id="为什么-ClickHouse-是-MySQL-的天然补充？"><a href="#为什么-ClickHouse-是-MySQL-的天然补充？" class="headerlink" title="为什么 ClickHouse 是 MySQL 的天然补充？"></a>为什么 ClickHouse 是 MySQL 的天然补充？</h2><p><code>MySQL</code> 是用于事务处理的杰出数据库。然而，使 <code>MySQL</code> 高效运行的特性——<strong>按行存储数据</strong>、<strong>单线程查询</strong>和<strong>针对高并发特性的优化</strong>——与在大型数据集上执行聚合计算的分析查询所需要的特性恰恰相反。</p><p>另一方面，<code>ClickHouse</code> 是为分析处理而设计的。它将数据存储在列中，并进行了优化，以最小化 <code>I/O</code> 非常高效地执行聚合计算，并进行并行化查询优化。在许多情况下，<code>ClickHouse</code> 几乎可以立即响应复杂的分析问题处理，从而使用户可以快速筛选数据，因为 <code>ClickHouse</code> 计算聚合的效率很高，所以最终用户可以在没有应用程序设计人员帮助的情况下以多种方式处理问题。</p><p>这些都是非常有力特性说明，要了解它们，了解 <code>ClickHouse</code> 与 <code>MySQL</code> 的不同之处会很有帮助，下图说明了每种数据库为读取某张表的三列数据是如何通过查询提取数据的。</p><p><code>MySQL</code> 是按行存储表数据，它必须读取整行来获取仅三列的数据。<code>MySQL</code> 生产系统通常也不使用压缩，因为它在事务处理方面存在性能缺陷。最后，<code>MySQL</code> 使用单线程进行查询处理，无法并行工作。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-0.png" style="width:100%"><p>相比之下，<code>ClickHouse</code> 可以只读取查询中引用的列，将数据存储在列中使得 <code>ClickHouse</code> 能够将数据压缩超过 <code>90%</code>，最后 <code>ClickHouse</code> 将表分段存储后可以使用并行扫描。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-1.png" style="width:100%"><p><code>MySQL</code> 和 <code>ClickHouse</code> 给出了相同的答案。但是为了获得结果，<code>MySQL</code> 读取了 <code>59GB</code> 的数据，而 <code>ClickHouse</code> 只读取了 <code>21MB</code>，这几乎是 <code>3000</code> 倍的 <code>I/O</code> 差距，因此访问数据的时间也相差很多。为了进一步提高性能，<code>ClickHouse</code> 还做了很多优化，可以进行并行化查询，这使得在 <code>ClickHouse</code> 运行的分析查询的运行速度比在 <code>MySQL</code> 上快数百甚至数千倍。</p><p><code>ClickHouse</code> 还提供了一系列丰富的功能，可以快速高效地运行分析查询。其中包括大型聚合函数库、某些场景下可用的<a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">SIMD 指令</a>、支持从 <code>Kafka</code> 事件流读取数据以及高效的物化视图能力，这里仅举几个例子。</p><p><code>ClickHouse</code> 最大的一个优势是：可以与 <code>MySQL</code> 进行出色集成，这里有一些例子。</p><ul><li><code>ClickHouse</code> 可以将 <code>mysqldump</code> 和 <code>CSV</code> 的数据直接提取到 <code>ClickHouse</code> 表中；</li><li><code>ClickHouse</code> 可以对 <code>MySQL</code> 表执行远程查询，这提供了另一种快速提取数据的方法；</li><li><code>ClickHouse</code> 查询方言与 <code>MySQL</code> 非常类似，支持例如 <code>SHOW PROCESSLIST</code> 等系统命令；</li><li><code>ClickHouse</code> 甚至在 <code>3306</code> 端口上支持 <code>MySQL</code> 协议。</li></ul><p>由于以上这些原因，<code>ClickHouse</code> 是扩展 <code>MySQL</code> 分析处理能力的天然选择。</p><h2 id="为什么-MySQL-是-ClickHouse-的天然补充？"><a href="#为什么-MySQL-是-ClickHouse-的天然补充？" class="headerlink" title="为什么 MySQL 是 ClickHouse 的天然补充？"></a>为什么 MySQL 是 ClickHouse 的天然补充？</h2><p>正如 <code>ClickHouse</code> 可以为 <code>MySQL</code> 扩展有益的功能一样，重要的是 <code>MySQL</code> 同样为 <code>ClickHouse</code> 扩展了有益的功能。<code>ClickHouse</code> 在分析处理方面非常出色，但它在很多方面做得并不好，这里有一些示例。</p><ul><li><strong>事务处理</strong>——<code>ClickHouse</code> 没有完整的 <code>ACID</code> 事务能力，我们不要使用 <code>ClickHouse</code> 来处理在线订单业务，<code>MySQL</code> 在这方面做得很出色；</li><li><strong>单行快速更新</strong>——读取某一行数据的所有列在 <code>ClickHouse</code> 中效率非常低，因为我们必须读取许多文件；更新单行可能需要重写大量数据，我们不要将电子商务会话数据存储到 <code>ClickHouse</code> 中，这是 <code>MySQL</code> 的典型应用场景；</li><li><strong>大量并发查询</strong>——<code>ClickHouse</code> 查询旨在使用尽可能多的资源，而不是在许多用户之间共享这些资源，我们不要使用 <code>ClickHouse</code> 来保存微服务的元数据，但 <code>MySQL</code> 通常用于此类场景。</li></ul><p>事实上，<code>MySQL</code> 和 <code>ClickHouse</code> 是高度互补的。当 <code>ClickHouse</code> 和 <code>MySQL</code> 一起使用时，用户可以构建非常强大的应用程序。</p><h2 id="将-ClickHouse-集成进-MySQL"><a href="#将-ClickHouse-集成进-MySQL" class="headerlink" title="将 ClickHouse 集成进 MySQL"></a>将 ClickHouse 集成进 MySQL</h2><p>有三种方式可以将 <code>MySQL</code> 数据与 <code>ClickHouse</code> 分析功能集成到一起，它们建立在彼此之上。</p><ul><li>从 <code>ClickHouse</code> 查看 <code>MySQL</code> 数据，可以使用原生 <code>ClickHouse</code> <code>SQL</code> 语法通过 <code>ClickHouse</code> 查询 <code>MySQL</code> 数据，这对于查询 <code>MySQL</code> 数据以及与 <code>MySQL</code> 的数据进行 <code>join</code> 操作的场景是非常有用的；</li><li>将数据从 <code>MySQL</code> 永久迁移到 <code>ClickHouse</code>，<code>ClickHouse</code> 成为数据的存储系统，这会降低 <code>MySQL</code> 负载并提供更好的数据分析结果；</li><li>在 <code>ClickHouse</code> 存储 <code>MySQL</code> 数据镜像，将数据快照存入 <code>ClickHouse</code> 并使用复制机制保持数据同步，这允许用户在不增加交易处理系统负担的情况下分析交易数据的一些复杂问题。</li></ul><h3 id="通过-ClickHouse-访问-MySQL-数据"><a href="#通过-ClickHouse-访问-MySQL-数据" class="headerlink" title="通过 ClickHouse 访问 MySQL 数据"></a>通过 ClickHouse 访问 MySQL 数据</h3><p><code>ClickHouse</code> 可以使用<a href="https://clickhouse.com/docs/en/engines/database-engines/mysql">MySQL 数据库引擎</a>对 <code>MySQL</code> 数据进行查询，这使得在 <code>ClickHouse</code> 中使用 <code>MySQL</code> 数据就像在使用本地表数据一样，启用这个功能跟在 <code>ClickHouse</code> 上执行单个 <code>SQL</code> 命令一样简单，如下所示：</p><figure class="highlight sql"><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">CREATE</span> DATABASE sakila_from_mysql</span><br><span class="line">ENGINE<span class="operator">=</span>MySQLDatabase(<span class="string">&#x27;mydb:3306&#x27;</span>, <span class="string">&#x27;sakila&#x27;</span>, <span class="string">&#x27;user&#x27;</span>, <span class="string">&#x27;password&#x27;</span>)</span><br></pre></td></tr></table></figure><p>这是有关 <code>MySQL</code> 数据库引擎的简单说明。</p><p><code>MySQL</code> 数据库引擎使得在 <code>ClickHouse</code> 中查询 <code>MySQL</code> 数据并在 <code>ClickHouse</code> 中复制数据变得非常简单。<code>ClickHouse</code> 对远程数据的查询甚至可能比在 <code>MySQL</code> 中运行得更快！这是因为 <code>ClickHouse</code> 有时可以对远程数据进行并行查询。一旦存储了这些数据，<code>ClickHouse</code> 能够提供更加高效的聚合操作。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-2.png" style="width:100%"><h3 id="将-MySQL-数据移动到-ClickHouse"><a href="#将-MySQL-数据移动到-ClickHouse" class="headerlink" title="将 MySQL 数据移动到 ClickHouse"></a>将 MySQL 数据移动到 ClickHouse</h3><p>将具有不可变记录的大型表永久迁移到 <code>ClickHouse</code> 可以大大提高查询分析的性能，同时降低 <code>MySQL</code> 的负载。下图说明了如何将包含 <code>Web</code> 访问日志的表从 <code>ClickHouse</code> 迁移到 <code>MySQL</code>。（这里作者应该有笔误：<strong>从 MySQL 迁移到 ClickHouse</strong>）</p><p>在 <code>ClickHouse</code> 系统中，我们通常会选择使用 <code>MergeTree</code> 表引擎或其变体之一，例如 <code>ReplicatedMergeTree</code>。<code>MergeTree</code> 是 <code>ClickHouse</code> 上大数据的首选引擎，以下三个重要功能将帮助我们充分利用 <code>ClickHouse</code>。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-3.png" style="width:100%"><ol><li><strong>分区</strong>——<code>MergeTree</code> 使用分区键将数据分成多个部分。访问日志内容或其他大数据内容往往是按时间排序的，因此通常按天、按周或按月来划分数据；为获得最佳性能，建议设置的分区数少于 <code>1000</code>；</li><li><strong>排序</strong>——<code>MergeTree</code> 可以对数据进行排序并在行上构建索引以匹配我们所选择的排序。重要的是确定一种排序方式，以便在扫描数据时为我们提供大型“运行”场景；例如我们可以选择按租户排序，然后再按时间排序，这意味着对租户数据的查询不需要跳来跳去查找与该租户相关的数据行；</li><li><strong>压缩和<a href="https://en.wikipedia.org/wiki/Codec">编解码器</a></strong> ——<code>ClickHouse</code> 默认使用 <code>LZ4</code> 压缩，但也提供 <code>ZSTD</code> 压缩和编解码器；编解码器在将列数据转为压缩之前减少数据列内容。</li></ol><p>这些特性可以在性能上产生巨大的差异，我们在 <code>Altinity</code> 视频（查看<a href="https://www.youtube.com/watch?v=phTu24qCIw0">此处</a>和<a href="https://www.youtube.com/watch?v=rawoPXXGiYY">此处</a>）以及博客文章中介绍了它们并添加了更多性能说明。</p><p><code>ClickHouse</code> <code>MySQL</code> 数据库引擎在这种场景下也非常有用，它使 <code>ClickHouse</code> 能够从 <code>MySQL</code> 的远程事务表中查询和选择数据，我们的 <code>ClickHouse</code> 查询可以将本地表与远程的 <code>MySQL</code> 事务表的数据进行连接操作，同时 <code>MySQL</code> 也可以高效且安全地处理事务操作。</p><p>将表迁移到 <code>ClickHouse</code> 通常按以下方式进行，我们将使用前面描述的访问日志示例。</p><ol><li>为 <code>ClickHouse</code> 上的访问日志数据创建匹配的 <code>schema</code> 结构；</li><li>使用以下几种工具将数据从 <code>MySQL</code> 转储/加载到 <code>ClickHouse</code>：<ul><li><a href="https://github.com/mydumper/mydumper">Mydumper</a> —— 一种处理 <code>mysqldump</code> 和 <code>CSV</code> 格式的并行转储/加载工具；</li><li><a href="https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-features.html">MySQL Shell</a> —— 用于管理 <code>MySQL</code> 导入和导出表的的通用工具；</li><li>在 <code>MySQL</code> 数据库引擎表上使用 <code>SELECT</code> 复制数据；</li><li>本机数据库命令 – 使用 <code>MySQL SELECT OUTFILE</code> 将数据转储到 <code>CSV</code> 并使用 <code>ClickHouse INSERT SELECT FROM file()</code> 读取数据，<code>ClickHouse</code> 甚至可以读取 <code>mysqldump</code> 格式数据。</li></ul></li><li>关注性能，确保查询操作是否恰当；对 <code>schema</code> 进行调整并在必要时重新加载；</li><li>收集前端访问日志存储到 <code>ClickHouse</code>；</li><li>并行运行两个系统进行测试工作；</li><li>从单独的 <code>MySQL</code> 架构切换到 <code>MySQL</code> + <code>ClickHouse</code> 扩展架构。</li></ol><p>迁移可能只需要几天时间，但在大型系统中更常见的是需要几周到几个月的时间，这有助于确保一切都经过充分的测试，并顺利上线运行。</p><h3 id="在-ClickHouse-中存储-MySQL-数据镜像"><a href="#在-ClickHouse-中存储-MySQL-数据镜像" class="headerlink" title="在 ClickHouse 中存储 MySQL 数据镜像"></a>在 ClickHouse 中存储 MySQL 数据镜像</h3><p>另一种扩展 <code>MySQL</code> 的方法是将数据作为镜像存储到 <code>ClickHouse</code> 中，并使用复制机制保持数据同步。镜像允许用户在不更改 <code>MySQL</code> 及其应用程序，并且不影响生产系统性能的情况下，对交易数据执行复杂的查询分析操作。</p><p>以下是镜像设置的工作机制。</p><p><code>ClickHouse</code> 有一种内置的方式来处理镜像：实验性的<a href="https://clickhouse.com/docs/en/engines/database-engines/materialized-mysql">MaterializedMySQL 数据库引擎</a>，它直接从 <code>MySQL</code> 主数据库读取 <code>binlog</code> 日志记录，并将数据复制到 <code>ClickHouse</code> 表中。这种方法很简单，但是还未在生产系统广泛使用。它可能对 <code>1</code> 对 <code>1</code> 镜像场景非常有用，但在生产系统广泛使用之前需要额外的测试工作。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-4.png" style="width:100%"><p><code>Altinity</code> 使用<a href="https://debezium.io/">Debezium</a>、兼容<a href="https://kafka.apache.org/">Kafka</a>的事件流和用于<a href="https://github.com/Altinity/clickhouse-sink-connector">ClickHouse 的 Altinity Sink Connector</a>开发了一种新的复制方法。镜像配置如下所示。</p><p>外部化方案有许多优点，它们包括当前使用的 <code>ClickHouse</code> 版本，利用快速转储/加载程序（如 <code>mydumper</code> 或使用 <code>MySQL</code> 数据库引擎直接进行 <code>SELECT</code> 操作），支持镜像到复制表，以及添加新表或重置旧表等等一些简单过程。最后，它可以扩展支持将多个上游 <code>MySQL</code> 系统复制到单个 <code>ClickHouse</code> 集群。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-5.png" style="width:100%"><p>由于<a href="https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree">ReplacingMergeTree</a>表的独特功能，<code>ClickHouse</code> 可以从 <code>MySQL</code> 拉取镜像数据。它有一种处理插入、更新和删除的有效方法，非常适合用于复制数据。如前所述，<code>ClickHouse</code> 无法轻松更新单行数据，但它插入数据的速度非常快，并且可以在后台高效地合并数据行。<code>ReplicatingMergeTree</code> 以这些功能为基础，以<strong>ClickHouse 方式</strong>处理数据变更。</p><p>复制的表行使用版本和符号列来标识更改行的版本以及更改是插入还是删除操作。<code>ReplacingMergeTree</code> 只会保留一行的最后一个版本，实际上这一行可能已被删除。符号列让我们使用另一个 <code>ClickHouse</code> 特性使那些已被删除的行不可访问，被称为<a href="https://clickhouse.com/docs/en/sql-reference/statements/create/row-policy">行策略</a>。使用行策略，我们可以确保符号列为负数的任何行不可见。</p><p>下面是 <code>ReplacingMergeTree</code> 的一个示例，它结合了版本和符号列的效果来处理可变数据。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/13-Using-Clickhouse-Analytic-Extension-MySQL/altinity-6.png" style="width:100%"><p>将数据镜像到 <code>ClickHouse</code> 可能看起来比迁移更复杂，但实际上相对简单，因为不需要更改 <code>MySQL</code> 架构或应用程序，并且 <code>ClickHouse</code> 架构存储镜像数据遵循通用的模式。实施过程包括以下步骤：</p><ol><li>在 <code>ClickHouse</code> 中为复制表创建 <code>schema</code>；</li><li>配置并运行从 <code>MySQL</code> 到 <code>ClickHouse</code> 的复制；</li><li>使用与迁移相同的工具将数据从 <code>MySQL</code> 转储/加载到 <code>ClickHouse</code>。</li></ol><p>此时，用户可以自由地开始在 <code>ClickHouse</code> 上运行分析或构建其他应用程序，同时不断从 <code>MySQL</code> 复制更改数据。</p><h2 id="工具仍在不断提升！"><a href="#工具仍在不断提升！" class="headerlink" title="工具仍在不断提升！"></a>工具仍在不断提升！</h2><p><code>MySQL</code> 到 <code>ClickHouse</code> 的数据迁移是 <code>Altinity</code> 和整个 <code>ClickHouse</code> 社区都在积极推进的一个领域。改进分为三大类：</p><p><strong>转储/加载工具</strong>——<code>Altinity</code> 正在开发一种新的实用程序来迁移数据，减少模式创建和数据传输到单一集群的工作内容。我们将在以后的博客文章中对此进行更多讨论；</p><p><strong>复制</strong>——<code>Altinity</code> 正在赞助开发 <code>ClickHouse</code> 的 <code>Sink Connector</code>，它可以自动执行高速复制，包括监控功能，并集成到 <code>Altinity.Cloud</code> 中，我们的目标同样是将复制设置减少到单个命令；</p><p><strong>ReplacingMergeTree</strong>——目前用户必须在表名中包含 <code>FINAL</code> 关键字以强制合并数据变更，还需要添加行策略，让被删除的行自动隐藏。正在开发的 <code>pull requests</code> 添加了 <code>MergeTree</code> 属性实现在查询中自动添加 <code>FINAL</code> 以及使已被删除的行即使在没有行策略的情况下自动隐藏，它们一起工作将数据复制更新和数据删除的处理对用户完全透明。</p><p>我们也在仔细观察 <code>MaterializedMySQL</code> 的改进以及其他高效集成 <code>ClickHouse</code> 和 <code>MySQL</code> 的方法。我们可以期待将来有更多关于这些和相关主题的博客文章。敬请关注！</p><h2 id="总结并重新起航"><a href="#总结并重新起航" class="headerlink" title="总结并重新起航"></a>总结并重新起航</h2><p><code>ClickHouse</code> 是对现有 <code>MySQL</code> 应用程序的强大补充。具有不可变数据的大型表、复杂的聚合管道操作以及有关 <code>MySQL</code> 事务的未解决问题都是明确的特征，表明集成 <code>ClickHouse</code> 是为用户提供快速、高效的数据分析的下一步选择。</p><p>根据我们的应用程序，使用复制将数据镜像到 <code>ClickHouse</code> 或者将一些表迁移到 <code>ClickHouse</code> 可能是很有意义的。<code>ClickHouse</code> 已经可以与 <code>MySQL</code> 很好地集成，并且很快就会有更好的工具出现。不用说，<code>Altinity</code> 在该领域的所有贡献都是开源的，在 <code>Apache 2.0</code> 许可下发布。</p><p>最重要的一点是从 <code>MySQL</code> 和 <code>ClickHouse</code> 共同工作的角度来思考，而不是将其视为一个替代另一个。每个数据库都有独特而持久的优势，最好的应用程序将建立在这些之上，为用户提供比单独使用任何一个数据库更快、更灵活的功能。</p><p>开源数据库领域的知名专家 <code>Percona</code> 与 <code>Altinity</code> 合作，为 <code>MySQL</code> 应用程序提供强大的分析能力。如果您想了解更多关于 <code>MySQL</code> 与 <code>ClickHouse</code> 集成的信息，请随时联系我们或在我们的论坛上留言。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://www.percona.com/blog/using-clickhouse-as-an-analytic-extension-for-mysql/&quot;&gt;https://www.percona.com/blo
      
    
    </summary>
    
    
      <category term="数据库" scheme="https://dongzl.github.io/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
      <category term="MySQL" scheme="https://dongzl.github.io/tags/MySQL/"/>
    
      <category term="ClickHouse" scheme="https://dongzl.github.io/tags/ClickHouse/"/>
    
  </entry>
  
  <entry>
    <title>面向 5-10 年工作经验的开发人员的十大微服务问题解决方案</title>
    <link href="https://dongzl.github.io/2023/04/27/12-Top-10-Microservices-Problem-Solving-Questions/"/>
    <id>https://dongzl.github.io/2023/04/27/12-Top-10-Microservices-Problem-Solving-Questions/</id>
    <published>2023-04-27T10:24:22.000Z</published>
    <updated>2023-12-31T06:59:02.149Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-experienced-developers-3391e4f6b591">https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-experienced-developers-3391e4f6b591</a></p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/01.webp" style="width:100%"><p>朋友们大家好，在云计算和分布式系统时代微服务架构变得越来越流行，经验丰富的开发人员需要深入了解如何处理微服务架构中出现的一些常见的问题和挑战。</p><p>虽然我们都在单体和模块化的应用中做过开发工作，但是当我们切换到微服务架构时，还是感到有些无所适从，从开发到调试，从部署到监控，一切都在变化。</p><p>从性能问题到服务间通信问题，在构建和维护微服务系统时可能会出现各种各样复杂的情况，这些也是在面试中经常被问到的问题。</p><p>过去，我分享了<a href="https://medium.com/javarevisited/50-microservices-interview-questions-for-java-programmers-70a4a68c4349">50 个微服务面试问题</a>，内容涵盖基本的<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">微服务概念</a>、<a href="https://medium.com/javarevisited/difference-between-microservices-and-monolithic-architecture-for-java-interviews-af525908c2d5">架构</a>、<a href="https://medium.com/javarevisited/top-10-microservice-design-patterns-for-experienced-developers-f4f5f782810e">模式</a>、<a href="https://medium.com/javarevisited/10-microservices-design-principles-every-developer-should-know-44f2f69e960f">原则和最佳实践</a>；在本文中，我们将探讨 <code>10</code> 个基于实际场景的问题的解决方案，这些问题即使是经验丰富的开发人员在使用微服务时可能会遇到。</p><p>如果我们还没有在微服务领域工作过，这些问题可能很难回答，但是请不要担心，我还将针对如何处理这些问题，给出一些提示，对于解决这些挑战性问题的最佳实践和有效策略提供一些见解。</p><p>在文章结尾，我们将能更好地理解如何解决微服务问题以及微服务架构中的常见问题，并为自己在工作中处理这些问题做好准备。</p><hr><h2 id="面向-5-10-年工作经验的开发人员的十大微服务问题解决方案"><a href="#面向-5-10-年工作经验的开发人员的十大微服务问题解决方案" class="headerlink" title="面向 5-10 年工作经验的开发人员的十大微服务问题解决方案"></a>面向 5-10 年工作经验的开发人员的十大微服务问题解决方案</h2><p>以下是 5-10 年工作经验的开发人员通常会被问到的一些实际场景的微服务问题：</p><blockquote><ol><li>假设我们正在开发一个负责处理订单的微服务，但是由于一些问题，服务目前处于宕机状态，我们将如何确保订单不会丢失并且在服务恢复后可以正常处理这些订单？</li></ol></blockquote><p><strong>提示</strong>：这个问题需要基于我们系统架构进行讨论，如何设计系统架构来降低服务之间耦合；一种可能的解决方案是在创建订单服务和订单处理服务之间使用消息队列。</p><p>订单可以积压在消息队列中，直到订单处理服务恢复，可以继续拉取和处理订单。对于消息队列，我们可以选择使用 <code>RabbitMQ</code> 或 <code>Apache Kafka</code>，基于这两个框架，我们可以创建异步微服务架构，避免服务之间出现强依赖。</p><p>以下是<strong>基于 Apache Kafka 的微服务架构</strong>的示例：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/02.webp" style="width:100%"><blockquote><ol start="2"><li>假设我们有一个负责用户身份验证的微服务，我们将如何确保该服务可以处理高并发请求并且具备高可用性？</li></ol></blockquote><p><strong>提示</strong>：这个问题是考察我们的设计技能，如何设计可扩展且强大的系统，该系统可以处理数上百万的请求；一种可能的解决方案是<strong>使用负载均衡和集群</strong>。</p><p>该服务可以部署在多个服务器上，负载均衡器在它们之间负责分发传入的请求。</p><p>此外，该服务需要<strong>设计为无状态</strong>的，这意味着每个请求都可以被独立处理，而无需访问共享资源。</p><p>我们还可以使用<a href="https://medium.com/javarevisited/what-is-api-gateway-pattern-in-microservices-architecture-what-problem-does-it-solve-ebf75ae84698">API 网关设计模式</a>在微服务架构中实现用户身份验证。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/03.webp" style="width:100%"><blockquote><ol start="3"><li>设想一下，我们正在开发一个微服务系统，负责生成数据报告，我们将如何确保正确高效地生成报告，同时最大限度地减少对系统中其他服务的影响？</li></ol></blockquote><p><strong>提示</strong>：这个问题也和上一个问题类似，一种可能的解决方案是<strong>使用缓存和批处理</strong>。该服务可以缓存以前生成的报告并在可能的情况下重用它们，从而避免每次都重新开始生成新报告。</p><p>此外，该服务可以使用批处理来提前批量生成报告，而不是按需生成报告，从而进一步降低系统的负载。</p><blockquote><ol start="4"><li>假设我们有一个微服务系统负责处理支付，我们将如何确保服务安全以及保护敏感的支付数据信息？</li></ol></blockquote><p><strong>提示</strong>：如果参与过相关的微服务面试，那么我们可能知道支付处理是面试官最喜欢讨论的话题，因为这涉及到事务管理、服务安全，而且我们必须确保数据不能丢失。</p><p>这个问题的一种可能解决方案是<strong>使用加密和令牌</strong>，支付信息可以在传输到服务之前进行<strong>加密</strong>，确保其在传输过程中受到保护。</p><p>另外，该服务还可以使用令牌以安全的方式存储支付信息，用可以安全存储和传输的非敏感令牌代替敏感信息。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/04.webp" style="width:100%"><blockquote><ol start="5"><li>假设我们有一个负责处理用户反馈的微服务，我们将如何确保快速准确地处理用户反馈，同时将垃圾邮件和恶意调用服务的风险降至最低？</li></ol></blockquote><p><strong>提示</strong>：这个问题考察我们如何保护系统免受恶意调用的能力，一种可能的解决方案是结合使用自动和手动审核。</p><p>自动审核可以过滤掉明显的垃圾邮件和恶意性内容，而更复杂的情况可以标记为人工审查。</p><p>此外，对于该服务可以实施<strong>限流</strong>操作，防止用户在短时间内恶意提交大量反馈内容。</p><blockquote><ol start="6"><li>我们的团队构建了一个与其他几个服务通信的新的微服务系统，但是我们观察到新的微服务性能很差，造成这种情况的潜在原因是什么，我们将会采用什么方法来排除故障和解决问题？</li></ol></blockquote><p>这是另一个流行的微服务问题，因为它涉及如何查找服务性能问题和如何解决这些问题，微服务性能问题可能有多种潜在原因，例如：</p><ol><li><strong>网络延迟</strong>：服务之间存在网络延迟或网络连接比较差；</li><li><strong>系统瓶颈</strong>：微服务代码或数据库查询中存在性能瓶颈；</li><li><strong>资源不足</strong>：分配给微服务或其依赖项的资源不足；</li><li><strong>通信效率低下</strong>：低效的通信协议或糟糕的服务设计。</li></ol><p>要排除故障并解决问题，我们采用如下一些方法：</p><ol><li>对微服务架构进行全面分析，包括所使用的依赖项和通信协议；</li><li>使用一些监控工具来发现代码、数据库查询或网络连接中存在的瓶颈；</li><li>使用分布式追踪框架来定位服务之间的网络延迟或网络问题；</li><li>检查资源分配并根据需要进行调整；</li><li>如果有必要，进行代码和查询优化。</li></ol><p>确定问题后，我们可以通过修改微服务架构、优化代码、数据库查询和调整资源分配来解决问题；如果有必要我们还可以考虑重新设计通信协议或者对微服务系统进行重构。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/05.webp" style="width:100%"><blockquote><ol start="7"><li>设想一下我们整个应用正在一个分布式系统上运行，其中一条消息被发布到消息队列，并且有多个服务正在消费它，但是其中一个服务未能正常消费该消息，我们将采用什么方法来确定问题的根本原因并加以解决？</li></ol></blockquote><p>这个问题在实际工作中也很常见，但是不要回答面试官说我们的运维团队会处理这个问题，或者我们有可以运行脚本来解决问题，面试官对通过技术手段解决这个问题更感兴趣。</p><p>根据我的经验，可以采用以下方法来确定问题的根本原因并加以解决：</p><ol><li><strong>日志检查</strong>：查看应用消费消息的日志内容，查看是否有错误信息或抛出异常；</li><li><strong>检查服务配置</strong>：检查服务的配置，确保配置正确，能够正常消费队列中的消息；</li><li><strong>检查服务和消息队列之间的网络连接</strong>：检查服务与消息队列之间的连接，确保连接建立并正常工作；</li><li><strong>检查消息队列</strong>：检查消息队列查看消息是否已正确发布，以及是否可以被正常消费；</li><li><strong>网络或防火墙问题</strong>：如果问题仍未解决，需要检查是否有任何网络或防火墙问题可能会阻止服务消费消息。</li></ol><p>要解决此问题，可以采用如下下步骤：</p><ol><li>如果问题是由于配置或代码问题引起的，则可以通过更新服务的配置或代码来解决；</li><li>如果问题是由于网络或防火墙问题引起的，则应通知相关运维团队解决网络问题；</li><li>如果问题是由于消息队列的问题引起的，则应检查队列并解决相关问题；</li><li>如果消息不能被消费，则应将其移至错误队列以供进一步分析和处理。</li></ol><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/06.webp" style="width:100%"><blockquote><ol start="8"><li>考虑这样一个场景：我们的团队正在开发一个需要依赖另外一个服务的数据（静态数据或动态数据）的新微服务，但是提供数据的服务具有不同的数据模式定义，我们无法更改它，这种情况将如何处理？</li></ol></blockquote><p>这也是我们在开发项目时都会遇到的常见问题，处理这种问题的一种方案是<strong>在两个服务之间添加一个适配器或转换层</strong>，这个适配器可以将数据从源服务所提供的格式转换为目标服务期望的格式。</p><p>另一种方法是<strong>使用 <code>Apache Kafka</code> 或 <code>Apache Nifi</code> 等数据集成平台</strong>，这些框架能够动态转换数据并提供给目标服务使用。</p><p>或者，我们可以根据目标服务的数据模式创建一套数据副本，并使用数据复制工具（例如 <code>Apache Flink</code> 或 <code>Apache Spark</code>）定期将其与源服务保持数据同步。</p><p>重要一点是要确保所选择的方案<strong>不会出现数据不一致或数据质量问题</strong>，应该进行充分的测试和验证，以确保数据在服务之间准确地转换和集成。</p><blockquote><ol start="9"><li>设想系统正在微服务架构上运行，其中多个服务依赖于共享数据库，但是我们注意到其中一项服务导致数据库出现死锁，我们该如何处理这种情况并防止它再次发生？</li></ol></blockquote><p>如果其中一个服务导致共享数据库出现死锁，可以采用以下方法来处理这种情况并防止它再次发生：</p><ol><li><strong>确定死锁出现的原因</strong>：第一步是确定导致死锁的服务以及导致死锁的查询语句，可以通过分析数据库日志和监控数据库活动来完成；</li><li><strong>修复死锁</strong>：一旦确定了死锁的原因，下一步就是修复它，可以通过更改查询、添加索引或优化数据库模式来完成，在某些情况下，可能需要修改导致死锁的服务代码；</li><li><strong>使用锁和并发控制</strong>：为防止未来死锁再次发生，使用恰当的并发锁和并发控制非常重要，可以通过使用数据库提供的锁机制来完成，例如行级锁、表级锁或数据库级锁；</li><li><strong>使用分布式事务</strong>：另一种防止死锁的方法是使用分布式事务，分布式事务可以使多个服务以事务方式协同工作，确保跨服务保持数据一致性；</li><li><strong>执行负载测试</strong>：最后，执行负载测试以确保系统可以处理高并发请求，并且数据库可以正常处理请求同时不会出现死锁，这一点很重要，负载测试可以帮助识别任何性能瓶颈，并能够提前采取措施。</li></ol><p>为避免此类问题，建议每个服务都使用自己单独的数据库。还有一种架构模式被称为<a href="https://medium.com/javarevisited/top-10-microservice-design-patterns-for-experienced-developers-f4f5f782810e">单个微服务独享数据库</a>，这个模式也值得每个 <code>Java</code> 开发人员去了解。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/12-Top-10-Microservices-Problem-Solving-Questions/07.webp" style="width:100%"><blockquote><ol start="10"><li>假设我们的团队正在开发一个新的微服务，该微服务使用 <code>RESTful API</code> 与其他几个服务进行通信，但是这些 <code>API</code> 没有完善的说明文档，而且我们也无权访问其他服务的源代码，那么我们将如何处理这种情况？</li></ol></blockquote><p><strong>提示</strong>：这也是我们在开发应用程序实际工作中经常会遇到的场景，更多时候我们在使用这些接口之前需要努力理解其他系统的 <a href="https://medium.com/javarevisited/7-difference-between-rest-and-soap-web-services-87510b06b687">REST API</a> 。</p><p>处理这种情况的一种可能方法是<strong>使用 <code>Swagger</code> 之类的工具来自动记录 <code>API</code></strong> 。<code>Swagger</code> 可以分析 <code>API</code> 并生成可以与其他开发人员共享的 <code>API</code> 文档。此外还可以<strong>使用 <code>Postman</code> 等工具来测试 <code>API</code></strong>并更好地了解它们的行为。</p><p>另一种方法是尝试与其他服务的负责人沟通并索要 <code>API</code> 文档或访问源代码，如果这不可行，则可以通过分析网络流量和请求响应结果来对 <code>API</code> 进行逆向工程。</p><p>这种方法有助于理解 <code>API</code> 的行为，但是这种方法可能不是很可靠或者不可扩展。</p><p>无论如何，谨慎处理此类情况很重要，因为没有文档记录的 <code>API</code> 可能容易出现意外行为，或者在没有通知情况下被更改，从而影响微服务架构的性能和可靠性。</p><hr><blockquote><ol start="11"><li>我们正在开发微服务架构，其中多个服务使用同步 <code>RESTful API</code> 相互通信，但是我们注意到服务的响应时间很长，而且系统不可扩展，我们能够采用什么方法来提高系统的性能和可扩展性？</li></ol></blockquote><p>我们可以采用多种方法来提高同步 <code>RESTful API</code> 微服务架构的性能和可扩展性。</p><p>以下是一些可能的解决方案：</p><ol><li><strong>缓存实现</strong>：缓存可以显着缩短响应时间并降低系统负载，通过缓存被频繁访问的数据或响应结果，我们可以避免昂贵的服务往返开销并显著降低响应时间；</li><li><strong>使用异步消息</strong>：异步消息传递允许服务无需等待响应结果的情况下进行通信，从而提高系统可扩展性并降低响应时间；通过使用消息队列来对服务进行解耦，我们可以并行处理请求并减少响应时间；</li><li><strong>负载均衡实现</strong>：负载均衡可以在服务的多个实例之间分配负载并提高系统可伸缩性，通过使用负载均衡，我们可以确保请求均衡分布到所有机器并避免某一项服务流量过载；</li><li><strong>优化数据库</strong>：索引优化、数据分片和数据分区等数据库优化手段可以提高系统性能和可扩展性，通过对数据库优化，可以降低响应时间，提高系统的可扩展性；</li><li><strong>使用 API 网关</strong>：<a href="https://medium.com/javarevisited/difference-between-api-gateway-and-load-balancer-in-microservices-8c8b552a024">API网关</a>可以为服务提供一个中心化代理入口，并有助于实现负载均衡、缓存和限流，通过使用 <code>API</code> 网关，可以提高系统的可扩展性和可靠性；</li><li><strong>考虑重新设计微服务</strong>：如果上述解决方案都无效，可能需要重新设计微服务系统，减少服务依赖并提高可扩展性，通过将整个系统分割为更小、更独立的服务，我们可以提高系统的可扩展性和性能。</li></ol><hr><h2 id="为什么要练习基于实际场景的微服务面试题？"><a href="#为什么要练习基于实际场景的微服务面试题？" class="headerlink" title="为什么要练习基于实际场景的微服务面试题？"></a>为什么要练习基于实际场景的微服务面试题？</h2><p>正如我所提到的，微服务已经成为现代软件架构中不可或缺的一部分，特别是对于云原生开发，经验丰富的开发人员应该充分地理解随之而来的常见问题和挑战，这些基于实际场景的问题提供机会去体验微服务，即使并没有在微服务架构中工作过。</p><p>要在微服务开发中脱颖而出，开发人员应该对分布式系统、设计模式和架构原则有充分的理解，我们还应该熟悉可用于构建和管理微服务的各种工具和技术。</p><p>对于使用微服务的有经验的开发人员来说，一个重要的提示是及时了解<strong>微服务架构的最新趋势和最佳实践</strong>，我们还应该通过培训、认证和参与社区活动不断提高个人的技能。</p><p>通过积累坚实的微服务基础并不断学习和适应新挑战，经验丰富的开发人员可以成功构建健壮、高扩展性、高可靠性的微服务系统。</p><p>这些场景和问题以及解决问题思路可以检测开发人员解决问题的能力和对微服务架构原则的理解，例如容错、可扩展性和弹性。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这就是所有<strong>基于微服常见场景的面试题和解决问题的思路</strong>，本文中讨论的实际场景和解决问题的思路能够使我们窥见在使用微服务时可能面临的实际挑战。</p><p>在本文中我试图涵盖常见的陷阱和挑战，例如性能、安全性、服务集成以及缓存使用、异步消息、消息队列、设计模式和工具（例如 <code>Swagger</code> 和 <code>Postman</code>）提供的解决方案。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/javarevisited/top-10-microservices-problem-solving-questions-for-5-to-10-years-exper
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="Microservices" scheme="https://dongzl.github.io/tags/Microservices/"/>
    
  </entry>
  
  <entry>
    <title>探索 MySQL 索引优化神器</title>
    <link href="https://dongzl.github.io/2023/04/22/11-Understand-MySQL-Index-Optimization-Artifact/"/>
    <id>https://dongzl.github.io/2023/04/22/11-Understand-MySQL-Index-Optimization-Artifact/</id>
    <published>2023-04-22T10:24:22.000Z</published>
    <updated>2023-12-31T06:59:02.145Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://betterprogramming.pub/understand-the-mysql-index-optimization-artifact-d4d7c6eb31f3">https://betterprogramming.pub/understand-the-mysql-index-optimization-artifact-d4d7c6eb31f3</a></p></blockquote><p>随着用户数量和数据量的增长，慢查询可能是一个无法回避的问题。一般来说，如果出现慢查询，都会伴随着出现接口响应慢，接口超时等问题。</p><p>如果是在高并发场景，可能会导致数据库连接被打满，直接导致服务不可用。</p><p>慢查询会引起很多问题。那么我们该如何优化慢查询呢？</p><p>主要的解决方式有如下一些：</p><ul><li>监控执行的 <code>SQL</code>，发送邮件和手机短信告警，方便快速定位慢查询 <code>SQL</code>；</li><li>开启数据库慢查询日志功能；</li><li>简化业务逻辑；</li><li>代码重构和优化；</li><li>异步处理；</li><li><code>SQL</code> 优化；</li><li>索引优化。</li></ul><p>这篇文章我主要会关注索引优化，因为索引优化是解决慢查询 <code>SQL</code> 问题最有效的一种方式。</p><p>如何查看 <code>SQL</code> 索引的执行状态？</p><p>是的，通过在 <code>SQL</code> 语句前面添加 <code>explain</code> 关键字，我们可以查看 <code>SQL</code> 的执行计划。通过执行计划，我们可以清晰地看到表和索引的执行情况，索引是否使用，索引执行的顺序，使用索引的类型等等。</p><p>优化索引的步骤如下：</p><ul><li>使用 <code>explain</code> 查看 <code>SQL</code> 执行计划；</li><li>确定哪些索引使用不当；</li><li>优化 <code>SQL</code>，可能需要多次优化 <code>SQL</code> 才能达到索引使用的最佳效果。</li></ul><h2 id="Explain-是什么"><a href="#Explain-是什么" class="headerlink" title="Explain 是什么"></a>Explain 是什么</h2><p>我们来看看 <code>MySQL</code> 的官方文档是怎么描述 <code>explain</code> 的：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/01.webp" style="width:100%"><div style="color:DarkGray;font-size:14px;text-align:center;"> [Click to read documentation](https://dev.mysql.com/doc/refman/8.0/en/explain.html) </div><h3 id="explain-语法"><a href="#explain-语法" class="headerlink" title="explain 语法"></a>explain 语法</h3><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><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></pre></td><td class="code"><pre><span class="line">&#123;EXPLAIN <span class="operator">|</span> <span class="keyword">DESCRIBE</span> <span class="operator">|</span> <span class="keyword">DESC</span>&#125;</span><br><span class="line">    tbl_name [col_name <span class="operator">|</span> wild]</span><br><span class="line"></span><br><span class="line">&#123;EXPLAIN <span class="operator">|</span> <span class="keyword">DESCRIBE</span> <span class="operator">|</span> <span class="keyword">DESC</span>&#125;</span><br><span class="line">    [explain_type]</span><br><span class="line">    &#123;explainable_stmt <span class="operator">|</span> FORCONNECTION connection_id&#125;</span><br><span class="line"></span><br><span class="line">explain_type: &#123;</span><br><span class="line">    EXTENDED</span><br><span class="line">  <span class="operator">|</span> PARTITIONS</span><br><span class="line">  <span class="operator">|</span> FORMAT <span class="operator">=</span> format_name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">format_name: &#123;</span><br><span class="line">    TRADITIONAL</span><br><span class="line">  <span class="operator">|</span> JSON</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">explainable_stmt: &#123;</span><br><span class="line">    SELECTstatement</span><br><span class="line">  <span class="operator">|</span> DELETEstatement</span><br><span class="line">  <span class="operator">|</span> INSERTstatement</span><br><span class="line">  <span class="operator">|</span> REPLACEstatement</span><br><span class="line">  <span class="operator">|</span> UPDATEstatement</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用一个简单的 <code>SQL</code> 看看使用 <code>explain</code> 关键字的效果：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test1;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/02.webp" style="width:100%"><p>从上图可以看出，执行结果中会显示 <code>12</code> 列信息。</p><p>每个列具体信息如下：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/03.webp" style="width:100%"><p>说白了，我们需要了解这些列的具体含义，才能正常判断索引的使用情况。事不宜迟，让我们马上开始。</p><h3 id="id-列"><a href="#id-列" class="headerlink" title="id 列"></a>id 列</h3><p>该列的值为 <code>select</code> 查询中的序号，如 <code>1、2、3、4</code> 等，决定了表的执行顺序。</p><p>一条 <code>SQL</code> 的执行计划一般有三种情况：</p><ul><li>相同 <code>id</code>；</li><li>不同 <code>id</code>；</li><li>相同 <code>id</code> 和不同 <code>id</code> 同时出现。</li></ul><p>那么，在这三个 <code>case</code> 中表的执行顺序是怎样的呢？</p><h4 id="1-相同-id"><a href="#1-相同-id" class="headerlink" title="1. 相同 id"></a>1. 相同 id</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test1 t1 <span class="keyword">inner</span> <span class="keyword">join</span> test1 t2 <span class="keyword">on</span> t1.id<span class="operator">=</span>t2.id</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/04.webp" style="width:100%"><p>我们可以看到执行结果中的两条数据 <code>id</code> 是相同的，都是 <code>1</code>。</p><p>在这个场景中表的执行顺序是什么样的呢？</p><p>答案：从上到下开始执行，首先执行表 <code>t1</code>，接着执行表 <code>t2</code>。</p><h4 id="2-不同-id"><a href="#2-不同-id" class="headerlink" title="2. 不同 id"></a>2. 不同 id</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test1 t1 <span class="keyword">where</span> t1.id <span class="operator">=</span> (<span class="keyword">select</span> id <span class="keyword">from</span>  test1 t2 <span class="keyword">where</span>  t2.id<span class="operator">=</span><span class="number">2</span>);</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/05.webp" style="width:100%"><p>我们可以看到执行结果中的两条数据 <code>id</code> 是不同的，第一条数据 <code>1</code>，第二条数据是 <code>2</code>。</p><p>在这个场景中表的执行顺序是什么样的呢？</p><p>答案：序号大的会首先被执行。在这里将会从下到上开始执行，表 <code>t2</code> 将首先被执行，接着表 <code>t1</code> 将被执行。</p><h4 id="3-相同-id-和-不同-id-同时出现"><a href="#3-相同-id-和-不同-id-同时出现" class="headerlink" title="3. 相同 id 和 不同 id 同时出现"></a>3. 相同 id 和 不同 id 同时出现</h4><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></pre></td><td class="code"><pre><span class="line">explain</span><br><span class="line"><span class="keyword">select</span> t1.<span class="operator">*</span> <span class="keyword">from</span> test1 t1</span><br><span class="line"><span class="keyword">inner</span> <span class="keyword">join</span> (<span class="keyword">select</span> <span class="built_in">max</span>(id) mid <span class="keyword">from</span> test1 <span class="keyword">group</span> <span class="keyword">by</span> id) t2</span><br><span class="line"><span class="keyword">on</span> t1.id<span class="operator">=</span>t2.mid</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/06.webp" style="width:100%"><p>我们在执行结果中看到了三条数据。前两条数据 <code>id</code> 相同，第三条数据 <code>id</code> 与前一条不同。</p><p>在这个场景中表的执行顺序是什么样的呢？</p><p>答案：先执行序号大的，从下往上执行。当序号相同时，从上往下执行。因此，此列中表的顺序是 <code>test1</code>、<code>t1</code>。</p><p><strong>注意</strong>：有一个特殊的表名称，内容为 <code>&lt;derived2&gt;</code>，表示是派生表，文章后面会详细介绍。</p><h3 id="select-type-列"><a href="#select-type-列" class="headerlink" title="select_type 列"></a>select_type 列</h3><p>这一列表示 <code>select</code> 的类型，具体包括以下 <code>11</code> 种类型：</p><ul><li><code>SIMPLE</code>：简单查询；</li><li><code>PRIMARY</code>：最外层查询；</li><li><code>UNION</code>：<code>UNION</code> 之后的第二个或以后的查询；</li><li><code>DEPENDENT UNION</code>：<code>UNION</code> 之后的第二个或后面的查询，取决于外部查询；</li><li><code>UNION RESULT</code>：<code>UNION</code> 的结果；</li><li><code>SUBQUERY</code>：第一个子查询；</li><li><code>DEPENDENT SUBQUERY</code>：第一个子查询，取决于外部查询；</li><li><code>DERIVED</code>：派生表；</li><li><code>MATERIALIZED</code>：物化子查询；</li><li><code>UNCACHEABLE SUBQUERY</code>：结果无法缓存的子查询；</li><li><code>UNCACHEABLE UNION</code>：无法缓存结果的 <code>UNION</code> 之后的第二个查询或后面的查询。</li></ul><p>最常用的有以下几种类型。</p><ul><li><code>SIMPLE</code>：简单的 <code>SELECT</code> 查询，不包含子查询和 <code>UNION</code> 操作；</li><li><code>PRIMARY</code>：复杂查询中最外层的查询，代表主查询；</li><li><code>SUBQUERY</code>：包含在 <code>SELECT</code> 或 <code>WHERE</code> 列表中的子查询；</li><li><code>DERIVED</code>：<code>FROM</code> 列表中包含的子查询，即派生的；</li><li><code>UNION</code>：<code>UNION</code> 关键字之后的查询；</li><li><code>UNION RESULT</code>：<code>UNION</code> 操作之后从表中获取结果集。</li></ul><p>让我们看一下这些 <code>SELECT</code> 类型是如何出现的？</p><h4 id="1-SIMPLE"><a href="#1-SIMPLE" class="headerlink" title="1. SIMPLE"></a>1. SIMPLE</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test1;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/07.webp" style="width:100%"><p>它只出现在简单的 <code>SELECT</code> 查询中，不包含子查询和 <code>UNION</code> 操作，这种类型比较直观，就不多说了。</p><h4 id="2-PRIMARY-和-SUBQUERY"><a href="#2-PRIMARY-和-SUBQUERY" class="headerlink" title="2. PRIMARY 和 SUBQUERY"></a>2. PRIMARY 和 SUBQUERY</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test1 t1 <span class="keyword">where</span> t1.id <span class="operator">=</span> (<span class="keyword">select</span> id <span class="keyword">from</span>  test1 t2 <span class="keyword">where</span>  t2.id<span class="operator">=</span><span class="number">2</span>);</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/08.webp" style="width:100%"><p>我们看到在这个嵌套查询的 <code>SQL</code> 中，最外层的 <code>t1</code> 表是 <code>PRIMARY</code> 类型，最里面的子查询 <code>t2</code> 表是 <code>SUBQUERY</code> 类型。</p><h4 id="3-DERIVED"><a href="#3-DERIVED" class="headerlink" title="3. DERIVED"></a>3. DERIVED</h4><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></pre></td><td class="code"><pre><span class="line">explain</span><br><span class="line"><span class="keyword">select</span> t1.<span class="operator">*</span> <span class="keyword">from</span> test1 t1</span><br><span class="line"><span class="keyword">inner</span> <span class="keyword">join</span> (<span class="keyword">select</span> <span class="built_in">max</span>(id) mid <span class="keyword">from</span> test1 <span class="keyword">group</span> <span class="keyword">by</span> id) t2</span><br><span class="line"><span class="keyword">on</span> t1.id<span class="operator">=</span>t2.mid</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/09.webp" style="width:100%"><p>最后一条记录是派生表，一般是 <code>FROM</code> 列表中包含的子查询，这里是 <code>SQL</code> 语句中的分组子查询。</p><h4 id="4-UNION-and-UNION-RESULT"><a href="#4-UNION-and-UNION-RESULT" class="headerlink" title="4. UNION and UNION RESULT"></a>4. UNION and UNION RESULT</h4><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></pre></td><td class="code"><pre><span class="line">explain</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test1</span><br><span class="line"><span class="keyword">union</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test2</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/10.webp" style="width:100%"><p>表 <code>test2</code> 是 <code>UNION</code> 关键字之后的查询，所以它被标识为 <code>UNION</code>，表 <code>test1</code> 是主表，被标识为 <code>PRIMARY</code>。而 <code>&lt;union1,2&gt;</code> 表示 <code>id=1</code> 和 <code>id=2</code> 的表并集，结果被标记为 <code>UNION RESULT</code>。</p><p>所以 <code>UNION</code> 和 <code>UNION RESULT</code> 通常是成对出现的。</p><h3 id="table-列"><a href="#table-列" class="headerlink" title="table 列"></a>table 列</h3><p>该列的值表示输出行所引用的表名，如前面的：<code>test1</code>、<code>test2</code> 等。</p><p>但它也可以是以下值之一：</p><ul><li><code>&lt;unionM,N&gt;</code>：<code>M</code> 和 <code>N</code> 并集操作的行记录和记录 <code>id</code>；</li><li><code>&lt;derivedN&gt;</code>：用于与此行关联的派生表结果 <code>id</code> 的值 <code>N</code>。派生表可能来自（例如）<code>FROM</code> 子句中的子查询；</li><li><code>&lt;subqueryN&gt;</code>：子查询的结果，其 <code>id</code> 值为 <code>N</code>。</li></ul><h3 id="partitions-列"><a href="#partitions-列" class="headerlink" title="partitions 列"></a>partitions 列</h3><p>此列的值表示匹配查询记录结果的分区。</p><h3 id="type-列"><a href="#type-列" class="headerlink" title="type 列"></a>type 列</h3><p>该列的值表示连接类型，是索引执行情况的重要指标。</p><p>这包含以下类型：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/11.webp" style="width:100%"><p>执行结果从最好到最差的顺序是从上到下。</p><p>我们需要关注以下类型：</p><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">system &gt; const &gt; eq_ref &gt; ref &gt; range &gt; index &gt; all</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">test2 table structure</span></span><br><span class="line">id    code    name</span><br><span class="line">1     001     city1</span><br></pre></td></tr></table></figure><p>在 <code>code</code> 字段上建立一个普通索引。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/12.webp" style="width:100%"><p>下面我们一一看看几种常见的连接类型是如何出现的。</p><h4 id="1-System"><a href="#1-System" class="headerlink" title="1. System"></a>1. System</h4><p>这种类型只需要数据库表中的一条数据，是 <code>const</code> 类型的特例，一般不会出现。</p><h4 id="2-Const"><a href="#2-Const" class="headerlink" title="2. Const"></a>2. Const</h4><p>通过一个索引可以找到数据，一般用在以主键或唯一索引为条件的查询 <code>SQL</code> 语句中。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test2 <span class="keyword">where</span> id<span class="operator">=</span><span class="number">1</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/13.webp" style="width:100%"><h4 id="3-Eq-ref"><a href="#3-Eq-ref" class="headerlink" title="3. Eq_ref"></a>3. Eq_ref</h4><p>通常用于主键或唯一索引扫描。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test2 t1 <span class="keyword">inner</span> <span class="keyword">join</span> test2 t2 <span class="keyword">on</span> t1.id<span class="operator">=</span>t2.id;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/14.webp" style="width:100%"><p><code>const</code> 和 <code>eq_ref</code> 都是对主键或唯一索引的扫描，那这两种类型有什么区别？</p><p>答案：<code>const</code> 只会被索引一次，<code>eq_ref</code> 的主键与数据记录的主键匹配。由于表中有多条数据，一般情况下，需要对数据进行多次索引才能全部匹配。</p><h4 id="4-Ref"><a href="#4-Ref" class="headerlink" title="4. Ref"></a>4. Ref</h4><p>常用于非主键索引和唯一索引扫描。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test2 <span class="keyword">where</span> code <span class="operator">=</span> <span class="string">&#x27;001&#x27;</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/15.webp" style="width:100%"><h4 id="5-Range"><a href="#5-Range" class="headerlink" title="5. Range"></a>5. Range</h4><p>通常用于范围查询，例如：<code>between...and</code> 或者是 <code>in</code> 操作。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> test2 <span class="keyword">where</span> id <span class="keyword">between</span> <span class="number">1</span> <span class="keyword">and</span> <span class="number">2</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/16.webp" style="width:100%"><h4 id="6-Index"><a href="#6-Index" class="headerlink" title="6. Index"></a>6. Index</h4><p>全索引扫描。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> code <span class="keyword">from</span> test2;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/17.webp" style="width:100%"><h5 id="7-All"><a href="#7-All" class="headerlink" title="7. All"></a>7. All</h5><p>全表扫描。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span>  <span class="keyword">from</span> test2;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/18.webp" style="width:100%"><h3 id="possible-keys-列"><a href="#possible-keys-列" class="headerlink" title="possible_keys 列"></a>possible_keys 列</h3><p>此列表示可能被选择使用的索引。</p><p>请注意，此列完全独立于表顺序，这意味着在实际中 <code>possible_keys</code> 列显示的某些索引可能与生成的表顺序并不完全一致。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/19.webp" style="width:100%"><p>如果此列结果为 <code>NULL</code>，则表示没有关联索引，在这种情况下，我们可以通过检查 <code>WHERE</code> 子句，查看是否引用了一些符合索引条件的列来提高查询性能。</p><h3 id="Key-列"><a href="#Key-列" class="headerlink" title="Key 列"></a>Key 列</h3><p>此列表示实际使用的索引。有可能会出现 <code>possible_keys</code> 列为空，但是 <code>key</code> 列不为空的情况。</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">test1 table structure</span></span><br><span class="line">id(bigint)    code(varchar30)    name(varchar30)</span><br><span class="line">1             001                foo</span><br><span class="line">2             002                bar</span><br></pre></td></tr></table></figure><p><code>code</code> 和 <code>name</code> 列创建了联合索引。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/20.webp" style="width:100%"><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> code <span class="keyword">from</span> test1;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/21.webp" style="width:100%"><p>这条 <code>SQL</code> 预计不会使用索引，但实际上使用了全索引扫描。</p><h3 id="key-len-列"><a href="#key-len-列" class="headerlink" title="key_len 列"></a>key_len 列</h3><p>此列表示被使用到的索引的长度。上面的 <code>key</code> 列可以看出索引是否被使用，<code>key_len</code> 列可以进一步看出索引是否被充分利用，毫无疑问，它是非常重要的列。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/22.webp" style="width:100%"><p><code>key_len</code> 是如何计算的呢？</p><p>有三个因素决定了 <code>key_len</code> 的结果：</p><ol><li>字符集（<code>Character set</code>）</li><li>字段长度（<code>Length</code>）</li><li>是否为空（<code>Is it empty</code>）</li></ol><p>常用字符编码占用的字节数如下：</p><ul><li><code>GBK</code>：<code>2</code> 字节；</li><li><code>UTF8</code>：<code>3</code> 字节；</li><li><code>ISO8859–1</code>：<code>1</code> 字节；</li><li><code>GB2312</code>：<code>2</code> 字节；</li><li><code>UTF-16</code>：<code>2</code> 字节。</li></ul><p>MySQL 一些常用字段类型占用的字节数：</p><ul><li><code>char(n)</code>：<code>n</code> 字节；</li><li><code>varchar(n)</code>：<code>n + 2</code> 字节；</li><li><code>tinyint</code>：<code>1</code> 字节；</li><li><code>smallint</code>：<code>2</code> 字节；</li><li><code>int</code>：<code>4</code> 字节；</li><li><code>bigint</code>：<code>8</code> 字节；</li><li><code>date</code>：<code>3</code> 字节；</li><li><code>timestamp</code>：<code>4</code> 字节；</li><li><code>datetime</code>：<code>8</code> 字节。</li></ul><p>另外，如果字段类型允许为空，则添加一个字节。</p><p>上图中 <code>184</code> 的值是怎么计算出来的？</p><p>首先，我使用的数据库的字符编码格式：<code>UTF8</code>，占三个字节。</p><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">184 = 30 * 3 + 2 + 30 * 3 + 2</span><br></pre></td></tr></table></figure><p>然后，把 <code>test1</code> 表的 <code>code</code> 字段类型改成 <code>char</code>，改成允许为空，再测试。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> code  <span class="keyword">from</span> test1;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/23.webp" style="width:100%"><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">183 = 30 * 3 + 1 + 30 * 3 + 2</span><br></pre></td></tr></table></figure><p>还有一个问题：为什么这一列可以显示索引是否被完全使用？</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> code  <span class="keyword">from</span> test1 <span class="keyword">where</span> code<span class="operator">=</span><span class="string">&#x27;001&#x27;</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/24.webp" style="width:100%"><p>上图中使用了联合索引：<code>idx_code_name</code>。如果索引匹配所有的 <code>key_len</code>，应该是 <code>183</code>，但实际上是 <code>92</code>，也就是说没有使用到所有的索引，索引没有被完全使用。</p><h3 id="ref-列"><a href="#ref-列" class="headerlink" title="ref 列"></a>ref 列</h3><p>此列表示索引命中的列或常量。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> <span class="operator">*</span>  <span class="keyword">from</span> test1 t1 <span class="keyword">inner</span> <span class="keyword">join</span> test1 t2 <span class="keyword">on</span> t1.id<span class="operator">=</span>t2.id <span class="keyword">where</span> t1.code<span class="operator">=</span><span class="string">&#x27;001&#x27;</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/25.webp" style="width:100%"><p>我们看到表 <code>t1</code> 命中的索引是 <code>const</code>（常量），<code>t2</code> 命中的索引是 <code>sue</code> 库的 <code>t1</code> 表的 <code>id</code> 字段。</p><h3 id="rows-列"><a href="#rows-列" class="headerlink" title="rows 列"></a>rows 列</h3><p>此列表示 <code>MySQL</code> 认为执行查询需要扫描的行数。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/26.webp" style="width:100%"><p>对于 <code>InnoDB</code> 引擎表，这个数字是一个估计值，可能并不总是准确的。</p><h3 id="filtered-列"><a href="#filtered-列" class="headerlink" title="filtered 列"></a>filtered 列</h3><p>此列表示按条件过滤的行数所占表行数百分比的估算值。最大值为 <code>100</code>，这意味着没有过滤任何数据。从 <code>100</code> 开始数值减小表示增加数据过滤。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/27.webp" style="width:100%"><p><code>Rows</code> 结果显示估算会扫描的行数，<code>rows × filtered</code> 结果表示与后面的表进行连接操作的行数。</p><p>例如，如果行数为 <code>1,000</code>，过滤为 <code>50.00（50%）</code>，则与下表连接的行数为 <code>1000 × 50% = 500</code>。</p><h3 id="extra-列"><a href="#extra-列" class="headerlink" title="extra 列"></a>extra 列</h3><p>该字段包含有关 <code>MySQL</code> 如何解析查询的其他信息。这个列信息还是很重要的，但是里面的值太多了，就不一一介绍了，只列举几个常见的。</p><h4 id="1-Impossible-WHERE"><a href="#1-Impossible-WHERE" class="headerlink" title="1. Impossible WHERE"></a>1. Impossible WHERE</h4><p>假设指定 <code>WHERE</code> 后面的条件始终为 <code>false</code>。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> code  <span class="keyword">from</span> test1 <span class="keyword">where</span> <span class="string">&#x27;a&#x27;</span> <span class="operator">=</span> <span class="string">&#x27;b&#x27;</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/28.webp" style="width:100%"><h4 id="2-Using-filesort"><a href="#2-Using-filesort" class="headerlink" title="2. Using filesort"></a>2. Using filesort</h4><p>表示按文件排序，一般出现在指定排序和索引排序不一致的情况下。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> code  <span class="keyword">from</span> test1 <span class="keyword">order</span> <span class="keyword">by</span> name <span class="keyword">desc</span>;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/29.webp" style="width:100%"><p>这里创建了 <code>code</code> 和 <code>name</code> 的联合索引，顺序是 <code>code</code> 列在前，<code>name</code> 列在后；<code>SQL</code> 语句里按 <code>name</code> 字段直接降序，与之前的联合索引排序不同。</p><h4 id="3-Using-index"><a href="#3-Using-index" class="headerlink" title="3. Using index"></a>3. Using index</h4><p>表示是否使用了覆盖索引，说白了就是获取的列值是否都经过了索引。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/30.webp" style="width:100%"><p>在上面的例子中，实际使用的是：<code>Using index</code>，因为只返回一列代码，所以对其字段进行了索引。</p><h4 id="4-Using-temporary"><a href="#4-Using-temporary" class="headerlink" title="4. Using temporary"></a>4. Using temporary</h4><p>表示是否使用临时表，一般见于 <code>order by</code> 和 <code>group by</code> 语句。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">explain <span class="keyword">select</span> name  <span class="keyword">from</span> test1 <span class="keyword">group</span> <span class="keyword">by</span> name;</span><br></pre></td></tr></table></figure><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/11-Understand-MySQL-Index-Optimization-Artifact/31.webp" style="width:100%"><h4 id="5-Using-where"><a href="#5-Using-where" class="headerlink" title="5. Using where"></a>5. Using where</h4><p>表示使用了 <code>WHERE</code> 条件过滤器。</p><h4 id="6-Using-join-buffer"><a href="#6-Using-join-buffer" class="headerlink" title="6. Using join buffer"></a>6. Using join buffer</h4><p>表示是否使用了连接缓冲，优先被连接的表的一部分数据被读入连接缓冲区，然后使用缓冲区中的数据与当前表执行连接操作。</p><p>下面是索引优化的过程：</p><ol><li>首先，使用慢查询日志定位需要优化的 <code>SQL</code> 语句；</li><li>使用 <code>explain</code> 查询计划查询索引使用情况；</li><li>关注 <code>key</code>、<code>key_len</code>、<code>type</code>、<code>extra</code> 信息，一般情况下，根据这四列就可以找到索引问题了；</li><li>根据第 <code>3</code> 步发现的索引问题优化 <code>SQL</code> 语句；</li><li>返回到第 <code>2</code> 步重复操作。</li></ol><p>感谢您阅读本文，敬请期待更多精彩文章。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://betterprogramming.pub/understand-the-mysql-index-optimization-artifact-d4d7c6eb31f3&quot;&gt;https://be
      
    
    </summary>
    
    
      <category term="数据库" scheme="https://dongzl.github.io/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
      <category term="MySQL" scheme="https://dongzl.github.io/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>Redis 集群高可用和数据持久化</title>
    <link href="https://dongzl.github.io/2023/04/15/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/"/>
    <id>https://dongzl.github.io/2023/04/15/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/</id>
    <published>2023-04-15T13:39:53.000Z</published>
    <updated>2023-12-31T06:59:02.145Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/@bb8s/how-redis-cluster-achieves-high-availability-and-data-persistence-8cdc899764e8">https://medium.com/@bb8s/how-redis-cluster-achieves-high-availability-and-data-persistence-8cdc899764e8</a></p></blockquote><p>对于 <code>Redis</code> 集群等有状态分布式系统而言，系统的高可靠性需要有两个内在要求：</p><ul><li><strong>持久性</strong>：即使集群中的某个实例发生故障也不会有数据丢失。<code>Redis</code> 集群提供两种持久化机制：<code>RDB（Redis Database）</code> 时间点快照和 <code>AOF（Append Only File）</code> 日志来保证数据的持久性；</li><li><strong>高可用性</strong>：即使出现网络故障、机器故障等，<code>Redis</code> 依然可以对外提供服务。</li></ul><p>首先让我们研究一下 <code>Redis</code> 集群如何实现数据持久化，这是在由每个 <code>Redis</code> 实例来保障的。</p><h2 id="Redis-持久化"><a href="#Redis-持久化" class="headerlink" title="Redis 持久化"></a>Redis 持久化</h2><p><code>Redis</code> 通常被广泛用于基于内存的 <code>KV</code> 缓存。如果某个 <code>Redis</code> 实例在生产环境中出现不可用，通常意味着该节点上的所有内存数据都会丢失。一种直接的解决方案是允许事故发生，并依靠底层数据库（如 <code>MySQL</code>）继续提供数据，保证服务可用；虽然这种方法在理论上是可行的，但是在实际生产环境中却很少被使用。缓存的意外故障会导致数据库的流量突然变大，很容易使数据库过载。此外即使数据库可以处理突然的过高负载，但是数据库的数据请求延迟通常比缓存要高很多。对于大多数业务场景来说，这种延迟地增加几乎是不可接受的。在 <code>Redis</code> 缓存层的持久化特性，可以大大减少直接访问数据库出现的概率。</p><p><code>Redis</code> 提供了两种持久化机制：</p><ul><li><code>AOF</code> 日志</li><li><code>RDB</code> 快照</li></ul><h2 id="什么是-AOF-日志"><a href="#什么是-AOF-日志" class="headerlink" title="什么是 AOF 日志"></a>什么是 AOF 日志</h2><p>我们知道 <code>MySQL</code> 等关系型数据库通常使用预写日志（<code>WAL</code>）来避免发生故障时的数据丢失。写操作数据首先追加到一个 <code>WAL</code>，然后再写入内存缓冲区和磁盘。<code>Redis</code> 使用了不同的解决方案，在 <code>Redis</code> 中，写入操作首先将数据写入到内存中，只有写入到内存中成功时，该操作才会继续持久化到日志中，也称为后写日志 (<code>WBL</code>)；如果数据写入内存失败，<code>Redis</code> 将不会写入日志，直接向客户端返回错误。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/01.webp" style="width:100%"><p>使用 <code>WBL</code> 方案的优势是：</p><ul><li><strong>不需要校验写入数据从而产生额外的请求开销</strong>：来自客户端的写请求可能是无效的并且永远不会成功，因此需要进行请求校验或健全性检查。在 <code>WBL</code> 的情况下，写入内存操作已经完成数据校验，因此在写入日志时不需要额外的健全性检查。</li><li><strong>不会阻塞当前的写入操作</strong>：可以配置 <code>Redis</code>，在将数据写入到内存之后，就可以向客户端返回操作成功。</li></ul><p>三种常用的 <code>AOF</code> 配置是：</p><table><thead><tr><th><code>AOF</code> 配置</th><th>保存日志时机</th><th>优势</th><th>劣势</th></tr></thead><tbody><tr><td><code>always</code></td><td>同步写入，每次请求同步写入日志</td><td>最大限度减少数据丢失，如果实例出现故障，最后一次写入操作可能会丢失</td><td>较高写入延迟，每次请求需要占用更多 <code>CPU</code> 周期，所以每个实例的吞吐量最低</td></tr><tr><td><code>everysecond</code></td><td>异步写入，每秒写入一次</td><td>写入延迟、吞吐量等性能居中</td><td>可能会丢失最后一秒的数据变更</td></tr><tr><td><code>no</code></td><td>异步写入，由本机操作系统决定将日志刷新到磁盘时间</td><td>三种配置当中性能最好</td><td>实例出现故障可能会导致更多数据丢失，依赖于操作系统配置</td></tr></tbody></table><p>到目前为止感觉一切都很美好，但是假设将成百上千的写请求发送到 <code>Redis</code> 实例，会发生什么情况呢？如果 <code>Redis</code> 为每一次写操作保存一条日志记录，<code>AOF</code> 文件最终会变得非常大，从而导致新的问题：</p><ul><li>文件系统通常对可以存储的单个文件大小有上限限制；</li><li>如果日志文件非常大，继续追加写入操作会随着文件大小的进一步增加而变得非常慢；</li><li>如果日志文件非常大，当 <code>Redis</code> 实例发生故障时，通过重放 <code>AOF</code> 日志中的每一个操作（即每条记录）来恢复数据，这个恢复过程将会非常慢。</li></ul><h2 id="AOF-压缩（日志重写）"><a href="#AOF-压缩（日志重写）" class="headerlink" title="AOF 压缩（日志重写）"></a>AOF 压缩（日志重写）</h2><p><code>AOF</code> 压缩将同一个 <code>Key</code>（即未压缩日志中的多条记录）上的多个变更（也称为写入）合并为一个变更，并作为一条记录重写到新日志。每当我们发出 <code>BGREWRITEAOF</code> 命令时，<code>Redis</code> 会将内存中重建当前数据集所需的最短命令序列的日志内容写入文件。由于实际业务场景中 <code>Key</code> 的数量通常是有限的，并且增长速度小于写请求的数量，<code>AOF</code> 压缩或 <code>AOF</code> 重写有助于缓解日志文件的快速增长。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/03.webp" style="width:100%"><p><code>AOF</code> 重写如何在后台工作？重新操作对 <code>Redis</code> 处理正常读/写请求性能指标会产生哪些影响？</p><p>原始未压缩的 <code>AOF</code> 日志通常由主进程写入，压缩操作由 <code>BGREWRITEAOF</code> 子进程完成，这样主进程永远不会被 <code>AOF</code> 重写操作所阻塞；也就是说，无论 <code>AOF</code> 重写是否正在执行，主进程都可以继续处理正常的请求流量——读/写 <code>KV</code> 记录——并不会受影响。</p><p><code>AOF</code> 重写操作由两个步骤组成：</p><ul><li>复制原日志文件，生成两个独立的日志文件；</li><li>保持两个日志文件同步。</li></ul><h3 id="文件拷贝"><a href="#文件拷贝" class="headerlink" title="文件拷贝"></a>文件拷贝</h3><p>每次触发重写时，主进程都会通过系统调用 <code>fork()</code> 函数来派生一个 <code>BGREWRITEAOF</code> 子进程。本质上这一步是创建了一个父进程的复制进程——也包括内存中所有状态的副本，此内存副中本包含所有最近的数据变更内容。<code>fork</code> 之后子进程会进行日志压缩，本质上是对键值数据的每个 <code>Key</code> 进行合并操作，并将合并结果持久化到磁盘上的一个新文件（重写日志）中。</p><blockquote><p>在 Linux fork() 函数实现中，子进程的内存“副本”不是直接复制父进程所有内存页。相反它将内存页标记为“写时复制”，也就是说子进程和父进程最初共享所有内存页，只有当父进程有新的数据写入后，才会将内存页复制到子进程地址空间的单独内存页中。<a href="https://www.rubrik.com/blog/architecture/21/6/fork-is-the-way-lets-make-it-hurt-less#:~:text=Address%20space%20copying%20latency%3A%20Traditional,on%2Dwrite%20during%20fork().">查看更多详细信息</a>。</p></blockquote><h3 id="同步两份日志"><a href="#同步两份日志" class="headerlink" title="同步两份日志"></a>同步两份日志</h3><p>此时主进程没有被阻塞，而且还在正常处理读/写请求，所以未压缩的原始 <code>AOF</code> 日志在 <code>fork</code> 之后会不断变化，将新增的变更数据同步到重写日志的功能在不同的 <code>Redis</code> 版本有不同的实现。</p><p>对于 <code>Redis &lt; 7.0</code> 的版本首先将变更操作（写入操作）保存在内存缓冲区中，并按照上述 <code>AOF</code> 配置的时间间隔保存到原始 <code>AOF</code> 日志，这时如果 <code>Redis</code> 集群出现故障需要恢复数据，则使用原始 <code>AOF</code> 日志通过重放所有数据变更来恢复数据。</p><p><code>fork</code> 之后的变更数据也将写入 <code>AOF</code> 重写日志的缓冲区，也称为写时复制。当子进程重写完文件后，父进程会收到一个信号，将内存缓冲区中的记录持久化到 <code>AOF</code> 重写日志文件中。日志压缩完成后，将执行原子操作进行日志切换，原始 <code>AOF</code> 日志将被压缩后的 <code>AOF</code> 日志替换，然后被删除掉。</p><p>对于 <code>Redis ≥ 7.0</code>，内存缓冲区被一个新的临时 <code>AOF</code> 文件取代，以记录从 <code>fork</code> 开始后的增量数据，这个增量文件和旧的基础文件组合到一起代表发生变更后的全部数据。</p><h2 id="RDB-快照"><a href="#RDB-快照" class="headerlink" title="RDB 快照"></a>RDB 快照</h2><p>当我们使用 <code>AOF</code> 恢复数据时，所有的变更都需要重放。如果有大量的写入操作，即使经过日志压缩，重放日志操作也会非常慢。<code>Redis</code> 提供了另外一种快速的数据恢复方式：<strong>RDB 快照</strong>。</p><p><code>RDB</code> 快照就像拍照一样，它捕获在特定时间戳“冻结”的所有 <code>KV</code> 数据的状态，并将快照结果保存到磁盘。在这种情况下，当 <code>Redis</code> 集群发生故障时，可以通过将最近的快照文件加载回内存来恢复数据。</p><p>有几个细节需要注意：</p><ul><li>进行快照操作时，数据还可以变更吗？换句话说，正常的 <code>Redis</code> 读/写操作是否被快照操作所阻塞？</li><li>我们应该多久做一次快照？</li></ul><p><code>Redis</code> 为 <code>RDB</code> 快照提供了两个命令：<code>save</code> 和 <code>bgsave</code>。</p><ul><li><strong>save</strong>：由主进程进行快照操作，会阻塞数据读写；</li><li><strong>bgsave</strong>：创建一个专门用于生成 <code>RDB</code> 快照的子进程，避免阻塞主进程，这是 <code>Redis</code> 集群中使用的默认配置。</li></ul><p>一般来说，在生成快照时阻塞主进程并不是一个好主意。<code>Redis</code> 使用操作系统提供的 <code>COW（copy-on-write）</code> 机制来解决这个问题。简而言之，<code>bgsave</code> 子进程是从主进程派生出来的，当生成 <code>bgsave</code> 进程时，它开始将数据写入临时 <code>RDB</code> 快照文件，如果主进程需要读取共享内存中的数据，例如读取图中 <code>Key</code> 为 <code>A</code> 的键值数据，则不会发生冲突——因为快照只需要读取共享内存数据。但是当主进程需要修改数据时，例如插入/更新/删除 <code>Key</code> 为 <code>B</code> 的键值对，则修改后的内存页将被复制到子进程地址空间中的新内存页。本质上现在相同的数据有两个副本，<code>bgsave</code> 进程会访问新的副本生成 <code>RDB</code> 文件，而主进程仍然可以对旧副本中的键值对 <code>B</code> 进行变更。如果没有 <code>COW</code>，两个进程将需要依靠锁来协调对共享内存空间的访问以确保数据一致性，这在高并发场景下带来显着的性能开销——而 <code>Redis</code> 通常会在高并发场景下使用。 </p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/04.webp" style="width:100%"><p>保存 <code>RDB</code> 快照的触发条件可以在 <code>redis.conf</code> 文件中配置。例如：</p><ul><li><strong>save 900 1</strong>：表示如果每 <code>900</code> 秒有至少 <code>1</code> 个 <code>Key</code> 发生变更，则触发生成快照；</li><li><strong>save 300 10</strong>：表示如果每 <code>300</code> 秒有至少 <code>10</code> 个 <code>Key</code> 发生变更，则触发生成快照；</li><li><strong>save 60 10000</strong>：表示如果每 <code>60</code> 秒有至少 <code>10000</code> 个 <code>Key</code> 发生变更，则触发生成快照。</li></ul><p><code>Redis</code> 是如何理解这些配置并根据配置执行 <code>save</code> 或 <code>bgsave</code> 命令呢？在底层，<code>Redis</code> 维护着这两个关于快照的状态：</p><ul><li><strong>脏计数器</strong>：脏计数器保存自上次成功执行 <code>save</code> 或 <code>bgsave</code> 以来发生的变更（插入、更新、删除）次数；</li><li><strong>last_save 时间戳</strong>：<code>last_save</code> 记录最后一次成功执行 <code>save</code> 或 <code>bgsave</code> 的时间戳。</li></ul><p>当 <code>Redis</code> 成功执行键值对变更后，<strong>脏计数器</strong>会加 <code>1</code>，而 <code>last_save</code> 属性保持不变，存储的是最后一次创建快照的时间戳。<code>Redis</code> 内部还有一个 <code>serverCron</code> 函数，默认每 <code>100</code> 毫秒周期性执地行一次，该函数会迭代 <code>saveparams</code> 中的所有快照触发器配置，<code>saveparams</code> 是上述 <code>redis.conf</code> 文件配置规则在内存中的表示，只要满足一个条件就执行 <code>bgsave</code> 命令。</p><p>当 <code>bgsave</code> 命令执行成功时，脏计数器重置为 <code>0</code>，并更新 <code>last_save</code> 时间戳。</p><p>从 <code>RDB</code> 快照中恢复数据通常比从 <code>AOF</code> 日志中恢复要更快，因为前者直接按原样存储键值对，而后者是所有变更操作的时间线，需要从日志起始重放。然而快照的执行频率同样需要权衡：如果执行频率过高，将会产生较大的资源开销；但是如果执行频率太低，那么当 <code>Redis</code> 发生故障时，尚未保存到快照的变更数据将会丢失。那有没有办法结合两个方案的优点呢？理想情况下，我们希望 <code>RDB</code> 快照提供快速数据恢复能力，同时最大限度地减少数据丢失和资源占用。</p><p><code>Redis 4.0</code> 版本提出了 <code>AOF</code> 日志和 <code>RDB</code> 快照结合的方式。简单来说，<code>RDB</code> 快照以一定的频率执行，在两个快照之间，<code>AOF</code> 日志用于持久化所有增量数据变更。在这种场景下，执行快照的频率可以较低，避免主进程频繁 <code>fork</code> 操作带来的资源开销。另外，<code>AOF</code> 日志只是用来记录两个快照之间的变化，而不是记录从始到终的整个历史数据，日志文件不会过大，压缩/重写所占用的资源也相应减少。</p><p>一般来说，如果业务场景可以容忍一些数据丢失，那么我们可以只使用 <code>RDB</code> 快照来持久化，否则需要 <code>AOF</code> 日志占用更多的资源为代价来减少数据丢失。</p><h2 id="Redis-集群高可用"><a href="#Redis-集群高可用" class="headerlink" title="Redis 集群高可用"></a>Redis 集群高可用</h2><h3 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h3><p><code>Redis</code> 支持创建多个副本，每个分区可以有一个写副本（主节点）和多个读副本（从节点）。当一个新的从节点实例加入集群时，它会首先从主节点那里加载最新的 <code>RDB</code> 快照文件，然后开始消费复制缓冲区增量数据保持与主节点同步。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/05.webp" style="width:100%"><h3 id="哨兵节点"><a href="#哨兵节点" class="headerlink" title="哨兵节点"></a>哨兵节点</h3><p><code>Redis</code> 集群也使用哨兵节点来保证高可用性。哨兵节点是特殊的 <code>Redis</code> 进程（是相对于存储键值数据的主节点和从节点，又名数据节点而言），哨兵节点有三个作用：</p><ul><li><strong>监控</strong>：监听数据节点的心跳，判断哪些节点健康，哪些不可用；</li><li><strong>领导选举</strong>：当主节点挂掉或者没有响应时，负责选举出一个新的主节点；</li><li><strong>通知</strong>：通知从节点与主节点保持同步。</li></ul><h3 id="监控"><a href="#监控" class="headerlink" title="监控"></a>监控</h3><p>哨兵节点连接到所有 <code>Redis</code> 数据节点，并使用 <code>PING</code> 命令检查其与数据节点的连接。如果对某个数据节点 <code>PING</code> 失败，根据它是主节点还是从节点，要分两种情况处理。</p><p>如果它是一个从节点，不管从节点是否真的出现故障——也许这个节点只是暂时不可用，很快就能恢复——它仍然会被哨兵节点标记为死亡，并停止接收请求流量。</p><p>如果是主节点，我们在设计哨兵节点逻辑的时候就要考虑到可能出现的误报。主节点可能完全健康并正常提供服务，但只是它与哨兵的连接遇到问题——也许数据中心的网络出现了一些故障。如果触发了领导者故障转移，随后的领导者选举和通知将产生大量的计算和网络资源占用，进一步给网络带来压力，并可能触发雪崩故障。</p><p>那如何在主节点出现故障需要下线时避免出现误报呢？<code>Redis</code> 中使用共识协议。只有当所有哨兵节点中的大多数判断一个主节点不可用时，这个主节点才会被标记为死亡，并触发故障转移进行领导者选举过程。 </p><h3 id="领导选举"><a href="#领导选举" class="headerlink" title="领导选举"></a>领导选举</h3><p>当旧的主节点出现故障时，将从其他从节点中选举出新的主节点。<code>Redis</code> 使用三条规则来确定应将哪个从节点提升为新的主节点：</p><ul><li>首先，优先级最高的从节点将获得高分；</li><li>其次，与旧主节点数据最同步的从节点将获得高分；</li><li>最后，具有最小 <code>instance_id</code> 的从节点将获得高分。</li></ul><p>配置属性 <code>slave-priority</code> 可用于为每个从节点设置用户定义的优先级。例如，假设我们有两个具有不同内存空间的从节点实例，我们可以为具有更多内存的实例设置高优先级，在领导者选举中，哨兵节点将首先给具有最高用户定义优先级的节点打分——因此拥有更大内存的节点将成为新的主节点。</p><p>在两个从节点的 <code>slave-priority</code> 配置相同的情况下，哨兵节点将使用第二条规则来确定新的主节点，原因是在这种情况下，新领导者将拥有最新的数据。众所周知，主从复制可能由于网络传输延迟而存在复制滞后性，在复制过程中，主节点使用 <code>master_repl_offset</code> 记录 <code>repl_backlog_buffer</code> 中最近一次写入的日志位置，从节点使用 <code>slave_repl_offset</code> 记录其最近写入的日志位置。</p><p>此外，每个 <code>Redis</code> 实例都有一个唯一的数字 <code>instance_id</code>。当第三条规则适用时——即当所有从节点的 <code>slave-priority</code> 和同步日志位置都相同时，<code>instance_id</code> 最小的节点将被选为新的主节点。</p><h3 id="分片"><a href="#分片" class="headerlink" title="分片"></a>分片</h3><p>当数据量增加时，比如从 <code>5G</code> 增加到 <code>25G</code>，我们就需要对 <code>Redis</code> 集群进行扩容，扩容有两种实现方式：</p><ul><li>垂直扩容：我们可以使用配置更高<strong>CPU</strong>和<strong>内存</strong>的机器。但是高端机器很快就会变得过于昂贵；此外当数据量变大时，生成 <code>RDB</code> 快照和 <code>fork()</code> 系统调用等操作将会很耗时，因为同一个实例处理了过多的数据；</li><li>水平扩容：通过添加更多的一般规格的机器来扩充容量。</li></ul><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/06.webp" style="width:100%"><p>在分片集群中数据分布在所有实例中。<code>Redis</code> 使用哈希槽将键值记录映射到某个实例。在当前的 <code>Redis</code> 实现中，最多有 <code>16384</code> 个哈希槽。每个键值记录都散列到其中一个槽中。具体来说，</p><ul><li><code>CRC16</code> 哈希用于使用键值记录的 <code>Key</code> 计算 <code>16</code> 位哈希值；</li><li>然后通过计算后的 <code>16</code> 位哈希值和 <code>16384</code> 的模来计算 <code>[0, 16383]</code> 范围内的哈希槽号。</li></ul><p>简单来说，有两层映射：</p><ul><li>一个键值记录首先映射到一个哈希槽，这种映射是确定性的，由计算节点完成。</li><li>然后将哈希槽映射到特定的 <code>Redis</code> 实例。此映射关系存储在所有 <code>Redis</code> 实例上，并通过 <code>gossip</code> 协议在所有实例之间保持同步。当我们向集群添加/删除实例时，或者当 <code>Redis</code> 决定将哈希槽重新分配给实例以平衡每个实例上的负载时，这个映射关系是可以动态调整的，并且可能会发生变化。</li></ul><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/10-How-Redis-Cluster-Achieves-High-Availability-Data-Persistence/07.webp" style="width:100%"><p><code>Redis</code> 提供了一种“重定向”机制来处理哈希槽重新分配时的客户端请求。简单来说，当客户端向一个 <code>Redis</code> 实例发送请求时，假设这个特定实例没有请求的数据记录，客户端将被重定向到另一个具有请求的数据记录的实例。</p><p>有两种类型的重定向，取决于包含请求的 <code>Key</code> 的哈希槽是否已完全迁移到另一个实例。</p><p>如果槽完全迁移到另一个实例，旧实例返回包含新实例<strong>IP:端口</strong>的 <code>MOVED</code> 重定向，客户端需要连接到新实例来获取键值数据。</p><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">get key1</span><br><span class="line">(error) MOVED 12999 192.168.0.1:6380</span><br></pre></td></tr></table></figure><p>如果哈希槽仅完成部分迁移，则返回 <code>ASK</code> 重定向。在发出实际的读/写命令之前，客户端需要先向新实例发出 <code>ASKING</code> 命令。</p><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">get key1</span><br><span class="line">(error) ASK 12999 192.168.0.1:6380</span><br></pre></td></tr></table></figure><p>有关 <code>Redis</code> 分片的更多详细信息，请查看我的<a href="https://dongzl.github.io/2023/03/24/08-Deep-Dive-Into-Redis-Cluster/">深入探索 Redis 集群：分片算法和架构</a>这篇文章。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/@bb8s/how-redis-cluster-achieves-high-availability-and-data-persistence-8cdc899764e8
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="Redis" scheme="https://dongzl.github.io/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>深入探索 MySQL 内部实现原理</title>
    <link href="https://dongzl.github.io/2023/04/04/09-Deep-Dive-Into-MySQL-Inner-Details/"/>
    <id>https://dongzl.github.io/2023/04/04/09-Deep-Dive-Into-MySQL-Inner-Details/</id>
    <published>2023-04-04T10:24:22.000Z</published>
    <updated>2023-12-31T06:59:02.141Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接（请科学上网）：<a href="https://medium.com/@bb8s/mysql-from-5000ft-above-to-inner-details-i-6a81186064de">https://medium.com/@bb8s/mysql-from-5000ft-above-to-inner-details-i-6a81186064de</a></p></blockquote><h2 id="初学者眼中的-MySQL"><a href="#初学者眼中的-MySQL" class="headerlink" title="初学者眼中的 MySQL"></a>初学者眼中的 MySQL</h2><p>对于我们许多初学者来说，<code>MySQL</code> 就是这样的：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/01.webp" style="width:100%"><p>一切看起来都很简单，但是 <code>MySQL</code> 如何在后台处理 <code>SQL</code> 请求呢？换句话说，工程师和数据科学家编写的 <code>SQL</code> 查询语句通常都是纯文本字符串内容，并发送到 <code>MySQL</code> 的。那么 <code>MySQL</code> 是如何解析这个字符串并知道要查找哪个数据表以及要获取哪些记录呢？</p><h2 id="连接池"><a href="#连接池" class="headerlink" title="连接池"></a>连接池</h2><p>就像我们此时正在浏览这个页面一样，<code>Web</code> 浏览器（<code>chrome</code>、<code>safari</code>）会保持与 <code>Medium</code> 网站的连接；同样我们的应用服务器需要通过网络连接到 <code>MySQL</code> 服务器，然后发送 <code>SQL</code> 查询文本内容，连接池通常用于管理网络连接。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/02.webp" style="width:100%"><p>连接池允许重用已有的网络连接，避免在创建新连接时所产生初始化和断开连接时释放资源所产生的成本开销。另外还可以将用户认证内置到这一层，拒绝未经授权的连接访问数据库。</p><p>通常每个连接都映射到一个线程，当处理一个 <code>SQL</code> 查询请求时，应用服务器的线程会从连接池中取出一个连接，并向 <code>MySQL</code> 服务器发送一个请求，<code>MySQL</code> 服务器中的另一个线程将接收到 <code>SQL</code> 字符串格式的请求，并执行后续操作步骤。</p><p>那么后续操作步骤是什么？</p><h2 id="SQL-解析"><a href="#SQL-解析" class="headerlink" title="SQL 解析"></a>SQL 解析</h2><p><code>MySQL</code> 服务器需要了解查询语句试图要做什么，它是要尝试读取一些数据、更新数据或删除数据？</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/03.webp" style="width:100%"><p>接收到查询请求后，首先需要对 <code>SQL</code> 内容进行解析，主要工作是将其本质上文本格式的内容转换为 <code>MySQL</code> 内部二进制结构的组合，方便优化器程序进行优化操作。</p><h2 id="查询优化器"><a href="#查询优化器" class="headerlink" title="查询优化器"></a>查询优化器</h2><p>在 <code>MySQL</code> 执行查询之前，它会确定如何完成查询，即选择最好的查询方法。</p><blockquote><p>例如，您要出去参加一次大型家庭旅行，每个人都坐在车里准备离开，但是你突然发现忘了带20瓶水，你很快想起所有的瓶装水都在储藏室里，但是需要尽快把它们放到你的车上，因为其他人都在等你；你开始思考，每次可以手拿4瓶，来回跑5次，或者你也可以随身带一个箱子，把20瓶都装在箱子里，一起带上车不用再来回。这就是优化器所做的事情，它分析满足请求的所有不同方法，并选择其中最优的方法。</p></blockquote><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/04.webp" style="width:100%"><p>让我们看一个简单的 <code>SQL</code> 查询：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> name <span class="keyword">FROM</span> employee_table <span class="keyword">WHERE</span> employee_id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>假设 <code>employee_table</code> 有 <code>10K</code> 条员工记录，至少有两种方法（或者是正式术语中的两个执行计划）：</p><ul><li><strong>执行计划一</strong>：扫描 <code>name</code> 列中的所有姓名，对于每个姓名，检查其对应的 <code>employee_id</code> 是否为 <code>1</code>，如果 <code>employee_id = 1</code> 则返回该姓名；</li><li><strong>执行计划二</strong>：使用主键索引查找 <code>employee_id = 1</code> 的记录，并返回该记录名称内容。</li></ul><p>方法二几乎总是会执行地更快，优化器会选择使用方法二，下一步就是真正的执行计划。</p><h2 id="执行引擎"><a href="#执行引擎" class="headerlink" title="执行引擎"></a>执行引擎</h2><p>执行引擎将调用存储引擎的 <code>API</code> 来执行查询优化器所选择的执行计划。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/05.webp" style="width:100%"><h2 id="存储引擎"><a href="#存储引擎" class="headerlink" title="存储引擎"></a>存储引擎</h2><p>很多软件系统都可以分为计算层和存储层。计算层的效率在很大程度上往往取决于数据在存储层中的组织方式。在本节中，让我们深入了解 <code>MySQL</code> 的存储引擎，以及为加快存储引擎的读写速度而精心设计的许多优化。</p><p><code>MySQL</code> 可以集成许多种不同类型的存储引擎。对于不同的使用场景，每一种存储引擎都有各自的优缺点。换句话说存储引擎可以看作是一个接口，有不同的底层实现，比如有 <code>InnoDB</code>、<code>MyISAM</code>、<code>Memory</code>、<code>CSV</code>、<code>Archive</code>、<code>Merge</code>、<code>Blackhole</code>。</p><p><code>InnoDB</code> 毫无无疑是使用最广泛的存储引擎，这是 <code>MySQL 5.5</code> 版本以后的默认设置。</p><p>就像我们使用的笔记本电脑一样，<code>InnoDB</code> 将数据存储在内存和磁盘上。从上层的角度来看，当写入 <code>InnoDB</code> 时，数据总是先写入内存的缓存空间中，然后再持久化到磁盘。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/06.webp" style="width:100%"><p><code>InnoDB</code> 将内存分为两部分：</p><ul><li>缓冲池；</li><li>日志缓冲区。</li></ul><p>缓冲池对于 <code>InnoDB</code> 来说非常重要。<code>MySQL</code> 在处理查询时通常速度非常快，原因是数据实际上是存储在内存中并对外提供的服务的（在大多数情况下并不是从磁盘读取数据，这与许多人的想法恰恰相反），这个内存组件就是缓冲池。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/07.webp" style="width:100%"><h2 id="缓冲池"><a href="#缓冲池" class="headerlink" title="缓冲池"></a>缓冲池</h2><p>通常来说宿主机 <code>80%</code> 的内存会分配给缓冲池使用，更大的内存可以缓存更多的数据到内存中，可以使读取速度更快。</p><p>缓冲池除了将数据简单地放在内存中，还针对数据在内存中的组织形式进行了精心设计，以加快数据库的读写速度，让我们来进一步深入了解这个详细设计。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/08.webp" style="width:100%"><h2 id="页"><a href="#页" class="headerlink" title="页"></a>页</h2><p>类似于图书馆组织书籍的方式，使用 <code>ID</code> 标识书籍，并按字母或数字顺序放置在图书馆书架上，缓冲池也以类似的排序方式组织数据。</p><p><code>InnoDB</code> 将缓冲池分成很多数据页，还包括一个 <code>change buffer</code>（后面会说明）。所有数据页都以双向链表的形式链接起来，也就是说我们可以很容易地从当前页到达下一页，或者从当前页到达上一页。</p><p>那么数据如何在页中存储的？</p><h2 id="用户记录"><a href="#用户记录" class="headerlink" title="用户记录"></a>用户记录</h2><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/09.webp" style="width:100%"><p>在一个数据页中包括如下内容：</p><ul><li>指向上一个数据页的指针；</li><li>指向下一个数据页的指针；</li><li>用户记录；</li><li>其他属性信息。</li></ul><p>指向上一个数据页和下一个数据页的指针简单来说就是一个指针，用户记录是存储每<strong>行</strong>数据的位置，每行都有一个指向下一行的 <code>next</code> 指针，形成一个单向链表。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/10.webp" style="width:100%"><p>那么问题来了。</p><p>我们知道关系型数据库中的一行记录通常由一个主键字段和许多其他字段组成，这些记录通常按主键排序，以便在查找具有特定主键的记录时可以使用二分查找等算法来快速检索数据，降低延迟；但是如果每次我们向用户记录添加新数据时，<code>InnoDB</code> 都需要重新排列所有记录（行）以保持有序，这将是一个非常耗时的操作。</p><p>实际上用户记录中的行是按插入顺序排列的，添加新记录只是意味着附加到用户记录的末尾，所需的主键顺序是通过 <code>next</code> 指针实现的，每行的 <code>next</code> 指针根据主键顺序指向下一个逻辑行，而不是内存中物理的下一行。</p><p>现在有一个新问题，前面我们提到不需要遍历所有记录就可以找到具有特定主键的目标数据，但是如果所有的行本质上都是一个单向链表，单向链表的特性决定了我们只能遍历整个链表才能找到特定的行，我们知道遍历链表是 <code>O(n)</code> 时间复杂度的操作，这是非常耗时的，那么 <code>InnoDB</code> 如何让它更快的呢？还记得我们之前提到的 <code>other fields</code> 吗？它们正是用于此目的。</p><h2 id="下界与上界"><a href="#下界与上界" class="headerlink" title="下界与上界"></a>下界与上界</h2><p>这两个属性分别代表数据页中存储记录最大和最小的行，也就是说两者构成了一个 <code>min-max filter</code>。通过 <code>O(1)</code> 时间复杂度算法检查这两个字段，<code>InnoDB</code> 可以决定要查找的行是否存储在指定数据页中。</p><p>假设我们的主键是数值类型，并且我们的某个数据页的 <code>supremum = 1</code>，<code>infimum = 99</code>，如果我们试图查找 <code>primary key = 101</code> 的行，那么显然在 <code>O(1)</code> 时间内，<code>InnoDB</code> 就能够决定它不在这个数据页中，并且马上会转到另一个数据页继续查找。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/11.webp" style="width:100%"><p>如果要查找的记录不在某个数据页中，<code>InnoDB</code> 会跳过该页，但是如果该行记录在这个数据页中，那么 <code>InnoDB</code> 是否仍然需要遍历整个链表呢？答案是否定的，<code>other fields</code> 信息会再次派上用场。</p><h2 id="页目录"><a href="#页目录" class="headerlink" title="页目录"></a>页目录</h2><p>顾名思义，它就像一本书的<strong>目录</strong>。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/09-Deep-Dive-Into-MySQL-Inner-Details/12.webp" style="width:100%"><p>页目录存储指向行块的指针，假设我们有第 <code>1</code> 行到第 <code>n</code> 行存储在用户记录中，页目录将有指向第 <code>1</code> 行、第 <code>7</code> 行、第 <code>13</code> 行……的指针，块大小为 <code>6</code>，这种设计与跳表结构非常相似。</p><p>正如我们现在能够想象的那样，<code>InnoDB</code> 首先检查数据页目录，然后快速确定要查找哪个数据块，然后利用指针跳转到单向链表中数据块对应的起始行，从这个位置开始遍历。这样 <code>InnoDB</code> 就避免了遍历整个列表，只需要遍历一个小得多的子列表就可以检索到数据。</p><p>我们上面提到的块，官方称之为 <code>Slot</code>。一个页目录会有多个槽，每个槽都有一个指针指向用户记录中的一行数据。</p><p>每次向用户记录插入新数据时，<code>InnoDB</code> 也会同时更新页目录以使两者保持一致，<code>InnoDB</code> 会每 <code>6</code> 行创建一个槽。</p><h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h2><p><code>InnoDB</code> 使用 <code>B+</code> 树（读作<strong>B加树</strong>）进行数据存储。<code>MySQL</code> 支持两种类型的索引：<strong>主键索引</strong>和<strong>二级索引</strong>。一旦我们理解了数据页的概念，索引就变得显而易见了。</p><blockquote><p>主键索引的叶子节点存储“page”</p><p>相反二级索引的叶子节点存储主键信息，当查询命中二级索引时，首先从二级索引中检索主键值，一旦我们知道了主键值，就可以使用主键索引检索目标数据行。</p></blockquote><p>至于为什么使用 <code>B+</code> 树而不是 <code>B-</code> 树（<code>B</code> 树，不是读作 <code>B</code> 减树）：</p><ul><li><code>B+</code> 树减少了所需要的 <code>IO</code> 操作；</li><li>查询效率更稳定；</li><li>可以更好地支持范围查询。</li></ul><hr><p>所有这些优秀的设计，例如用户记录之间使用单向链表、<code>infimum-supremum</code> 和页目录，主要目的是加快数据读取。那当我们想要更新一行记录时会发生什么？现在让我们研究一下 <code>MySQL</code> 是如何处理数据更新的。</p><h2 id="更新数据"><a href="#更新数据" class="headerlink" title="更新数据"></a>更新数据</h2><p>当我们向 <code>MySQL</code> 中插入一行记录，比如 <code>id = 100</code>，假如我们需要马上再更新这一行数据，由于这行数据刚刚插入可能还保存在缓冲池中。</p><p>但是如果我们等待相当长一段时间，然后再更新这一行，那么 <code>id = 100</code> 的这行记录很可能已经不在缓冲池中了。</p><p>因为内存有限，我们不能一直将插入的数据保存在内存中，假如这样内存很快就会被装满。清除策略，通常是说 <code>ttl(time-to-live)</code>，用于清理内存空间，例如，<code>Redis</code> 通常用作缓存，<code>Redis</code> 中的 <code>ttl</code> 表示键被删除（只标记为已删除且显示为不可读，然后实际由后台进程将其批量删除）。对于 <code>MySQL</code> 数据需要持久化，清除的意思是将数据持久化到磁盘并从内存中删除。</p><p>因此当尝试更新一行记录时，如果该行记录不在缓冲池中，<code>InnoDB</code> 会将数据从磁盘加载到缓冲池，这里的问题是 <code>InnoDB</code> 不能只加载 <code>id = 100</code> 这一行记录，它需要加载包含该行的整个数据页，当整个数据页加载到缓冲池时，就可以更新指定数据行了。</p><p>到目前为止好像一切看起来都不错，但这只是描述了对主键索引的更新，那么二级索引呢？</p><h2 id="变更缓冲区"><a href="#变更缓冲区" class="headerlink" title="变更缓冲区"></a>变更缓冲区</h2><p>假设我们更新了 <code>id = 100</code> 行的某些字段信息，如果在其中一个字段上建立二级索引，那么在更新字段信息时需要同时更新二级索引。但是如果包含二级索引的数据页不在缓冲池中，那么 <code>InnoDB</code> 是否也会将数据页加载到缓冲池中呢？</p><p>假如二级索引并不是马上被使用到，如果每次更新相关字段的时候都立即将数据加载到缓冲池中，就会产生很多随机的磁盘 <code>I/O</code> 操作，这必然会拖慢 <code>InnoDB</code>。</p><p>这就是为什么 <code>Change Buffer</code> 被设计用来<strong>延迟</strong>更新二级索引的原因。</p><p>当二级索引需要更新时，而包含它的数据页不在缓冲池中时，更新的数据将会被暂时存储在变更缓冲区中。稍后如果数据页被加载到缓冲池中（由于主键索引的更新），<code>InnoDB</code> 会将变更缓冲区中保存的变更数据合并到缓冲池的数据页中。</p><p>不过这种变更缓冲区设计有一个缺点，如果变更缓冲区已经积累了大量的临时变更数据，如果我们要一次性全部合并到 <code>Buffer Pool</code> 中，可能需要几个小时才能完成！！并且在合并的过程中，会产生大量的磁盘 <code>I/O</code> 操作，占用 <code>CPU</code> 周期，影响 <code>MySQL</code> 的整体性能。</p><p>所以这里进一步做了一些权衡，我们之前提到当包含二级索引的页面加载到缓冲池时会触发合并操作。在 <code>InnoDB</code> 设计中，为避免出现可能需要几个小时的大型合并操作，合并操作也可以由其他事件触发，这些事件包括<strong>事务</strong>、<strong>服务器关闭</strong>和<strong>服务器重启</strong>等。</p><h2 id="自适应哈希索引"><a href="#自适应哈希索引" class="headerlink" title="自适应哈希索引"></a>自适应哈希索引</h2><p>自适应哈希索引旨在与缓冲区一起工作，自适应哈希索引使 <code>InnoDB</code> 存储引擎的性能更像内存数据库。自适应哈希索引可以通过 <code>innodb_adaptive_hash_index</code> 变量开启，或在服务器启动时通过 <code>--skip-innodb-adaptive-hash-index</code> 关闭。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接（请科学上网）：&lt;a href=&quot;https://medium.com/@bb8s/mysql-from-5000ft-above-to-inner-details-i-6a81186064de&quot;&gt;https://medium.com/@b
      
    
    </summary>
    
    
      <category term="数据库" scheme="https://dongzl.github.io/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
      <category term="MySQL" scheme="https://dongzl.github.io/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>深入探索 Redis 集群：分片算法和架构</title>
    <link href="https://dongzl.github.io/2023/03/24/08-Deep-Dive-Into-Redis-Cluster/"/>
    <id>https://dongzl.github.io/2023/03/24/08-Deep-Dive-Into-Redis-Cluster/</id>
    <published>2023-03-24T20:03:22.000Z</published>
    <updated>2023-12-31T06:59:02.129Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://medium.com/@bb8s/deep-dive-into-redis-cluster-sharding-algorithms-and-architecture-a093a92e97af">https://medium.com/@bb8s/deep-dive-into-redis-cluster-sharding-algorithms-and-architecture-a093a92e97af</a></p></blockquote><p><code>Redis</code> 是在许多公司中被广泛使用的缓存框架，由于所有的操作都是在内存中完成，所以单个 <code>Redis</code> 实例可以处理百万级 <code>QPS</code> 的读/写操作。除了像 <code>Memcached</code> 一样，作为一种简单的内存缓存框架外，<code>Redis</code> 还提供了很多其它特性，例如在许多场景中，<code>Redis</code> 同样可以作为持久化存储的工具，因为 <code>Redis</code> 内置了 <code>AOF</code>（<code>append only file</code>） 机制，可以将每一次的写操作持久化到磁盘。 <code>AOF</code> 有两种运行模式，可以在设置 <code>Redis</code> 实例时进行配置。</p><ul><li><strong>同步 AOF</strong>：同步 <code>AOF</code> 的意思是每一次写操作都需要成功持久化到磁盘后才能向客户端返回操作结果（也被称作是<strong>阻塞写</strong>）。在这种模式下，<code>Redis</code> 不再是纯粹的 <code>in-mem</code> 存储。同步 <code>AOF</code> 能够确保数据持久化，但是写入操作延迟会比较高，这一点是需要权衡的。在我们的生产环境中同步 <code>AOF</code> 的单个写入操作平均需要 <code>1s</code> 才能完成；</li><li><strong>异步 AOF</strong>：在这种模式下，写入操作是非阻塞的，这意味着 <code>Redis</code> 仍然可以被客户端视为是 <code>in-mem</code> 的，只是内存中的数据会在后台定期持久化到磁盘，这对客户端是透明的。这种模式提供了非常低的写入延迟，因为所有的客户端操作都是在内存中完成的，但这种模式需要权衡数据丢失，如果一个 <code>Redis</code> 节点发生故障或者重启，写入到这个 <code>Redis</code> 节点但是还没有持久化到磁盘的数据就将会永久性丢失。</li></ul><h2 id="Redis-集群"><a href="#Redis-集群" class="headerlink" title="Redis 集群"></a>Redis 集群</h2><p>随着业务发展，有可能数据量、读写 <code>QPS</code> 很快就会超过单个 <code>Redis</code> 实例的上限。我们需要使用 <code>Redis</code> 集群来存储键值数据，在集群中，<code>Key</code> 被分布到多个实例。通常情况下，可以通过三种方式将 <code>Key</code> 分片到多个实例：</p><h3 id="客户端分片"><a href="#客户端分片" class="headerlink" title="客户端分片"></a>客户端分片</h3><blockquote><p>假设我们有一个 ad-ranking 引擎，它本质上是一种推荐服务，它从广告数据库中召回相关广告并对每个 ad_request 的广告进行排名。排名引擎需要检索每个广告的实时出价，以便进行广告拍卖，由于业务对请求延迟高度敏感，因此广告的所有实时出价都经过计算并预加载到 Redis 集群中；Ad-ranking 引擎在其二进制文件中内置了 Redis 客户端，以便于从 Redis 集群中读取数据。</p></blockquote><p>在客户端分片模式中，<code>Redis</code> 客户端包括了分片和路由逻辑；因此它可以说是一个非常厚重的客户端。这种架构的优点是不依赖任何中间件，只有两方：<code>Redis</code> 客户端和 <code>Redis</code> 节点。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/01.png" style="width:100%"><h3 id="中心化代理（又名中间件）的服务器端分片"><a href="#中心化代理（又名中间件）的服务器端分片" class="headerlink" title="中心化代理（又名中间件）的服务器端分片"></a>中心化代理（又名中间件）的服务器端分片</h3><p>中间件被用作代理。来自 <code>ad-ranking</code> 引擎的请求流量将打到代理中间件。中间件代理包含分片和路由逻辑，用来确定访问哪个 <code>Redis</code> 实例检索数据，在这种情况下，<code>Redis</code> 客户端可以做地非常轻量，因为它不再包含分片和路由逻辑。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/02.png" style="width:100%"><h3 id="去中心化的服务器端分片"><a href="#去中心化的服务器端分片" class="headerlink" title="去中心化的服务器端分片"></a>去中心化的服务器端分片</h3><p>去中心化分片是 <code>Redis</code> 官方提供的集群解决方案。集群中的每个节点都在本地维护一份<strong>路由表</strong>的副本，并通过 <code>gossip</code> 协议交互不断更新自己的路由表。换句话说，不再有中心化的代理；恰恰相反每个 <code>Redis</code> 节点都包含充当代理所需的全部元数据信息。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/03.png" style="width:100%"><p>一个请求可能会打到任意一个 <code>Redis</code> 节点；每个节点都知道集群中的所有其他节点的网络地址、存储的 <code>Key</code> 等。接收请求的节点首先会检查请求需要的数据是在本地节点还是在其他节点，如果数据存储在其它节点，则将请求重定向到正确的节点。</p><h2 id="分片算法"><a href="#分片算法" class="headerlink" title="分片算法"></a>分片算法</h2><h3 id="一致性哈希"><a href="#一致性哈希" class="headerlink" title="一致性哈希"></a>一致性哈希</h3><p>一致性哈希是一个经典的分片算法。本质上所有的内容，包括 <code>Redis</code> 集群中的每个节点，每一条记录的 <code>Key</code>，都映射到一个 <code>0~2³²-1</code>（或 <code>0~2⁶⁴-1</code>）的哈希环上。当客户端想要检索一条 <code>k-v</code> 记录时，它首先计算 <code>Key</code> 的散列，然后在哈希环上顺时针找到下一个 <code>Redis</code> 节点，并从该节点获取数据。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/04.png" style="width:100%"><p>当<strong>添加/删除</strong>节点时，一致性哈希能够确保只有相邻节点需要迁移数据，而集群中的其它大多数节点不受影响。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/05.png" style="width:100%"><p>一致性哈希有一个很大的缺点：数据分片不均衡或者说是数据偏斜。通常一个缓存集群可能只有几十到几百个节点。在极端情况下，假设只有 <code>2</code> 个节点，可能大部分键值数据都位于节点 <code>A</code> 上，而只有少量位于节点 <code>B</code> 上。即使对于最初分片均衡的集群，由于系统演进，例如添加/删除节点、<code>Key</code> 过期、添加新 <code>Key</code> 等原因，集群最终都可能会出现数据不平衡。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/06.png" style="width:100%"><p>为了使分片尽量平衡，常用的方法包括：</p><ul><li><strong>引入微分片的概念</strong>：例如，每个物理节点映射到 <code>n</code> 个虚拟节点。如果集群有 <code>m</code> 个节点，那么总共会有 <code>m*n</code> 个虚拟节点，虚拟节点越多，数据分布就越均衡；</li><li><strong>定期重新分片</strong>：例如，每个月重新计算每个分片上的键分布，并在所有节点上执行全局数据迁移，使集群恢复到分片均衡的状态。</li></ul><h3 id="Redis-集群中使用的哈希槽分片"><a href="#Redis-集群中使用的哈希槽分片" class="headerlink" title="Redis 集群中使用的哈希槽分片"></a>Redis 集群中使用的哈希槽分片</h3><p><code>Redis</code> 集群没有使用上面提到的一致性哈希，相反使用的是哈希槽。系统中的所有键被散列成 <code>0~16383</code> 的整数范围，计算公式是 <code>slot = CRC16(key) &amp; 16383</code>。每个节点负责存储一组槽，以及与槽关联的键值对。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/07.png" style="width:100%"><p>本质上哈希槽是另外一层抽象。它解耦数据记录（键值对）和节点。每个节点只需要知道应该在其上存储哪些槽，槽被编码为 <code>bit</code> 数组。该数组的大小为 <code>16384/8 = 2048</code> 字节。换句话说，一个 <code>2048</code> 字节的数组包含节点的所有信息，能够确定 <code>16384</code> 个槽中的某个槽是否存储在这个节点上。例如，对于槽 <code>1</code>，节点只需要检查第二位是否为 <code>1</code>（因为槽索引从 <code>0</code> 开始），这是一个时间复杂度为 <code>O(1)</code> 的操作，执行非常高效。</p><p>添加一层哈希槽能够使得节点的<strong>添加/删除</strong>操作变得非常简单。假设我们有一个由 <code>3</code> 个节点 <code>A</code>、<code>B</code> 和 <code>C</code> 组成的集群，每个节点将存储一定范围的槽：</p><blockquote><p>Node A: slot 0–5460<br>Node B: slot 5461–10922<br>Node C: slot 10923–16383</p></blockquote><ul><li>假设我们需要向集群添加一个新的节点 <code>D</code>。在这种情况下，在 <code>A</code>、<code>B</code> 和 <code>C</code> 节点上的一些槽将会迁移到 <code>D</code> 节点。由于槽及其存储的键值记录是不可分割的，所以在跨节点迁移时是作为一个原子单元进行迁移的。</li><li>同样当我们从集群中移除 <code>A</code> 节点时，只需要把 <code>A</code> 中的槽迁移到 <code>B</code> 和 <code>C</code> 上即可，这样就可以安全移除节点 <code>A</code> 了。</li></ul><p>总之，将哈希槽从一个节点移动到另一个节点不需要集群停止操作；添加和删除节点，或调整某个节点上哈希槽的百分比，不需要任何集群停机时间。</p><h2 id="分片架构"><a href="#分片架构" class="headerlink" title="分片架构"></a>分片架构</h2><h3 id="原生-Redis-集群"><a href="#原生-Redis-集群" class="headerlink" title="原生 Redis 集群"></a>原生 Redis 集群</h3><p>原生集群的实现与上述算法完全相同。所有功能，例如<strong>路由/分片</strong>、<strong>集群拓扑元数据</strong>、<strong>实例健康监控</strong>等都集成在集群中，没有其它依赖项，实例之间使用 <code>gossip</code> 协议来相互交互。</p><p>原生集群通常可以支持 <code>300 ~ 400</code> 个实例，每个实例可以处理 <code>8</code> 万 <code>QPS</code> 的读操作，集群总共可以处理 <code>2000 ~ 3000</code> 万 <code>QPS</code>。</p><p>但是，如果需要处理更高的 <code>QPS</code>，一旦集群超过 <code>400</code> 个实例，添加更多的 <code>Redis</code> 实例就不再是一个好主意。原因是随着集群中实例数量的增加，<code>gossip</code> 协议的资源占用也会迅速增加；当系统架构需要进一步扩展时，其它架构的使用更为广泛。</p><h3 id="Twemproxy-原生-Redis-集群"><a href="#Twemproxy-原生-Redis-集群" class="headerlink" title="Twemproxy + 原生 Redis 集群"></a>Twemproxy + 原生 Redis 集群</h3><p>下图展示了 <code>Twemproxy</code> 在多个 <code>Redis</code> 实例环境下工作的基本架构。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/08.png" style="width:100%"><p><code>Twemproxy</code> 是前面描述具有集中代理的服务器端分片的架构示例。 <code>Twemproxy</code> 是由 <code>Twitter</code> 开源的。<code>Twemproxy</code> 可以接受来自多个客户端服务的请求，并将请求定向到底层的 <code>Redis</code> 节点等待响应，然后直接将响应结果返回给客户端。<code>Twemproxy</code> 还支持如下一些非常有用的功能：</p><ul><li>自动删除故障 <code>Redis</code> 节点；</li><li>支持 <code>Hashtag</code> 能力。例如，如果我们想要确保一组 <code>Key</code> 被哈希到同一个 <code>Redis</code> 实例节点，我们就可以给这些 <code>Key</code> 设置相同的 <code>Hashtag</code>；</li><li>支持多种哈希算法。</li></ul><p><code>Twemproxy</code> 可以与原生 <code>Redis</code> 集群协同工作，如图：</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/09.png" style="width:100%"><p>在这种情况下，<code>Twemproxy</code> 不再处理<strong>路由/分片</strong>，而是用于存储元数据，例如高级 <code>Redis</code> 集群拓扑、访问控制列表，并可用于监控常见的可能导致本机 <code>Redis</code> 集群出现故障的<strong>热 Key</strong>、<strong>大 Key</strong>问题等。<strong>分片/路由</strong>仍然由底层原生 <code>Redis</code> 集群处理。换句话说，一个 <code>Twemproxy</code> 节点维护与所有 <code>Redis</code> 节点的连接，并且可以向底层所有的 <code>Redis</code> 节点发送请求，接收请求的 <code>Redis</code> 节点将在需要时将请求重新路由到集群内的另一个 <code>Redis</code> 节点。</p><p><code>AWS</code> <code>ElasticCache</code> 就是使用的这种架构。<code>ElasticCache</code> 由一个代理服务器和一个支持主从复制的 <code>Redis</code> 集群组成，其中 <code>primary</code> 节点主要用于数据写入，<code>replicas</code> 用于数据读取。</p><h3 id="使用-Codis-进行集中分片"><a href="#使用-Codis-进行集中分片" class="headerlink" title="使用 Codis 进行集中分片"></a>使用 Codis 进行集中分片</h3><p><code>Codis</code> 引入了 <code>group</code> 的概念，每个 <code>group</code> 包含一个 <code>Redis</code> 主节点（<code>Redis master</code>），和 <code>1</code> 到多个 <code>Redis</code> 副本节点（<code>Redis slave</code>），如果主节点挂掉，一个副本节点可以被提升为新的主节点。</p><p><code>Codis</code> 同样使用了预分片机制，与原生的 <code>Redis</code> 集群的哈希槽功能类似，所有的 <code>Key</code> 被分布到 <code>1024</code> 个槽中，这意味着总共可以有多达 <code>1024</code> 个 <code>group</code>。路由信息（即元数据）存储在强一致性的数据存储框架中，例如 <code>Etcd</code> 或 <code>Zookeeper</code>。</p><img src="https://cdn.jsdelivr.net/gh/dongzl/dongzl.github.io@hexo/source/images/2023/08-Deep-Dive-Into-Redis-Cluster/10.png" style="width:100%"><p>每个 <code>Redis</code> 组映射一段范围区间的槽，例如 <code>0~127</code>。映射信息会持久化到 <code>Zookeeper</code> 中，在请求处理路径中，首先使用 <code>crc32(key) % 1024</code> 计算哈希槽，然后代理使用存储在 <code>Zookeeper</code> 的 <code>slot_id</code> 检索 <code>Redis</code> 组的地址。</p><p><code>Codis</code> 和 <code>Twemproxy + Redis cluster</code> 架构有两个非常主要的区别：</p><ul><li>在 <code>Codis</code> 架构中，代理是无状态的。它不需要存储底层 <code>Redis</code> 节点的任何状态。相反，存储所有的元数据信息到 <code>Zookeeper</code> 中。由于所有的<strong>路由/分片</strong>功能都是由代理和 <code>Zookeeper</code> 完成的，每一个 <code>Redis</code> 节点都只负责存储 <code>KV</code> 键值对数据，不需要通过 <code>gossip</code> 协议与其它 <code>Redis</code> 节点进行交互。</li><li>恰恰相反，当使用 <code>Twemproxy + Redis 集群</code>时，相关元数据存储在原生的 <code>Redis</code> 集群中，即存储在每个 <code>Redis</code> 节点上。</li></ul><h2 id="热-Key-和-大-Key-问题"><a href="#热-Key-和-大-Key-问题" class="headerlink" title="热 Key 和 大 Key 问题"></a>热 Key 和 大 Key 问题</h2><h3 id="热-Key-问题"><a href="#热-Key-问题" class="headerlink" title="热 Key 问题"></a>热 Key 问题</h3><p>热 <code>Key</code> 是非常普遍的，尤其是对于 <code>Youtube</code>、<code>Instagram</code>、<code>Twitter</code> 等基于内容的服务应用而言。一组 <code>Key</code>，有时候可能就是某个 <code>Key</code>，可能是承载了大部分的用户流量，例如在 <code>Youtube</code>，头部的 <code>5 ~ 10%</code> 的内容承载了 <code>90%</code> 的流量。</p><p>热 <code>Key</code> 对 <code>Redis</code> 集群的影响是流量可能集中到少数的几个 <code>Redis</code> 实例上，极端情况可能所有热 <code>Key</code> 都存储在一个 <code>Redis</code> 实例上。这些不幸的实例会因此变得负载很高，而集群中的其他大多数实例负载很低。总而言之一句话，热 <code>Key</code> 可能会给集群产生很大威胁。</p><p>有三种常用的方法来处理缓存中的热 <code>Key</code>：</p><ol><li><strong>使用客户端本地缓存</strong>。当热 <code>Key</code> 缓存在 <code>Redis</code> 客户端时，热 <code>Key</code> 的数据请求流量根本就不会发送到服务器端。 <code>LFU</code>（最近最少使用）通常是在客户端缓存中使用的缓存淘汰策略，当缓存达到容量上限时，<code>LFU</code> 会淘汰掉最不常用的 <code>Key</code>，从而确保热 <code>Key</code> 保存到客户端缓存中；但是，由于客户端缓存本质上是另外一层缓存，因此与远程缓存（<code>Redis</code> 集群）会存在缓存一致性问题；</li><li><strong>人为地将热键拆分成多个 Key</strong>。我们可以在热 <code>Key</code> 上附加或预先添加一个随机数。假设我们有一个热 <code>Key</code> 字符串 <code>xoxogossipgirl</code>（<code>Redis</code> 主要使用字符串作为键，而不是整数），那么在 <code>Redis</code> 侧我们首先通过在 <code>xoxogossipgirl</code> 前加上 <code>1-100</code> 来生成 <code>100</code> 个键，这给了我们 <code>1xoxogossipgirl</code>、<code>2xoxogossipgirl</code>、<code>3xoxogossipgirl</code>、…、<code>100xoxogossipgirl</code>，也就是说热键被人为复制了 <code>100</code> 份。这些 <code>Key</code> 会被映射到多个哈希槽，并存储在多个不同的 <code>Redis</code> 实例上。当进行数据查询时，每当客户端请求这个热键 <code>xoxogossipgirlis</code> 时，我们都会在热 Key 前添加一个 <code>[1, 100]</code> 范围内的随机数，这个前置逻辑可以在代理服务器上完成。通过这种方式，热 <code>key</code> 流量就被分散到集群中的多个实例上，而不是集中在少数几个甚至单个实例上；</li><li><strong>读写分离</strong>。对于内容类应用，通常对于热 <code>Key</code> 数据读取的 <code>QPS</code> 是非常高的，而热 <code>Key</code> 数据写入 <code>QPS</code> 不会太高。还记得我前面提到的 <code>group</code> 概念吗？我们可以配置一组 <code>Redis</code> 实例，让 <code>primary</code> 实例只承担写流量，其他 <code>follower</code> 实例承担读流量，我们可以有一个写副本，但读副本需要多少就添加多少。</li></ol><h3 id="大-Key-问题"><a href="#大-Key-问题" class="headerlink" title="大 Key 问题"></a>大 Key 问题</h3><p>大 <code>key</code> 的含义是 <code>KV</code> 键值对中 <code>value</code> 的存储需要使用超出平均值的内存空间。大 <code>Key</code> 可能与热 <code>Key</code> 有关系，但是也可能没关系。例如，在 <code>Instagram</code> 上，热门图片通常会有更多的评论，假设在我们的 <code>Redis</code> 中，<code>key</code> 是 <code>content_id</code>，<code>value</code> 是所有评论的列表，在这种情况下，热 <code>Key</code> 也同样是一个大 <code>Key</code>。</p><p>大 <code>Key</code> 的存在会导致数据偏斜。存储大 <code>Key</code> 的实例由于内存占用异常高，可能会因此出现 <code>OOM</code>，这样的故障很容易引起多米诺骨牌效应，导致整个集群宕机；特别是对于自动故障转移不太智能的集群，出现 <code>OOM</code> 的实例上的大 <code>Key</code> 可能会被迁移到另一个实例，并很快出现 <code>OOM</code> 并杀死这个新实例，然后再次迁移到另一个实例，也将其杀死，直到整个集群宕机。</p><p>如何降低大 <code>Key</code> 的影响呢？可能你已经猜到了，只需要将 <code>Key</code> 进行拆分！</p><ol><li>对于简单的键值类型，我们可以稍微重新设计数据模式。<code>Redis</code> 支持具有<strong>键-子键-值</strong>的哈希结构。换句话说，假设 <code>giantValueStruct</code> 这个 <code>Key</code> 中的值是一个有数百个字段的结构，我们可以将它分成多个<code>smallValueStruct</code> 的子 <code>Key</code>，这里的每个值都是前面大 <code>Key</code> 结构的一个小分片，可能是不到 <code>10</code> 个字段. <code>Redis</code> 原生支持 <code>hget/hset</code> 命令来修改 <code>smallValueStruct</code>，无需像 <code>get/set</code> 命令那样更改 <code>giantValueStruct</code>。</li><li>如果使用 <code>Redis Hash</code>、<code>Redis Set</code> 结构后，仍然有大 <code>Key</code> 怎么办？我们可以添加另一层哈希桶来进一步分片键和子键。例如，对于普通的 <code>Redis Hash</code>，我们操作是：</li></ol><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">hget(hashKey, subkey)</span><br><span class="line">hset(hashKey, subkey, value)</span><br></pre></td></tr></table></figure><p>子 <code>Key</code> 有时称为属性，因此我们可能会在其他文章中看到<strong>键-属性-值</strong>的描述。</p><p>要分片大 <code>Key</code>，类似于处理热 <code>Key</code> 问题中的第二种方法，我们可以这样做：</p><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></pre></td><td class="code"><pre><span class="line">newHashKey = hashKey + (hash(field) % 10000);</span><br><span class="line">hset(newHashKey, subkey, value);</span><br><span class="line">hget(newHashKey, subkey);</span><br></pre></td></tr></table></figure><ol start="3"><li>上述方法可能不适用于 <code>Redis zset</code> 结构，因为 <code>zset</code> 包含用于排序的分值信息，如果我们预测会出现大 <code>Key</code> 问题，请避免使用 <code>zset</code> 结构。</li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://medium.com/@bb8s/deep-dive-into-redis-cluster-sharding-algorithms-and-architecture-a093a92e97af&quot;&gt;https
      
    
    </summary>
    
    
      <category term="架构设计" scheme="https://dongzl.github.io/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    
      <category term="Redis" scheme="https://dongzl.github.io/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>MySQL 8 中如何处理 Too Many Connections 错误</title>
    <link href="https://dongzl.github.io/2023/03/18/07-Dealing-With-Too-Many-Connections-Error-in-MySQL-8/"/>
    <id>https://dongzl.github.io/2023/03/18/07-Dealing-With-Too-Many-Connections-Error-in-MySQL-8/</id>
    <published>2023-03-18T20:03:22.000Z</published>
    <updated>2023-12-31T06:59:02.129Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原文链接：<a href="https://www.percona.com/blog/dealing-with-too-many-connections-error-in-mysql-8/">https://www.percona.com/blog/dealing-with-too-many-connections-error-in-mysql-8/</a></p></blockquote><p>在做 <code>DBA</code> 的这些年里，我处理过各种各样的数据库问题。我遇到的最常见问题之一就是——<code>ERROR 1040 (08004): Too many connections</code> 相关的一些问题，这个错误可以说众所周知；关于这个错误已经有很多文章了，尽管如此用户还是不断掉入这个问题的陷阱，这可能是因为数据库配置不当、应用程序组件发生变化，或者只是因为应用程序中的连接突然增加所导致的。在某些时候，我们可能都会在工作中遇到这个问题，而且可能还不止一次遇到。这篇文章的主要目的是研究 <code>MySQL 8</code> 新提供的 <code>administrative connections</code> 特性，使用这些管理连接可以在出现这个问题时无需重启数据库实例。</p><h2 id="默认行为"><a href="#默认行为" class="headerlink" title="默认行为"></a>默认行为</h2><p>我们知道数据库中允许创建的连接数是由 <code>max_connections</code> 参数定义的，这个参数的默认值是 <code>151</code>，并且支持动态调整，这意味着无需重启数据库实例就可以调整连接数。如果数据库中的连接数达到最大值，我们将看到这个糟糕的错误——<code>ERROR 1040 (08004): Too many connections</code>。重要的是还要记住一个开箱即用功能，<code>MySQL</code> 允许创建一个额外的连接，这个连接是为 <code>SUPER</code> 权限（已废弃<a href="https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html#priv_super">这里</a>）或 <code>CONNECTION_ADMIN</code> 权限的用户保留的。</p><p>我准备用一个示例演示这个功能，对于这个示例需要创建一个 <code>max_connections=20</code> 的数据库实例，并创建三个用户，用户 <code>monitor1</code> 只有 <code>PROCESS</code> 权限，用户 <code>admin1</code> 拥有 <code>PROCESS</code> 和 <code>CONNECTION_ADMIN</code> 权限，最后一个用户 <code>admin2</code> 拥有超级用户权限（已废弃）。我们将演示 <code>MySQL</code> 在用户连接数达到最大值的情况下如何处理这些连接：</p><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><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><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- execute all 20 concurrent connections</span></span><br><span class="line">sysbench oltp_read_write <span class="comment">--table-size=1000000 --db-driver=mysql --mysql-host=localhost --mysql-db=sbtest --mysql-user=root --mysql-password=&quot;***&quot; --num-threads=20 --time=0 --report-interval=1 run</span></span><br><span class="line"><span class="comment">-- test with user monitor1 </span></span><br><span class="line">[root<span class="variable">@rocky</span><span class="operator">-</span>test1 <span class="operator">~</span>]# mysql <span class="operator">-</span>u monitor1 <span class="operator">-</span>p</span><br><span class="line">Enter password:</span><br><span class="line">ERROR <span class="number">1040</span> (<span class="number">08004</span>): Too many connections</span><br><span class="line"></span><br><span class="line"><span class="comment">-- test with user admin1</span></span><br><span class="line">[root<span class="variable">@rocky</span><span class="operator">-</span>test1 <span class="operator">~</span>]# mysql <span class="operator">-</span>u admin1 <span class="operator">-</span>p</span><br><span class="line">Enter password:</span><br><span class="line">Welcome <span class="keyword">to</span> the MySQL monitor.  Commands <span class="keyword">end</span> <span class="keyword">with</span> ; <span class="keyword">or</span> g.</span><br><span class="line">Your MySQL connection id <span class="keyword">is</span> <span class="number">144</span></span><br><span class="line">Server version: <span class="number">8.0</span><span class="number">.29</span><span class="number">-21</span> Percona Server (GPL), <span class="keyword">Release</span> <span class="number">21</span>, Revision c59f87d2854</span><br><span class="line"></span><br><span class="line">Copyright (c) <span class="number">2009</span><span class="number">-2022</span> Percona LLC <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its affiliates</span><br><span class="line">Copyright (c) <span class="number">2000</span>, <span class="number">2022</span>, Oracle <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its affiliates.</span><br><span class="line"></span><br><span class="line">Oracle <span class="keyword">is</span> a registered trademark <span class="keyword">of</span> Oracle Corporation <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its</span><br><span class="line">affiliates. Other names may be trademarks <span class="keyword">of</span> their respective</span><br><span class="line">owners.</span><br><span class="line"></span><br><span class="line">Type <span class="string">&#x27;help;&#x27;</span> <span class="keyword">or</span> <span class="string">&#x27;h&#x27;</span> <span class="keyword">for</span> help. Type <span class="string">&#x27;c&#x27;</span> <span class="keyword">to</span> clear the <span class="keyword">current</span> input statement.</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> grants;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> Grants <span class="keyword">for</span> admin1@<span class="operator">%</span>                           <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">GRANT</span> PROCESS <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> `admin1`@`<span class="operator">%</span>`          <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">GRANT</span> CONNECTION_ADMIN <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> `admin1`@`<span class="operator">%</span>` <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------------------------------------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="built_in">count</span>(<span class="number">1</span>) <span class="keyword">from</span> information_schema.processlist;</span><br><span class="line"><span class="operator">+</span><span class="comment">----------+</span></span><br><span class="line"><span class="operator">|</span> <span class="built_in">count</span>(<span class="number">1</span>) <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------+</span></span><br><span class="line"><span class="operator">|</span>       <span class="number">22</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">-- test with user admin2 </span></span><br><span class="line">[root<span class="variable">@rocky</span><span class="operator">-</span>test1 <span class="operator">~</span>]# mysql <span class="operator">-</span>u admin2 <span class="operator">-</span>p</span><br><span class="line">Enter password:</span><br><span class="line">Welcome <span class="keyword">to</span> the MySQL monitor.  Commands <span class="keyword">end</span> <span class="keyword">with</span> ; <span class="keyword">or</span> g.</span><br><span class="line">Your MySQL connection id <span class="keyword">is</span> <span class="number">145</span></span><br><span class="line">Server version: <span class="number">8.0</span><span class="number">.29</span><span class="number">-21</span> Percona Server (GPL), <span class="keyword">Release</span> <span class="number">21</span>, Revision c59f87d2854</span><br><span class="line"></span><br><span class="line">Copyright (c) <span class="number">2009</span><span class="number">-2022</span> Percona LLC <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its affiliates</span><br><span class="line">Copyright (c) <span class="number">2000</span>, <span class="number">2022</span>, Oracle <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its affiliates.</span><br><span class="line"></span><br><span class="line">Oracle <span class="keyword">is</span> a registered trademark <span class="keyword">of</span> Oracle Corporation <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its</span><br><span class="line">affiliates. Other names may be trademarks <span class="keyword">of</span> their respective</span><br><span class="line">owners.</span><br><span class="line"></span><br><span class="line">Type <span class="string">&#x27;help;&#x27;</span> <span class="keyword">or</span> <span class="string">&#x27;h&#x27;</span> <span class="keyword">for</span> help. Type <span class="string">&#x27;c&#x27;</span> <span class="keyword">to</span> clear the <span class="keyword">current</span> input statement.</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> grants;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> Grants <span class="keyword">for</span> admin2@<span class="operator">%</span>                <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">GRANT</span> SUPER <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> `admin2`@`<span class="operator">%</span>` <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="built_in">count</span>(<span class="number">1</span>) <span class="keyword">from</span> information_schema.processlist;</span><br><span class="line"><span class="operator">+</span><span class="comment">----------+</span></span><br><span class="line"><span class="operator">|</span> <span class="built_in">count</span>(<span class="number">1</span>) <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------+</span></span><br><span class="line"><span class="operator">|</span>        <span class="number">1</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>正如我们所演示的，<code>MySQL</code> 允许拥有 <code>CONNECTION_ADMIN</code> 或者 <code>SUPER</code> 权限的用户进行连接；所以当用户 <code>monitor1</code> 尝试进行连接时是不允许的，因为它没有被授予这些权限。一旦我们获得了对数据库的访问权限，我们就可以通过在线更改参数 <code>max_connections</code> 轻松增加连接数量，然后再继续排查导致连接数量不够问题的根本原因。重要的是要记住只是授予这些权限的用户连接才可以执行这个操作，所以不要轻易将这些权限授予某个用户，否则我们仍然可能被锁定在数据库之外。</p><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></pre></td><td class="code"><pre><span class="line">– trying a <span class="keyword">second</span> connection <span class="keyword">with</span> <span class="keyword">user</span> admin1</span><br><span class="line"></span><br><span class="line">[root<span class="variable">@rocky</span><span class="operator">-</span>test1 <span class="operator">~</span>]# mysql <span class="operator">-</span>u admin1 <span class="operator">-</span>p</span><br><span class="line">Enter password:</span><br><span class="line">ERROR <span class="number">1040</span> (HY000): Too many connections</span><br></pre></td></tr></table></figure><p>通常出现这个问题时，我们是无法访问 <code>MySQL</code> 数据库的，紧急的处理方式是重启数据库实例并处理由此所导致的一些问题，但是嘿……这会导致业务系统在正常运行时有几分钟时间无法连接到数据库；还有另一种获取数据库访问权限的方法，即使用 <code>GDB</code> 工具，但这个办法并一定是可行的，<a href="https://www.percona.com/blog/too-many-connections-no-problem/">Too many connections? No problem!</a>是我过去写的一篇关于这个工具的文章，文章有点久了但这个办法仍然有效。</p><h2 id="Percona-Server-为-MySQL-和-MariaDB-边注说明"><a href="#Percona-Server-为-MySQL-和-MariaDB-边注说明" class="headerlink" title="Percona Server 为 MySQL 和 MariaDB 边注说明"></a>Percona Server 为 MySQL 和 MariaDB 边注说明</h2><p><code>Percona Server for MySQL</code> 在 <code>MySQL 8.0.14</code> 之前的版本中，有另一种访问数据库实例的方法，与 <code>MySQL 8.0.14</code> 版本中引入的新功能类似，它是通过启用变量 <a href="https://docs.percona.com/percona-server/8.0/performance/threadpool.html#extra_port">extra_port</a> 和 <a href="https://docs.percona.com/percona-server/8.0/performance/threadpool.html#extra_max_connections">extra_max_connections</a> 来实现的，这些变量的使用超出了这篇博文的范围，这些变量的目的同样是在数据库的最大连接数已经达到上限的情况下允许继续连接到数据库。不过需要记住，这些变量已在 <code>MySQL 8.0.14</code> 版本中删除，如果在配置文件中出现这些变量，<code>MySQL</code> 数据库实例将无法正常启动并显示错误信息。与 <code>Percona Server for MySQL</code> 一样，<code>MariaDB</code> 数据库对这些变量也有类似的实现。<code>MariaDB</code> 的文档可以在<a href="https://mariadb.com/kb/en/thread-pool-system-status-variables/#extra_port">这里</a>找到。</p><h2 id="新特性"><a href="#新特性" class="headerlink" title="新特性"></a>新特性</h2><p>从 <code>MySQL 8.0.14</code> 版本开始，引入了新的 <code>Administrative Connections</code> 或 <code>Administrative Network Interface</code> 特性。这个特性允许通过管理端口连接到数据库，并且对管理连接的数量没有限制。这个特性与上面示例中显示的单个连接之间的区别在于，这个特性是通过一个不同的端口，并且它不会限制只有一个连接，而是在需要时允许创建多个连接；这个特性使我们在用户连接达到上限时还可以继续访问数据库，从而可以增加连接数量或终止一些应用程序的连接。</p><p>启用 <code>Administrative Connections</code> 最简单的方式是设置 <code>admin_address</code> 参数，这是管理连接将会监听的 <code>IP</code> 地址；例如，如果我们只允许本地连接，我们可以将这个参数设置为 <code>127.0.0.1</code>，或者如果我们想通过网络进行连接，我们可以将这个变参数定义为服务器的 <code>IP</code> 地址。这个参数不能够动态设置，这意味着修改这个参数需要重启数据库；默认情况下，此参数值为空，表示禁用管理连接功能。另一个相关参数是 <code>admin_port</code>，此参数定义 <code>MySQL</code> 为管理连接所提供的监听端口，此参数的默认值为 <code>33062</code>，定义这两个参数并重启数据库实例后，我们将会在错误日志中看到一条消息，显示管理接已准备好并等待连接：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2023</span><span class="number">-02</span><span class="number">-28</span>T14:<span class="number">42</span>:<span class="number">44.383663</span>Z <span class="number">0</span> [<span class="keyword">System</span>] [MY<span class="number">-013292</span>] [Server] Admin interface ready <span class="keyword">for</span> connections, address: <span class="string">&#x27;127.0.0.1&#x27;</span>  port: <span class="number">33062</span></span><br></pre></td></tr></table></figure><p>现在管理接口已配置就绪，我们需要定义可以访问此管理连接的用户。这些用户将需要拥有 <code>SERVICE_CONNECTION_ADMIN</code> 权限；否则将没有权限进行连接。按照我们的初始示例，我已将 <code>SERVICE_CONNECTION_ADMIN</code> 授予用户 <code>admin1</code> 但未授予用户 <code>admin2</code>。</p><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">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> grants <span class="keyword">for</span> admin1;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> Grants <span class="keyword">for</span> admin1@<span class="operator">%</span>                                                    <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">GRANT</span> PROCESS <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> `admin1`@`<span class="operator">%</span>`                                   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">GRANT</span> CONNECTION_ADMIN,SERVICE_CONNECTION_ADMIN <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> `admin1`@`<span class="operator">%</span>` <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------------------------------------------+</span></span><br><span class="line"><span class="number">2</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> grants <span class="keyword">for</span> admin2;</span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> Grants <span class="keyword">for</span> admin2@<span class="operator">%</span>                <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">GRANT</span> SUPER <span class="keyword">ON</span> <span class="operator">*</span>.<span class="operator">*</span> <span class="keyword">TO</span> `admin2`@`<span class="operator">%</span>` <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">------------------------------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>测试与管理接口的连接，我们看到只允许用户 <code>admin1</code> 进行连接，而用户 <code>admin2</code> 因没有 <code>SERVICE_CONNECTION_ADMIN</code> 权限而被拒绝。此外，我们可以确认用户 <code>admin1</code> 用户连接到了 <code>33062</code> 端口，这是用于管理连接特性的端口。</p><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><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="comment">-- testing user admin1</span></span><br><span class="line"></span><br><span class="line">[root<span class="variable">@rocky</span><span class="operator">-</span>test1 <span class="operator">~</span>]# mysql <span class="operator">-</span>h <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span> <span class="operator">-</span>P <span class="number">33062</span> <span class="operator">-</span>u admin1 <span class="operator">-</span>p</span><br><span class="line">Enter password:</span><br><span class="line">Welcome <span class="keyword">to</span> the MySQL monitor.  Commands <span class="keyword">end</span> <span class="keyword">with</span> ; <span class="keyword">or</span> g.</span><br><span class="line">Your MySQL connection id <span class="keyword">is</span> <span class="number">23</span></span><br><span class="line">Server version: <span class="number">8.0</span><span class="number">.29</span><span class="number">-21</span> Percona Server (GPL), <span class="keyword">Release</span> <span class="number">21</span>, Revision c59f87d2854</span><br><span class="line"></span><br><span class="line">Copyright (c) <span class="number">2009</span><span class="number">-2022</span> Percona LLC <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its affiliates</span><br><span class="line">Copyright (c) <span class="number">2000</span>, <span class="number">2022</span>, Oracle <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its affiliates.</span><br><span class="line"></span><br><span class="line">Oracle <span class="keyword">is</span> a registered trademark <span class="keyword">of</span> Oracle Corporation <span class="keyword">and</span><span class="operator">/</span><span class="keyword">or</span> its</span><br><span class="line">affiliates. Other names may be trademarks <span class="keyword">of</span> their respective</span><br><span class="line">owners.</span><br><span class="line"></span><br><span class="line">Type <span class="string">&#x27;help;&#x27;</span> <span class="keyword">or</span> <span class="string">&#x27;h&#x27;</span> <span class="keyword">for</span> help. Type <span class="string">&#x27;c&#x27;</span> <span class="keyword">to</span> clear the <span class="keyword">current</span> input statement.</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> s</span><br><span class="line"><span class="comment">--------------</span></span><br><span class="line">mysql  Ver <span class="number">8.0</span><span class="number">.29</span><span class="number">-21</span> <span class="keyword">for</span> Linux <span class="keyword">on</span> x86_64 (Percona Server (GPL), <span class="keyword">Release</span> <span class="number">21</span>, Revision c59f87d2854)</span><br><span class="line"></span><br><span class="line">Connection id:<span class="number">23</span></span><br><span class="line"><span class="keyword">Current</span> database:</span><br><span class="line"><span class="keyword">Current</span> <span class="keyword">user</span>:admin1<span class="variable">@localhost</span></span><br><span class="line">SSL:Cipher <span class="keyword">in</span> use <span class="keyword">is</span> TLS_AES_256_GCM_SHA384</span><br><span class="line"><span class="keyword">Current</span> pager:stdout</span><br><span class="line"><span class="keyword">Using</span> outfile:<span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="keyword">Using</span> delimiter:;</span><br><span class="line">Server version:<span class="number">8.0</span><span class="number">.29</span><span class="number">-21</span> Percona Server (GPL), <span class="keyword">Release</span> <span class="number">21</span>, Revision c59f87d2854</span><br><span class="line">Protocol version:<span class="number">10</span></span><br><span class="line">Connection:<span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span> via TCP<span class="operator">/</span>IP</span><br><span class="line">Server characterset:utf8mb4</span><br><span class="line">Db     characterset:utf8mb4</span><br><span class="line">Client characterset:utf8mb4</span><br><span class="line">Conn.  characterset:utf8mb4</span><br><span class="line"><span class="operator">&lt;</span>strong<span class="operator">&gt;</span>TCP port:<span class="number">33062</span><span class="operator">&lt;</span><span class="operator">/</span>strong<span class="operator">&gt;</span></span><br><span class="line"><span class="type">Binary</span> data <span class="keyword">as</span>:Hexadecimal</span><br><span class="line">Uptime:<span class="number">50</span> min <span class="number">27</span> sec</span><br><span class="line"></span><br><span class="line">Threads: <span class="number">3</span>  Questions: <span class="number">188</span>  Slow queries: <span class="number">0</span>  Opens: <span class="number">335</span>  Flush tables: <span class="number">3</span>  <span class="keyword">Open</span> tables: <span class="number">269</span>  Queries <span class="keyword">per</span> <span class="keyword">second</span> avg: <span class="number">0.062</span></span><br><span class="line"><span class="comment">--------------</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- testing user admin2</span></span><br><span class="line"></span><br><span class="line">[root<span class="variable">@rocky</span><span class="operator">-</span>test1 <span class="operator">~</span>]# mysql <span class="operator">-</span>h <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span> <span class="operator">-</span>P <span class="number">33062</span> <span class="operator">-</span>u admin2 <span class="operator">-</span>p</span><br><span class="line">Enter password:</span><br><span class="line"><span class="operator">&lt;</span>strong<span class="operator">&gt;</span>ERROR <span class="number">1227</span> (<span class="number">42000</span>): Access denied; you need (<span class="keyword">at</span> least <span class="keyword">one</span> <span class="keyword">of</span>) the SERVICE_CONNECTION_ADMIN privilege(s) <span class="keyword">for</span> this operation<span class="operator">&lt;</span><span class="operator">/</span>strong<span class="operator">&gt;</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>如果我们使用的是 <code>MySQL 8.0.14</code> 或更高版本，我们应用启用管理连接功能；正如示例所见开启这个能非常简单，使用这个新特性在触发 <code>ERROR 1040 (08004): Too many connections</code> 错误时允许 <code>DBA</code> 继续访问数据库。这个新特性不会影响正常的数据库性能，但是可以对 <code>DBA</code> 工作发挥很大作用。</p><p>我们应当考虑仅向管理员用户添加 <code>SERVICE_CONNECTION_ADMIN</code> 权限，而不是普通应用程序用户，这样做的目的是不要滥用此功能。如果我们还在使用较低版本的 <code>Percona Server for MySQL</code> 时，如果遇到最大连接问题，我们可以配置参数 <code>extra_port</code> 和 <code>extra_max_connections</code> 来访问数据库。</p><hr><h1 id="Too-many-connections-No-problem"><a href="#Too-many-connections-No-problem" class="headerlink" title="Too many connections? No problem!"></a>Too many connections? No problem!</h1><blockquote><p><a href="https://www.percona.com/blog/too-many-connections-no-problem/">https://www.percona.com/blog/too-many-connections-no-problem/</a></p></blockquote><p>你在生产中遇到过这种情况吗？</p><figure class="highlight sql"><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">[percona<span class="variable">@sandbox</span> msb_5_0_87]$ .<span class="operator">/</span>use</span><br><span class="line">ERROR <span class="number">1040</span> (<span class="number">00000</span>): Too many connections</span><br></pre></td></tr></table></figure><p>刚刚发生在我们的一位客户身上。想知道我们是怎么处理的吗？</p><p>出于演示目的，我在此处使用沙箱环境（因为 <code>./use</code> 实际上正在执行 <code>MySQL CLI</code> 工具）。请注意这不是通用的最佳实践，而是类似于服务器恶意闯入式的黑客攻击。因此当这种情况发生在生产环境中时，我们需要处理的问题是：</p><ul><li>如何快速重新获得对 <code>MySQL</code> 服务器的访问权限以便查看所有会话都在执行什么操作；</li><li>如何能够不重启应用程序的情况下连接到 <code>MySQL</code> 服务器。</li></ul><p>下面方法是一个小窍门：</p><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></pre></td><td class="code"><pre><span class="line">[percona<span class="variable">@sandbox</span> msb_5_0_87]$ gdb <span class="operator">-</span>p $(cat data<span class="operator">/</span>mysql_sandbox5087.pid) \</span><br><span class="line">                                     <span class="operator">-</span>ex &quot;set max_connections=5000&quot; <span class="operator">-</span>batch</span><br><span class="line">[Thread debugging <span class="keyword">using</span> libthread_db enabled]</span><br><span class="line">[<span class="keyword">New</span> Thread <span class="number">0x2ad3fe33b5c0</span> (LWP <span class="number">1809</span>)]</span><br><span class="line">[<span class="keyword">New</span> Thread <span class="number">0x4ed19940</span> (LWP <span class="number">27302</span>)]</span><br><span class="line">[<span class="keyword">New</span> Thread <span class="number">0x41a8b940</span> (LWP <span class="number">27203</span>)]</span><br><span class="line">...</span><br><span class="line">[<span class="keyword">New</span> Thread <span class="number">0x42ec5940</span> (LWP <span class="number">1813</span>)]</span><br><span class="line">[<span class="keyword">New</span> Thread <span class="number">0x424c4940</span> (LWP <span class="number">1812</span>)]</span><br><span class="line"><span class="number">0x00000035f36cc4c2</span> <span class="keyword">in</span> <span class="keyword">select</span> () <span class="keyword">from</span> <span class="operator">/</span>lib64<span class="operator">/</span>libc.so<span class="number">.6</span></span><br></pre></td></tr></table></figure><p>结果如下：</p><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></pre></td><td class="code"><pre><span class="line">[percona<span class="variable">@test9</span> msb_5_0_87]$ .<span class="operator">/</span>use </span><br><span class="line">Welcome <span class="keyword">to</span> the MySQL monitor.  Commands <span class="keyword">end</span> <span class="keyword">with</span> ; <span class="keyword">or</span> \g.</span><br><span class="line">Your MySQL connection id <span class="keyword">is</span> <span class="number">8</span></span><br><span class="line">Server version: <span class="number">5.0</span><span class="number">.87</span><span class="operator">-</span>percona<span class="operator">-</span>highperf<span class="operator">-</span>log MySQL Percona High Performance Edition, Revision <span class="number">61</span> (GPL)</span><br><span class="line"></span><br><span class="line">Type <span class="string">&#x27;help;&#x27;</span> <span class="keyword">or</span> <span class="string">&#x27;\h&#x27;</span> <span class="keyword">for</span> help. Type <span class="string">&#x27;\c&#x27;</span> <span class="keyword">to</span> clear the <span class="keyword">current</span> input statement.</span><br><span class="line"></span><br><span class="line">mysql [localhost] &#123;msandbox&#125; ((<span class="keyword">none</span>)) <span class="operator">&gt;</span> <span class="keyword">select</span> @<span class="variable">@global</span>.max_connections;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span> @<span class="variable">@global</span>.max_connections <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="operator">|</span>                     <span class="number">5000</span> <span class="operator">|</span> </span><br><span class="line"><span class="operator">+</span><span class="comment">--------------------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p><a href="https://dom.as/tag/gdb/">gdb magic</a> 的能力需要归功于 <a href="https://dom.as/">Domas</a>。</p><p><strong>几点注意事项</strong>：</p><ul><li>我们通常会为 <code>SUPER</code> 权限用户保留一个连接，但如果应用程序正在使用 <code>SUPER</code> 用户身份连接数据库（无论如何这不是一个好主意），这个方法就行不通了；</li><li>这个办法在 <code>5.0.87-percona-highperf</code> 上是有效的，但使用这个方案需要承担一些风险，并且在实际生产环境中使用之前需要进行充分测试；</li><li>这个示例假设配置的 <code>max_connections</code> 数量小于 <code>5000</code>。</li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;原文链接：&lt;a href=&quot;https://www.percona.com/blog/dealing-with-too-many-connections-error-in-mysql-8/&quot;&gt;https://www.percona.com/blog
      
    
    </summary>
    
    
      <category term="数据库" scheme="https://dongzl.github.io/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
      <category term="MySQL" scheme="https://dongzl.github.io/tags/MySQL/"/>
    
  </entry>
  
</feed>
