<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>云端漫步</title>
    <link>https://www.yhblog.xyz/</link>
    
    <atom:link href="https://www.yhblog.xyz/rss2.xml" rel="self" type="application/rss+xml"/>
    
    <description>遨游知识之海</description>
    <pubDate>Sun, 20 Sep 2026 07:13:17 GMT</pubDate>
    <generator>http://hexo.io/</generator>
    
    <item>
      <title>数据结构 · 笔试必考「数量关系」结论与推导</title>
      <link>https://www.yhblog.xyz/2026/08/22/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</link>
      <guid>https://www.yhblog.xyz/2026/08/22/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/</guid>
      <pubDate>Sat, 22 Aug 2026 09:33:59 GMT</pubDate>
      
        
        
      <description>&lt;blockquote&gt;
&lt;p&gt;这不是概念讲解，全部是&lt;strong&gt;选择题&amp;#x2F;填空题直接套用的数字结论&lt;/strong&gt;。&lt;br&gt;每条都带推导，能记住推导就能现场算出来，不靠死背。&lt;br&gt;已用代码逐一验证，结论可靠。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2</description>
        
      
      
      
      <content:encoded><![CDATA[<blockquote><p>这不是概念讲解，全部是<strong>选择题&#x2F;填空题直接套用的数字结论</strong>。<br>每条都带推导，能记住推导就能现场算出来，不靠死背。<br>已用代码逐一验证，结论可靠。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80%E6%A0%91%E7%BB%93%E7%82%B9--%E8%BE%B9--%E5%8F%B6%E5%AD%90--%E5%BA%A6%E6%95%B0">树：结点 &#x2F; 边 &#x2F; 叶子 &#x2F; 度数</a></li><li><a href="#%E4%BA%8C%E4%BA%8C%E5%8F%89%E6%A0%91%E5%B1%82--%E6%B7%B1%E5%BA%A6--%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91--%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91">二叉树：层 &#x2F; 深度 &#x2F; 完全二叉树 &#x2F; 满二叉树</a></li><li><a href="#%E4%B8%89%E4%BA%8C%E5%8F%89%E9%93%BE%E8%A1%A8%E7%A9%BA%E6%8C%87%E9%92%88%E5%9F%9F--%E7%BA%BF%E7%B4%A2%E4%BA%8C%E5%8F%89%E6%A0%91">二叉链表空指针域 &#x2F; 线索二叉树</a></li><li><a href="#%E5%9B%9Bavl-%E6%9C%80%E5%B0%91%E7%BB%93%E7%82%B9%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91">AVL 最少结点（斐波那契）</a></li><li><a href="#%E4%BA%94%E5%93%88%E5%A4%AB%E6%9B%BC%E6%A0%91">哈夫曼树</a></li><li><a href="#%E5%85%AD%E5%8D%A1%E7%89%B9%E5%85%B0%E6%95%B0%E5%BD%A2%E6%80%81%E6%95%B0--%E5%87%BA%E6%A0%88%E5%BA%8F%E5%88%97--%E6%8B%AC%E5%8F%B7">卡特兰数（形态数 &#x2F; 出栈序列 &#x2F; 括号）</a></li><li><a href="#%E4%B8%83%E6%A0%91%E4%B8%8E%E6%A3%AE%E6%9E%97%E8%BD%AC%E6%8D%A2">树与森林转换</a></li><li><a href="#%E5%85%AB%E5%9B%BE%E8%BE%B9%E6%95%B0--%E5%BA%A6%E6%95%B0--%E8%BF%9E%E9%80%9A%E6%80%A7--%E7%94%9F%E6%88%90%E6%A0%91">图：边数 &#x2F; 度数 &#x2F; 连通性 &#x2F; 生成树</a></li><li><a href="#%E4%B9%9D%E7%BA%BF%E6%80%A7%E8%A1%A8%E5%B9%B3%E5%9D%87%E7%A7%BB%E5%8A%A8--%E6%AF%94%E8%BE%83%E6%AC%A1%E6%95%B0">线性表：平均移动 &#x2F; 比较次数</a></li><li><a href="#%E5%8D%81%E6%9F%A5%E6%89%BE%E9%A1%BA%E5%BA%8F--%E6%8A%98%E5%8D%8A--%E5%93%88%E5%B8%8C%E8%A3%85%E5%A1%AB%E5%9B%A0%E5%AD%90">查找：顺序 &#x2F; 折半 &#x2F; 哈希装填因子</a></li><li><a href="#%E5%8D%81%E4%B8%80%E6%A0%88--%E9%98%9F%E5%88%97--%E4%B8%B2--%E5%B9%BF%E4%B9%89%E8%A1%A8">栈 &#x2F; 队列 &#x2F; 串 &#x2F; 广义表</a></li><li><a href="#%E5%8D%81%E4%BA%8C%E7%9F%A9%E9%98%B5%E5%8E%8B%E7%BC%A9%E5%AD%98%E5%82%A8%E5%9C%B0%E5%9D%80%E8%AE%A1%E7%AE%97">矩阵压缩存储地址计算</a></li><li><a href="#%E5%8D%81%E4%B8%89%E6%8E%92%E5%BA%8F%E6%AF%94%E8%BE%83%E6%AC%A1%E6%95%B0%E4%B8%8B%E7%95%8C--%E8%B6%9F%E6%95%B0">排序：比较次数下界 &#x2F; 趟数</a></li><li><a href="#%E5%8D%81%E5%9B%9B%E9%80%92%E6%8E%A8%E5%85%AC%E5%BC%8F%E9%80%9F%E8%AE%B0%E6%B1%89%E8%AF%BA%E5%A1%94--%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91">递推公式速记（汉诺塔 &#x2F; 斐波那契）</a></li><li><a href="#%E5%8D%81%E4%BA%94%E6%98%93%E6%B7%B7%E6%98%93%E9%94%99%E5%AF%B9%E7%85%A7%E8%A1%A8">易混易错对照表</a></li></ol><hr><h2 id="一、树：结点-边-叶子-度数"><a href="#一、树：结点-边-叶子-度数" class="headerlink" title="一、树：结点 &#x2F; 边 &#x2F; 叶子 &#x2F; 度数"></a>一、树：结点 &#x2F; 边 &#x2F; 叶子 &#x2F; 度数</h2><h3 id="结论-1：n-个结点的树有-n−1-条边"><a href="#结论-1：n-个结点的树有-n−1-条边" class="headerlink" title="结论 1：n 个结点的树有 n−1 条边"></a>结论 1：n 个结点的树有 <strong>n−1</strong> 条边</h3><p><strong>推导</strong>：除根结点外，每个结点有且仅有 1 条「父边」指向它。n 个结点中只有根没有父边 → 边数 &#x3D; n−1。</p><h3 id="结论-2（最核心）：任何二叉树-n₀-n₂-1（叶子数-度为2的结点数-1）"><a href="#结论-2（最核心）：任何二叉树-n₀-n₂-1（叶子数-度为2的结点数-1）" class="headerlink" title="结论 2（最核心）：任何二叉树 n₀ &#x3D; n₂ + 1（叶子数 &#x3D; 度为2的结点数 + 1）"></a>结论 2（最核心）：任何二叉树 <strong>n₀ &#x3D; n₂ + 1</strong>（叶子数 &#x3D; 度为2的结点数 + 1）</h3><p><strong>推导</strong>（两步）：</p><ul><li>设度为 0&#x2F;1&#x2F;2 的结点数分别为 n₀、n₁、n₂，总结点数 <code>N = n₀ + n₁ + n₂</code></li><li>「度」按孩子数算，所有结点的度之和 &#x3D; 边数 &#x3D; N−1，即 <code>n₁ + 2n₂ = n₀ + n₁ + n₂ − 1</code></li><li>消去 n₁：<code>n₂ = n₀ − 1</code> → <strong>n₀ &#x3D; n₂ + 1</strong> ✔</li></ul><h3 id="结论-3（推广到-m-叉树）：n₀-1-Σ-j−1-·nⱼ（j-≥-2）"><a href="#结论-3（推广到-m-叉树）：n₀-1-Σ-j−1-·nⱼ（j-≥-2）" class="headerlink" title="结论 3（推广到 m 叉树）：n₀ &#x3D; 1 + Σ(j−1)·nⱼ（j ≥ 2）"></a>结论 3（推广到 m 叉树）：<strong>n₀ &#x3D; 1 + Σ(j−1)·nⱼ</strong>（j ≥ 2）</h3><p><strong>推导</strong>：同结论 2，<code>Σ j·nⱼ = N − 1</code>，代入 <code>N = Σ nⱼ</code> 得 <code>n₀ = 1 + n₂ + 2n₃ + 3n₄ + …</code>。</p><ul><li>特例：满 k 叉树（所有分支结点度为 k），内部结点数 **n_内 &#x3D; (n₀−1)&#x2F;(k−1)**，总结点数 **(k·n₀−1)&#x2F;(k−1)**。</li></ul><h3 id="结论-4：已知各度结点数求叶子-总结点"><a href="#结论-4：已知各度结点数求叶子-总结点" class="headerlink" title="结论 4：已知各度结点数求叶子&#x2F;总结点"></a>结论 4：已知各度结点数求叶子&#x2F;总结点</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">边数 E = N − 1 且 Σ(度×个数) = 2E   ← 本质是图的握手定理</span><br></pre></td></tr></table></figure><p>例：二叉树中度 1 的结点 3 个、度 2 的结点 5 个 → 叶子 &#x3D; 5+1 &#x3D; 6，总结点 &#x3D; 3+5+6 &#x3D; 14。</p><h3 id="结论-5：森林有-t-棵树、n-个结点，则边数-n-−-t"><a href="#结论-5：森林有-t-棵树、n-个结点，则边数-n-−-t" class="headerlink" title="结论 5：森林有 t 棵树、n 个结点，则边数 &#x3D; n − t"></a>结论 5：森林有 t 棵树、n 个结点，则边数 &#x3D; <strong>n − t</strong></h3><p><strong>推导</strong>：每棵树 nᵢ 个结点有 nᵢ−1 条边，求和 <code>Σ(nᵢ−1) = n − t</code>。</p><hr><h2 id="二、二叉树：层-深度-完全二叉树-满二叉树"><a href="#二、二叉树：层-深度-完全二叉树-满二叉树" class="headerlink" title="二、二叉树：层 &#x2F; 深度 &#x2F; 完全二叉树 &#x2F; 满二叉树"></a>二、二叉树：层 &#x2F; 深度 &#x2F; 完全二叉树 &#x2F; 满二叉树</h2><h3 id="结论-1：第-i-层最多-2-i−1-个结点"><a href="#结论-1：第-i-层最多-2-i−1-个结点" class="headerlink" title="结论 1：第 i 层最多 2^(i−1) 个结点"></a>结论 1：第 i 层最多 <strong>2^(i−1)</strong> 个结点</h3><p>等比数列，第一层 1 个，每层翻倍。</p><h3 id="结论-2：深度为-k-的二叉树最多-2-k-−-1-个结点"><a href="#结论-2：深度为-k-的二叉树最多-2-k-−-1-个结点" class="headerlink" title="结论 2：深度为 k 的二叉树最多 2^k − 1 个结点"></a>结论 2：深度为 k 的二叉树最多 <strong>2^k − 1</strong> 个结点</h3><p><code>1+2+4+…+2^(k−1) = 2^k − 1</code>。达到上限即满二叉树。</p><h3 id="结论-3：n-个结点的二叉树"><a href="#结论-3：n-个结点的二叉树" class="headerlink" title="结论 3：n 个结点的二叉树"></a>结论 3：n 个结点的二叉树</h3><ul><li>最少深度（高度）：<strong>⌈log₂(n+1)⌉</strong> &#x3D; ⌊log₂n⌋ + 1（高度最低&#x3D;接近满二叉树）</li><li>最多深度：<strong>n</strong>（退化为单支链）</li><li>反推：高度 h 的二叉树，结点数范围 <code>2^(h−1) ≤ n ≤ 2^h − 1</code></li></ul><h3 id="结论-4：完全二叉树（必考）"><a href="#结论-4：完全二叉树（必考）" class="headerlink" title="结论 4：完全二叉树（必考）"></a>结论 4：完全二叉树（必考）</h3><p>设 n 个结点（下标从 1 编号）：</p><table><thead><tr><th>量</th><th>结论</th></tr></thead><tbody><tr><td>深度</td><td><strong>⌊log₂n⌋ + 1</strong></td></tr><tr><td>叶子结点数 n₀</td><td><strong>⌈n&#x2F;2⌉</strong></td></tr><tr><td>度为 1 的结点数 n₁</td><td>n 为偶 → <strong>1</strong>；n 为奇 → <strong>0</strong></td></tr><tr><td>最后一个非叶结点编号</td><td><strong>⌊n&#x2F;2⌋</strong></td></tr><tr><td>叶子编号范围</td><td>⌊n&#x2F;2⌋ + 1 ～ n</td></tr><tr><td>结点 i 的父 &#x2F; 左 &#x2F; 右</td><td>⌊i&#x2F;2⌋ &#x2F; 2i &#x2F; 2i+1</td></tr><tr><td>i 有左孩子 ⟺</td><td>2i ≤ n</td></tr><tr><td>i 有右孩子 ⟺</td><td>2i+1 ≤ n</td></tr></tbody></table><p><strong>n₁ 的推导</strong>：n₀ &#x3D; ⌈n&#x2F;2⌉，n₂ &#x3D; n₀−1，故 n₁ &#x3D; n − n₀ − n₂ &#x3D; n − 2⌈n&#x2F;2⌉ + 1。</p><ul><li>n 偶 (2m)：n₁ &#x3D; 2m − 2m + 1 &#x3D; <strong>1</strong></li><li>n 奇 (2m+1)：n₁ &#x3D; 2m+1 − 2(m+1) + 1 &#x3D; <strong>0</strong></li></ul><h3 id="结论-5：满二叉树（深度-k）"><a href="#结论-5：满二叉树（深度-k）" class="headerlink" title="结论 5：满二叉树（深度 k）"></a>结论 5：满二叉树（深度 k）</h3><ul><li>结点总数 <strong>2^k − 1</strong>，叶子 **2^(k−1)**，分支结点 <strong>2^(k−1) − 1</strong></li><li>叶子数 &#x3D; 分支结点数 + 1</li></ul><h3 id="结论-6：二叉树-n-个结点，恰好只有-n−1-条边（所有树通用），不可能是-n-条"><a href="#结论-6：二叉树-n-个结点，恰好只有-n−1-条边（所有树通用），不可能是-n-条" class="headerlink" title="结论 6：二叉树 n 个结点，恰好只有 n−1 条边（所有树通用），不可能是 n 条"></a>结论 6：二叉树 n 个结点，恰好只有 n−1 条边（所有树通用），不可能是 n 条</h3><h3 id="易混点：二叉树的「度」-孩子数（≤2）；普通树的「度」-孩子数；图论里「度」-邻接边数（含父边）。不同上下文别混用。"><a href="#易混点：二叉树的「度」-孩子数（≤2）；普通树的「度」-孩子数；图论里「度」-邻接边数（含父边）。不同上下文别混用。" class="headerlink" title="易混点：二叉树的「度」&#x3D; 孩子数（≤2）；普通树的「度」&#x3D; 孩子数；图论里「度」&#x3D; 邻接边数（含父边）。不同上下文别混用。"></a>易混点：二叉树的「度」&#x3D; 孩子数（≤2）；普通树的「度」&#x3D; 孩子数；图论里「度」&#x3D; 邻接边数（含父边）。<strong>不同上下文别混用。</strong></h3><hr><h2 id="三、二叉链表空指针域-线索二叉树"><a href="#三、二叉链表空指针域-线索二叉树" class="headerlink" title="三、二叉链表空指针域 &#x2F; 线索二叉树"></a>三、二叉链表空指针域 &#x2F; 线索二叉树</h2><h3 id="结论：n-个结点的二叉链表有-n-1-个空指针域"><a href="#结论：n-个结点的二叉链表有-n-1-个空指针域" class="headerlink" title="结论：n 个结点的二叉链表有 n+1 个空指针域"></a>结论：n 个结点的二叉链表有 <strong>n+1</strong> 个空指针域</h3><p><strong>推导</strong>：每个结点 2 个指针域，共 2n 个；n−1 条边用掉 n−1 个（每条边&#x3D;一个孩子指针），空域 &#x3D; 2n − (n−1) &#x3D; <strong>n+1</strong>。</p><blockquote><p>线索二叉树就是利用这 n+1 个空域存前驱&#x2F;后继 → 线索数 &#x3D; n+1。</p></blockquote><h3 id="推广：三叉链表（lchild-rchild-parent）"><a href="#推广：三叉链表（lchild-rchild-parent）" class="headerlink" title="推广：三叉链表（lchild + rchild + parent）"></a>推广：三叉链表（lchild + rchild + parent）</h3><p>每条边占 2 个指针（一个孩子指针 + 一个父指针），用掉 2(n−1) 个，共 3n 个域 → 空域 &#x3D; 3n − 2(n−1) &#x3D; <strong>n+2</strong>。</p><hr><h2 id="四、AVL-最少结点（斐波那契）"><a href="#四、AVL-最少结点（斐波那契）" class="headerlink" title="四、AVL 最少结点（斐波那契）"></a>四、AVL 最少结点（斐波那契）</h2><h3 id="结论：高度为-h-的-AVL-树最少结点数满足"><a href="#结论：高度为-h-的-AVL-树最少结点数满足" class="headerlink" title="结论：高度为 h 的 AVL 树最少结点数满足"></a>结论：高度为 h 的 AVL 树最少结点数满足</h3><figure class="highlight plaintext"><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">N(h) = N(h−1) + N(h−2) + 1,   N(0)=0, N(1)=1</span><br><span class="line">即   N(h) = Fib(h+2) − 1</span><br></pre></td></tr></table></figure><p><strong>数值速查</strong>：</p><table><thead><tr><th>高度 h</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th></tr></thead><tbody><tr><td>最少结点</td><td>1</td><td>2</td><td>4</td><td>7</td><td>12</td><td>20</td><td>33</td></tr></tbody></table><p><strong>推导</strong>：AVL 树左右子树高度差 ≤ 1，要结点最少，就让左子树高度 h−1、右子树高度 h−2（差 1），再加根结点 → 递推式。解斐波那契即得。</p><p><strong>反用</strong>：含 n 个结点的 AVL 树最大高度 ≈ 1.44·log₂(n+1)（最坏也 O(log n)）。</p><hr><h2 id="五、哈夫曼树"><a href="#五、哈夫曼树" class="headerlink" title="五、哈夫曼树"></a>五、哈夫曼树</h2><h3 id="结论：n-个叶子（权值）的哈夫曼树有-2n−1-个结点"><a href="#结论：n-个叶子（权值）的哈夫曼树有-2n−1-个结点" class="headerlink" title="结论：n 个叶子（权值）的哈夫曼树有 2n−1 个结点"></a>结论：n 个叶子（权值）的哈夫曼树有 <strong>2n−1</strong> 个结点</h3><p><strong>推导</strong>：每次合并两个结点生成一个新内部结点，合并 n−1 次 → 新增 n−1 个内部结点，总 &#x3D; n + (n−1) &#x3D; <strong>2n−1</strong>。</p><h3 id="其他性质"><a href="#其他性质" class="headerlink" title="其他性质"></a>其他性质</h3><ul><li>哈夫曼树<strong>没有度为 1 的结点</strong>（n₁ &#x3D; 0）→ n₀ &#x3D; n₂ + 1 恒成立</li><li>权值越小的结点离根越远（编码越长）</li><li>哈夫曼编码是<strong>前缀编码</strong>（任一编码不是另一编码的前缀）</li><li>WPL &#x3D; Σ(权 × 路径长度) 最小；权值互异时 WPL 唯一</li></ul><hr><h2 id="六、卡特兰数（形态数-出栈序列-括号）"><a href="#六、卡特兰数（形态数-出栈序列-括号）" class="headerlink" title="六、卡特兰数（形态数 &#x2F; 出栈序列 &#x2F; 括号）"></a>六、卡特兰数（形态数 &#x2F; 出栈序列 &#x2F; 括号）</h2><h3 id="公式：Cₙ-C-2n-n-n-1"><a href="#公式：Cₙ-C-2n-n-n-1" class="headerlink" title="公式：Cₙ &#x3D; C(2n, n) &#x2F; (n+1)"></a>公式：<strong>Cₙ &#x3D; C(2n, n) &#x2F; (n+1)</strong></h3><p><strong>数值速查</strong>：1, 1, 2, 5, 14, 42, 132, 429, 1430…</p><h3 id="四大应用（考到就是这几种）"><a href="#四大应用（考到就是这几种）" class="headerlink" title="四大应用（考到就是这几种）"></a>四大应用（考到就是这几种）</h3><ol><li>n 个结点的二叉树<strong>形态数</strong></li><li>n 个元素进栈，<strong>可能的出栈序列数</strong></li><li>n 对括号的<strong>合法匹配数</strong></li><li>凸 n+2 边形三角剖分数（即 Cₙ）</li></ol><p><strong>记忆推导</strong>：把二叉树的左右子树递归拆分，<code>Tₙ = Σ Tₖ·Tₙ₋₁₋ₖ</code>，解出封闭式 <code>C(2n,n)/(n+1)</code>。</p><blockquote><p>例：3 个结点二叉树形态 &#x3D; C₃ &#x3D; 5（✓ 符合常识）。</p></blockquote><hr><h2 id="七、树与森林转换"><a href="#七、树与森林转换" class="headerlink" title="七、树与森林转换"></a>七、树与森林转换</h2><table><thead><tr><th>转换</th><th>结论</th></tr></thead><tbody><tr><td>树 → 二叉树</td><td>结点数不变；<strong>根结点的右子树必为空</strong>（根没有兄弟）</td></tr><tr><td>森林 t 棵树 → 二叉树</td><td>结点数不变；二叉树的根即第一棵树的根</td></tr><tr><td>森林边数</td><td>n − t（见第一节结论 5）</td></tr><tr><td>还原判定</td><td>二叉树根无右孩子 ⟹ 对应<strong>一棵树</strong>；根有右孩子 ⟹ 对应森林</td></tr></tbody></table><hr><h2 id="八、图：边数-度数-连通性-生成树"><a href="#八、图：边数-度数-连通性-生成树" class="headerlink" title="八、图：边数 &#x2F; 度数 &#x2F; 连通性 &#x2F; 生成树"></a>八、图：边数 &#x2F; 度数 &#x2F; 连通性 &#x2F; 生成树</h2><h3 id="结论-1：握手定理"><a href="#结论-1：握手定理" class="headerlink" title="结论 1：握手定理"></a>结论 1：握手定理</h3><ul><li>无向图：<strong>Σ(顶点度) &#x3D; 2E</strong>，故度之和必为偶数 → <strong>奇度顶点个数必为偶数</strong></li><li>有向图：<strong>Σ入度 &#x3D; Σ出度 &#x3D; E</strong></li></ul><h3 id="结论-2：完全图边数（必考）"><a href="#结论-2：完全图边数（必考）" class="headerlink" title="结论 2：完全图边数（必考）"></a>结论 2：完全图边数（必考）</h3><table><thead><tr><th>图</th><th>边数</th></tr></thead><tbody><tr><td>无向完全图 Kₙ</td><td><strong>n(n−1)&#x2F;2</strong></td></tr><tr><td>有向完全图（每对顶点两条有向边）</td><td><strong>n(n−1)</strong></td></tr><tr><td>完全二分图 K(a,b)</td><td><strong>a·b</strong></td></tr></tbody></table><p><strong>推导</strong>：Kₙ 每两点连一条边 &#x3D; 从 n 个点取 2 个组合数 C(n,2) &#x3D; n(n−1)&#x2F;2。</p><h3 id="结论-3：连通性最少边数（高频易混，务必分清）"><a href="#结论-3：连通性最少边数（高频易混，务必分清）" class="headerlink" title="结论 3：连通性最少边数（高频易混，务必分清）"></a>结论 3：连通性最少边数（高频易混，务必分清）</h3><table><thead><tr><th>问题</th><th>最少边数</th></tr></thead><tbody><tr><td>n 顶点无向图<strong>恰好连通</strong>（构造出来连通）</td><td><strong>n−1</strong></td></tr><tr><td>n 顶点有向图<strong>恰好强连通</strong></td><td><strong>n</strong>（构成一个大环，每点入&#x3D;出&#x3D;1）</td></tr><tr><td>n 顶点无向图<strong>保证无论怎么连都连通</strong></td><td><strong>C(n−1,2) + 1</strong></td></tr><tr><td>n 顶点有向图（忽略方向）弱连通</td><td>n−1</td></tr></tbody></table><p><strong>「保证连通」推导</strong>：最坏情况是 n−1 个顶点先构成完全图（C(n−1,2) 条边）把第 n 个顶点孤立，再加 1 条边连它 → 任何连线方式下都必然连通。</p><blockquote><p>例：n&#x3D;5 → 4 个顶点完全图 6 条 + 1 &#x3D; 7 条（✓ 已验证）。</p></blockquote><h3 id="结论-4：生成树"><a href="#结论-4：生成树" class="headerlink" title="结论 4：生成树"></a>结论 4：生成树</h3><ul><li>连通图 n 顶点 m 条边 → 生成树恰有 <strong>n−1</strong> 条边</li><li>完全图 Kₙ 的生成树个数 &#x3D; **n^(n−2)**（Cayley 公式）<blockquote><p>K₃ → 3 棵，K₄ → 16 棵</p></blockquote></li></ul><h3 id="结论-5：平面图（欧拉公式）"><a href="#结论-5：平面图（欧拉公式）" class="headerlink" title="结论 5：平面图（欧拉公式）"></a>结论 5：平面图（欧拉公式）</h3><ul><li>连通平面图：<strong>V − E + F &#x3D; 2</strong>（F 为面数）</li><li>简单平面图必有 E ≤ 3V − 6；无三角形则 E ≤ 2V − 4</li><li>推论：K₅、K₃,₃ 都是非平面图</li></ul><h3 id="结论-6：欧拉图"><a href="#结论-6：欧拉图" class="headerlink" title="结论 6：欧拉图"></a>结论 6：欧拉图</h3><ul><li>存在欧拉回路 ⟺ 连通且<strong>所有顶点度为偶</strong></li><li>存在欧拉通路（不回起点）⟺ 连通且恰有 <strong>0 或 2 个</strong>奇度顶点</li><li>有向欧拉回路 ⟺ 入度 &#x3D; 出度（且连通）</li></ul><h3 id="结论-7：二分图-⟺-无奇环-⟺-2-可着色"><a href="#结论-7：二分图-⟺-无奇环-⟺-2-可着色" class="headerlink" title="结论 7：二分图 ⟺ 无奇环 ⟺ 2-可着色"></a>结论 7：二分图 ⟺ 无奇环 ⟺ 2-可着色</h3><h3 id="结论-8：拓扑排序成功-⟺-无环（DAG）；序列唯一-⟺-每步入度为-0-的顶点恰一个"><a href="#结论-8：拓扑排序成功-⟺-无环（DAG）；序列唯一-⟺-每步入度为-0-的顶点恰一个" class="headerlink" title="结论 8：拓扑排序成功 ⟺ 无环（DAG）；序列唯一 ⟺ 每步入度为 0 的顶点恰一个"></a>结论 8：拓扑排序成功 ⟺ 无环（DAG）；序列唯一 ⟺ 每步入度为 0 的顶点恰一个</h3><hr><h2 id="九、线性表：平均移动-比较次数"><a href="#九、线性表：平均移动-比较次数" class="headerlink" title="九、线性表：平均移动 &#x2F; 比较次数"></a>九、线性表：平均移动 &#x2F; 比较次数</h2><h3 id="结论-1：顺序表插入-删除平均移动次数（高频！）"><a href="#结论-1：顺序表插入-删除平均移动次数（高频！）" class="headerlink" title="结论 1：顺序表插入&#x2F;删除平均移动次数（高频！）"></a>结论 1：顺序表插入&#x2F;删除平均移动次数（高频！）</h3><table><thead><tr><th>操作</th><th>平均移动次数</th></tr></thead><tbody><tr><td>在长度为 n 的顺序表<strong>插入</strong>（n+1 个位置）</td><td><strong>n&#x2F;2</strong></td></tr><tr><td><strong>删除</strong>（n 个位置）</td><td><strong>(n−1)&#x2F;2</strong></td></tr><tr><td>无序查找（比较次数）</td><td><strong>(n+1)&#x2F;2</strong></td></tr></tbody></table><p><strong>插入推导</strong>：在第 i 个位置插入需移动 n−i+1 个，<code>平均 = Σ(n−i+1)/(n+1) = [n+(n−1)+…+0]/(n+1) = n(n+1)/2 ÷ (n+1) = n/2</code>。<br><strong>删除推导</strong>：<code>平均 = Σ(n−i)/n = (n−1)/2</code>。</p><h3 id="结论-2：链表插入-删除"><a href="#结论-2：链表插入-删除" class="headerlink" title="结论 2：链表插入&#x2F;删除"></a>结论 2：链表插入&#x2F;删除</h3><ul><li>已知位置指针 → O(1)；已知值 → 需先 O(n) 查找，总体 O(n)。</li><li>无头结点 vs 有头结点：头插&#x2F;头删在有头结点时统一逻辑。</li></ul><hr><h2 id="十、查找：顺序-折半-哈希装填因子"><a href="#十、查找：顺序-折半-哈希装填因子" class="headerlink" title="十、查找：顺序 &#x2F; 折半 &#x2F; 哈希装填因子"></a>十、查找：顺序 &#x2F; 折半 &#x2F; 哈希装填因子</h2><h3 id="结论-1：顺序查找"><a href="#结论-1：顺序查找" class="headerlink" title="结论 1：顺序查找"></a>结论 1：顺序查找</h3><ul><li>成功 ASL &#x3D; <strong>(n+1)&#x2F;2</strong>；失败 ASL &#x3D; n+1（比较到末尾再+哨兵）</li></ul><h3 id="结论-2：折半查找（基于有序数组）"><a href="#结论-2：折半查找（基于有序数组）" class="headerlink" title="结论 2：折半查找（基于有序数组）"></a>结论 2：折半查找（基于有序数组）</h3><ul><li>最多比较 <strong>⌊log₂n⌋ + 1</strong> 次（判定树高度）</li><li>成功 ASL ≈ <strong>log₂(n+1) − 1</strong>（n &#x3D; 2^k − 1 满判定树时精确）</li><li>比较次数与元素值无关，只取决于位置（判定树）。</li></ul><h3 id="结论-3：哈希表"><a href="#结论-3：哈希表" class="headerlink" title="结论 3：哈希表"></a>结论 3：哈希表</h3><ul><li><strong>装填因子 α &#x3D; 表中元素个数 n &#x2F; 表长 m</strong></li><li>线性探测：成功 ASL ≈ (1&#x2F;2)(1 + 1&#x2F;(1−α))；失败 ASL ≈ (1&#x2F;2)(1 + 1&#x2F;(1−α)²)</li><li>链地址法：成功 ASL ≈ 1 + α&#x2F;2</li><li>α 越大冲突越多；α &gt; 0.75 时冲突急剧上升（这也是 HashMap 0.75 的由来）</li></ul><hr><h2 id="十一、栈-队列-串-广义表"><a href="#十一、栈-队列-串-广义表" class="headerlink" title="十一、栈 &#x2F; 队列 &#x2F; 串 &#x2F; 广义表"></a>十一、栈 &#x2F; 队列 &#x2F; 串 &#x2F; 广义表</h2><h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><ul><li>n 个元素进栈，出栈序列数 &#x3D; <strong>卡特兰数 Cₙ</strong>（见第六节）</li><li>例：3 个元素进栈有 C₃ &#x3D; 5 种出栈序列</li></ul><h3 id="队列（循环队列，容量-M，头-front-尾-rear）"><a href="#队列（循环队列，容量-M，头-front-尾-rear）" class="headerlink" title="队列（循环队列，容量 M，头 front 尾 rear）"></a>队列（循环队列，容量 M，头 front 尾 rear）</h3><ul><li><strong>元素个数 &#x3D; (rear − front + M) % M</strong></li><li>判空 front &#x3D;&#x3D; rear；判满 (rear+1) % M &#x3D;&#x3D; front（留一个空位）</li><li>判满推导：不留空位的话 front&#x3D;&#x3D;rear 无法区分空与满（状态数 2^? 表示冲突）</li></ul><h3 id="串"><a href="#串" class="headerlink" title="串"></a>串</h3><ul><li>长度为 n 的串，<strong>子串个数 &#x3D; n(n+1)&#x2F;2 + 1</strong>（含 1 个空串）；非空子串 &#x3D; n(n+1)&#x2F;2</li><li>KMP：O(n+m)；朴素匹配最坏 O(n·m)</li><li>next 数组 &#x3D; 失配时最长公共前后缀长度</li></ul><h3 id="广义表"><a href="#广义表" class="headerlink" title="广义表"></a>广义表</h3><ul><li>长度 &#x3D; 最外层元素个数；深度 &#x3D; 括号最大嵌套层数</li><li>表头 &#x3D; 第一个元素；表尾 &#x3D; 除第一个元素外的<strong>子表</strong></li></ul><hr><h2 id="十二、矩阵压缩存储地址计算（填空必考）"><a href="#十二、矩阵压缩存储地址计算（填空必考）" class="headerlink" title="十二、矩阵压缩存储地址计算（填空必考）"></a>十二、矩阵压缩存储地址计算（填空必考）</h2><h3 id="对称矩阵-A-1-n-1-n-行优先压缩存一维-B"><a href="#对称矩阵-A-1-n-1-n-行优先压缩存一维-B" class="headerlink" title="对称矩阵 A[1..n][1..n] 行优先压缩存一维 B"></a>对称矩阵 A[1..n][1..n] 行优先压缩存一维 B</h3><p>下三角（含对角线）元素总数 &#x3D; <strong>n(n+1)&#x2F;2</strong>。<br><strong>A[i][j]（i ≥ j）存到 B 的 1-based 下标 k &#x3D; i(i−1)&#x2F;2 + j</strong><br><strong>推导</strong>：前 i−1 行共 1+2+…+(i−1) &#x3D; i(i−1)&#x2F;2 个元素，第 i 行第 j 列是第 j 个 → k &#x3D; i(i−1)&#x2F;2 + j。</p><ul><li>若 k 从 0 计：k &#x3D; i(i−1)&#x2F;2 + j − 1</li><li>上三角元素 A[i][j]（i&lt;j）用对称性查 A[j][i]</li></ul><h3 id="三角矩阵"><a href="#三角矩阵" class="headerlink" title="三角矩阵"></a>三角矩阵</h3><ul><li>下三角 + 常数 &#x3D; <strong>n(n+1)&#x2F;2 + 1</strong></li></ul><h3 id="三对角矩阵（带宽-1）"><a href="#三对角矩阵（带宽-1）" class="headerlink" title="三对角矩阵（带宽 1）"></a>三对角矩阵（带宽 1）</h3><ul><li>元素总数 &#x3D; <strong>3n − 2</strong>（第 1、n 行各 2 个，中间 n−2 行各 3 个）</li></ul><h3 id="稀疏矩阵"><a href="#稀疏矩阵" class="headerlink" title="稀疏矩阵"></a>稀疏矩阵</h3><ul><li>三元组表存储：(行, 列, 值)，空间 O(非零元素个数)</li><li>十字链表适合动态修改</li></ul><hr><h2 id="十三、排序：比较次数下界-趟数"><a href="#十三、排序：比较次数下界-趟数" class="headerlink" title="十三、排序：比较次数下界 &#x2F; 趟数"></a>十三、排序：比较次数下界 &#x2F; 趟数</h2><h3 id="结论-1：比较排序下界-Ω-n-log-n"><a href="#结论-1：比较排序下界-Ω-n-log-n" class="headerlink" title="结论 1：比较排序下界 Ω(n log n)"></a>结论 1：比较排序下界 <strong>Ω(n log n)</strong></h3><p><strong>推导</strong>：n 个元素有 n! 种排列，比较排序的决策树是二叉的，区分所有排列至少需要高度 ≥ ⌈log₂(n!)⌉，而 log₂(n!) &#x3D; n log₂ n − 1.44n ≈ n log₂ n。<br>→ 归并&#x2F;快排&#x2F;堆排的 O(n log n) 就是<strong>理论最优</strong>（常数内）。</p><blockquote><p>例：n&#x3D;8 → 至少 ⌈log₂ 8!⌉ &#x3D; ⌈15.3⌉ &#x3D; <strong>16</strong> 次比较。</p></blockquote><h3 id="结论-2：趟数-比较次数速记"><a href="#结论-2：趟数-比较次数速记" class="headerlink" title="结论 2：趟数 &#x2F; 比较次数速记"></a>结论 2：趟数 &#x2F; 比较次数速记</h3><table><thead><tr><th>排序</th><th>趟数</th><th>最坏比较次数</th><th>平均比较次数</th></tr></thead><tbody><tr><td>冒泡</td><td>≤ n−1</td><td>n(n−1)&#x2F;2</td><td>n(n−1)&#x2F;2</td></tr><tr><td>插入</td><td>n−1</td><td>n(n−1)&#x2F;2</td><td>n²&#x2F;4</td></tr><tr><td>选择</td><td>n−1</td><td>n(n−1)&#x2F;2</td><td>n(n−1)&#x2F;2</td></tr><tr><td>快排</td><td>递归树高度</td><td>n(n−1)&#x2F;2（基本有序）</td><td>≈ 1.39n log n</td></tr><tr><td>归并</td><td>log n 层</td><td>n log n</td><td>n log n</td></tr><tr><td>堆排</td><td>n−1</td><td>n log n</td><td>n log n</td></tr></tbody></table><h3 id="结论-3：稳定排序-插、冒、归、基（口诀「插冒归基」）"><a href="#结论-3：稳定排序-插、冒、归、基（口诀「插冒归基」）" class="headerlink" title="结论 3：稳定排序 &#x3D; 插、冒、归、基（口诀「插冒归基」）"></a>结论 3：稳定排序 &#x3D; 插、冒、归、基（口诀「插冒归基」）</h3><h3 id="结论-4：已基本有序-→-插入排序最优（O-n-）；内存不够-→-外部归并"><a href="#结论-4：已基本有序-→-插入排序最优（O-n-）；内存不够-→-外部归并" class="headerlink" title="结论 4：已基本有序 → 插入排序最优（O(n)）；内存不够 → 外部归并"></a>结论 4：已基本有序 → 插入排序最优（O(n)）；内存不够 → 外部归并</h3><hr><h2 id="十四、递推公式速记（汉诺塔-斐波那契）"><a href="#十四、递推公式速记（汉诺塔-斐波那契）" class="headerlink" title="十四、递推公式速记（汉诺塔 &#x2F; 斐波那契）"></a>十四、递推公式速记（汉诺塔 &#x2F; 斐波那契）</h2><table><thead><tr><th>递推</th><th>结果</th><th>推导要点</th></tr></thead><tbody><tr><td>汉诺塔 T(n) &#x3D; 2T(n−1) + 1</td><td><strong>2^n − 1</strong></td><td>先把 n−1 盘移走 + 移大盘 + 再移回</td></tr><tr><td>斐波那契 递归</td><td>O(2^n)</td><td>T(n)&#x3D;T(n−1)+T(n−2)，指数爆炸</td></tr><tr><td>斐波那契 迭代&#x2F;矩阵</td><td>O(n) &#x2F; O(log n)</td><td>滚动变量；矩阵快速幂</td></tr><tr><td>AVL 最少结点（第五节）</td><td>Fib(h+2) − 1</td><td>N(h)&#x3D;N(h−1)+N(h−2)+1</td></tr><tr><td>归并 T(n)&#x3D;2T(n&#x2F;2)+O(n)</td><td>O(n log n)</td><td>每层 O(n)，log n 层</td></tr><tr><td>二分 T(n)&#x3D;T(n&#x2F;2)+O(1)</td><td>O(log n)</td><td>每次折半</td></tr></tbody></table><hr><h2 id="十五、易混易错对照表"><a href="#十五、易混易错对照表" class="headerlink" title="十五、易混易错对照表"></a>十五、易混易错对照表</h2><table><thead><tr><th>易混点</th><th>正确答案</th><th>错误答案（避坑）</th></tr></thead><tbody><tr><td>n 个结点树边数</td><td>n−1</td><td>n</td></tr><tr><td>二叉树叶子与度为2</td><td>n₀ &#x3D; n₂ + 1</td><td>n₀ &#x3D; n₂</td></tr><tr><td>完全二叉树叶子</td><td>⌈n&#x2F;2⌉</td><td>⌊n&#x2F;2⌋（相差在 n 为奇时）</td></tr><tr><td>完全二叉树 n₁</td><td>奇→0，偶→1</td><td>恒为 0</td></tr><tr><td>二叉链表空域</td><td>n+1</td><td>2n</td></tr><tr><td>三叉链表空域</td><td>n+2</td><td>n+1</td></tr><tr><td>恰好连通最少边</td><td>n−1</td><td>C(n−1,2)+1（那是”保证”）</td></tr><tr><td>保证连通最少边</td><td>C(n−1,2)+1</td><td>n−1（那是”恰好”）</td></tr><tr><td>有向强连通最少边</td><td>n</td><td>n−1</td></tr><tr><td>无向完全图边数</td><td>n(n−1)&#x2F;2</td><td>n(n−1)（那是有向）</td></tr><tr><td>顺序表插入平均移动</td><td>n&#x2F;2</td><td>(n−1)&#x2F;2（那是删除）</td></tr><tr><td>折半查找最多比较</td><td>⌊log₂n⌋+1</td><td>log₂n（差 1，n 非 2 幂时别取整错）</td></tr><tr><td>哈夫曼总结点数</td><td>2n−1</td><td>n（忽略内部结点）</td></tr><tr><td>完全二叉树深度</td><td>⌊log₂n⌋+1</td><td>⌈log₂(n+1)⌉（两者相等，别混）</td></tr><tr><td>比较排序下界</td><td>Ω(n log n)</td><td>O(n log n)（那是上界&#x2F;平均）</td></tr></tbody></table><hr><h2 id="附录：一句话推导精华（考前-5-分钟）"><a href="#附录：一句话推导精华（考前-5-分钟）" class="headerlink" title="附录：一句话推导精华（考前 5 分钟）"></a>附录：一句话推导精华（考前 5 分钟）</h2><ol><li><strong>边数 n−1</strong>：除根外每点一条父边。</li><li><strong>n₀&#x3D;n₂+1</strong>：Σ度 &#x3D; N−1。</li><li><strong>完全二叉树叶子 ⌈n&#x2F;2⌉</strong>：满树叶子最多，去掉下层右半边。</li><li><strong>空链域 n+1</strong>：2n − (n−1)。</li><li><strong>AVL 最少结点</strong>：左高 h−1 + 右高 h−2 + 1 &#x3D; 斐波那契。</li><li><strong>哈夫曼 2n−1</strong>：n−1 次合并。</li><li>**卡特兰 C(2n,n)&#x2F;(n+1)**：左右子树递归拆分。</li><li><strong>握手定理 Σ度&#x3D;2E</strong>：每条边贡献 2。</li><li><strong>保证连通 C(n−1,2)+1</strong>：最坏 n−1 点全连接 + 1 条边。</li><li><strong>顺序表插入 n&#x2F;2、删除 (n−1)&#x2F;2</strong>：等差求和平均。</li><li><strong>折半 ⌊log₂n⌋+1</strong>：判定树高度。</li><li><strong>α &#x3D; n&#x2F;m</strong>：装填因子。</li><li><strong>循环队列 (rear−front+M)%M</strong>。</li><li><strong>对称矩阵 k &#x3D; i(i−1)&#x2F;2 + j</strong>。</li><li><strong>比较排序下界 n log n</strong>：决策树 n! 种排列。</li></ol><hr><p><em>所有数字结论均已用程序验证。考前把「十五、易混易错表」和「附录」过一遍即可。</em></p>]]></content:encoded>
      
      
      
      
      <comments>https://www.yhblog.xyz/2026/08/22/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>分布式事务</title>
      <link>https://www.yhblog.xyz/2024/09/03/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/</link>
      <guid>https://www.yhblog.xyz/2024/09/03/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/</guid>
      <pubDate>Tue, 03 Sep 2024 12:54:09 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;分布式事务问题&quot;&gt;&lt;a href=&quot;#分布式事务问题&quot; class=&quot;headerlink&quot; title=&quot;分布式事务问题&quot;&gt;&lt;/a&gt;分布式事务问题&lt;/h2&gt;&lt;p&gt;在分布式系统中，通常一个业务需要跨越多个数据源去远程调用获取数据，而如果在使用传统的单体事务控制，则</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="分布式事务问题"><a href="#分布式事务问题" class="headerlink" title="分布式事务问题"></a>分布式事务问题</h2><p>在分布式系统中，通常一个业务需要跨越多个数据源去远程调用获取数据，而如果在使用传统的单体事务控制，则只能保证调用者所在的数据库的ACID，而无法满足整体的一致性，此时我们就要引入分布式事务</p><h2 id="CAP定理"><a href="#CAP定理" class="headerlink" title="CAP定理"></a>CAP定理</h2><p>CAP定理，也被称为Brewer定理，是分布式计算中的一个核心概念，它强调了分布式系统中一致性（Consistency）、可用性（Availability）和分区容忍性（Partition Tolerance）这三个关键属性之间的固有权衡。这一理论由计算机科学家Eric Brewer在2000年首次提出。</p><h3 id="CAP定理的组成"><a href="#CAP定理的组成" class="headerlink" title="CAP定理的组成"></a>CAP定理的组成</h3><ul><li><strong>一致性（Consistency）</strong>：在分布式系统中，一致性意味着系统中的所有节点在同一时间看到相同的数据。换句话说，当发生写操作时，所有后续的读操作都应反映该写操作。这是分布式系统数据一致性的基本要求。</li><li><strong>可用性（Availability）</strong>：可用性指的是系统中每个节点对读和写请求的响应能力，即使一些节点经历故障或延迟。一个可用的系统能够确保请求得到响应，但不保证响应中包含最新的写入。</li><li><strong>分区容忍性（Partition Tolerance）</strong>：分区容忍性涉及系统在发生网络分区（即通信失败）时继续运行和提供服务的能力。它要求系统能够容忍消息的丢失或节点间通信的延迟，以保证系统的整体稳定性和可靠性。</li></ul><h3 id="CAP定理的核心观点"><a href="#CAP定理的核心观点" class="headerlink" title="CAP定理的核心观点"></a>CAP定理的核心观点</h3><p>CAP定理指出，一个分布式系统最多只能同时实现这三个属性中的两个。这是因为在分布式环境中，网络分区是不可避免的，而在分区发生时，系统需要在一致性和可用性之间做出选择。具体来说：</p><ul><li>如果系统选择保证一致性，那么在网络分区期间可能会牺牲可用性，因为系统需要等待所有节点就绪以保证数据一致。</li><li>如果系统选择保证可用性，那么在网络分区期间可能会牺牲一致性，因为系统需要允许某些节点响应请求以维持服务。</li></ul><h2 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h2><p>BASE理论可以看作是CAP定理的一种延伸和补充。CAP定理指出一个分布式系统不可能同时满足一致性、可用性和分区容错性三个属性中的全部，而BASE理论则通过放宽一致性的要求来实现在分区容错性和可用性之间的平衡。因此，在设计分布式系统时，可以根据具体需求选择合适的策略来平衡这三个属性之间的关系。</p><h3 id="三个思想"><a href="#三个思想" class="headerlink" title="三个思想"></a>三个思想</h3><h4 id="基本可用（Basically-Available）"><a href="#基本可用（Basically-Available）" class="headerlink" title="基本可用（Basically Available）"></a>基本可用（Basically Available）</h4><ul><li><strong>定义</strong>：系统保证在出现故障或者数据损坏的情况下，依然能够保持核心功能的可用性，并且尽可能地提供其他功能的可用性。这意味着系统应该尽量避免完全不可用的情况，即使部分功能或性能受损，也应保证基本的响应能力。</li><li><strong>应用场景</strong>：在电商网站的高峰期，为了保证系统的整体可用性，可能会采用降级页面或延迟响应等方式，允许部分请求得到响应，但可能牺牲部分实时性或数据一致性。</li></ul><h4 id="软状态（Soft-State）"><a href="#软状态（Soft-State）" class="headerlink" title="软状态（Soft State）"></a>软状态（Soft State）</h4><ul><li><strong>定义</strong>：系统中的数据可以没有时效性，即数据不需要一直保持一致，可以存在一段时间的不一致状态。这种状态是暂时的，系统会通过后续的处理来逐渐将数据状态调整为一致。</li><li><strong>特点</strong>：软状态允许系统在不同节点的数据副本之间进行数据同步的过程存在延时，这种延时不会影响系统的整体可用性。</li><li><strong>应用场景</strong>：在分布式缓存系统中，可能会采用异步复制的方式来进行数据同步，以提高响应速度和吞吐量。这种异步复制方式就体现了软状态的特点。</li></ul><h4 id="最终一致性（Eventually-Consistent）"><a href="#最终一致性（Eventually-Consistent）" class="headerlink" title="最终一致性（Eventually Consistent）"></a>最终一致性（Eventually Consistent）</h4><ul><li><strong>定义</strong>：系统不需要保证在每个节点上的数据都是实时一致的，但是系统会确保所有节点上的数据在经过一定时间的同步后最终达到一致状态。这是一种弱一致性模型，它允许系统在某些时刻不满足强一致性要求，但保证最终数据会一致。</li><li><strong>应用场景</strong>：在社交媒体平台中，用户需要实时地与其他用户进行互动，并获取最新的信息更新。为了提高性能，平台可以采用缓存技术和分布式数据存储，以加速数据访问和查询。在一致性方面，可以采用最终一致性的策略，即用户可以看到稍有延迟的最新数据，而不需要立即保证所有数据副本的一致性。</li></ul><h2 id="分布式事务模型"><a href="#分布式事务模型" class="headerlink" title="分布式事务模型"></a>分布式事务模型</h2><p>解决分布式事务，各个子系统之间必须能感知彼此的事务状态，才能保持状态一致，因此需要一个事务协调者来协调每一个事务的参与者（子系统事务）。这里的子系统事务称为分支事务，有关联的各个分支事务在一起称为全局事务。</p><h2 id="Seata"><a href="#Seata" class="headerlink" title="Seata"></a>Seata</h2><p>Seata是一款开源的分布式事务解决方案，由阿里巴巴发起并维护，致力于提供高性能和简单易用的分布式事务服务。它支持多种分布式事务模式，包括AT（原子性事务）、TCC（尝试、确认、取消）、Saga（有限状态机）和XA（两阶段提交）等，为用户打造一站式的分布式解决方案。以下是关于Seata的详细介绍：</p><h3 id="三个重要角色"><a href="#三个重要角色" class="headerlink" title="三个重要角色"></a>三个重要角色</h3><ol><li><p>TC（Transaction Coordinator）- 事务协调者：</p><ul><li><strong>职责</strong>：<strong>维护全局和分支事务的状态，协调全局事务的提交或回滚</strong>。它是事务管理的核心，负责接收来自事务管理器（TM）的请求，并根据这些请求来驱动资源管理器（RM）执行相应的操作。</li><li><strong>特点</strong>：TC是一个独立的微服务，通常作为单独部署的Server服务端运行，不包含任何业务代码，仅负责事务的协调工作。</li></ul></li><li><p>TM（Transaction Manager）- 事务管理器：</p><ul><li><strong>职责</strong>：<strong>定义全局事务的范围，开始全局事务，提交或回滚全局事务</strong>。它是分布式事务的入口，负责在业务逻辑开始时开启一个全局事务，并在业务逻辑结束时根据执行结果来决定是提交还是回滚这个全局事务。</li><li><strong>特点</strong>：TM通常被嵌入到应用中的Client客户端，通过拦截业务方法的执行来监控全局事务的范围。它会与TC进行交互，以注册全局事务、报告事务状态等。</li></ul></li><li><p>RM（Resource Manager）- 资源管理器：</p><ul><li><p><strong>职责</strong>：<strong>管理分支事务处理的资源，与TC交谈以注册分支事务和报告分支事务的状态，并驱动分支事务提交或回滚</strong>。它是事务的参与者，负责执行具体的分支事务操作，如数据库操作、服务调用等。</p></li><li><p><strong>特点</strong>：RM同样被嵌入到应用中的Client客户端，与TM一起协作完成全局事务的执行。它会在TC的协调下，执行本地事务的提交或回滚操作，以确保全局事务的一致性。</p></li></ul></li></ol><h3 id="Seata工作模式"><a href="#Seata工作模式" class="headerlink" title="Seata工作模式"></a>Seata工作模式</h3><p>1.XA模式</p><ul><li><strong>简介</strong>：基于数据库的XA协议来实现两阶段提交（2PC）。</li><li><strong>工作原理</strong>：通过XA协议来管理分布式事务，确保全局事务的一致性和完整性。</li><li><strong>适用场景</strong>：适用于强一致性的场景，如金融、银行等。</li><li><strong>工作流程</strong>：由TM通知TC开启全局事务，TM调用其中的每一个RM，RM开始将自己注册到TC中，并开始执行自己的业务sql，在执行过后向TC报告事务状态，在所有RM执行完后，由TM通知TC要开始决策是提交还是回滚事务，TC根据刚才每一个RM报告的事务状态通知每一个RM是执行提交还是回滚</li><li><strong>优点</strong>：事务的强一致性，满足ACID原则，常用数据库都支持，实现简单，没有代码侵入</li><li><strong>缺点</strong>：TC需要等待每一个RM将状态报告完毕，才能进行决定提交或回滚，这是同步的，性能较差，依赖关系型数据库实现事务</li></ul><p>2.AT模式（默认模式）</p><ul><li><strong>简介</strong>：提供无侵入自动补偿的事务模式，目前已支持MySQL、Oracle、PostgreSQL、TiDB和MariaDB等数据库。</li><li><strong>工作原理</strong>：通过协调各个分支事务的执行状态，确保分布式事务的一致性。如果发生异常，Seata能够协调回滚所有相关分支事务，保持数据的一致性。</li><li><strong>适用场景</strong>：适用于需要强一致性的分布式事务场景。</li><li><strong>工作流程</strong>：由TM通知TC开启全局事务，TM调用其中的每一个RM，RM开始将自己注册到TC中，并开始执行自己的业务sql并提交，记录更新前后的快照，向TC报告事务状态，待所有RM执行完后，TM通知TC提交或回滚全局事务，TC根据RM报告的状态是否一致，若全部成功，则通知RM删除快照数据，若有一个失败，则根据之前的快照数据进行回复并删除快照数据</li><li><strong>与XA区别</strong>：XA模式第一阶段不提交事务，锁定资源，AT模式一阶段直接提交事务，不锁定资源。XA模式依赖数据库机制实现回滚，AT模式利用数据快照实现数据回滚。XA模式强一致，AT模式最终一致</li><li><strong>优点</strong>：一阶段直接提交事务，释放数据库资源，性能比较好，利用全局锁实现读写隔离，没有代码侵入，框架自动完成回滚和提交</li><li><strong>缺点</strong>：两阶段中间属于软状态，属于最终一致，框架的快照功能会影响性能，但比XA模式要好很多</li></ul><p>3.TCC模式</p><ul><li><strong>简介</strong>：Try Confirm&#x2F;Cancel，即尝试、确认、取消模式。</li><li><strong>工作原理</strong>：首先执行Try操作，用于尝试执行分支事务；如果所有分支事务的Try操作都成功，则执行Confirm操作，用于确认并提交已经执行的分支事务；如果任何分支事务的Try操作失败，或者在Confirm操作期间发生了异常，则执行Cancel操作，用于回滚已经执行的分支事务。</li><li><strong>适用场景</strong>：对业务代码侵入性较强，必要时可能还要修改数据库，适用于需要高度灵活控制事务流程的场景。</li><li><strong>工作流程</strong>：整体的工作流程与AT模式较为相似，TCC模式通过编程的方式实现事务控制逻辑，需要开发者在业务代码中显式地编写Try、Confirm和Cancel三个阶段的逻辑。Try阶段用于执行业务逻辑并预留必要的资源，Confirm阶段用于在Try阶段成功后实际提交事务，Cancel阶段则用于在Try或Confirm失败时回滚预留的资源。</li><li><strong>优点</strong>：一阶段直接提交事务，释放数据库资源，性能好，相比AT模型，无需生成快照，无需使用全局锁，性能最强。不依赖数据库事务，而是依赖补偿操作，可以用于非事务型数据库</li><li><strong>缺点</strong>：有代码侵入，需要认为编写try,confirm,cancel接口，太麻烦。软状态，事务时最终一致。需要考虑confirm和cancel的失败情况，做好幂等处理</li></ul><p>4.Saga模式</p><ul><li><strong>简介</strong>：长事务解决方案，基于状态机来实现。</li><li><strong>工作原理</strong>：业务流程中每个参与者都提交本地事务，当出现某一个参与者失败时，则补偿前面已经成功的参与者。一阶段正向服务和二阶段补偿服务都由业务开发实现。</li><li><strong>适用场景</strong>：业务流程长、业务流程多，参与者包含其它公司或遗留系统服务，无法提供TCC模式要求的三个接口。</li><li><strong>工作流程</strong>：第一阶段直接提交本地事务，的第二阶段成功则什么也不做，失败则通过补偿业务来回滚</li><li><strong>优点</strong>：事务参与者可以基于事件驱动实现异步调用，吞吐高。一阶段直接提交事务，无锁性能好。不用编写TCC的三个阶段，实现简单</li><li><strong>缺点</strong>：软状态持续时间不确定，时效性差。没有锁，没有事务隔离，会有脏写</li></ul><table><thead><tr><th></th><th>XA</th><th>AT</th><th>TCC</th><th>SAGA</th></tr></thead><tbody><tr><td>一致性</td><td>强一致</td><td>弱一致</td><td>弱一致</td><td>弱一致</td></tr><tr><td>隔离性</td><td>完全隔离</td><td>基于全局锁隔离</td><td>基于资源预留隔离</td><td>无隔离</td></tr><tr><td>代码侵入</td><td>无</td><td>无</td><td>有，要编写三个接口</td><td>有，要编写状态机和补偿业务</td></tr><tr><td>性能</td><td>差</td><td>好</td><td>非常好</td><td>非常好</td></tr><tr><td>场景</td><td>对一致性，隔离性有高要求的业务场景</td><td>基于关系型数据库的大部分分布式事务场景都可以</td><td>对性能有较高要求，有非关系型数据库要参与的事务</td><td>业务流程长多，参与者包含其他公司或遗留的系统服务，无法提供TCC模式要求的三个接口</td></tr></tbody></table>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/09/03/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>MQ常见问题总结</title>
      <link>https://www.yhblog.xyz/2024/08/05/MQ%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/</link>
      <guid>https://www.yhblog.xyz/2024/08/05/MQ%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/</guid>
      <pubDate>Mon, 05 Aug 2024 13:18:24 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;消息可靠性问题&quot;&gt;&lt;a href=&quot;#消息可靠性问题&quot; class=&quot;headerlink&quot; title=&quot;消息可靠性问题&quot;&gt;&lt;/a&gt;消息可靠性问题&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;消息丢失的可能性&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送时丢失：&lt;ul&gt;
</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="消息可靠性问题"><a href="#消息可靠性问题" class="headerlink" title="消息可靠性问题"></a>消息可靠性问题</h2><p><strong>消息丢失的可能性</strong></p><ul><li>发送时丢失：<ul><li>生产者发送的消息未送达exchange</li><li>消息到达exchange后未到达queue</li></ul></li><li>MQ宕机，queue将消息丢失</li><li>consumer接受到消息后未消费就宕机</li></ul><p>根据以上几种可能，我们分别有生产者消息确认，消息持久化，消费者消息确认和消费失败重试机制来解决以上的问题</p><h3 id="生产者消息确认"><a href="#生产者消息确认" class="headerlink" title="生产者消息确认"></a>生产者消息确认</h3><p>RabbitMQ提供了发送者确认和发送者回执两种机制来确认消息不会在发送过程中丢失</p><h4 id="Publisher-Confirm（发送者确认）"><a href="#Publisher-Confirm（发送者确认）" class="headerlink" title="Publisher Confirm（发送者确认）"></a>Publisher Confirm（发送者确认）</h4><p><code>Publisher Confirm</code>机制是RabbitMQ用来确保消息被成功发送到RabbitMQ服务器（至少是成功到达交换机）的一种机制。当启用这个机制时，RabbitMQ会向生产者（publisher）发送一个确认（ack）或否定确认（nack）来告知消息是否被成功处理。</p><ul><li><strong>ack（确认）</strong>：当消息成功到达交换机时，RabbitMQ会向生产者发送一个ack，表示消息已经被正确处理。</li><li><strong>nack（否定确认）</strong>：如果RabbitMQ因为内部错误（如资源不足）而无法处理消息，它会发送一个nack给生产者。但需要注意的是，在标准的RabbitMQ行为中，如果消息因为路由键不匹配任何队列而被交换机丢弃，RabbitMQ<strong>不会</strong>发送nack，而是可能通过<code>publisher return</code>机制来处理。</li></ul><h4 id="Publisher-Return（发送者回执）"><a href="#Publisher-Return（发送者回执）" class="headerlink" title="Publisher Return（发送者回执）"></a>Publisher Return（发送者回执）</h4><p><code>Publisher Return</code>是RabbitMQ提供的另一种机制，用于处理那些无法路由到任何队列的消息。当消息被交换机接收，但由于没有匹配的绑定（即没有队列订阅了消息所使用的路由键）而无法投递到任何队列时，RabbitMQ会将这个消息返回给生产者，并附带一个路由失败的原因。</p><ul><li><strong>返回消息</strong>：消息本身会被返回给生产者，同时还会附带一个<code>Basic.Return</code>命令，其中包含路由失败的原因（如路由键未找到匹配的队列）。</li><li><strong>注意</strong>：这里重要的是要理解，<code>publisher return</code>并不与<code>ack</code>或<code>nack</code>直接相关。<code>ack</code>&#x2F;<code>nack</code>是确认消息是否到达交换机的，而<code>publisher return</code>是处理那些已经到达交换机但无法被路由到任何队列的消息的。</li></ul><h4 id="如何实现"><a href="#如何实现" class="headerlink" title="如何实现"></a>如何实现</h4><p><strong>首先添加配置</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">rabbitmq:</span>    </span><br><span class="line">  <span class="attr">publisher-confirm-type:</span> <span class="string">correlated</span> </span><br><span class="line">  <span class="attr">publisher-returns:</span> <span class="literal">true</span>     </span><br><span class="line">  <span class="attr">template:</span>      </span><br><span class="line">    <span class="attr">mandatory:</span> <span class="literal">true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>配置说明：</p><ul><li>publish-confirm-type：开启publisher-confirm，这里支持两种类型：<ul><li>simple：同步等待confirm结果，直到超时</li><li>correlated：异步回调，定义ConfirmCallback，MQ返回结果时会回调这个ConfirmCallback</li></ul></li><li>publish-returns：开启publish-return功能，同样是基于callback机制，不过是定义ReturnCallback</li><li>template.mandatory：定义消息路由失败时的策略。true，则调用ReturnCallback；false：则直接丢弃消息</li></ul><p><strong>配置这两方法</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CommonConfig</span> <span class="keyword">implements</span> <span class="title class_">ApplicationContextAware</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setApplicationContext</span><span class="params">(ApplicationContext applicationContext)</span> <span class="keyword">throws</span> BeansException &#123;</span><br><span class="line">        <span class="comment">// 获取RabbitTemplate</span></span><br><span class="line">        <span class="type">RabbitTemplate</span> <span class="variable">rabbitTemplate</span> <span class="operator">=</span> applicationContext.getBean(RabbitTemplate.class);</span><br><span class="line">        <span class="comment">//设置confirmCallback  判断是否到达交换机</span></span><br><span class="line">        rabbitTemplate.setConfirmCallback(<span class="keyword">new</span> <span class="title class_">RabbitTemplate</span>.ConfirmCallback() &#123;</span><br><span class="line">            <span class="comment">/**</span></span><br><span class="line"><span class="comment">             * <span class="doctag">@param</span> correlationData  自定义的数据</span></span><br><span class="line"><span class="comment">             * <span class="doctag">@param</span> ack  是否确认</span></span><br><span class="line"><span class="comment">             * <span class="doctag">@param</span> cause  原因</span></span><br><span class="line"><span class="comment">             */</span></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">confirm</span><span class="params">(CorrelationData correlationData, <span class="type">boolean</span> ack, String cause)</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span>(ack)&#123;</span><br><span class="line">                        <span class="comment">// 3.1.ack，消息成功</span></span><br><span class="line">                        log.debug(<span class="string">&quot;消息发送成功, ID:&#123;&#125;&quot;</span>, correlationData.getId());</span><br><span class="line">                    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                        <span class="comment">// 3.2.nack，消息失败</span></span><br><span class="line">                        log.error(<span class="string">&quot;消息发送失败, ID:&#123;&#125;, 原因&#123;&#125;&quot;</span>,correlationData.getId(), cause);</span><br><span class="line">                    &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 设置ReturnCallback  处理无法路由到队列的消息</span></span><br><span class="line">        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -&gt; &#123;</span><br><span class="line">            <span class="comment">// 投递失败，记录日志</span></span><br><span class="line">            log.info(<span class="string">&quot;消息发送失败，应答码&#123;&#125;，原因&#123;&#125;，交换机&#123;&#125;，路由键&#123;&#125;,消息&#123;&#125;&quot;</span>,</span><br><span class="line">                    replyCode, replyText, exchange, routingKey, message.toString());</span><br><span class="line">            <span class="comment">// 如果有业务需要，可以重发消息</span></span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="消息持久化"><a href="#消息持久化" class="headerlink" title="消息持久化"></a>消息持久化</h3><p>在写代码过程中，交换机，队列和消息都是默认持久化的，不需要做额外的操作，这里为了更好的理解，将显示调用如何持久化，在实际生产过程中，一下步骤并不需要做</p><p><strong>交换机持久化</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> DirectExchange <span class="title function_">simpleExchange</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">// 三个参数：交换机名称、是否持久化、当没有queue与其绑定时是否自动删除</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DirectExchange</span>(<span class="string">&quot;simple.direct&quot;</span>, <span class="literal">true</span>, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>队列持久化</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line">   <span class="keyword">public</span> Queue <span class="title function_">simpleQueue</span><span class="params">()</span>&#123;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Queue</span>(<span class="string">&quot;simple.queue&quot;</span>,<span class="literal">true</span>);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>消息持久化</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> MessageBuilder</span><br><span class="line">    .withBody(message.getBytes(StandardCharsets.UTF_8)) <span class="comment">// 消息体   </span></span><br><span class="line">    .setDeliveryMode(MessageDeliveryMode.PERSISTENT) <span class="comment">// 持久化         </span></span><br><span class="line">    .build();</span><br></pre></td></tr></table></figure><h3 id="消费者消息确认"><a href="#消费者消息确认" class="headerlink" title="消费者消息确认"></a>消费者消息确认</h3><p>RabbitMQ支持消费者确认机制，即：消费者处理消息后可以向MQ发送ack回执，MQ收到ack回执后才会删除该消息。而SpringAMQP则允许配置三种确认模式：</p><p>•manual：手动ack，需要在业务代码结束后，调用api发送ack。</p><p>•auto：自动ack，由spring监测listener代码是否出现异常，没有异常则返回ack；抛出异常则返回nack</p><p>•none：关闭ack，MQ假定消费者获取消息后会成功处理，因此消息投递后立即被删除，此种较为危险，如果消费者获取消息后，在处理消息的过程中发生异常，那么此时消息以及被删除，即消息丢失</p><p><strong>配置方式</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">rabbitmq:</span></span><br><span class="line">    <span class="attr">listener:</span></span><br><span class="line">      <span class="attr">simple:</span></span><br><span class="line">        <span class="attr">acknowledge-mode:</span> <span class="string">none</span> <span class="comment"># 关闭ack</span></span><br></pre></td></tr></table></figure><p>在auto方法中，如果消费者消费消息时出现了异常，消息并不会丢失，而是会重新回到队列中再次发消息，知道消息能够接收成功，所以这里就需要搭配上消息重试机制，否则消息就会一直无限次被发往交换机，对服务器的压力会很大</p><h3 id="失败重试机制"><a href="#失败重试机制" class="headerlink" title="失败重试机制"></a>失败重试机制</h3><h4 id="开启失败重试"><a href="#开启失败重试" class="headerlink" title="开启失败重试"></a>开启失败重试</h4><p>我们可以利用Spring的retry机制，在消费者出现异常时利用本地重试，而不是无限制的requeue到mq队列。</p><p>修改consumer服务的application.yml文件，添加内容：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">rabbitmq:</span></span><br><span class="line">    <span class="attr">listener:</span></span><br><span class="line">      <span class="attr">simple:</span></span><br><span class="line">        <span class="attr">retry:</span></span><br><span class="line">          <span class="attr">enabled:</span> <span class="literal">true</span> <span class="comment"># 开启消费者失败重试</span></span><br><span class="line">          <span class="attr">initial-interval:</span> <span class="string">1000ms</span> <span class="comment"># 初识的失败等待时长为1秒</span></span><br><span class="line">          <span class="attr">multiplier:</span> <span class="number">1</span> <span class="comment"># 失败的等待时长倍数，下次等待时长 = multiplier * last-interval</span></span><br><span class="line">          <span class="attr">max-attempts:</span> <span class="number">3</span> <span class="comment"># 最大重试次数</span></span><br><span class="line">          <span class="attr">stateless:</span> <span class="literal">true</span> <span class="comment"># true无状态；false有状态。如果业务中包含事务，这里改为false</span></span><br></pre></td></tr></table></figure><p>重启consumer服务，重复之前的测试。可以发现：</p><ul><li>在重试3次后，SpringAMQP会抛出异常AmqpRejectAndDontRequeueException，说明本地重试触发了</li><li>查看RabbitMQ控制台，发现消息被删除了，说明最后SpringAMQP返回的是ack，mq删除消息了</li></ul><p>结论：</p><ul><li>开启本地重试时，消息处理过程中抛出异常，不会requeue到队列，而是在消费者本地重试</li><li>重试达到最大次数后，Spring会返回ack，消息会被丢弃</li></ul><p>在以上重试机制下，如果到达了最大重试次数，消息依然会丢失，所以我们要根据失败重试策略来处理</p><h4 id="失败消息处理策略"><a href="#失败消息处理策略" class="headerlink" title="失败消息处理策略"></a>失败消息处理策略</h4><p>在开启重试模式后，重试次数耗尽，如果消息依然失败，则需要有MessageRecovery接口来处理，它包含三种不同的实现：</p><ul><li><p>RejectAndDontRequeueRecoverer：重试耗尽后，直接reject，丢弃消息。默认就是这种方式</p></li><li><p>ImmediateRequeueMessageRecoverer：重试耗尽后，返回nack，消息重新入队</p></li><li><p>RepublishMessageRecoverer：重试耗尽后，将失败消息投递到指定的交换机</p></li></ul><h2 id="死信交换机"><a href="#死信交换机" class="headerlink" title="死信交换机"></a>死信交换机</h2><p>当一个队列中的消息满足下列情况之一时，可以成为死信（dead letter）：</p><ul><li>消费者使用basic.reject或 basic.nack声明消费失败，并且消息的requeue参数设置为false</li><li>消息是一个过期消息，超时无人消费</li><li>要投递的队列消息满了，无法投递</li></ul><p>如果这个包含死信的队列配置了<code>dead-letter-exchange</code>属性，指定了一个交换机，那么队列中的死信就会投递到这个交换机中，而这个交换机称为<strong>死信交换机</strong>（Dead Letter Exchange，检查DLX）。使用死信交换机需要将队列上绑定<code>deadLetterExchange</code>和<code>deadLetterRoutingKey</code>，根据这里我们可以知道，他的效果与全局配置失败消息处理策略差不多，下面我将对比两者的优劣。</p><h3 id="死信交换机的劣势和优势"><a href="#死信交换机的劣势和优势" class="headerlink" title="死信交换机的劣势和优势"></a>死信交换机的劣势和优势</h3><h4 id="劣势"><a href="#劣势" class="headerlink" title="劣势"></a>劣势</h4><ol><li><strong>配置复杂性</strong>：使用死信交换机需要在队列创建时额外配置<code>dead-letter-exchange</code>和<code>dead-letter-routing-key</code>属性，增加了配置的复杂性。</li><li><strong>性能开销</strong>：当消息被发送到死信交换机时，需要经过额外的路由和存储过程，可能会对系统性能造成一定影响。</li><li><strong>管理复杂性</strong>：需要管理额外的死信队列和交换机，增加了系统的管理复杂性。</li></ol><h4 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h4><ol><li><strong>灵活性</strong>：可以为不同的队列设置不同的死信交换机和死信队列，提供了更高的灵活性。</li><li><strong>可追踪性</strong>：所有发送失败的消息都会被收集到死信队列中，便于追踪和定位问题。</li><li><strong>可扩展性</strong>：可以根据需要增加死信队列的数量和容量，以应对更高的失败消息量。</li></ol><h3 id="配置消息失败处理策略的优势和劣势"><a href="#配置消息失败处理策略的优势和劣势" class="headerlink" title="配置消息失败处理策略的优势和劣势"></a>配置消息失败处理策略的优势和劣势</h3><h4 id="劣势-1"><a href="#劣势-1" class="headerlink" title="劣势"></a>劣势</h4><ol><li><strong>全局性</strong>：该策略是全局性的，可能无法针对特定队列或消息类型进行细粒度的控制。</li><li><strong>局限性</strong>：只能实现简单的重试逻辑，无法像死信交换机那样提供多种失败消息处理策略。</li><li><strong>缺乏灵活性</strong>：无法根据消息的失败原因或类型来动态选择处理策略。</li></ol><h4 id="优势-1"><a href="#优势-1" class="headerlink" title="优势"></a>优势</h4><ol><li><strong>配置简单</strong>：只需在全局范围内配置一次，即可应用于所有相关的消息处理。</li><li><strong>易于实现</strong>：通过实现<code>MessageRecovery</code>接口，可以轻松地定义失败消息的重试逻辑。</li><li><strong>性能影响小</strong>：由于不需要额外的队列和交换机，对系统性能的影响相对较小。</li></ol><h3 id="综合比较"><a href="#综合比较" class="headerlink" title="综合比较"></a>综合比较</h3><table><thead><tr><th></th><th>死信交换机</th><th>消息失败处理策略</th></tr></thead><tbody><tr><td><strong>配置复杂性</strong></td><td>高（需要额外配置死信队列和交换机）</td><td>低（全局配置）</td></tr><tr><td><strong>性能开销</strong></td><td>较高（涉及额外的路由和存储）</td><td>较低（仅涉及重试逻辑）</td></tr><tr><td><strong>管理复杂性</strong></td><td>高（需要管理额外的队列和交换机）</td><td>低（全局管理）</td></tr><tr><td><strong>灵活性</strong></td><td>高（可以针对特定队列或消息类型设置）</td><td>低（全局性策略）</td></tr><tr><td><strong>可追踪性</strong></td><td>高（所有失败消息都被收集到死信队列中）</td><td>较低（依赖于日志或其他追踪机制）</td></tr><tr><td><strong>可扩展性</strong></td><td>高（可以增加死信队列数量和容量）</td><td>较低（受限于全局配置）</td></tr></tbody></table><h3 id="延迟消息"><a href="#延迟消息" class="headerlink" title="延迟消息"></a>延迟消息</h3><p>通过死信交换机我们可以达到延迟队列的效果，只需要为需要延迟发放消息的队列设置过期之间或者为消息设置过期时间，并且不给这个队列绑定消费者，到时间过期后，消息就会进入死信交换机，我们将要处理消息的消费者绑定到死信队列上，就可以达到延迟消息的功能，不过rabbitMQ现在推出的有专门做延迟消息的方法，所以这种方法不经常使用</p><p><strong>使用 RabbitMQ 插件实现延迟队列</strong></p><p>RabbitMQ 社区提供了一个非常方便的插件 <code>rabbitmq-delayed-message-exchange</code>，它允许你直接在交换机级别设置消息的延迟时间，而无需使用死信队列的复杂设置。使用这种方法，你可以：</p><ol><li><strong>安装插件</strong>：首先，你需要在 RabbitMQ 服务器上安装 <code>rabbitmq-delayed-message-exchange</code> 插件。</li><li><strong>定义延迟交换机</strong>：在 RabbitMQ 管理界面或通过 API 定义一个类型为 <code>x-delayed-message</code> 的交换机。</li><li><strong>发送延迟消息</strong>：当发送消息到该交换机时，你可以在消息的头部（header）中指定延迟时间（以毫秒为单位）。</li><li><strong>绑定队列</strong>：像普通交换机一样，将队列绑定到这个延迟交换机上。</li><li><strong>消费者监听</strong>：消费者监听这个队列，当消息的延迟时间到达后，消息将被发送到队列中，消费者可以像处理普通消息一样处理它。</li></ol><h2 id="惰性队列"><a href="#惰性队列" class="headerlink" title="惰性队列"></a>惰性队列</h2><h3 id="消息堆积问题"><a href="#消息堆积问题" class="headerlink" title="消息堆积问题"></a>消息堆积问题</h3><p>当生产者发送消息的速度超过了消费者处理消息的速度，就会导致队列中的消息堆积，直到队列存储消息达到上限。之后发送的消息就会成为死信，可能会被丢弃，这就是消息堆积问题。</p><p>解决消息堆积有三种思路：</p><ul><li>增加更多消费者，提高消费速度。也就是我们之前说的work queue模式</li><li>扩大队列容积，提高堆积上限</li><li>在消费者内开启线程池加快消息处理速度</li></ul><p>根据第二条思路，我们提出了一种解决办法就是引入惰性队列</p><h3 id="引入惰性队列"><a href="#引入惰性队列" class="headerlink" title="引入惰性队列"></a>引入惰性队列</h3><p>从RabbitMQ的3.6.0版本开始，就增加了Lazy Queues的概念，也就是惰性队列。惰性队列的特征如下：</p><ul><li>接收到消息后直接存入磁盘而非内存</li><li>消费者要消费消息时才会从磁盘中读取并加载到内存</li><li>支持数百万条的消息存储</li></ul><p>由于消息存入的时磁盘，所以消息可存储的量大大提高了，并且普通队列可能会因为内存中的消息过多而需要定期将部分消息写入磁盘以释放内存空间，这个过程称为page-out。而惰性队列由于直接写入磁盘，避免了这种间歇性的page-out操作，从而提高了系统的稳定性。</p><h4 id="声明惰性队列"><a href="#声明惰性队列" class="headerlink" title="声明惰性队列"></a>声明惰性队列</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 惰性队列</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> Queue <span class="title function_">lazyQueue</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> QueueBuilder.durable(<span class="string">&quot;lazy.queue&quot;</span>)</span><br><span class="line">            .lazy()</span><br><span class="line">            .build();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 普通队列</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> Queue <span class="title function_">normalQueue</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> QueueBuilder.durable(<span class="string">&quot;normal.queue&quot;</span>)</span><br><span class="line">            .build();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不过惰性队列也有缺陷，例如性能受限于磁盘IO，基于磁盘存储，消息时效性也会降低</p><h2 id="集群"><a href="#集群" class="headerlink" title="集群"></a>集群</h2><p>在面对高并发的问题中，我们通常会选择采用集群来解决，在RabbitMQ中的集群有两种模式</p><ul><li><p><strong>普通集群</strong>：是一种分布式集群，将队列分散到集群的各个节点，从而提高整个集群的并发能力。</p></li><li><p><strong>镜像集群</strong>：是一种主从集群，普通集群的基础上，添加了主从备份功能，提高集群的数据可用性。</p></li></ul><p>镜像集群虽然支持主从，但主从同步并不是强一致的，某些情况下可能有数据丢失的风险。因此在RabbitMQ的3.8版本以后，推出了新的功能：<strong>仲裁队列</strong>来代替镜像集群，底层采用Raft协议确保主从的数据一致性。</p><h3 id="普通集群"><a href="#普通集群" class="headerlink" title="普通集群"></a>普通集群</h3><p><strong>优势</strong>：</p><ol><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>：管理多个节点和确保它们之间的正确通信需要复杂的配置和维护工作。这可能会增加运维成本和难度。</li></ol><h3 id="镜像集群"><a href="#镜像集群" class="headerlink" title="镜像集群"></a>镜像集群</h3><p><strong>优势</strong>：</p><ol><li><strong>高可用性</strong>：镜像集群通过在不同节点上创建队列的镜像来提供高可用性。即使某个节点出现故障，其他节点上的镜像队列仍然可以继续处理消息，确保服务的连续性。</li><li><strong>数据可靠性</strong>：镜像集群通过同步队列的元数据和消息到多个节点，提高了数据的可靠性。即使某个节点上的数据丢失，也可以从其他节点上的副本中恢复。</li></ol><p><strong>劣势</strong>：</p><ol><li><strong>性能开销</strong>：由于需要在多个节点之间同步队列的元数据和消息，镜像集群可能会引入额外的性能开销。这可能会影响系统的响应速度和吞吐量。</li><li><strong>无法线性扩容</strong>：镜像集群中的每个节点都包含整个集群的数据副本，这限制了集群的线性扩容能力。当单个节点的存储容量达到上限时，可能需要采取其他措施（如增加单个节点的存储容量或重新设计集群架构）来扩展集群。</li></ol><h3 id="仲裁队列"><a href="#仲裁队列" class="headerlink" title="仲裁队列"></a>仲裁队列</h3><p><strong>优势</strong>：</p><ol><li><strong>高一致性</strong>：仲裁队列基于Raft共识算法或其变种，确保了数据在多个节点之间的一致性。这种一致性是强一致的，有助于减少数据丢失的风险。</li><li><strong>非阻塞复制</strong>：仲裁队列在复制数据时不会阻塞队列的其他操作，这有助于提高系统的可用性和性能。即使某个节点出现故障并重新上线，主副本也可以从从副本中断的地方开始复制消息，而无需同步整个队列的数据。</li></ol><p><strong>劣势</strong>：</p><ol><li><strong>资源消耗</strong>：仲裁队列需要消耗更多的内存和磁盘资源来存储数据的多个副本。这可能会增加系统的运行成本。</li><li><strong>适用场景限制</strong>：仲裁队列适用于对队列容错和数据安全要求高、对延迟和队列特性要求相对低的场景。在可能出现消息大量堆积的场景中，仲裁队列的写入放大会造成成倍的磁盘占用，因此可能不适合使用。</li></ol>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/08/05/MQ%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>微服务中的redis</title>
      <link>https://www.yhblog.xyz/2024/08/04/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%AD%E7%9A%84redis/</link>
      <guid>https://www.yhblog.xyz/2024/08/04/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%AD%E7%9A%84redis/</guid>
      <pubDate>Sun, 04 Aug 2024 05:14:15 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;Redis面临的的问题&quot;&gt;&lt;a href=&quot;#Redis面临的的问题&quot; class=&quot;headerlink&quot; title=&quot;Redis面临的的问题&quot;&gt;&lt;/a&gt;Redis面临的的问题&lt;/h2&gt;&lt;p&gt;在单点的redis中，redis经常面临着数据丢失，并发能力弱，故障</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="Redis面临的的问题"><a href="#Redis面临的的问题" class="headerlink" title="Redis面临的的问题"></a>Redis面临的的问题</h2><p>在单点的redis中，redis经常面临着数据丢失，并发能力弱，故障恢复能力弱，以及存储能力弱，这都使其在微服务的架构中难以立足，接下来，我将以这四方面的问题出发，介绍每一种问题的解决办法</p><h2 id="Redis持久化"><a href="#Redis持久化" class="headerlink" title="Redis持久化"></a>Redis持久化</h2><p>由于redis时基于内存存储，这会导致一旦服务器重启，存储在服务器中redis的数据将会丢失，即使redis中存储的一般是相对与不那么重要的热点数据，但一旦丢失，仍会造成很大的影响，解决这个问题自然要做到redis数据的持久化，常见的持久化分为两种，分别是RDB和AOF</p><h3 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a>RDB持久化</h3><p>RDB持久化通过创建Redis在某一时间点的快照（snapshot）来实现数据的持久化。这个快照文件是一个经过压缩的二进制文件，包含了Redis在某个时间点上的数据库状态。简单来说就是把内存中所有数据记录到磁盘中，当redis重启后，从磁盘中读取快照文件，恢复数据</p><h4 id="RDB持久化的工作原理"><a href="#RDB持久化的工作原理" class="headerlink" title="RDB持久化的工作原理"></a>RDB持久化的工作原理</h4><ol><li><strong>触发条件</strong>：RDB持久化可以通过两种方式来触发：<ul><li><strong>手动触发</strong>：通过执行<code>SAVE</code>或<code>BGSAVE</code>命令。<code>SAVE</code>命令会阻塞Redis服务器进程，直到RDB文件创建完毕为止，因此不推荐在生产环境中使用。而<code>BGSAVE</code>命令会创建一个子进程来负责创建RDB文件，这样Redis服务器进程可以继续处理客户端请求，不会造成阻塞。</li><li><strong>自动触发</strong>：Redis配置文件中可以设置一些条件，当满足这些条件时，Redis会自动执行<code>BGSAVE</code>命令。例如，可以设置“在一定时间内，如果修改了指定数量的键，则自动执行BGSAVE”，在停机前也会自动触发一次快照。</li></ul></li><li><strong>快照生成</strong>：当<code>BGSAVE</code>命令执行时，Redis会创建一个子进程，这个子进程会遍历当前数据库中的所有键值对，并将它们写入到一个临时文件中。当所有键值对都写入完成后，这个临时文件会被替换为旧的RDB文件，从而实现数据的持久化。</li><li><strong>数据恢复</strong>：当Redis服务器启动时，如果检测到RDB文件存在，Redis会自动加载RDB文件中的数据到内存中，从而实现数据的恢复。</li></ol><h4 id="RDB持久化的优缺点"><a href="#RDB持久化的优缺点" class="headerlink" title="RDB持久化的优缺点"></a>RDB持久化的优缺点</h4><p><strong>优点</strong>：</p><ul><li>RDB文件是一个紧凑的二进制文件，它保存了Redis在某个时间点的完整数据快照，非常适合用于数据备份和灾难恢复。</li><li>RDB文件的生成和加载速度都比较快，因此可以在较短的时间内完成数据的备份和恢复。</li><li>RDB持久化对Redis服务器的性能影响较小，尤其是在使用<code>BGSAVE</code>命令时。</li></ul><p><strong>缺点</strong>：</p><ul><li>RDB持久化是基于快照的，因此它可能会丢失最后一次快照之后到Redis停机之间的数据。</li><li>如果Redis中的数据量非常大，那么RDB文件的生成可能会消耗较多的CPU和内存资源，并且生成RDB文件的时间也会比较长。</li></ul><h4 id="COW-Copy-On-Write-写时复制"><a href="#COW-Copy-On-Write-写时复制" class="headerlink" title="COW(Copy-On-Write)写时复制"></a>COW(Copy-On-Write)写时复制</h4><p>在Redis的RDB持久化过程中，COW技术主要通过操作系统的fork系统调用来实现。具体过程如下：</p><ol><li>fork子进程：<ul><li>当Redis执行bgsave命令时，会调用操作系统的fork()函数来创建一个子进程。这个子进程是父进程（即Redis主进程）的一个完整副本，包括内存空间、文件描述符等。</li><li>fork操作完成后，子进程和父进程共享相同的内存空间，但此时内存空间是被标记为只读的。</li></ul></li><li>写时复制：<ul><li>在子进程开始执行RDB持久化任务（即将内存数据写入磁盘文件）的过程中，如果父进程（Redis主进程）需要修改内存中的数据，操作系统会检测到这种写操作，并触发COW机制。</li><li>触发COW机制后，操作系统会复制被修改的内存页到一个新的位置，并将父进程的内存页指针指向这个新位置。这样，父进程就可以在不影响子进程的情况下继续执行写操作。</li><li>子进程则继续在其原始的内存页（只读）上执行RDB持久化任务，直到完成。</li></ul></li><li>RDB文件生成：<ul><li>子进程完成所有内存数据的遍历和写入后，会生成一个完整的RDB文件，并将其保存到磁盘上。</li><li>父进程（Redis主进程）则继续处理客户端的请求，不受子进程持久化操作的影响。</li></ul></li></ol><h3 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a>AOF持久化</h3><p>AOF（Append Only File）持久化是Redis数据库中的一种数据持久化方式，主要通过保存数据库执行的写命令来记录数据库的状态。以下是对AOF持久化的详细介绍：</p><h4 id="AOF持久化的基本原理"><a href="#AOF持久化的基本原理" class="headerlink" title="AOF持久化的基本原理"></a>AOF持久化的基本原理</h4><p>AOF持久化以日志的形式记录每个写操作，包括数据的增加、删除、修改等，但不记录读操作。Redis服务重启时，会根据AOF文件中的命令重新执行一遍，以达到恢复数据的目的。AOF文件是一个仅追加的日志文件，即使在写入过程中发生宕机，也不会破坏已存在的内容。</p><h4 id="AOF持久化的流程"><a href="#AOF持久化的流程" class="headerlink" title="AOF持久化的流程"></a>AOF持久化的流程</h4><p>AOF持久化主要分为以下几个步骤：</p><ol><li>命令追加：<ul><li>当AOF持久化功能处于开启状态时，服务器在执行完一个写命令后，会将该命令以协议格式追加到AOF文件数据缓冲区（aof_buf）中。</li></ul></li><li>文件写入：<ul><li>在每一个事件循环结束前，Redis会调用flushAppendOnlyFile函数，将aof_buf中的内容写入到AOF文件中。</li></ul></li><li>文件同步：<ul><li>AOF文件会根据appendfsync参数设置的持久化策略同步到磁盘。Redis提供了三种同步策略：<ul><li><strong>Always</strong>：每次写命令写入aof_buf后，都同步将aof_buf中的写命令fsync到磁盘。这种方式可靠性最高，但性能影响最大。</li><li><strong>Everysec</strong>：每次写命令写入aof_buf后，每隔一秒将aof_buf中的写命令fsync到磁盘。这是默认策略，性能适中，最多丢失一秒的数据。</li><li><strong>No</strong>：写命令写入aof_buf后，由操作系统决定何时将aof_buf中的写命令fsync到磁盘。这种方式性能最好，但宕机时丢失的数据最多。</li></ul></li></ul></li></ol><h4 id="AOF文件重写"><a href="#AOF文件重写" class="headerlink" title="AOF文件重写"></a>AOF文件重写</h4><p>随着写入AOF内容的增加，AOF文件会变得越来越大，这不仅会占用大量的磁盘空间，还会增加Redis服务重启时的数据恢复时间。为了解决这个问题，Redis提供了AOF文件重写机制。</p><p>AOF文件重写的触发机制有两种：</p><ol><li><strong>手动触发</strong>：通过执行BGREWRITEAOF命令。</li><li><strong>自动触发</strong>：与auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数有关。当AOF文件大小超过上一次重写后AOF文件大小的指定百分比（默认为100%）且AOF文件大小大于指定最小值（默认为64MB）时，会自动触发重写。</li></ol><p>重写过程中，Redis会fork出一个子进程来处理重写操作，父进程继续处理其他客户端命令请求。子进程根据内存中的当前数据状态，生成一个新的AOF文件，并替换掉旧的AOF文件。</p><h4 id="AOF持久化的优缺点"><a href="#AOF持久化的优缺点" class="headerlink" title="AOF持久化的优缺点"></a>AOF持久化的优缺点</h4><p>优点：</p><ol><li><strong>数据安全性高</strong>：AOF持久化提供了多种同步策略，可以根据需要选择最适合的同步方式，确保数据的可靠性。</li><li><strong>易于恢复</strong>：AOF文件内容易于理解，方便进行手动修复和恢复。</li><li><strong>灵活性高</strong>：AOF文件是追加写入的，即使出现写入故障，也不会破坏已存在的数据。</li></ol><p>缺点：</p><ol><li><strong>文件体积大</strong>：AOF文件通常比相同数据集的RDB文件大，占用更多的磁盘空间。</li><li><strong>恢复速度慢</strong>：当AOF文件很大时，Redis服务重启时的数据恢复时间会比较长。</li><li><strong>性能影响</strong>：AOF持久化对性能有一定的影响，尤其是在使用Always同步策略时。</li></ol><h3 id="两种持久化的对比"><a href="#两种持久化的对比" class="headerlink" title="两种持久化的对比"></a>两种持久化的对比</h3><table><thead><tr><th></th><th>RDB</th><th>AOF</th></tr></thead><tbody><tr><td>持久化方式</td><td>定时对整个内存做快照</td><td>记录每一次执行的命令</td></tr><tr><td>数据完整性</td><td>不完整，两次备份之间会有丢失</td><td>相对完整，取决于刷盘策略</td></tr><tr><td>文件大小</td><td>会有压缩，文件体积小</td><td>记录命令，文件体积很大</td></tr><tr><td>宕机恢复速度</td><td>很快</td><td>满</td></tr><tr><td>数据恢复优先级</td><td>低，因为数据完整性不如AOF</td><td>高，因为数据完整性更高</td></tr><tr><td>系统资源占用</td><td>高，大量CPU和内存消耗</td><td>低，主要时磁盘IO资源 但AOF重写会占用大量CPU和内存资源</td></tr><tr><td>使用场景</td><td>可以容忍数分钟的数据丢失，追求更快的启动速度</td><td>对数据安全性要求较高</td></tr></tbody></table><h2 id="Redis主从集群"><a href="#Redis主从集群" class="headerlink" title="Redis主从集群"></a>Redis主从集群</h2><p>Redis通过搭建主从集群，主节点负责写操作，将数据同步给从节点，从节点负责读操作，通过这样的读写分离，可显著提高redis处理高并发问题的能力</p><h3 id="主从集群的工作原理"><a href="#主从集群的工作原理" class="headerlink" title="主从集群的工作原理"></a>主从集群的工作原理</h3><ol><li>数据复制：<ul><li>当主节点接收到写操作时，它会先将数据写入本地，并将写操作记录到复制缓冲区中。</li><li>从节点通过定期发送SYNC命令或PSYNC命令向主节点请求数据同步。</li><li>主节点在接收到同步请求后，会将复制缓冲区中的数据发送给从节点，从而实现数据的同步。</li></ul></li><li>读写分离：<ul><li>客户端通过配置或负载均衡器将写请求发送到主节点，将读请求发送到从节点。</li><li>这种方式可以显著提高系统的吞吐量和响应速度，因为读操作通常比写操作更频繁且对实时性要求较低。</li></ul></li><li>故障转移：<ul><li>在主节点出现故障时，从节点可以通过选举机制或外部管理工具（如Redis Sentinel）自动或手动升级为主节点，以接管写操作。</li><li>同时，其他从节点会重新与新的主节点建立同步关系，确保数据的一致性和可用性。</li></ul></li></ol><h3 id="配置主节点"><a href="#配置主节点" class="headerlink" title="配置主节点"></a>配置主节点</h3><ol><li><p>修改Redis配置文件</p><p>（例如，</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis.conf</span><br></pre></td></tr></table></figure><p>）：</p><ul><li>设置<code>bind</code>指令以监听所有网段（如果需要远程访问）或特定IP地址。</li><li>开启守护进程模式（<code>daemonize yes</code>）。</li><li>配置日志文件和工作目录。</li><li>开启AOF或RDB持久化功能（根据需要）。</li></ul></li><li><p>重启Redis服务：</p><ul><li>应用配置文件更改后，重启Redis服务以使更改生效。</li></ul></li></ol><h3 id="配置从节点"><a href="#配置从节点" class="headerlink" title="配置从节点"></a>配置从节点</h3><ol><li>复制Redis配置文件：<ul><li>为每个从节点复制一份Redis配置文件，并进行适当的修改。</li></ul></li><li>修改从节点配置文件：<ul><li>设置不同的端口号以避免冲突（例如，6380、6381等）。</li><li>配置<code>replicaof</code>（在Redis 5.0之前为<code>slaveof</code>）指令，指定主节点的IP地址和端口号。</li><li>配置其他必要的参数，如持久化、日志记录等。</li></ul></li><li>启动从节点Redis服务：<ul><li>使用修改后的配置文件启动从节点的Redis服务。</li></ul></li></ol><h3 id="主从集群的第一次数据同步"><a href="#主从集群的第一次数据同步" class="headerlink" title="主从集群的第一次数据同步"></a>主从集群的第一次数据同步</h3><p> <strong>建立连接与协商</strong></p><ul><li><strong>执行命令</strong>：在从节点上执行<code>replicaof</code>（Redis 5.0之前使用<code>slaveof</code>）命令，指定主节点的IP地址和端口号。</li><li><strong>发送请求</strong>：从节点向主节点发送数据同步请求。</li><li><strong>判断首次同步</strong>：主节点接收到同步请求后，会判断这是否是第一次同步。判断依据通常是从节点发送的replication ID（简称replid）与主节点不一致，如果是第一次同步，则拒绝增量同步而是用全量同步，通过偏移量（offset）判断是否需要同步数据，在判断是第一次同步后，主节点会将自己的replid和offset传给从节点，让从节点继承他的replid。</li></ul><p> <strong>准备数据快照</strong></p><ul><li><strong>生成RDB文件</strong>：主节点执行<code>bgsave</code>命令，在后台生成当前数据库的快照（RDB文件）。这个过程中，主节点仍然可以正常处理客户端的请求。</li><li><strong>记录新命令</strong>：在生成RDB文件的过程中，主节点会将新接收到的写操作命令记录在<code>repl_backlog</code>缓冲区中，以确保这些操作在后续能够同步给从节点。</li></ul><p> <strong>发送RDB文件</strong></p><ul><li><strong>传输文件</strong>：RDB文件生成后，主节点会将该文件发送给从节点。</li><li><strong>清空并加载数据</strong>：从节点接收到RDB文件后，会先清空本地数据库，然后加载RDB文件中的数据。</li></ul><p><strong>同步增量数据</strong></p><ul><li><strong>发送repl_backlog中的命令</strong>：在从节点加载完RDB文件后，主节点会将<code>repl_backlog</code>缓冲区中记录的、在RDB文件生成之后接收到的所有写操作命令发送给从节点。</li><li><strong>执行命令</strong>：从节点接收到这些命令后，会重新执行它们，以确保与主节点的数据完全一致。</li></ul><p> <strong>完成同步</strong></p><ul><li><strong>建立持续复制</strong>：完成上述步骤后，主从节点之间会建立起一个持续复制的连接。主节点会将后续接收到的所有写操作命令实时同步给从节点，以保持数据的一致性。</li></ul><h3 id="主从集群的非第一次数据同步"><a href="#主从集群的非第一次数据同步" class="headerlink" title="主从集群的非第一次数据同步"></a>主从集群的非第一次数据同步</h3><ol><li>记录偏移量：<ul><li>主节点和从节点都会维护一个偏移量（offset），用于记录当前复制的位置。</li><li>每次主节点处理完写操作后，都会更新自己的偏移量。</li></ul></li><li>从节点请求同步：<ul><li>从节点会定期（或根据配置）向主节点发送自己的偏移量和复制ID（replid），如果是非第一次同步，则从节点的replid和主节点是一致的，则进行增量同步，请求同步。</li></ul></li><li>主节点判断同步方式：<ul><li>主节点接收到从节点的请求后，会比较从节点的偏移量和自己的偏移量。</li><li>如果从节点的偏移量小于主节点的偏移量，说明从节点有部分数据未同步，此时将执行增量同步，如果从节点的复制偏移量在<code>repl_backlog</code>中找不到对应的数据（即该偏移量对应的数据已经被覆盖），可能是因为repl_backlog的大小达到上限或是从节点断开太久，环形缓冲区中的数据被覆盖，则此时应该改进全量同步。</li></ul></li><li>发送增量数据：<ul><li>主节点会从自己的<code>repl_backlog</code>缓冲区中，获取从节点偏移量之后的写操作命令。</li><li>将这些增量数据发送给从节点。</li></ul></li><li>从节点执行命令：<ul><li>从节点接收到增量数据后，会按顺序执行这些命令，更新自己的数据集。</li></ul></li><li>更新偏移量：<ul><li>从节点执行完命令后，会更新自己的偏移量，并上报给主节点。</li></ul></li></ol><h3 id="主从集群优化"><a href="#主从集群优化" class="headerlink" title="主从集群优化"></a>主从集群优化</h3><ul><li>在master中配置repl-diskless-sync yes启用无磁盘复制，避免全量同步时的磁盘IO</li><li>Redis单节点上的内存不要占太大，减少RDB导致的过多磁盘IO</li><li>适当提高repl_backlog的大小，发现slave宕机尽快实现故障修复，尽量避免全量同步</li><li>限制一个matser的slave的节点数量，如果实在太多slave，尽可能采取主-从-从链式结构，减少matser压力</li></ul><h2 id="Redis哨兵集群"><a href="#Redis哨兵集群" class="headerlink" title="Redis哨兵集群"></a>Redis哨兵集群</h2><p>Redis的哨兵集群（Redis Sentinel）是一个高可用的解决方案，它主要用于管理多个Redis服务器实例，提供监控、自动故障转移和通知功能。</p><h3 id="哨兵集群的组成"><a href="#哨兵集群的组成" class="headerlink" title="哨兵集群的组成"></a>哨兵集群的组成</h3><ul><li><strong>哨兵节点</strong>：哨兵集群由多个哨兵节点组成，每个哨兵节点都是一个运行在特殊模式下的Redis进程，但它不提供读写服务，主要用于监控和管理Redis服务器。</li><li><strong>Redis服务器实例</strong>：哨兵集群监控的Redis服务器实例包括主节点（master）和从节点（slave）。主节点负责处理客户端的读写请求，而从节点则复制主节点的数据，并在主节点故障时接替其工作。</li></ul><h3 id="哨兵集群的工作原理"><a href="#哨兵集群的工作原理" class="headerlink" title="哨兵集群的工作原理"></a>哨兵集群的工作原理</h3><ol><li><strong>监控</strong>：哨兵节点会周期性地向所有被监控的Redis服务器实例发送PING命令，以检测它们是否仍然在线运行。如果某个Redis服务器实例在规定时间内没有响应哨兵的PING命令，哨兵就会将其标记为“主观下线”。</li><li><strong>客观下线</strong>：当多个哨兵节点都将同一个Redis服务器实例标记为“主观下线”时，该实例会被标记为“客观下线”，即确认该实例已经故障。</li><li><strong>自动故障转移</strong>：一旦主节点被标记为“客观下线”，哨兵集群就会开始自动故障转移流程。首先，哨兵集群会通过投票选举出哨兵中的matser，由他做下面的操作，然后，哨兵集群会从从节点中选择一个作为新的主节点（选主过程涉及多个规则，如从库优先级、复制进度等）。然后，哨兵会更新所有从节点和新主节点的配置，让它们开始复制新的主节点。最后，哨兵会通知客户端新的主节点地址，以便客户端可以重新连接到Redis服务。</li><li><strong>通知</strong>：在整个故障转移过程中，哨兵节点会通过API向管理员或其他应用程序发送通知，以便及时了解Redis服务的状态变化。</li></ol><h3 id="选举master的规则"><a href="#选举master的规则" class="headerlink" title="选举master的规则"></a>选举master的规则</h3><ul><li>首先会判断slave节点与matser节点断开时间的长短，如果超过指定值(down-after-milliseconds*10)则会排除该slave节点，因为此时该从节点与原主节点的数据严重不匹配</li><li>再判断slave节点的slave-priority值，越小优先级越高，如果时0则永远不参加选举</li><li>如果slave-prority一样，则判断slave节点的offset值，越大说明数据越新，优先级越高</li><li>最后判断slave节点的运行id大小，越小优先级越高</li></ul><h3 id="实现故障转移"><a href="#实现故障转移" class="headerlink" title="实现故障转移"></a>实现故障转移</h3><p>当选举了其中一个slave为新的matser后，会有一下三部</p><ul><li>sentinel给备选的slave节点发送slaveof no one命令，让该节点成为matser</li><li>sentinel给所有其它slave发送slaveof 备选节点的ip 命令，让这些slave成为新matser的从节点，开始从新的matser上同步数据</li><li>最后，sentinel将故障节点标记为slave，当故障节点恢复后会自动成为新的matser的slave节点</li></ul><h3 id="哨兵集群的优势"><a href="#哨兵集群的优势" class="headerlink" title="哨兵集群的优势"></a>哨兵集群的优势</h3><ul><li><strong>高可用性</strong>：哨兵集群可以自动处理Redis主节点的故障，确保Redis服务的高可用性。</li><li><strong>自动故障转移</strong>：无需人工干预，哨兵集群可以自动完成故障转移流程，减少服务中断时间。</li><li><strong>灵活配置</strong>：哨兵集群支持多种配置选项，可以根据实际需求进行灵活配置。</li></ul><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ul><li><strong>节点数量</strong>：为了确保哨兵集群的可靠性和稳定性，建议部署奇数个哨兵节点（如3个、5个等），以满足多数投票机制的需求。</li><li><strong>网络分区</strong>：在部署哨兵集群时，需要考虑网络分区的问题。为了避免因网络分区导致的误判和故障转移，建议将哨兵节点分布在不同的网络区域中。</li><li><strong>监控和日志</strong>：定期监控哨兵集群的运行状态和日志信息，以便及时发现和解决问题。</li></ul><h2 id="Redis分片集群"><a href="#Redis分片集群" class="headerlink" title="Redis分片集群"></a>Redis分片集群</h2><p>以上的集群以经解决了Redis的持久化，高并发能力，故障转移，但由于主节点只有一个，而仅有主节点可以存储数据，所以我们接下来将搭建分片集群，横向扩展主节点，以提高redis的存储能力，在分片集群中有多个主节点，每个主节点都可以存储数据，每个主节点下也会有多个从节点，主节点间通过ping检测彼此健康状态，彼此承担哨兵的的职责，由于主节点间不会做数据的拷贝同步，所以主节点间的数据是不同的，但客户端访问任意节点，最终都会通过心跳转发到正确节点</p><h3 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h3><ol><li><strong>数据分配</strong>：在Redis分片集群中，数据被分为多个片段，每个片段存储在不同的节点上。这些节点可以是物理服务器或虚拟服务器。</li><li><strong>请求处理</strong>：<ul><li>客户端发送命令到Redis分片集群中的任意一个节点。</li><li>节点根据命令涉及的键的哈希值计算出对应的槽号。</li><li>节点根据槽号确定该槽所在的节点，节点之间会通过心跳机制进行重定向，并将命令路由到该节点。</li><li>目标节点处理命令并返回结果给客户端。</li></ul></li><li><strong>数据同步与故障恢复</strong>：<ul><li>主节点与从节点之间通过异步复制进行数据同步。</li><li>当主节点发生故障时，集群会自动进行故障转移，选举一个从节点作为新的主节点，以保证系统的可用性。</li></ul></li></ol><h3 id="优势与特点"><a href="#优势与特点" class="headerlink" title="优势与特点"></a>优势与特点</h3><ol><li><strong>高可用性</strong>：通过数据冗余和自动故障转移机制，即使某个节点发生故障，集群仍然可以对外提供服务。</li><li><strong>高性能</strong>：通过将数据分散到多个节点上，可以实现并行处理，从而提高读写吞吐量和响应速度。</li><li><strong>可伸缩性</strong>：集群的节点数量可以根据需要进行动态调整，以适应不同的负载和存储需求。</li><li><strong>负载均衡</strong>：通过智能路由算法将请求分配到不同的节点上，以实现负载均衡，提高系统整体性能。</li></ol>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/08/04/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%AD%E7%9A%84redis/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>redis缓存问题</title>
      <link>https://www.yhblog.xyz/2024/08/02/redis%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98/</link>
      <guid>https://www.yhblog.xyz/2024/08/02/redis%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98/</guid>
      <pubDate>Fri, 02 Aug 2024 09:18:23 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;缓存击穿&quot;&gt;&lt;a href=&quot;#缓存击穿&quot; class=&quot;headerlink&quot; title=&quot;缓存击穿&quot;&gt;&lt;/a&gt;缓存击穿&lt;/h2&gt;&lt;h3 id=&quot;缓存击穿问题详细介绍&quot;&gt;&lt;a href=&quot;#缓存击穿问题详细介绍&quot; class=&quot;headerlink&quot; titl</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h2><h3 id="缓存击穿问题详细介绍"><a href="#缓存击穿问题详细介绍" class="headerlink" title="缓存击穿问题详细介绍"></a>缓存击穿问题详细介绍</h3><p>缓存击穿是指在高并发场景下，一个或多个请求查询一个不存在的缓存数据（通常是热点数据）可能是由于该缓存数据已过期，由于缓存中没有该数据，导致这些请求直接穿透到数据库，造成数据库的压力过大，进而导致系统性能下降的问题。</p><p>缓存击穿的主要原因包括：</p><ol><li><strong>缓存失效</strong>：缓存中的数据由于过期或被主动删除，导致大量请求无法从缓存中获取数据，从而直接访问数据库。</li><li><strong>并发请求</strong>：在高并发场景下，多个进程或线程同时查询一个不存在的缓存数据，导致多个请求同时访问数据库。</li></ol><p>缓存击穿可能带来的问题包括：</p><ol><li><strong>数据库压力增大</strong>：大量请求直接访问数据库，导致数据库负载增大，性能下降，甚至可能发生宕机。</li><li><strong>响应时间增加</strong>：由于直接访问数据库需要花费更多的时间，因此缓存击穿会导致响应时间增加，降低用户体验。</li></ol><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="热点数据永不过期"><a href="#热点数据永不过期" class="headerlink" title="热点数据永不过期"></a><strong>热点数据永不过期</strong></h4><ul><li><strong>方法描述</strong>：对于频繁访问且重要的热点数据，可以设置其永不过期。同时，通过异步线程定期刷新缓存中的数据，以保持数据的实时性。</li><li><strong>优势</strong>：可以避免因缓存过期而导致的缓存击穿问题，提高系统的稳定性和性能。</li><li><strong>注意事项</strong>：需要确保定期刷新缓存的线程能够稳定运行，并且及时更新缓存中的数据以保持数据的准确性。</li></ul><h4 id="互斥锁"><a href="#互斥锁" class="headerlink" title="互斥锁"></a><strong>互斥锁</strong></h4><ul><li><strong>方法描述</strong>：使用分布式锁或缓存锁机制，保证只有一个请求能够从数据库中加载数据，其他请求等待并使用锁中的数据。当缓存未命中时，请求会尝试获取锁，获取到锁的请求会查询数据库并更新缓存，未获取到锁的请求则等待一段时间后重试。</li><li><strong>实现方式</strong>：可以通过Redis的SETNX命令或其他分布式锁实现方式来实现。</li><li><strong>优势</strong>：可以有效减少数据库的并发访问压力，避免缓存击穿。</li><li><strong>注意事项</strong>：需要合理设置锁的等待时间和重试次数，以避免死锁或过度占用系统资源。</li></ul><h2 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h2><h3 id="缓存雪崩问题介绍"><a href="#缓存雪崩问题介绍" class="headerlink" title="缓存雪崩问题介绍"></a>缓存雪崩问题介绍</h3><p>缓存雪崩是指在高并发情况下，由于大量的缓存数据同时过期或缓存服务宕机，导致大量请求直接穿透到数据库，造成数据库压力过大，甚至引发系统崩溃的现象。缓存雪崩影响范围广泛，影响大于缓存击穿，严重时会导致服务不可用</p><p>缓存雪崩通常是由于以下原因导致的：</p><ol><li><strong>缓存集体过期</strong>：大量缓存数据被设置为同一时间过期，当这些缓存数据同时失效时，大量请求会涌入数据库。</li><li><strong>Redis服务宕机</strong>：Redis作为缓存服务，其稳定性直接影响到整个系统的性能。一旦Redis服务宕机，所有缓存数据将无法访问，所有请求都将直接打到数据库上。</li><li><strong>大量突发请求</strong>：在某些情况下，如秒杀活动、热门事件等，可能会在短时间内产生大量的请求，这些请求如果都未能在缓存中找到数据，就会直接冲击数据库。</li></ol><p>缓存雪崩的影响是灾难性的，它不仅会导致数据库负载过高、响应时间延长，甚至可能引发服务挂掉，影响整个系统的正常运行。</p><h3 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h3><h4 id="差异化设置过期时间"><a href="#差异化设置过期时间" class="headerlink" title="差异化设置过期时间"></a><strong>差异化设置过期时间</strong></h4><ul><li><strong>策略描述</strong>：在设置缓存时，避免将大量数据的过期时间设置为同一时刻。可以通过给每个键的过期时间加上一个随机偏移量，使得数据的过期时间分散开来。</li><li><strong>实施方式</strong>：在代码层面调整缓存的过期时间设置逻辑，确保不同数据的过期时间有所差异。</li><li><strong>效果</strong>：减少缓存同时失效的概率，分散数据库压力。</li></ul><p>自定义<code>RedisCacheManager</code>对有效期时间进行随机设置。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 自定义CacheManager，用于设置不同的过期时间，防止雪崩问题的发生</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyRedisCacheManager</span> <span class="keyword">extends</span> <span class="title class_">RedisCacheManager</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyRedisCacheManager</span><span class="params">(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(cacheWriter, defaultCacheConfiguration);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> RedisCache <span class="title function_">createRedisCache</span><span class="params">(String name, RedisCacheConfiguration cacheConfig)</span> &#123;</span><br><span class="line">        <span class="comment">//获取到原有过期时间</span></span><br><span class="line">        <span class="type">Duration</span> <span class="variable">duration</span> <span class="operator">=</span> cacheConfig.getTtl();</span><br><span class="line">        <span class="keyword">if</span> (ObjectUtil.isNotEmpty(duration)) &#123;</span><br><span class="line">            <span class="comment">//在原有时间上随机增加1~10分钟</span></span><br><span class="line">            <span class="type">Duration</span> <span class="variable">newDuration</span> <span class="operator">=</span> duration.plusMinutes(RandomUtil.randomInt(<span class="number">1</span>, <span class="number">11</span>));</span><br><span class="line">            cacheConfig = cacheConfig.entryTtl(newDuration);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.createRedisCache(name, cacheConfig);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用MyRedisCacheManager</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisCacheManager <span class="title function_">redisCacheManager</span><span class="params">(RedisTemplate redisTemplate)</span> &#123;</span><br><span class="line">        <span class="comment">// 默认配置</span></span><br><span class="line">        <span class="type">RedisCacheConfiguration</span> <span class="variable">defaultCacheConfiguration</span> <span class="operator">=</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">                <span class="comment">// 设置key的序列化方式为字符串</span></span><br><span class="line">                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>()))</span><br><span class="line">                <span class="comment">// 设置value的序列化方式为json格式</span></span><br><span class="line">                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(<span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>()))</span><br><span class="line">                .disableCachingNullValues() <span class="comment">// 不缓存null</span></span><br><span class="line">                .entryTtl(Duration.ofHours(redisTtl));  <span class="comment">// 默认缓存数据保存1小时</span></span><br><span class="line">        <span class="comment">// 构redis缓存管理器</span></span><br><span class="line">        <span class="comment">// RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder</span></span><br><span class="line">        <span class="comment">//         .fromConnectionFactory(redisTemplate.getConnectionFactory())</span></span><br><span class="line">        <span class="comment">//         .cacheDefaults(defaultCacheConfiguration)</span></span><br><span class="line">        <span class="comment">//         .transactionAware()</span></span><br><span class="line">        <span class="comment">//         .build();</span></span><br><span class="line">        <span class="comment">//使用自定义缓存管理器</span></span><br><span class="line">        <span class="type">RedisCacheWriter</span> <span class="variable">redisCacheWriter</span> <span class="operator">=</span> RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());</span><br><span class="line">        <span class="type">MyRedisCacheManager</span> <span class="variable">myRedisCacheManager</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyRedisCacheManager</span>(redisCacheWriter, defaultCacheConfiguration);</span><br><span class="line">        myRedisCacheManager.setTransactionAware(<span class="literal">true</span>); <span class="comment">// 只在事务成功提交后才会进行缓存的put/evict操作</span></span><br><span class="line">        <span class="keyword">return</span> myRedisCacheManager;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h4 id="服务降级"><a href="#服务降级" class="headerlink" title="服务降级"></a><strong>服务降级</strong></h4><ul><li><strong>策略描述</strong>：在缓存失效或数据库压力过大的情况下，通过服务降级策略来减少对非核心业务的请求处理，确保核心业务的正常运行。</li><li><strong>实施方式</strong>：使用限流组件（如Hystrix、Sentinel等）来限制请求量，或返回默认数据或静态页面等。</li><li><strong>效果</strong>：保护系统不受极端情况的冲击，确保核心业务的服务质量。</li></ul><h4 id="使用Redis集群或哨兵模式"><a href="#使用Redis集群或哨兵模式" class="headerlink" title="使用Redis集群或哨兵模式"></a><strong>使用Redis集群或哨兵模式</strong></h4><ul><li><strong>策略描述</strong>：通过部署Redis集群或哨兵模式来提高缓存服务的可用性。当单个Redis节点故障时，可以自动切换到其他节点继续提供服务。</li><li><strong>实施方式</strong>：配置Redis集群或哨兵模式，监控Redis节点的状态，并实现故障自动转移。</li><li><strong>效果</strong>：提高缓存服务的稳定性和可靠性，降低缓存雪崩的风险。</li></ul><h2 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h2><h3 id="缓存穿透问题介绍"><a href="#缓存穿透问题介绍" class="headerlink" title="缓存穿透问题介绍"></a>缓存穿透问题介绍</h3><p>缓存穿透是指用户查询一个数据库和缓存中都不存在的数据，导致每次查询都会直接打到数据库上，而数据库中也没有该数据，如果用户不断发起这样的请求，数据库压力会非常大，甚至可能拖垮数据库。这种情况通常是由于恶意的查询或者系统设计不当导致的。</p><h4 id="危害"><a href="#危害" class="headerlink" title="危害"></a>危害</h4><p>缓存穿透的主要危害在于，如果存在大量针对不存在的数据的查询请求，这些请求都会绕过缓存直接访问数据库，导致数据库压力剧增，影响系统性能，甚至可能引发数据库崩溃。</p><h3 id="解决方案-2"><a href="#解决方案-2" class="headerlink" title="解决方案"></a>解决方案</h3><p>针对缓存穿透问题，常见的解决方案有以下几种：</p><h4 id="布隆过滤器（Bloom-Filter）"><a href="#布隆过滤器（Bloom-Filter）" class="headerlink" title="布隆过滤器（Bloom Filter）"></a><strong>布隆过滤器（Bloom Filter）</strong></h4><ul><li><p><strong>原理</strong>：布隆过滤器是一种空间效率很高的概率型数据结构，用于判断一个元素是否在一个集合中。它通过使用位数组和多个哈希函数来减少误判率，但无法完全避免误判。</p></li><li><p><strong>应用</strong>：在查询数据库之前，先通过布隆过滤器判断该数据是否可能存在。如果布隆过滤器判断该数据不存在，则直接返回空值或错误信息，避免对数据库的访问。</p></li><li><p><strong>优缺点</strong>：</p><ul><li>优点：内存占用少，查询速度快。</li><li>缺点：存在误判率，且实现相对复杂。</li></ul><p><strong>实现</strong></p><p>关于布隆过滤器的使用，建议使用Google的Guava 或 Redission基于Redis实现，前者是在单体架构下比较适合，后者更适合在分布式场景下，便于多个服务节点之间共享。</p><p>Redission基于Redis，使用string类型数据，生成二进制数组进行存储，最大可用长度为：4294967294。</p><ul><li><p>引入依赖</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.redisson<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>redisson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>导入redisson配置、</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedissonConfiguration</span> &#123;</span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedisProperties redisProperties;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redissonSingle</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        <span class="type">SingleServerConfig</span> <span class="variable">serverConfig</span> <span class="operator">=</span> config.useSingleServer()</span><br><span class="line">                .setAddress(<span class="string">&quot;redis://&quot;</span> + redisProperties.getHost() + <span class="string">&quot;:&quot;</span> + redisProperties.getPort());</span><br><span class="line">        <span class="keyword">if</span> (<span class="literal">null</span> != (redisProperties.getTimeout())) &#123;</span><br><span class="line">            serverConfig.setTimeout(<span class="number">1000</span> * Convert.toInt(redisProperties.getTimeout().getSeconds()));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (StrUtil.isNotEmpty(redisProperties.getPassword())) &#123;</span><br><span class="line">            serverConfig.setPassword(redisProperties.getPassword());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>自定义布隆过滤器配置</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 布隆过滤器相关配置</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Getter</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BloomFilterConfig</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 名称，默认：sl-bloom-filter</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;bloom.name:sl-bloom-filter&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 布隆过滤器长度，最大支持Integer.MAX_VALUE*2，即：4294967294，默认：1千万</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;bloom.expectedInsertions:10000000&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> expectedInsertions;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 误判率，默认：0.05</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;bloom.falseProbability:0.05d&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> falseProbability;</span><br></pre></td></tr></table></figure></li><li><p>定义布隆过滤器接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 布隆过滤器服务</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BloomFilterService</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 初始化布隆过滤器</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 向布隆过滤器中添加数据</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> obj 待添加的数据</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 是否成功</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(Object obj)</span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 判断数据是否存在</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> obj 数据</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 是否存在</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">contains</span><span class="params">(Object obj)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>编写实现类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BloomFilterServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">BloomFilterService</span> &#123;</span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> BloomFilterConfig bloomFilterConfig;</span><br><span class="line">    <span class="keyword">private</span> RBloomFilter&lt;Object&gt; <span class="title function_">getBloomFilter</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.redissonClient.getBloomFilter(<span class="built_in">this</span>.bloomFilterConfig.getName());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="meta">@PostConstruct</span> <span class="comment">// spring启动后进行初始化</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">        RBloomFilter&lt;Object&gt; bloomFilter = <span class="built_in">this</span>.getBloomFilter();</span><br><span class="line">        bloomFilter.tryInit(<span class="built_in">this</span>.bloomFilterConfig.getExpectedInsertions(), <span class="built_in">this</span>.bloomFilterConfig.getFalseProbability());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.getBloomFilter().add(obj);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">contains</span><span class="params">(Object obj)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.getBloomFilter().contains(obj);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>后续在Controller直接使用布隆过滤器的contains可以直接判断该数据是否存在，从而实现过滤，在新增数据后，也需要使用add方法将数据添加到布隆过滤器，以便后面可以判断。</p><p><strong>局限</strong></p><p>由于布隆过滤器是采用hash算法将key值计算为一个数字存在bitMap中，所有有可能存在两个不同的键计算出的值相同有可能会造成误差，可以通过一个键使用多个hash算法，如果多个hash算法的结果均存在才说明该数据存在，即使这样，也会存在误差</p></li></ul></li></ul><h4 id="缓存空对象"><a href="#缓存空对象" class="headerlink" title="缓存空对象"></a><strong>缓存空对象</strong></h4><ul><li><strong>原理</strong>：当查询一个不存在的数据时，将空结果（如null或特定空值）缓存起来，并设置一个较短的过期时间。这样，在后续的查询中，如果仍然查询该不存在的数据，则可以直接从缓存中返回空结果，避免对数据库的访问。</li><li><strong>应用</strong>：适用于数据变动不频繁的场景，可以减少对数据库的无效查询。</li><li>优缺点：<ul><li>优点：实现简单，维护方便。</li><li>缺点：可能会浪费一定的缓存空间，且存在短期数据不一致的风险。</li></ul></li></ul><h4 id="控制层校验"><a href="#控制层校验" class="headerlink" title="控制层校验"></a><strong>控制层校验</strong></h4><ul><li><strong>原理</strong>：在业务系统的控制层（如API接口）增加校验逻辑，对查询参数进行合法性校验。如果查询参数不合法（如查询不存在的数据ID），则直接返回错误信息，避免对缓存和数据库的访问。</li><li><strong>应用</strong>：适用于可以通过校验规则提前判断查询是否有效的场景。</li><li>优缺点：<ul><li>优点：能够有效减少无效查询对系统的压力。</li><li>缺点：需要维护校验规则，且可能无法覆盖所有无效查询。</li></ul></li></ul>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/08/02/redis%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Caffeine缓存库</title>
      <link>https://www.yhblog.xyz/2024/08/01/Caffeine%E7%BC%93%E5%AD%98%E5%BA%93/</link>
      <guid>https://www.yhblog.xyz/2024/08/01/Caffeine%E7%BC%93%E5%AD%98%E5%BA%93/</guid>
      <pubDate>Thu, 01 Aug 2024 13:25:16 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能缓存库，它源自Google Guava的Cache设计，但提供了更多的</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能缓存库，它源自Google Guava的Cache设计，但提供了更多的优化和特性。以下是对Caffeine的详细解析：</p><h3 id="Caffeine的特点"><a href="#Caffeine的特点" class="headerlink" title="Caffeine的特点"></a>Caffeine的特点</h3><ol><li><strong>高性能</strong>：Caffeine通过优化的算法和设计，实现了接近最优的缓存性能。它支持高效的并发访问，能够在大规模数据和高并发访问的场景下保持良好的性能。</li><li><strong>灵活性</strong>：Caffeine提供了多种配置选项，允许开发者根据具体需求定制缓存行为。这包括缓存大小限制、过期时间、自动加载策略等。</li><li><strong>智能淘汰策略</strong>：Caffeine采用了基于TinyLFU（Least Frequently Used）的新型淘汰算法，该算法在保持高命中率的同时有效控制缓存大小。这意味着Caffeine能够自动移除“不常用”的数据，以保持内存的合理占用。</li><li><strong>易于集成</strong>：Caffeine提供了简单易用的API，并且与Guava的Cache有良好的兼容性，支持JSR-107（JCache）规范，可以与其他Java框架无缝融合。</li><li><strong>统计和监控</strong>：Caffeine还提供了缓存的统计信息，帮助开发者进行性能监控和调优。</li></ol><h2 id="Caffeine的基本用法"><a href="#Caffeine的基本用法" class="headerlink" title="Caffeine的基本用法"></a>Caffeine的基本用法</h2><ol><li><p><strong>构建缓存</strong>：<br>使用<code>Caffeine.newBuilder()</code>方法构建缓存配置，并通过链式调用设置缓存的各项参数，如过期时间、最大容量等。最后，调用<code>build()</code>方法创建缓存实例。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Cache&lt;Key, Graph&gt; cache = Caffeine.newBuilder()  </span><br><span class="line">    .expireAfterWrite(<span class="number">10</span>, TimeUnit.MINUTES)  </span><br><span class="line">    .maximumSize(<span class="number">10_000</span>)  </span><br><span class="line">    .build();</span><br></pre></td></tr></table></figure></li><li><p><strong>缓存操作</strong>：</p><ul><li><strong>添加或更新缓存</strong>：使用<code>cache.put(key, value)</code>方法添加或更新缓存元素。</li><li>获取缓存元素：<ul><li><code>cache.getIfPresent(key)</code>：如果缓存中存在该key对应的元素，则返回该元素；否则返回null。</li><li><code>cache.get(key, k -&gt; value)</code>：如果缓存中不存在该key对应的元素，则调用给定的函数生成该元素，并将其加入缓存后返回。</li></ul></li><li><strong>移除缓存元素</strong>：使用<code>cache.invalidate(key)</code>方法移除缓存中指定key的元素。</li></ul></li><li><p><strong>自动加载缓存</strong>：</p><ul><li><strong>LoadingCache</strong>：当调用<code>get()</code>方法且缓存不存在时，自动调用CacheLoader的<code>load()</code>方法加载缓存元素。</li><li><strong>AsyncLoadingCache</strong>：支持异步加载缓存元素，返回<code>CompletableFuture</code>对象。</li></ul></li></ol><h2 id="Caffeine的缓存策略"><a href="#Caffeine的缓存策略" class="headerlink" title="Caffeine的缓存策略"></a>Caffeine的缓存策略</h2><h3 id="基于大小的驱逐策略"><a href="#基于大小的驱逐策略" class="headerlink" title="基于大小的驱逐策略"></a>基于大小的驱逐策略</h3><p><strong>定义</strong>：<br>当缓存中的条目数量达到预设的最大阈值时，自动从缓存中移除一部分数据以维持缓存大小不超过限制。</p><p><strong>优点</strong>：</p><ul><li><strong>直接控制缓存占用空间</strong>：可以确保缓存不会无限制地增长，避免占用过多的系统资源。</li><li><strong>简单易懂</strong>：配置和理解起来相对简单，不需要复杂的算法支持。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>可能移除常用数据</strong>：如果缓存大小限制设置得太小，可能会导致一些频繁访问的数据被错误地移除。</li><li><strong>不够智能</strong>：无法根据数据的访问频率或重要性来智能地选择移除对象。</li></ul><h3 id="基于时间的驱逐策略"><a href="#基于时间的驱逐策略" class="headerlink" title="基于时间的驱逐策略"></a>基于时间的驱逐策略</h3><p><strong>定义</strong>：<br>包括两种主要类型：基于写入时间的驱逐（expireAfterWrite）和基于访问时间的驱逐（expireAfterAccess）。前者是指数据在写入后经过一定时间未被访问则被淘汰，后者是指数据在最后一次访问后经过一定时间未被再次访问则被淘汰。</p><p><strong>优点</strong>：</p><ul><li><strong>自动过期处理</strong>：能够自动清理过期数据，保持缓存数据的时效性。</li><li><strong>适用于特定场景</strong>：如需要定期更新数据的场景，可以确保缓存中的数据不会过时。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>可能导致数据频繁过期</strong>：如果过期时间设置得太短，可能会导致大量数据频繁过期，增加缓存的维护成本。</li><li><strong>不适用于所有场景</strong>：有些场景下数据的有效期可能很难确定，或者需要长期保留。</li></ul><h3 id="基于频率的驱逐策略（W-TinyLFU）"><a href="#基于频率的驱逐策略（W-TinyLFU）" class="headerlink" title="基于频率的驱逐策略（W-TinyLFU）"></a>基于频率的驱逐策略（W-TinyLFU）</h3><p><strong>定义</strong>：<br>W-TinyLFU是Caffeine默认采用的驱逐算法，它是对传统LFU（Least Frequently Used）算法的优化。该算法通过维护一个频率表来记录每个数据的访问频率，并根据频率来决定数据的淘汰优先级。</p><p><strong>优点</strong>：</p><ul><li><strong>高命中率</strong>：能够有效保护频繁访问的数据，提高缓存的命中率。</li><li><strong>优化空间占用</strong>：使用Count-Min Sketch算法存储访问频率，极大地节省了空间。</li><li><strong>应对访问模式变化</strong>：通过定期衰减操作和Window机制来应对访问模式的变化，减少缓存污染。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>算法复杂度较高</strong>：相比于简单的基于大小或时间的驱逐策略，W-TinyLFU算法的实现更为复杂。</li><li><strong>需要额外空间</strong>：虽然优化了空间占用，但仍然需要额外的空间来存储访问频率信息。</li></ul><h3 id="基于引用的缓存策略"><a href="#基于引用的缓存策略" class="headerlink" title="基于引用的缓存策略"></a>基于引用的缓存策略</h3><p>这里的“基于引用的缓存策略”并非Caffeine直接提供的一种独立策略，而是可以通过其提供的缓存配置和特性来间接实现的一种管理方式。</p><p>在Java中，引用（Reference）主要分为强引用（Strong Reference）、软引用（Soft Reference）和弱引用（Weak Reference）三种。这些引用类型在JVM的垃圾回收过程中扮演着不同的角色，从而影响缓存中对象的生命周期。</p><ul><li><strong>强引用</strong>：最常见的引用类型，只要强引用还存在，垃圾回收器就永远不会回收被引用的对象。</li><li><strong>软引用</strong>：非必须对象，在系统将要发生内存溢出异常之前，将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存，才会抛出内存溢出异常。</li><li><strong>弱引用</strong>：也是非必须对象，被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时，无论当前内存是否足够，都会回收掉只被弱引用关联的对象。</li></ul><p>虽然Caffeine没有直接提供设置引用类型的API，但你可以通过结合Java的引用类型和Caffeine的缓存过期策略、容量限制等特性来实现基于引用的缓存管理。例如，你可以使用软引用或弱引用来包装缓存中的对象，然后在JVM进行垃圾回收时，根据引用类型的不同，缓存中的对象可能会被自动清理。</p><p>然而，需要注意的是，这种方法可能会使得缓存的管理变得复杂和不可控，因为缓存的清理将依赖于JVM的垃圾回收机制，而这是不可预测的。因此，在实际应用中，更常见的是直接使用Caffeine提供的基于时间、大小或权重的驱逐策略来管理缓存。</p><p>此外，Caffeine还提供了丰富的API和配置选项，允许你根据实际需求定制缓存的行为，包括缓存的加载、写入、刷新、过期、淘汰等各个方面。这使得Caffeine成为了一个非常灵活和强大的缓存解决方案。</p><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><p>Caffeine适用于各种需要高效缓存解决方案的场景，包括但不限于：</p><ul><li><strong>Web应用</strong>：在高流量的web服务中缓存经常访问的数据以减少数据库压力。</li><li><strong>大数据处理</strong>：在分布式数据存储系统中作为本地缓存层来加速读取操作。</li><li><strong>流式处理平台</strong>：在处理大量事件时缓存中间结果以提高吞吐量。</li><li><strong>搜索引擎</strong>：用于快速响应查询。</li><li><strong>微服务框架</strong>：为微服务提供内置缓存支持。</li></ul><h2 id="缓存一致性问题"><a href="#缓存一致性问题" class="headerlink" title="缓存一致性问题"></a>缓存一致性问题</h2><p>由于Caffeine的缓存数据存储在JVM的堆内存中，这意味着他只能在其所在的JVM实例访问，而在分布式系统中，往往一个微服务会启动多个节点(JVM实例)来访问共同的资源，此时若其中一个节点修改了数据，但此时其他节点并未修改数据，就会导致缓存一致性问题</p><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><p>可以使用redis的分布式缓存解决该问题，Redis分布式缓存中的发布订阅（Pub&#x2F;Sub）机制是一种强大的通信模式，它允许发送者（发布者）将消息发送给订阅了特定频道的接收者（订阅者），而不需要知道订阅者的具体信息。这种机制在解决缓存不一致问题时具有显著的作用。</p><h4 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h4><p>Redis的发布订阅机制主要包括发布者（Publisher）、订阅者（Subscriber）和频道（Channel）三个部分。发布者将消息发送到指定的频道，所有订阅了该频道的订阅者都能接收到这条消息。这种机制与Redis的键值对存储机制是独立的，它不会影响Redis中的数据结构。</p><h4 id="具体方式"><a href="#具体方式" class="headerlink" title="具体方式"></a>具体方式</h4><p>当某个节点的应用更新了数据库中的数据，并希望同步更新到Redis缓存中时，该节点可以作为一个发布者，将缓存更新消息（如缓存键名、更新时间等）发送到特定的频道上。其他节点作为订阅者，订阅了这个频道后，就能实时接收到这个缓存更新消息。</p>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/08/01/Caffeine%E7%BC%93%E5%AD%98%E5%BA%93/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>MongoDB数据库</title>
      <link>https://www.yhblog.xyz/2024/07/23/MongoDB%E6%95%B0%E6%8D%AE%E5%BA%93/</link>
      <guid>https://www.yhblog.xyz/2024/07/23/MongoDB%E6%95%B0%E6%8D%AE%E5%BA%93/</guid>
      <pubDate>Tue, 23 Jul 2024 11:57:23 GMT</pubDate>
      
        
        
      <description>&lt;p&gt;MongoDB是一个基于分布式文件存储的开源数据库系统，由C++语言编写。它旨在为WEB应用提供可扩展的高性能数据存储解决方案。以下是对MongoDB的详细介绍&lt;/p&gt;
&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;</description>
        
      
      
      
      <content:encoded><![CDATA[<p>MongoDB是一个基于分布式文件存储的开源数据库系统，由C++语言编写。它旨在为WEB应用提供可扩展的高性能数据存储解决方案。以下是对MongoDB的详细介绍</p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><h3 id="基本概述"><a href="#基本概述" class="headerlink" title="基本概述"></a>基本概述</h3><ul><li><strong>类型</strong>：MongoDB是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。</li><li><strong>数据存储</strong>：MongoDB使用BSON（Binary JSON）格式存储数据，这种格式支持存储复杂的数据类型，类似于JSON对象，但具有更高的存储效率和性能。</li><li><strong>数据模型</strong>：MongoDB采用文档导向的数据模型，文档是一个键值对的集合，类似于关系型数据库中的行，但更为灵活。文档可以包含嵌入式文档、数组和其他复杂类型。</li></ul><h3 id="主要特点"><a href="#主要特点" class="headerlink" title="主要特点"></a>主要特点</h3><ol><li><strong>高性能</strong>：MongoDB使用内存映射文件和预分配空间等技术优化数据读写性能，支持快速的插入、更新和查询操作。</li><li><strong>可扩展性</strong>：MongoDB支持水平扩展和自动分片，可以方便地增加服务器和处理大规模数据。</li><li><strong>模式自由</strong>：MongoDB不需要事先定义表格结构，可以存储各种类型的数据，支持嵌入式文档和引用两种数据关联方式。</li><li><strong>强大的查询语言</strong>：MongoDB提供丰富的查询操作符和聚合管道功能，支持复杂的查询和分析需求。</li><li><strong>高可用性</strong>：MongoDB通过复制集（Replica Set）提供数据的冗余和故障恢复能力，主从复制可以提高系统的可用性和容错性。</li></ol><h3 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h3><ul><li><strong>网站实时数据处理</strong>：MongoDB非常适合实时的插入、更新与查询，并具备网站实时数据存储所需的复制及高度伸缩性。</li><li><strong>缓存</strong>：由于性能很高，MongoDB适合作为信息基础设施的缓存层，在系统重启之后，由它搭建的持久化缓存层可以避免下层的数据源过载。</li><li><strong>高伸缩性的场景</strong>：MongoDB非常适合由数十或数百台服务器组成的数据库，其路线图中已经包含对MapReduce引擎的内置支持。</li></ul><h3 id="概念对比"><a href="#概念对比" class="headerlink" title="概念对比"></a>概念对比</h3><table><thead><tr><th>SQL术语&#x2F;概念</th><th>MongoDB术语&#x2F;概念</th><th>解释&#x2F;说明</th></tr></thead><tbody><tr><td>database</td><td>database</td><td>数据库</td></tr><tr><td>table</td><td>collection</td><td>数据库表&#x2F;集合</td></tr><tr><td>row</td><td>document</td><td>数据记录行&#x2F;文档</td></tr><tr><td>column</td><td>field</td><td>数据字段&#x2F;域</td></tr><tr><td>index</td><td>index</td><td>索引</td></tr><tr><td>table joins</td><td></td><td>表连接，MongoDB不支持</td></tr><tr><td>primary key</td><td>primary key</td><td>主键，MongoDB自动将_id字段设置为主键</td></tr></tbody></table><h3 id="优势对比"><a href="#优势对比" class="headerlink" title="优势对比"></a>优势对比</h3><p>MongoDB与MySQL作为两种不同类型的数据库系统，各自具有独特的优势和适用场景。MongoDB作为一种非关系型数据库（NoSQL），与关系型数据库MySQL相比，在以下几个方面展现出明显的优势：</p><h3 id="1-数据模型灵活性"><a href="#1-数据模型灵活性" class="headerlink" title="1. 数据模型灵活性"></a>1. 数据模型灵活性</h3><ul><li><strong>MongoDB</strong>：使用文档型存储模型，类似于JSON格式，每个文档可以有不同的字段，支持嵌套结构和动态模式。这种灵活性使得MongoDB非常适合处理半结构化和非结构化数据，能够轻松应对数据结构的变化。</li><li><strong>MySQL</strong>：遵循关系模型，数据以表格的形式存储，有固定的列和行结构。虽然通过外键可以实现数据间的关联，但数据结构的变更相对复杂。</li></ul><h3 id="2-高性能和扩展性"><a href="#2-高性能和扩展性" class="headerlink" title="2. 高性能和扩展性"></a>2. 高性能和扩展性</h3><ul><li><strong>MongoDB</strong>：支持水平扩展，内置分片机制，可以轻松地通过增加服务器来扩展存储和处理能力，实现近乎线性的性能提升。同时，MongoDB的内存映射文件技术和高效的查询语言使得数据读写速度快，特别适合处理海量数据和高并发场景。</li><li><strong>MySQL</strong>：虽然也支持一定程度的扩展，但在处理海量数据时，若未进行合理的索引优化或分区分片，可能会出现性能下降。</li></ul><h3 id="3-查询能力"><a href="#3-查询能力" class="headerlink" title="3. 查询能力"></a>3. 查询能力</h3><ul><li><strong>MongoDB</strong>：提供了丰富的查询语言，包括文本搜索、地理位置搜索等功能，可以灵活地满足各种查询需求。其聚合框架还提供了强大的数据汇总和分析功能。</li><li><strong>MySQL</strong>：使用标准的SQL语言，提供了丰富的查询功能和高级特性，如复杂的JOIN操作、子查询等。但在处理非结构化或半结构化数据时，可能不如MongoDB灵活。</li></ul><h3 id="4-高可用性和可靠性"><a href="#4-高可用性和可靠性" class="headerlink" title="4. 高可用性和可靠性"></a>4. 高可用性和可靠性</h3><ul><li><strong>MongoDB</strong>：具有内置的复制和故障转移功能，可以保证数据的高可用性和可靠性。同时，MongoDB还提供了透明数据加密、自动备份、数据恢复等功能，进一步增强了其优势。</li><li><strong>MySQL</strong>：同样具有高可靠性和稳定性，能够在处理高负载的情况下保持高性能。但相比MongoDB，其高可用性和数据保护机制可能需要额外的配置和管理。</li></ul><h3 id="5-工具和生态系统"><a href="#5-工具和生态系统" class="headerlink" title="5. 工具和生态系统"></a>5. 工具和生态系统</h3><ul><li><strong>MongoDB</strong>：拥有丰富的工具和驱动程序，支持各种编程语言和开发环境，可以方便地集成到现有的应用程序中。同时，MongoDB还提供了专业的数据管理服务（如MongoDB Atlas）和数据传输服务（如DTS），支持管理多种数据库和数据源之间的数据交互。</li><li><strong>MySQL</strong>：拥有庞大的社区支持和广泛的工具生态系统，包括图形界面工具、备份和恢复工具、监控工具等。这些工具和资源为MySQL的广泛应用提供了有力支持。</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>MongoDB相较于MySQL的优势主要体现在数据模型灵活性、高性能和扩展性、查询能力、高可用性和可靠性以及工具和生态系统等方面。然而，选择哪种数据库系统应根据具体的应用场景、数据特性和业务需求来决定。如果数据结构复杂多变、需要高并发读写、对扩展性要求高或对半结构化数据处理有需求，MongoDB可能更为合适；而如果业务场景高度依赖ACID事务、数据结构固定、需要进行复杂的SQL查询和数据分析或对存储空间有严格要求，MySQL可能是更好的选择。</p><h2 id="MongoDB语法"><a href="#MongoDB语法" class="headerlink" title="MongoDB语法"></a>MongoDB语法</h2><h3 id="库操作"><a href="#库操作" class="headerlink" title="库操作"></a>库操作</h3><ul><li><p><strong>查看所有数据库</strong>：</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">show dbs</span><br></pre></td></tr></table></figure></li><li><p><strong>创建数据库</strong>：<br>在 MongoDB 中，创建数据库很简单，只需要使用 <code>use</code> 命令，如果数据库不存在，MongoDB 会在需要时自动创建它。</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">use myDatabase</span><br></pre></td></tr></table></figure></li><li><p><strong>删除数据库</strong>：</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">db.dropDatabase()</span><br></pre></td></tr></table></figure></li></ul><h3 id="集合操作"><a href="#集合操作" class="headerlink" title="集合操作"></a>集合操作</h3><ul><li><p><strong>查看当前数据库中的集合</strong>：</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">show collections</span><br></pre></td></tr></table></figure></li><li><p><strong>创建集合</strong>：<br>在 MongoDB 中，创建集合也是隐式的，当你第一次插入文档时，如果集合不存在，MongoDB 会自动创建它。</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">db.createCollection(&quot;myCollection&quot;)</span><br></pre></td></tr></table></figure></li><li><p><strong>删除集合</strong>：</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">db.myCollection.drop()</span><br></pre></td></tr></table></figure></li></ul><h3 id="文档操作"><a href="#文档操作" class="headerlink" title="文档操作"></a>文档操作</h3><ul><li><p><strong>插入文档</strong>：</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">db.myCollection.insert(&#123;name: &quot;John&quot;, age: 30&#125;)</span><br></pre></td></tr></table></figure><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">db.myCollection.insert([&#123;name: &quot;Jane&quot;, age: 25&#125;, &#123;name: &quot;Doe&quot;, age: 22&#125;])</span><br></pre></td></tr></table></figure></li><li><p><strong>查询文档</strong>：</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">db.mycollection.find([query],[fields])</span><br></pre></td></tr></table></figure><ul><li>query：可选，使用查询操作符指定查询条件，可理解为sql中where后面的内容</li><li>fields：可选，使用投影操作指定返回的键，可理解为sql中select后的字段</li></ul><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">db.myCollection.find(&#123;&#125;)  # 查询所有文档  </span><br><span class="line">db.myCollection.find(&#123;name: &quot;John&quot;&#125;)  # 查询特定条件的文档</span><br></pre></td></tr></table></figure></li><li><p><strong>更新文档</strong>：</p><p>更新模板</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">db.myCollection.update(</span><br><span class="line">  &lt;query&gt;,</span><br><span class="line">  &lt;update&gt;,</span><br><span class="line">  [</span><br><span class="line">    upsert: &lt;boolean&gt;,</span><br><span class="line">    multi: &lt;bollean&gt;,</span><br><span class="line">    writeConcern: &lt;document&gt;</span><br><span class="line">  ]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li>query：update的查询条件，类似sql update查询内where后面的</li><li>update：update的对象和一些更新的操作符，可理解为sql中update后面set的内容</li><li>upsert：可选，这个参数的意思是，如果不存在update的记录，是否以一个新的对象插入，默认是false</li><li>multi：可选，默认是false，只更新找到的第一条记录，如果这个条件是true，就把按条件查出来的多条记录更新</li><li>writeConcern：可选，抛出异常的级别</li></ul><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">db.myCollection.update(  </span><br><span class="line">   &#123; name: &quot;John&quot; &#125;,  </span><br><span class="line">   &#123; $set: &#123; age: 31 &#125; &#125;  </span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>或者更新多个文档：</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">db.myCollection.update(  </span><br><span class="line">   &#123; &#125;,  </span><br><span class="line">   &#123; $set: &#123; newField: &quot;value&quot; &#125; &#125;  </span><br><span class="line">)</span><br></pre></td></tr></table></figure></li><li><p><strong>删除文档</strong>：</p><p>删除模板</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></pre></td><td class="code"><pre><span class="line">db.mycollection.remove(</span><br><span class="line">  &lt;query&gt;,</span><br><span class="line">  &#123;</span><br><span class="line">    justOne: &lt;boolean&gt;,</span><br><span class="line">    writeConcern: &lt;document&gt;</span><br><span class="line">  &#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li>query：可选，删除文档的条件</li><li>justOne：可选，如果设为true或1，则只删除一个文档，如果不设置该参数，则使用默认值false，则删除所有匹配的文档</li><li>writeConcern：可选，抛出异常的级别</li></ul><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">db.myCollection.remove(&#123;name: &quot;John&quot;&#125;)  </span><br><span class="line">db.myCollection.remove(&#123;age: &#123;$lt: 25&#125;&#125;)</span><br></pre></td></tr></table></figure></li></ul><h3 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h3><ul><li><p><strong>创建索引</strong>：</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">db.myCollection.createIndex(&#123;name: 1&#125;)</span><br></pre></td></tr></table></figure></li><li><p><strong>查看索引</strong>：</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">db.myCollection.getIndexes()</span><br></pre></td></tr></table></figure></li><li><p><strong>删除索引</strong>：</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">db.myCollection.dropIndex(&#123;name: 1&#125;)</span><br></pre></td></tr></table></figure></li></ul><h2 id="SpringDataMongoDB"><a href="#SpringDataMongoDB" class="headerlink" title="SpringDataMongoDB"></a>SpringDataMongoDB</h2><h3 id="1-添加依赖"><a href="#1-添加依赖" class="headerlink" title="1. 添加依赖"></a>1. 添加依赖</h3><p>首先，需要在项目的构建文件（如Maven的<code>pom.xml</code>或Gradle的<code>build.gradle</code>）中添加Spring Data MongoDB的依赖项。以Maven为例，可以添加如下依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-mongodb<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个依赖包含了Spring Data MongoDB的核心功能，以及MongoDB Java驱动的依赖。</p><h3 id="2-配置MongoDB"><a href="#2-配置MongoDB" class="headerlink" title="2. 配置MongoDB"></a>2. 配置MongoDB</h3><p>接下来，在Spring Boot的配置文件（如<code>application.properties</code>或<code>application.yml</code>）中配置MongoDB的连接参数。以下是一个<code>application.yml</code>配置示例：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span>  </span><br><span class="line">  <span class="attr">data:</span>  </span><br><span class="line">    <span class="attr">mongodb:</span>  </span><br><span class="line">      <span class="attr">uri:</span> <span class="string">mongodb://username:password@localhost:27017/mydatabase</span>  </span><br><span class="line">      <span class="comment"># 或者分开配置  </span></span><br><span class="line">      <span class="comment"># host: localhost  </span></span><br><span class="line">      <span class="comment"># port: 27017  </span></span><br><span class="line">      <span class="comment"># database: mydatabase  </span></span><br><span class="line">      <span class="comment"># username: username  </span></span><br><span class="line">      <span class="comment"># password: password  </span></span><br><span class="line">      <span class="comment"># authenticationDatabase: admin # 如果需要认证的话</span></span><br></pre></td></tr></table></figure><p>这里配置了MongoDB的URI，或者分开配置了主机名、端口号、数据库名称、用户名和密码等信息。</p><h3 id="3-创建实体类"><a href="#3-创建实体类" class="headerlink" title="3. 创建实体类"></a>3. 创建实体类</h3><p>使用MongoDB的注解来定义实体类，并将其映射到MongoDB的集合（collection）。常用的注解包括<code>@Document</code>、<code>@Field</code>和<code>@Id</code>等。以下是一个简单的实体类示例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.data.annotation.Id;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.data.mongodb.core.mapping.Document;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Document(collection = &quot;users&quot;)</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;  </span><br><span class="line">    <span class="meta">@Id</span>  </span><br><span class="line">    <span class="keyword">private</span> String id;  </span><br><span class="line">      </span><br><span class="line">    <span class="meta">@Field(&quot;name&quot;)</span>  </span><br><span class="line">    <span class="keyword">private</span> String name;  </span><br><span class="line">      </span><br><span class="line">    <span class="meta">@Field(&quot;age&quot;)</span>  </span><br><span class="line">    <span class="keyword">private</span> Integer age;  </span><br><span class="line">      </span><br><span class="line">    <span class="comment">// 省略getter和setter方法  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-创建Repository接口"><a href="#4-创建Repository接口" class="headerlink" title="4. 创建Repository接口"></a>4. 创建Repository接口</h3><p>创建一个继承自<code>MongoRepository</code>（或其他Spring Data MongoDB提供的Repository接口）的接口，用于访问和操作实体类对应的MongoDB数据。Spring Data MongoDB会为这个接口提供一套基本的CRUD方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.data.mongodb.repository.MongoRepository;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserRepository</span> <span class="keyword">extends</span> <span class="title class_">MongoRepository</span>&lt;User, String&gt; &#123;  </span><br><span class="line">    <span class="comment">// 可根据需要添加自定义查询方法  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-使用Repository进行操作"><a href="#5-使用Repository进行操作" class="headerlink" title="5. 使用Repository进行操作"></a>5. 使用Repository进行操作</h3><p>在需要使用MongoDB操作的地方，通过注入Repository接口对象来调用相应的方法。例如，在Service层中使用Repository来保存和查询数据。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Service</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;  </span><br><span class="line">      </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> UserRepository userRepository;  </span><br><span class="line">      </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">saveUser</span><span class="params">(User user)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> userRepository.save(user);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">getUserById</span><span class="params">(String id)</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> userRepository.findById(id).orElse(<span class="literal">null</span>);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    <span class="comment">// 省略其他方法  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="6-使用MongoTemplate"><a href="#6-使用MongoTemplate" class="headerlink" title="6. 使用MongoTemplate"></a>6. 使用MongoTemplate</h3><p>除了使用Repository接口外，Spring Data MongoDB还提供了<code>MongoTemplate</code>类，它提供了更灵活的数据访问能力。可以在需要的时候注入<code>MongoTemplate</code>来实现更复杂的查询和操作。</p><h3 id="增加（Insert）"><a href="#增加（Insert）" class="headerlink" title="增加（Insert）"></a>增加（Insert）</h3><p>使用 <code>MongoTemplate</code> 的 <code>insert</code> 方法可以将对象插入到 MongoDB 的集合中。如果对象包含了一个 <code>_id</code> 字段，并且该字段的值已经存在于集合中，那么该操作会抛出一个异常。如果不希望自动生成 <code>_id</code>，可以在实体类中设置 <code>@Id</code> 注解的 <code>autoGenerated</code> 属性为 <code>false</code>（注意：通常我们让 MongoDB 自动管理 <code>_id</code>）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insertUser</span><span class="params">(User user)</span> &#123;  </span><br><span class="line">    mongoTemplate.insert(user, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里 <code>&quot;users&quot;</code> 是 MongoDB 中的集合名称。</p><h3 id="删除（Delete）"><a href="#删除（Delete）" class="headerlink" title="删除（Delete）"></a>删除（Delete）</h3><p>删除操作可以通过 <code>remove</code> 方法或 <code>findAllAndRemove</code> 方法来完成。<code>remove</code> 方法可以根据查询条件删除一个或多个文档。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 根据ID删除  </span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteUserById</span><span class="params">(String id)</span> &#123;  </span><br><span class="line">    <span class="type">Query</span> <span class="variable">query</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Query</span>();  </span><br><span class="line">    query.addCriteria(Criteria.where(<span class="string">&quot;id&quot;</span>).is(id));  </span><br><span class="line">    mongoTemplate.remove(query, User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="comment">// 删除所有  </span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">deleteAllUsers</span><span class="params">()</span> &#123;  </span><br><span class="line">    mongoTemplate.removeAll(User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="更新（Update）"><a href="#更新（Update）" class="headerlink" title="更新（Update）"></a>更新（Update）</h3><p>更新操作通常使用 <code>updateFirst</code> 或 <code>updateMulti</code> 方法。<code>updateFirst</code> 会更新查询到的第一个文档，而 <code>updateMulti</code> 会更新所有匹配的文档。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 更新第一个匹配的文档  </span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateUserFirstName</span><span class="params">(String id, String firstName)</span> &#123;  </span><br><span class="line">    <span class="type">Query</span> <span class="variable">query</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Query</span>();  </span><br><span class="line">    query.addCriteria(Criteria.where(<span class="string">&quot;id&quot;</span>).is(id));  </span><br><span class="line">    <span class="type">Update</span> <span class="variable">update</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Update</span>();  </span><br><span class="line">    update.set(<span class="string">&quot;firstName&quot;</span>, firstName);  </span><br><span class="line">    mongoTemplate.updateFirst(query, update, User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="comment">// 更新所有匹配的文档  </span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateAllUsersAge</span><span class="params">(<span class="type">int</span> newAge)</span> &#123;  </span><br><span class="line">    <span class="type">Query</span> <span class="variable">query</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Query</span>();  </span><br><span class="line">    <span class="comment">// 这里可以添加更具体的查询条件  </span></span><br><span class="line">    <span class="type">Update</span> <span class="variable">update</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Update</span>();  </span><br><span class="line">    update.set(<span class="string">&quot;age&quot;</span>, newAge);  </span><br><span class="line">    mongoTemplate.updateMulti(query, update, User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="查询（Query）"><a href="#查询（Query）" class="headerlink" title="查询（Query）"></a>查询（Query）</h3><p>查询操作非常灵活，<code>MongoTemplate</code> 提供了多种查询方法，如 <code>findOne</code>、<code>findAll</code>、<code>find</code> 等，它们可以接受 <code>Query</code> 对象作为参数来定义查询条件。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 根据ID查询单个文档  </span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">findUserById</span><span class="params">(String id)</span> &#123;  </span><br><span class="line">    <span class="keyword">return</span> mongoTemplate.findById(id, User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="comment">// 查询所有文档  </span></span><br><span class="line"><span class="keyword">public</span> List&lt;User&gt; <span class="title function_">findAllUsers</span><span class="params">()</span> &#123;  </span><br><span class="line">    <span class="keyword">return</span> mongoTemplate.findAll(User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="comment">// 使用Query对象进行复杂查询  </span></span><br><span class="line"><span class="keyword">public</span> List&lt;User&gt; <span class="title function_">findUsersByAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;  </span><br><span class="line">    <span class="type">Query</span> <span class="variable">query</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Query</span>();  </span><br><span class="line">    query.addCriteria(Criteria.where(<span class="string">&quot;age&quot;</span>).is(age));  </span><br><span class="line">    <span class="keyword">return</span> mongoTemplate.find(query, User.class, <span class="string">&quot;users&quot;</span>);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在使用 <code>MongoTemplate</code> 进行查询时，你可以通过 <code>Query</code> 和 <code>Criteria</code> 类来构建复杂的查询条件，包括分页、排序等高级功能。</p><p>总的来说，<code>MongoTemplate</code> 提供了丰富的API来执行 MongoDB 的增删改查操作，使得开发者能够以更灵活和强大的方式来操作 MongoDB 数据库。</p>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/07/23/MongoDB%E6%95%B0%E6%8D%AE%E5%BA%93/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>neo4j图数据库</title>
      <link>https://www.yhblog.xyz/2024/07/21/neo4j%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93/</link>
      <guid>https://www.yhblog.xyz/2024/07/21/neo4j%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93/</guid>
      <pubDate>Sun, 21 Jul 2024 07:03:53 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;介绍&quot;&gt;&lt;a href=&quot;#介绍&quot; class=&quot;headerlink&quot; title=&quot;介绍&quot;&gt;&lt;/a&gt;介绍&lt;/h2&gt;&lt;p&gt;Neo4j是一个高性能的、基于Java的NoSQL图形数据库，它将结构化数据存储在网络（从数学角度称为图）上而不是传统的表中。Neo4j因其</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>Neo4j是一个高性能的、基于Java的NoSQL图形数据库，它将结构化数据存储在网络（从数学角度称为图）上而不是传统的表中。Neo4j因其嵌入式、高性能、轻量级等优势，在数据存储、检索、分析和挖掘等方面有着广泛的应用。以下是对Neo4j的详细介绍</p><h3 id="一、Neo4j的基本特性"><a href="#一、Neo4j的基本特性" class="headerlink" title="一、Neo4j的基本特性"></a>一、Neo4j的基本特性</h3><ol><li><strong>高性能的图引擎</strong>：Neo4j可以被看作是一个高性能的图引擎，具有成熟和健壮的数据库的所有特性。它支持大规模可扩展性，可以处理数十亿节点&#x2F;关系&#x2F;属性的图，并且可以扩展到多台机器并行运行。</li><li><strong>面向对象的网络结构</strong>：程序员在Neo4j中工作在一个面向对象的、灵活的网络结构下，而不是严格、静态的表中。这种结构使得Neo4j非常适合表示复杂、互连接、低结构化的数据。</li><li><strong>完全的事务特性</strong>：Neo4j是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎。它支持完整的ACID（原子性、一致性、隔离性和持久性）规则，确保数据的一致性和完整性。</li><li><strong>CQL查询语言</strong>：Neo4j使用CQL（Cypher Query Language）作为查询语言，这是一种声明性模式匹配语言，其语法简单且人性化、可读性强。CQL遵循SQL语法，使得熟悉SQL的程序员可以快速上手。</li></ol><h3 id="二、Neo4j的优势"><a href="#二、Neo4j的优势" class="headerlink" title="二、Neo4j的优势"></a>二、Neo4j的优势</h3><ol><li><strong>性能优势</strong>：Neo4j对长程关系的查询速度快，能够迅速遍历节点与边，其遍历速度与构成图的数据量没有直接关系。此外，Neo4j还提供了非常快的图算法、推荐系统和OLAP风格的分析。</li><li><strong>数据表示优势</strong>：Neo4j很容易表示连接的数据和半结构化数据。检索、遍历和导航更多的连接数据是非常容易和快速的。它不需要复杂的连接来检索连接的&#x2F;相关的数据，因为它可以很容易地检索相邻节点或关系细节。</li><li><strong>灵活性和可扩展性</strong>：Neo4j的图形数据模型非常灵活，可以轻松地适应不断变化的业务需求。同时，Neo4j支持大规模可扩展性，可以处理大规模的数据集。</li></ol><h3 id="三、Neo4j的应用场景"><a href="#三、Neo4j的应用场景" class="headerlink" title="三、Neo4j的应用场景"></a>三、Neo4j的应用场景</h3><ol><li><strong>社交媒体</strong>：Neo4j可以用于存储和检索社交媒体中的用户关系、帖子、评论等数据，支持复杂的社交网络分析。</li><li><strong>推荐系统</strong>：Neo4j可以根据用户的行为和偏好构建推荐系统，通过图分析发现用户之间的潜在联系和兴趣点。</li><li><strong>身份和访问管理</strong>：使用图形数据库进行身份和访问管理时，可以快速有效地跟踪用户、资产、关系和授权。</li><li><strong>金融风控</strong>：Neo4j可以用于金融领域的风控分析，通过图分析发现潜在的欺诈行为和洗钱网络。</li><li><strong>其他领域</strong>：Neo4j还可以应用于知识图谱、物流网络、生物信息学等多个领域。</li></ol><h2 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h2><h3 id="节点（Nodes）"><a href="#节点（Nodes）" class="headerlink" title="节点（Nodes）"></a>节点（Nodes）</h3><ul><li><strong>定义</strong>：节点是图的基本单位，用于表达各种实体，如人、部门、物品等。每个节点都有唯一的ID，用于在图中唯一标识该节点。</li><li><strong>功能</strong>：节点用于存储和表示现实世界中的实体或对象，以及这些实体或对象的属性。</li><li><strong>示例</strong>：在社交网络中，节点可以代表用户；在推荐系统中，节点可以代表商品或用户。</li></ul><h3 id="关系（Relationships）"><a href="#关系（Relationships）" class="headerlink" title="关系（Relationships）"></a>关系（Relationships）</h3><ul><li><strong>定义</strong>：关系是图形数据库中连接节点的边，用于表示节点之间的连接、关联或依赖关系。关系可以具有方向性，即从一个节点指向另一个节点。</li><li><strong>功能</strong>：关系用于描述节点之间的相互作用或联系，是图数据库的核心组成部分。通过关系，可以轻松地遍历和查询图中的节点。</li><li><strong>属性</strong>：关系也可以包含属性，这些属性用于存储关于关系本身的信息，如关系的类型、权重、时间戳等。</li><li><strong>示例</strong>：在社交网络中，关系可以表示用户之间的好友关系、关注关系等；在推荐系统中，关系可以表示用户与商品之间的购买关系、浏览关系等。</li></ul><h3 id="属性（Properties）"><a href="#属性（Properties）" class="headerlink" title="属性（Properties）"></a>属性（Properties）</h3><ul><li><strong>定义</strong>：属性是用于描述图节点和关系的键值对。属性以键值对的形式存在，其中键是一个字符串，值可以是任何Neo4j支持的数据类型。</li><li><strong>功能</strong>：属性用于存储节点或关系的详细信息，如节点的名称、年龄、性别等，或关系的类型、权重等。这些属性使得节点和关系更加丰富和具体。</li><li><strong>示例</strong>：在社交网络中，用户节点的属性可以包括用户名、年龄、性别等；在推荐系统中，商品节点的属性可以包括商品名称、价格、描述等。</li></ul><h3 id="标签（Labels）"><a href="#标签（Labels）" class="headerlink" title="标签（Labels）"></a>标签（Labels）</h3><ul><li><strong>定义</strong>：标签是Neo4j中的一种元数据，用于对节点进行分类和组织。每个节点可以有一个或多个标签，这些标签用于标识节点的类型或属性。</li><li><strong>功能</strong>：通过为节点添加标签，可以更好地组织和管理数据，提高查询性能。标签可以用作索引，加快查询速度。同时，标签也使得数据模型更加灵活和可扩展。</li><li><strong>示例</strong>：在社交网络中，用户节点可以被打上“用户”或“VIP用户”等标签；在推荐系统中，商品节点可以被打上“电子产品”、“服装”等标签。</li></ul><p>综上所述，节点、关系、属性和标签共同构成了Neo4j图形数据库的基本数据模型。通过这些元素，Neo4j能够高效地存储、检索和分析复杂的数据关系网络。</p><h2 id="Cypher语句"><a href="#Cypher语句" class="headerlink" title="Cypher语句"></a>Cypher语句</h2><p>Cypher是neo4j的查询语言，类似于关系型数据库中的SQL，一些关键词来源于SQL，比如:CREATE，WHERE，RETURN等，对关键词的大小写不敏感</p><h3 id="匹配语句"><a href="#匹配语句" class="headerlink" title="匹配语句"></a>匹配语句</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">Match (n) return n</span><br><span class="line">//查询所有的数据</span><br><span class="line">MATCH (n:Person) RETURN n</span><br><span class="line">//该语句返回所有带有person标签的节点</span><br><span class="line">MATCH (n:Person &#123;name: &#x27;Alice&#x27;&#125;) RETURN n</span><br><span class="line">//返回所有具有Person标签且name属性为&#x27;Alice&#x27;的节点。</span><br><span class="line">MATCH (a:Person)-[:FRIEND]-&gt;(b:Person) RETURN a, b</span><br><span class="line">//返回所有具有FRIEND关系且两端都是Person节点的关系对。</span><br><span class="line">MATCH (n:OLT &#123;name: &quot;北京市转运中心&quot;&#125;) -- (m) RETURN n,m</span><br><span class="line">//查询所有与“北京市转运中心”有关系的节点</span><br><span class="line">MATCH (n:OLT &#123;name:&quot;北京市转运中心&quot;&#125;) --&gt; (m:OLT) RETURN n,m </span><br><span class="line">//查询所有&quot;北京市转运中心&quot;关联的一级转运中心</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">//分页查询网点，按照bid正序排序，每页查询2条数据，第一页</span><br><span class="line">MATCH (n:AGENCY) </span><br><span class="line">RETURN n ORDER BY n.bid ASC SKIP 0 LIMIT 2</span><br><span class="line"></span><br><span class="line">//第二页</span><br><span class="line">MATCH (n:AGENCY) </span><br><span class="line">RETURN n ORDER BY n.bid ASC SKIP 2 LIMIT 2</span><br></pre></td></tr></table></figure><h3 id="创建语句"><a href="#创建语句" class="headerlink" title="创建语句"></a>创建语句</h3><figure class="highlight plaintext"><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">CREATE (n &#123;name: $value&#125;) RETURN n   //创建节点,该节点具备name属性，n为该节点的变量,创建完成后返回该节点</span><br><span class="line">CREATE (n:$Tag &#123;name: $value&#125;) //创建节点，指定标签</span><br><span class="line">CREATE (n)-[r:KNOWS]-&gt;(m)  //创建n指向m的关系，并且指定关系类型为：KNOWS</span><br><span class="line">                 </span><br><span class="line">//示例</span><br><span class="line">CREATE (n &#123;name:&#x27;迪士尼营业部&#x27;&#125;)</span><br><span class="line">CREATE (n:AGENCY &#123;name:&#x27;航头营业部&#x27;&#125;)</span><br><span class="line">                   </span><br><span class="line">//创建浦东新区转运中心、上海转运中心节点，并且创建关系为：IN_LINE，创建完成后返回节点和关系</span><br><span class="line">//TLT -&gt; Two Level Transport（二级转运中心）</span><br><span class="line">//OLT -&gt; One Level Transport（一级转运中心）</span><br><span class="line">CREATE (n:TLT &#123;name:&#x27;浦东新区转运中心&#x27;&#125;) -[r:IN_LINE]-&gt; (m:OLT &#123;name:&#x27;上海转运中心&#x27;&#125;) RETURN n,r,m</span><br><span class="line"></span><br><span class="line">//关系也是可以反向，并且可以为关系中指定属性</span><br><span class="line">CREATE (n:TLT &#123;name:&#x27;浦东新区转运中心&#x27;&#125;) &lt;-[r:OUT_LINE]- (m:OLT &#123;name:&#x27;上海转运中心&#x27;&#125;) RETURN n,r,m</span><br></pre></td></tr></table></figure><h3 id="合并语句"><a href="#合并语句" class="headerlink" title="合并语句"></a>合并语句</h3><p>合并语句是创建语句和匹配语句的结合体，先尝试找到给定模式匹配的节点和关系，如果不存在则创建他们</p><figure class="highlight plaintext"><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">MERGE (a:Person &#123;name: &#x27;Alice&#x27;&#125;)-[:FRIEND]-&gt;(b:Person &#123;name: &#x27;Bob&#x27;&#125;)</span><br><span class="line">//如果Alice和Bob之间的Friend关系不存在，则创建他们</span><br></pre></td></tr></table></figure><h3 id="设置语句"><a href="#设置语句" class="headerlink" title="设置语句"></a>设置语句</h3><figure class="highlight plaintext"><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">MATCH (n:Person &#123;name: &#x27;Alice&#x27;&#125;) SET n.age = 31 RETURN n</span><br><span class="line">//将Alice的Person节点的age属性更新为31</span><br></pre></td></tr></table></figure><h3 id="删除语句"><a href="#删除语句" class="headerlink" title="删除语句"></a>删除语句</h3><figure class="highlight plaintext"><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">MATCH (n:Person &#123;name: &#x27;Alice&#x27;&#125;) DELETE n</span><br><span class="line">//删除名为Alice的节点</span><br><span class="line">MATCH (a:Person)-[r:FRIEND]-&gt;(b:Person) WHERE a.name = &#x27;Alice&#x27; AND b.name = &#x27;Bob&#x27; DELETE r</span><br><span class="line">//删除Alice和Bob之间的Friend关系</span><br><span class="line"></span><br><span class="line">//带有关系的节点不能删，可以先删关系或者强制删除节点和关系</span><br><span class="line">Match (n:Agency &#123;name:&quot;北京市昌平区新龙城&quot;&#125;) Detach Delete n</span><br></pre></td></tr></table></figure><h2 id="SDN-Spring-Data-Neo4j"><a href="#SDN-Spring-Data-Neo4j" class="headerlink" title="SDN(Spring Data Neo4j)"></a>SDN(Spring Data Neo4j)</h2><h3 id="1-添加依赖"><a href="#1-添加依赖" class="headerlink" title="1. 添加依赖"></a>1. 添加依赖</h3><p>首先，你需要在你的 Spring Boot 项目的 <code>pom.xml</code>（如果是 Maven 项目）或 <code>build.gradle</code>（如果是 Gradle 项目）中添加 Spring Data Neo4j 的依赖。</p><h4 id="Maven-示例"><a href="#Maven-示例" class="headerlink" title="Maven 示例"></a>Maven 示例</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span>  </span><br><span class="line">    <span class="comment">&lt;!-- Spring Boot Starter Data Neo4j --&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span>  </span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span>  </span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-neo4j<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span>  </span><br><span class="line">    <span class="comment">&lt;!-- 其他依赖 --&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-配置-Neo4j"><a href="#2-配置-Neo4j" class="headerlink" title="2. 配置 Neo4j"></a>2. 配置 Neo4j</h3><p>在 <code>application.properties</code> 或 <code>application.yml</code> 文件中配置 Neo4j 数据库的连接信息。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">   <span class="attr">neo4j:</span></span><br><span class="line">    <span class="attr">authentication:</span></span><br><span class="line">      <span class="attr">username:</span> <span class="string">username</span></span><br><span class="line">      <span class="attr">password:</span> <span class="string">password</span></span><br><span class="line">    <span class="attr">uri:</span> <span class="string">链接uri</span></span><br></pre></td></tr></table></figure><h3 id="3-定义图实体和仓库"><a href="#3-定义图实体和仓库" class="headerlink" title="3. 定义图实体和仓库"></a>3. 定义图实体和仓库</h3><p>使用 Spring Data Neo4j 的注解来定义你的图实体和仓库接口。</p><h4 id="实体类示例"><a href="#实体类示例" class="headerlink" title="实体类示例"></a>实体类示例</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.neo4j.springframework.data.core.schema.Id;  </span><br><span class="line"><span class="keyword">import</span> org.neo4j.springframework.data.core.schema.Node;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Node</span>  <span class="comment">//里面是在neo4j中的标签</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Person</span> &#123;  </span><br><span class="line">    <span class="meta">@Id</span>  </span><br><span class="line">    <span class="keyword">private</span> Long id;  </span><br><span class="line">    <span class="keyword">private</span> String name;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 构造函数、getter 和 setter 省略  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="仓库接口示例"><a href="#仓库接口示例" class="headerlink" title="仓库接口示例"></a>仓库接口示例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.neo4j.springframework.data.repository.Neo4jRepository;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">PersonRepository</span> <span class="keyword">extends</span> <span class="title class_">Neo4jRepository</span>&lt;Person, Long&gt; &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 继承自Neo4jRepository的标准方法，如save、findAll等  </span></span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 自定义查询方法，通过方法命名约定  </span></span><br><span class="line">    List&lt;Person&gt; <span class="title function_">findByName</span><span class="params">(String name)</span>;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 使用@Query注解定义复杂的Cypher查询  </span></span><br><span class="line">    <span class="meta">@Query(&quot;MATCH (p:Person)-[:KNOWS]-&gt;(friend:Person) WHERE p.name = $name RETURN friend&quot;)</span>  </span><br><span class="line">    List&lt;Person&gt; <span class="title function_">findFriendsByName</span><span class="params">(String name)</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-使用仓库"><a href="#4-使用仓库" class="headerlink" title="4. 使用仓库"></a>4. 使用仓库</h3><p>在你的服务层或控制器中注入仓库，并使用它来执行数据库操作。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;  </span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Service;  </span><br><span class="line">  </span><br><span class="line"><span class="meta">@Service</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PersonService</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Autowired</span>  </span><br><span class="line">    <span class="keyword">private</span> PersonRepository personRepository;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> List&lt;Person&gt; <span class="title function_">findAllPersons</span><span class="params">()</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> personRepository.findAll();  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 其他业务逻辑  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/07/21/neo4j%E5%9B%BE%E6%95%B0%E6%8D%AE%E5%BA%93/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Elasticsearch搜索引擎</title>
      <link>https://www.yhblog.xyz/2024/07/13/Elasticsearch%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/</link>
      <guid>https://www.yhblog.xyz/2024/07/13/Elasticsearch%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/</guid>
      <pubDate>Sat, 13 Jul 2024 14:17:05 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;索引类型&quot;&gt;&lt;a href=&quot;#索引类型&quot; class=&quot;headerlink&quot; title=&quot;索引类型&quot;&gt;&lt;/a&gt;索引类型&lt;/h2&gt;&lt;h3 id=&quot;正向索引&quot;&gt;&lt;a href=&quot;#正向索引&quot; class=&quot;headerlink&quot; title=&quot;正向索引&quot;&gt;&lt;/a</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="索引类型"><a href="#索引类型" class="headerlink" title="索引类型"></a>索引类型</h2><h3 id="正向索引"><a href="#正向索引" class="headerlink" title="正向索引"></a>正向索引</h3><h4 id="定义与结构"><a href="#定义与结构" class="headerlink" title="定义与结构"></a><strong>定义与结构</strong></h4><ul><li>正向索引是从文档到单词的映射索引结构，它将文档中的每个单词与包含该单词的文档进行关联。这种索引结构适合于根据文档查找单词。</li><li>正向索引通常是以文档的ID为关键字，表中记录文档中每个单词的位置信息。</li></ul><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>结构简单：建立和维护相对简单。</li><li>易于维护：当有新文档加入或旧文档删除时，索引的更新相对容易。</li></ul><h4 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h4><p>由于其在对包含关键字的查询中需要逐行排查，所以此种查询效率极低，只适合与数据库系统等较为简单的查询作用，并不适合庞大的搜索引擎</p><h3 id="倒排索引"><a href="#倒排索引" class="headerlink" title="倒排索引"></a>倒排索引</h3><h4 id="定义与结构-1"><a href="#定义与结构-1" class="headerlink" title="定义与结构"></a><strong>定义与结构</strong></h4><ul><li>倒排索引是一种索引方法，用于存储在全文搜索下某个单词在一个文档或一组文档中的存储位置的映射。它主要由“单词词典”和“倒排文件”两部分组成。单词词典记录了文档集合中出现过的所有单词，而倒排文件则包含了每个单词对应的倒排列表，即包含该单词的所有文档的列表及其相关信息（如文档编号、词频、位置等）。</li><li>倒排索引的设计是从单词到文档的映射，即它能够快速根据单词找到包含该单词的所有文档。</li></ul><h4 id="查询过程"><a href="#查询过程" class="headerlink" title="查询过程"></a>查询过程</h4><p>将搜索语句分为一个一个词，去词条列表中查询文档id，根据id去查询文档存入结果集</p><h4 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a><strong>优点</strong></h4><ul><li>查询效率高：能够快速根据单词找到包含该单词的所有文档，适用于全文搜索和复杂查询。</li><li>相关性分析：可以基于词频、文档频率等信息进行相关性分析，提高检索结果的准确性。</li></ul><h4 id="局限性-1"><a href="#局限性-1" class="headerlink" title="局限性"></a><strong>局限性</strong></h4><ul><li>存储需求大：对于大规模数据集，倒排索引可能需要大量的存储空间。</li><li>更新复杂：当文档集合发生变化时，倒排索引的更新相对复杂。</li></ul><p>此种索引由于其复杂查询的高效性与准确性，所以可以用于搜索引擎</p><h3 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h3><table><thead><tr><th></th><th>倒排索引（Inverted Index）</th><th>正向索引（Forward Index）</th></tr></thead><tbody><tr><td><strong>定义与结构</strong></td><td>从单词到文档的映射</td><td>从文档到单词的映射</td></tr><tr><td><strong>构建过程</strong></td><td>文本分词、建立索引、存储索引</td><td>以文档ID为关键字，记录文档中每个单词的位置信息</td></tr><tr><td><strong>优点</strong></td><td>查询效率高、支持相关性分析</td><td>结构简单、易于维护</td></tr><tr><td><strong>局限性</strong></td><td>存储需求大、更新复杂</td><td>查询效率低、实用性有限</td></tr><tr><td><strong>应用场景</strong></td><td>搜索引擎、数据库系统、信息检索系统</td><td>文档内容稳定且需要频繁更新文档的场景</td></tr></tbody></table><h2 id="IK分词器"><a href="#IK分词器" class="headerlink" title="IK分词器"></a>IK分词器</h2><h3 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h3><p>顾名思义，作用就是对一大段话进行分词，例如创建倒排索引时，对文档分词，用户搜索时，对输入的内容分词，由于其是通过遍历一个语句中的任意相邻组合结果与自己的词库中对比，从而达到的分词效果，所以对一些新词需要自己通过配置加入到词库中</p><h4 id="分词算法"><a href="#分词算法" class="headerlink" title="分词算法"></a>分词算法</h4><ul><li><code>ik_smart</code>：<code>ik_smart</code>为最少切分，即做最粗粒度的拆分，已被分出的词语将不会再次被其它词语占有</li><li><code>ik_max_word</code>:<code>ik_max_word</code>为最细粒度划分，将文本做最细粒度的拆分，尽可能多的拆分出词语。</li></ul><h4 id="扩展词条"><a href="#扩展词条" class="headerlink" title="扩展词条"></a>扩展词条</h4><p>利用config目录中的<code>IKAnalyzer.cfg.xml</code>文件添加拓展词典，在词典中添加拓展词条</p><h2 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h2><table><thead><tr><th>MySql</th><th>Elasticsearch</th><th>说明</th></tr></thead><tbody><tr><td><strong>Table</strong></td><td><strong>Index</strong></td><td>索引（Index），就是文档的集合，类似数据库的表（Table）</td></tr><tr><td><strong>Row</strong></td><td><strong>Document</strong></td><td>文档（Document），就是一条条的数据，类似数据库中的行（Row），文档都是Json格式</td></tr><tr><td><strong>Column</strong></td><td><strong>Filed</strong></td><td>字段（Filed），就是Json文档中的字段，类似数据库中的列（Column）</td></tr><tr><td><strong>Schema</strong></td><td><strong>Mapping</strong></td><td>Mapping（映射）时索引中文档的约束，例如字段类型的约束。类似数据库的表结构（Schema）</td></tr><tr><td><strong>Sql</strong></td><td><strong>DSL</strong></td><td>DSL是Elasticsearch提供的Json风格的请求语句，用来定义搜索条件</td></tr></tbody></table><h3 id="Mapping映射属性"><a href="#Mapping映射属性" class="headerlink" title="Mapping映射属性"></a>Mapping映射属性</h3><p>mapping是索引库中对文档的约束，常见的mapping属性包括：</p><p><strong>type</strong>：字段数据类型，常见的简单类型有</p><ul><li>字符串：text(可分词的文本，一般为较长的文本)，keyword（精确值，例如：品牌，国家，ip地址，一般不可分词，因为此种类型分此后便失去了本来的含义）</li><li>数值：long，integer，short，byte，double，float</li><li>布尔：boolean</li><li>日期：date</li><li>对象：object</li></ul><p><strong>index</strong>：是否创建索引，默认为true，为true即代表会为这个字段创建倒排索引，将来可以根据这个字段去做搜索排序</p><p><strong>analyzer</strong>：使用哪种分词器</p><p><strong>properties</strong>：该字段的子字段，例如在json格式中嵌套了另一个json格式</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">Put /heima</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;mapping&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">         <span class="attr">&quot;info&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;text&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analyzer&quot;</span><span class="punctuation">:</span><span class="string">&quot;ik_smart&quot;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;byte&quot;</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;keyword&quot;</span><span class="punctuation">,</span></span><br><span class="line">   <span class="attr">&quot;index&quot;</span><span class="punctuation">:</span><span class="literal"><span class="keyword">false</span></span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                  <span class="attr">&quot;firstName&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;keyword&quot;</span></span><br><span class="line">                  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                  <span class="attr">&quot;lastName&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;keyword&quot;</span></span><br><span class="line">                  <span class="punctuation">&#125;</span></span><br><span class="line">              <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">   <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="文档CRUD"><a href="#文档CRUD" class="headerlink" title="文档CRUD"></a>文档CRUD</h3><ul><li>创建文档：<code>POST/索引库名/_doc/文档id&#123;Json文档&#125;</code> </li><li>查询文档：<code>GET/索引库名/_doc/文档id</code></li><li>删除文档：<code>DELETE/索引库名/_doc/文档id</code></li><li>修改文档：<ul><li>全量修改：<code>PUT/索引库名/_doc/文档id&#123;Json文档&#125;</code>全量修改的原理是删掉旧的文档，添加为这个文档，所以如果id值不存在的情况下，其作用等同于增加，注意：如果是修改，Json文档中的字段要写全</li><li>增量修改：<code>POST/索引库名/_update/文档id&#123;&quot;doc&quot;:&#123;字段&#125;&#125;</code>增量修改中，只需要写你需要修改的字段即可</li></ul></li></ul><h4 id="批量处理"><a href="#批量处理" class="headerlink" title="批量处理"></a>批量处理</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">POST /_bulk</span><br><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;index&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span><span class="string">&quot;索引库名&quot;</span><span class="punctuation">,</span><span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span><span class="string">&quot;新增id&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;字段1&quot;</span><span class="punctuation">:</span><span class="string">&quot;值1&quot;</span><span class="punctuation">,</span><span class="attr">&quot;字段2&quot;</span><span class="punctuation">:</span><span class="string">&quot;值2&quot;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment">//这两行为新增，第一行为指定索引库以及数据id，第二行是具体数据，在批量处理中，可以同时执行多个</span></span><br><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;delete&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span><span class="string">&quot;索引库名&quot;</span><span class="punctuation">,</span><span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span><span class="string">&quot;删除id&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment">//这一行为删除，只需指定删除的索引库名以及具体id即可</span></span><br><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;update&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;_index&quot;</span><span class="punctuation">:</span><span class="string">&quot;索引库名&quot;</span><span class="punctuation">,</span><span class="attr">&quot;_id&quot;</span><span class="punctuation">:</span><span class="string">&quot;更新id&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;doc&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;字段1&quot;</span><span class="punctuation">:</span><span class="string">&quot;值1&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment">//这两行为修改，第一行指定索引库名和id，第二行是修改的具体内容</span></span><br></pre></td></tr></table></figure><h2 id="客户端JavaClient"><a href="#客户端JavaClient" class="headerlink" title="客户端JavaClient"></a>客户端JavaClient</h2><h3 id="索引库操作"><a href="#索引库操作" class="headerlink" title="索引库操作"></a>索引库操作</h3><h4 id="客户端初始化"><a href="#客户端初始化" class="headerlink" title="客户端初始化"></a>客户端初始化</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ElasticTest</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> RestHighLevelClient client;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@BeforeEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setUp</span><span class="params">()</span>&#123;</span><br><span class="line">        client = <span class="keyword">new</span> <span class="title class_">RestHighLevelClient</span>(RestClient.builder(</span><br><span class="line">            HttpHost.create(<span class="string">&quot;http://192.168.150.101:9200&quot;</span>)</span><br><span class="line">        ));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@AfterEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">tearDown</span><span class="params">()</span> <span class="keyword">throw</span> IOException &#123;</span><br><span class="line">        <span class="keyword">if</span>(client != <span class="literal">null</span>)&#123;</span><br><span class="line">            client.close();</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><h4 id="添加索引库"><a href="#添加索引库" class="headerlink" title="添加索引库"></a>添加索引库</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testCreateHotelIndex</span><span class="params">()</span><span class="keyword">throws</span> IOException&#123;</span><br><span class="line">    <span class="comment">//1.创建request对象</span></span><br><span class="line">    <span class="type">CreteIndexRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreteIndexRequest</span>(<span class="string">&quot;索引库名&quot;</span>);</span><br><span class="line">    <span class="comment">//2.请求参数，MAPPING_TEMPLATE是静态常量字符串，内容是JSON格式请求体</span></span><br><span class="line">    request.source(MAPPING_TEMPLATE,XContentType.JSON);</span><br><span class="line">    <span class="comment">//3.发起请求</span></span><br><span class="line">    client.indics.create(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="删除索引库"><a href="#删除索引库" class="headerlink" title="删除索引库"></a>删除索引库</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testCreateHotelIndex</span> <span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">//1.创建Request对象</span></span><br><span class="line">    <span class="type">CreteIndexRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreteIndexRequest</span>(<span class="string">&quot;索引库名&quot;</span>);</span><br><span class="line">    <span class="comment">//2.发起请求</span></span><br><span class="line">    client.indics.delete(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="查询索引库"><a href="#查询索引库" class="headerlink" title="查询索引库"></a>查询索引库</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testCreateHotelIndex</span> <span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">//1.创建Request对象</span></span><br><span class="line">    <span class="type">CreteIndexRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreteIndexRequest</span>(<span class="string">&quot;索引库名&quot;</span>);</span><br><span class="line">    <span class="comment">//2.发起请求</span></span><br><span class="line">    client.indics.get(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>索引库一般不支持修改已有的字段，但支持修改未有的字段，换句话说，就是支持增加新的字段</p><h3 id="文档操作"><a href="#文档操作" class="headerlink" title="文档操作"></a>文档操作</h3><h4 id="新增文档"><a href="#新增文档" class="headerlink" title="新增文档"></a>新增文档</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testIndexDocument</span> <span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">//1.创建request对象</span></span><br><span class="line">    <span class="type">IndexRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IndexRequest</span>(<span class="string">&quot;索引库名&quot;</span>).id(<span class="string">&quot;新增id&quot;</span>);</span><br><span class="line">    <span class="comment">//2.准备Json文档</span></span><br><span class="line">    request.source(JSONUtil.toJsonStr(item),XContentType.JSON);</span><br><span class="line">    <span class="comment">//3.发送请求</span></span><br><span class="line">    client.index(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="删除文档"><a href="#删除文档" class="headerlink" title="删除文档"></a>删除文档</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testIndexDocument</span> <span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">//1.创建request对象</span></span><br><span class="line">    <span class="type">DeleteRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DeleteRequest</span>(<span class="string">&quot;索引库名&quot;</span>,<span class="string">&quot;删除id&quot;</span>);</span><br><span class="line">    <span class="comment">//2.发送请求</span></span><br><span class="line">    client.delete(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="查询文档"><a href="#查询文档" class="headerlink" title="查询文档"></a>查询文档</h4><figure class="highlight 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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testIndexDocument</span> <span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">//1.创建request对象</span></span><br><span class="line">    <span class="type">GetRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GetRequest</span>(<span class="string">&quot;索引库名&quot;</span>,<span class="string">&quot;查询id&quot;</span>);</span><br><span class="line">    <span class="comment">//2.发送请求，得到结果</span></span><br><span class="line">    <span class="type">GetReponse</span> <span class="variable">response</span> <span class="operator">=</span> client.get(request,RequestOptions.DEFAULT);</span><br><span class="line">    <span class="comment">//3.解析结果</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> response.getSourceAsString();</span><br><span class="line">    <span class="type">Item</span> <span class="variable">item</span> <span class="operator">=</span> JSONUtil.toBean(json,Item.class);</span><br><span class="line">    System.out.println(item);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="修改文档"><a href="#修改文档" class="headerlink" title="修改文档"></a>修改文档</h4><ul><li><p>全量更新：与新增文档的API完全相同，只不过第一次使用如果没有id，就是新增，再次使用就是修改，注意要传入完整的参数</p></li><li><p>局部更新：只更新指定字段</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testIndexDocument</span> <span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">//1.创建Request对象</span></span><br><span class="line">    <span class="type">UpdateRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UpdateRequest</span>(<span class="string">&quot;索引库名&quot;</span>,<span class="string">&quot;修改id&quot;</span>);</span><br><span class="line">    <span class="comment">//2.准备参数</span></span><br><span class="line">    request.doc(</span><br><span class="line">        <span class="string">&quot;age&quot;</span>,<span class="number">18</span>,</span><br><span class="line">        <span class="string">&quot;name&quot;</span>,<span class="string">&quot;Rose&quot;</span></span><br><span class="line">    );</span><br><span class="line">    <span class="comment">//3.更新文档</span></span><br><span class="line">    client.update(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h4 id="批量处理-1"><a href="#批量处理-1" class="headerlink" title="批量处理"></a>批量处理</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testBulk</span> <span class="params">()</span> <span class="keyword">throws</span> IOException&#123;</span><br><span class="line">    <span class="comment">//1.创建Bulk请求</span></span><br><span class="line">    <span class="type">BulkRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BulkRequest</span>();</span><br><span class="line">    <span class="comment">//2.添加要批量提交的请求</span></span><br><span class="line">    request.add(<span class="keyword">new</span> <span class="title class_">IndexRequest</span>(<span class="string">&quot;索引库名&quot;</span>).id(<span class="string">&quot;添加id&quot;</span>).source(<span class="string">&quot;json source&quot;</span>,XContentType.JSON));</span><br><span class="line">    request.add(<span class="keyword">new</span> <span class="title class_">IndexRequest</span>(<span class="string">&quot;索引库名&quot;</span>).id(<span class="string">&quot;添加id&quot;</span>).source(<span class="string">&quot;json source&quot;</span>,XContentType.JSON));</span><br><span class="line">    <span class="comment">//3.发起bulkqingq</span></span><br><span class="line">    client.bulk(request,RequestOptions.DEFAULT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="DSL查询"><a href="#DSL查询" class="headerlink" title="DSL查询"></a>DSL查询</h2><p>由于以上的查询，都只能通过id查询，但是搜索引擎中一定要具备根据复杂条件查询的功能，所以DSL查询可以达到这种功能</p><h3 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;查询类型&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;查询条件&quot;</span><span class="punctuation">:</span><span class="string">&quot;条件值&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="叶子查询"><a href="#叶子查询" class="headerlink" title="叶子查询"></a>叶子查询</h3><p><strong>全文检索查询</strong>：利用分词器对用户输入内容分词，然后去词条列表中匹配，拿到相应的文档id，最后根据id查到文档。例如：<code>match_query</code>，<code>multi_match_query</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;FILED&quot;</span><span class="punctuation">:</span><span class="string">&quot;TEXT&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment">//multi_match允许同时查询多个字段</span></span><br><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;multi_match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="string">&quot;TEXT&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="string">&quot;FIELD1&quot;</span><span class="punctuation">,</span><span class="string">&quot;FIELD2&quot;</span><span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>精确查询</strong>：不对用户输入的内容分词，直接精确匹配，一般是查找keyword，数值，日期，布尔等类型。例如：<code>ids</code>,<code>range</code>,<code>term</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//term查询</span></span><br><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;FIELD&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span><span class="string">&quot;VALUE&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment">//range查询</span></span><br><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;range&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;FIELD&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;gte&quot;</span><span class="punctuation">:</span><span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;lte&quot;</span><span class="punctuation">:</span><span class="number">20</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>地理查询</strong>：用于搜索地理位置，搜索方式有很多，例如：<code>geo_distance</code>,<code>gep_bouding_box</code></p><h3 id="复合查询"><a href="#复合查询" class="headerlink" title="复合查询"></a>复合查询</h3><p>由多个叶子查询组成的称为符合查询，分为两类，一类是基于逻辑运算组合叶子查询，实现组合条件，例如布尔查询</p><p>另一类是基于某种算法修改查询时的文档相关性算分，从而改变文档排名</p><p><strong>布尔查询</strong>是一个或多个查询子句的组合。子查询的组合方式有：</p><ul><li>must：必须匹配每个子查询，类似”与”</li><li>should：选择性匹配子查询，类似”或”</li><li>must_not：必须不匹配，不参与算分，类似”非”</li><li>filter：必须匹配，不参与算分</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Get /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;must&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span><span class="attr">&quot;match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span><span class="string">&quot;手机&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;should&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span><span class="attr">&quot;term&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;brand&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span><span class="string">&quot;vivo&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span><span class="attr">&quot;term&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;brand&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;value&quot;</span><span class="punctuation">:</span><span class="string">&quot;小米&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;must_not&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span><span class="attr">&quot;range&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;price&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;gte&quot;</span><span class="punctuation">:</span><span class="number">2500</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;filter&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span><span class="attr">&quot;range&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;price&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;lte&quot;</span><span class="punctuation">:</span><span class="number">1000</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="排序与分页"><a href="#排序与分页" class="headerlink" title="排序与分页"></a>排序与分页</h3><p>默认根据相关性算分来排序，也可以指定字段排序。可以排序字段类型有：keyword类型，数值类型，地理坐标类型，日期类型等</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span>...<span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;sort&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">            <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;FIELD&quot;</span><span class="punctuation">:</span><span class="string">&quot;desc&quot;</span> </span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>ES默认情况下只返回top10的数据。如果想要查询更多数据就需要修改分页参数。ES通过修改<code>from</code>，<code>size</code>参数来控制要返回的分页结果，<code>from</code>时从第几个文档开始，<code>size</code>：总共查询几个文档</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Get/索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span>...<span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;from&quot;</span><span class="punctuation">:</span><span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;sort&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span><span class="attr">&quot;price&quot;</span><span class="punctuation">:</span><span class="string">&quot;asc&quot;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>深度分页问题</strong></p><p>例如我要查第一千页的数据，每页有十条，那也就是第9990条数据到第一万条数据，然后我实际需要查到前一万条数据，再从其中选出需要的数据，在分片集群中，我就需要在每一个分片中查一万条数据汇总取出前一万，以此类推，随着查询页数的增加，我要在每个分片中查到的数据增多，最后又可能造成爆内存的现象，这就是深度分页问题</p><p>解决方案 <strong>search after</strong></p><p><code>search_after</code>是一种基于游标的分页机制，它允许你指定从某个特定的文档之后开始检索数据，从而避免了传统的分页方式中需要跳过大量数据的问题。具体来说，<code>search_after</code>需要你在首次查询时指定排序字段和排序方向，并在查询结果中获取最后一个文档的排序字段值。在后续的查询中，你可以将这个值作为<code>search_after</code>参数传递给Elasticsearch，以从该文档之后开始检索数据。避免从零开始获取<code>from + size</code>条数据的低效方式。相反，<code>search_after</code>允许我们根据上一次查询结果中最后一个文档的排序字段值（即游标）来继续向后获取数据段。</p><ul><li>优点：显著提高查询性能，支持深度分页</li><li>缺点：只能向后逐页查询，不能随即分页</li></ul><h3 id="高亮显示"><a href="#高亮显示" class="headerlink" title="高亮显示"></a>高亮显示</h3><p>要想使查询关键词在文档中高亮，我们需要在文档中拿到所有关键词，在html页面中为其加入一个统一的标签，在css样式中为这种标签加入同意的样式就可以实现高亮显示了</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;FIELD&quot;</span><span class="punctuation">:</span><span class="string">&quot;TEXT&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;highlight&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;fields&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span>  <span class="comment">//指定要高亮的字段</span></span><br><span class="line">            <span class="attr">&quot;FIELD&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;pre_tags&quot;</span><span class="punctuation">:</span><span class="string">&quot;&lt;em&gt;&quot;</span><span class="punctuation">,</span>  <span class="comment">//高亮的前置标签</span></span><br><span class="line">                <span class="attr">&quot;post_tags&quot;</span><span class="punctuation">:</span><span class="string">&quot;&lt;/em&gt;&quot;</span>  <span class="comment">//高亮的后置标签</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="JavaClient查询"><a href="#JavaClient查询" class="headerlink" title="JavaClient查询"></a>JavaClient查询</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">testMAtchAll</span> <span class="params">()</span> <span class="keyword">throws</span> IOException&#123;</span><br><span class="line">    <span class="comment">//1.准备request</span></span><br><span class="line">    <span class="type">SearchRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchRequest</span>(<span class="string">&quot;索引库名&quot;</span>);</span><br><span class="line">    <span class="comment">//2.组织DSL参数</span></span><br><span class="line">    request.source()</span><br><span class="line">        .query(QueryBuilders.matchAllQuery());</span><br><span class="line">    <span class="comment">//3.发送请求，得到响应结果</span></span><br><span class="line">    <span class="type">SearchResponse</span> <span class="variable">response</span> <span class="operator">=</span> client.search(request,RequestOptions.DEFAULT);</span><br><span class="line">    <span class="comment">//4.解析结果</span></span><br><span class="line">    <span class="type">SearchHits</span> <span class="variable">searchHits</span> <span class="operator">=</span> response.getHits();</span><br><span class="line">    <span class="comment">//4.1查询的总条数</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">total</span> <span class="operator">=</span> searchHits.getTotalHits().value;</span><br><span class="line">    <span class="comment">//4.2查询的结果数组</span></span><br><span class="line">    SearchHits[] hits = searchHits.getHits();</span><br><span class="line">    <span class="keyword">for</span>(SearchHit : hits)&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> hit.getSourceAsString();</span><br><span class="line">        <span class="type">Item</span> <span class="variable">item</span> <span class="operator">=</span> JSONUtil.toBean(json,Item.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>复杂查询以及分页排序高亮显示</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//组织DSL参数</span></span><br><span class="line">request.source().query(QueryBuilders.boolQuery</span><br><span class="line">                      .must(QueryBuilders.matchQuery(<span class="string">&quot;name&quot;</span>,<span class="string">&quot;脱脂牛奶&quot;</span>))</span><br><span class="line">                      .filter(QueryBuilders.termQuery(<span class="string">&quot;brand&quot;</span>,<span class="string">&quot;德亚&quot;</span>))</span><br><span class="line">                      .filter(QueryBuilders.rangeQuery(<span class="string">&quot;price&quot;</span>).lt(<span class="number">30000</span>))</span><br><span class="line">);</span><br><span class="line"><span class="comment">//分页</span></span><br><span class="line">request.source.from(<span class="number">0</span>).size(<span class="number">5</span>);</span><br><span class="line"><span class="comment">//排序</span></span><br><span class="line">request.source.sort(<span class="string">&quot;price&quot;</span>,SortOrder.ASC);</span><br><span class="line"><span class="comment">//高亮显示</span></span><br><span class="line">request.source.highlighter(</span><br><span class="line">    SearchSourceBuilder.highlight()</span><br><span class="line">                .filed(<span class="string">&quot;name&quot;</span>)</span><br><span class="line">                .preTags(<span class="string">&quot;&lt;em&gt;&quot;</span>)</span><br><span class="line">                .postTags(<span class="string">&quot;&lt;/em&gt;&quot;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="数据聚合"><a href="#数据聚合" class="headerlink" title="数据聚合"></a>数据聚合</h2><p>聚合可以实现对文档数据的统计，分析，运算。聚合常见的有三类</p><ul><li><p>桶(Bucket)聚合：用来对文档做分组</p><p><code>TermAggregation</code>：按照文档字段值分组</p><p><code>Date Histogram</code>：按照日期阶梯分组，例如一周为一组，一月为一组</p></li><li><p>度量(Metric)聚合：用于计算一些值，比如：最大值，最小值，平均值等</p><p><code>Avg</code>：求平均值</p><p><code>Max</code>：求最大值</p><p><code>Min</code>：求最小值</p><p><code>Stats</code>：同时求<code>max</code>，<code>min</code>，<code>avg</code>，<code>sum</code>等</p></li><li><p>管道(popeline)聚合：其他聚合的结果为基础做聚合</p></li></ul><h3 id="DSL聚合"><a href="#DSL聚合" class="headerlink" title="DSL聚合"></a>DSL聚合</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//普通桶聚合</span></span><br><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;match_all&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span>  <span class="comment">//可以省略</span></span><br><span class="line">    <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span>  <span class="comment">//设置size为0，结果中不包含文档，只包含聚合结果</span></span><br><span class="line">    <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span>  <span class="comment">//定义聚合</span></span><br><span class="line">        <span class="attr">&quot;cateAgg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span>  <span class="comment">//给聚合起个名字</span></span><br><span class="line">            <span class="attr">&quot;terms&quot;</span> <span class="punctuation">:</span><span class="punctuation">&#123;</span>  <span class="comment">//聚合的类型，按照品牌值聚合，所以选term</span></span><br><span class="line">                <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;category&quot;</span><span class="punctuation">,</span> <span class="comment">//参与聚合的字段</span></span><br><span class="line">                <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">20</span>  <span class="comment">//希望获取的聚合结果数量</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="comment">//度量聚合</span></span><br><span class="line">GET /索引库名/_search</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;trem&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;category&quot;</span><span class="punctuation">:</span><span class="string">&quot;手机&quot;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;brand_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;brand&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span> <span class="comment">//做度量聚合，要在桶聚合内在嵌套一个聚合</span></span><br><span class="line">                <span class="attr">&quot;price_stats&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;stats&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;price&quot;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="Java客户端数据聚合"><a href="#Java客户端数据聚合" class="headerlink" title="Java客户端数据聚合"></a>Java客户端数据聚合</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">request.source.query(QueryBuilders.term(<span class="string">&quot;category&quot;</span>:<span class="string">&quot;手机&quot;</span>));</span><br><span class="line">request.source.size(<span class="number">0</span>);</span><br><span class="line">request.source.aggregation(</span><br><span class="line">    AggregationBuilders</span><br><span class="line">          .terms(<span class="string">&quot;brand_agg&quot;</span>)</span><br><span class="line">          .field(<span class="string">&quot;brand&quot;</span>)</span><br><span class="line">          .size(<span class="number">20</span>)</span><br><span class="line">);</span><br><span class="line"><span class="comment">//解析聚合结果</span></span><br><span class="line"><span class="type">Aggregations</span> <span class="variable">aggregations</span> <span class="operator">=</span> response.getAggregations();</span><br><span class="line"><span class="comment">//根据名称获取聚合结果</span></span><br><span class="line"><span class="type">Terms</span> <span class="variable">brandTerms</span> <span class="operator">=</span> aggregations.get(<span class="string">&quot;brand_agg&quot;</span>);</span><br><span class="line"><span class="comment">//获取桶</span></span><br><span class="line">List&lt;? <span class="keyword">extends</span> <span class="title class_">Term</span>.Bucket&gt; buckets = brandTerms.getBuckets();</span><br><span class="line"><span class="comment">//遍历</span></span><br><span class="line"><span class="keyword">for</span>(Terms.Bucket bucket : buckets)&#123;</span><br><span class="line">    <span class="comment">//获取key，也就是品牌信息</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">brandName</span> <span class="operator">=</span> bucket.getKeyAsString();</span><br><span class="line">    System.out.println(brandName);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/07/13/Elasticsearch%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>高并发问题优化</title>
      <link>https://www.yhblog.xyz/2024/07/13/%E9%AB%98%E5%B9%B6%E5%8F%91%E9%97%AE%E9%A2%98%E4%BC%98%E5%8C%96/</link>
      <guid>https://www.yhblog.xyz/2024/07/13/%E9%AB%98%E5%B9%B6%E5%8F%91%E9%97%AE%E9%A2%98%E4%BC%98%E5%8C%96/</guid>
      <pubDate>Sat, 13 Jul 2024 13:12:00 GMT</pubDate>
      
        
        
      <description>&lt;h2 id=&quot;几种常见事务失效原因&quot;&gt;&lt;a href=&quot;#几种常见事务失效原因&quot; class=&quot;headerlink&quot; title=&quot;几种常见事务失效原因&quot;&gt;&lt;/a&gt;几种常见事务失效原因&lt;/h2&gt;&lt;h3 id=&quot;方法自调用导致的事务失效&quot;&gt;&lt;a href=&quot;#方法自调用导致的事</description>
        
      
      
      
      <content:encoded><![CDATA[<h2 id="几种常见事务失效原因"><a href="#几种常见事务失效原因" class="headerlink" title="几种常见事务失效原因"></a>几种常见事务失效原因</h2><h3 id="方法自调用导致的事务失效"><a href="#方法自调用导致的事务失效" class="headerlink" title="方法自调用导致的事务失效"></a><strong>方法自调用导致的事务失效</strong></h3><ul><li>场景：在Spring中，声明式事务通常是通过AOP（面向切面编程）实现的，这意味着事务管理是通过代理对象对目标方法的调用进行增强的。然而，当同一个类中的方法A调用方法B，且方法B上使用了<code>@Transactional</code>注解时，如果这种调用是直接进行的（即非通过代理对象），那么事务的增强处理就不会被触发，从而导致事务失效。</li><li>原因：AOP代理通常是通过Spring的容器在运行时动态生成的，它仅对外部调用进行拦截和处理。对于类内部的直接方法调用，由于绕过了代理对象，因此无法应用事务的增强。</li><li>解决方案：既然调用的不是代理对象，那我们就想办法获取代理对象，通过<code>AopContext.currentProxy()</code>获取当前类的代理对象，并通过该代理对象调用方法B。但请注意，这种方式需要确保在Spring配置中启用了<code>@EnableAspectJAutoProxy(exposeProxy = true)</code></li></ul><h3 id="异常处理不当导致的事务失效"><a href="#异常处理不当导致的事务失效" class="headerlink" title="异常处理不当导致的事务失效"></a>异常处理不当导致的事务失效</h3><ul><li><p>场景：如果<code>@Transactional</code>注解的方法内部捕获了异常，并且没有将异常重新抛出，或者没有将捕获的异常类型指定为需要回滚的异常类型，那么Spring默认不会触发事务的回滚。</p></li><li><p>原因：Spring根据异常的抛出情况来决定是否回滚事务。默认情况下，只有运行时异常（<code>RuntimeException</code>及其子类）和错误（<code>Error</code>）会触发回滚。对于受检异常（<code>Exception</code>的子类，但排除<code>RuntimeException</code>及其子类），除非在<code>@Transactional</code>注解中明确指定，否则不会触发回滚。</p></li><li><p>解决方案</p><p>确保事务内部的方法被正确抛出</p><p>使用<code>@Transactional</code>注解的<code>rollbackFor</code>属性来指定需要回滚的异常类型，包括受检异常。</p></li></ul><h3 id="非Public方法上的事务失效"><a href="#非Public方法上的事务失效" class="headerlink" title="非Public方法上的事务失效"></a>非<code>Public</code>方法上的事务失效</h3><ul><li>场景：如果<code>@Transactional</code>注解被放置在非public方法上（如private、protected或默认访问权限的方法），那么事务将不会生效。</li><li>原因：Spring在创建代理对象时，仅会扫描和处理public方法的注解。非public方法由于访问权限的限制，无法被代理对象拦截和处理。</li><li>解决方案：将事务的方法改为<code>public</code>修饰</li></ul><h3 id="事务传播行为不对导致的事务失效"><a href="#事务传播行为不对导致的事务失效" class="headerlink" title="事务传播行为不对导致的事务失效"></a>事务传播行为不对导致的事务失效</h3><ul><li>场景：在一个事务方法中如果调用了其他事务方法，而事务的传播行为不当，例如使用了<code>REQUIRES_NEW</code>，就表示了进入该方法创建了一个新事务，这样会导致事务失效</li><li>原因：在抛出异常时，该方法作为独立事务，不会随主方法进行回滚</li><li>解决方案：慎用传播行为</li></ul><h3 id="没有被Spring管理导致的事务失效"><a href="#没有被Spring管理导致的事务失效" class="headerlink" title="没有被Spring管理导致的事务失效"></a>没有被Spring管理导致的事务失效</h3><ul><li>场景：若一个方法没有被<code>Spring</code>管理，则事务失效</li><li>原因：若不被<code>Spring</code>管理，就没有人替你生成代理对象，事务自然失效</li><li>解决方案：注意该类有没有被<code>Spring</code>所管理</li></ul><h3 id="额外知识补充（事务传播行为）"><a href="#额外知识补充（事务传播行为）" class="headerlink" title="额外知识补充（事务传播行为）"></a>额外知识补充（事务传播行为）</h3><ol><li><strong>PROPAGATION_REQUIRED</strong>（默认）：如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。</li><li><strong>PROPAGATION_SUPPORTS</strong>：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务方式执行。</li><li><strong>PROPAGATION_MANDATORY</strong>：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。</li><li><strong>PROPAGATION_REQUIRES_NEW</strong>：创建一个新的事务，并暂停当前事务（如果存在）。</li><li><strong>PROPAGATION_NOT_SUPPORTED</strong>：以非事务方式执行，如果当前存在事务，则把当前事务挂起。</li><li><strong>PROPAGATION_NEVER</strong>：以非事务方式执行，如果当前存在事务，则抛出异常。</li><li><strong>PROPAGATION_NESTED</strong>：如果当前存在事务，则在嵌套事务内执行。如果当前没有事务，则行为等同于<code>PROPAGATION_REQUIRED</code>。</li></ol><h2 id="高并发时的优化方案"><a href="#高并发时的优化方案" class="headerlink" title="高并发时的优化方案"></a>高并发时的优化方案</h2><h3 id="变同步为异步"><a href="#变同步为异步" class="headerlink" title="变同步为异步"></a>变同步为异步</h3><h4 id="适用范围"><a href="#适用范围" class="headerlink" title="适用范围"></a>适用范围</h4><p>变同步为异步主要用于那些业务逻辑复杂、处理时间较长、且对实时性要求不是非常高的场景。具体来说，当系统面临大量并发请求，且每个请求都需要进行多个数据库操作或调用多个远程服务时，采用同步处理方式会导致线程阻塞，影响系统整体的并发能力。此时，可以考虑将部分或全部业务逻辑异步化，以提高系统的响应速度和吞吐量。</p><h4 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h4><p>我们可以通过MQ(消息队列)将同步业务变为异步业务，从而提高效率，在业务处理流程中，当需要异步处理的任务产生时，将任务封装成消息发送到MQ的交换机中。交换机根据路由规则将消息发送到指定的队列。创建消费者监听队列中的消息。当队列中有消息时，消费者从队列中获取消息并进行处理。</p><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>无需等待复杂业务处理，大大减少响应时间</li><li>利用MQ暂存消息，起到流量削峰整形作用</li><li>降低写数据库频率，减轻数据库并发压力</li></ul><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ul><li>依赖于MQ的可靠性</li><li>降低了些频率，但是没有减少数据库写次数</li></ul><h3 id="合并写请求"><a href="#合并写请求" class="headerlink" title="合并写请求"></a>合并写请求</h3><h4 id="适用范围-1"><a href="#适用范围-1" class="headerlink" title="适用范围"></a>适用范围</h4><p>合并写请求主要用于那些写操作频繁且数据关联度较高的场景。例如，在电商系统中，用户可能同时购买多个商品并生成多个订单记录，这些订单记录可以合并为一个写请求发送到数据库进行处理。此外，对于日志记录、数据同步等场景，也可以考虑将多个写请求合并为一个进行处理。</p><h4 id="优化方案-1"><a href="#优化方案-1" class="headerlink" title="优化方案"></a>优化方案</h4><p>合并写请求就是指当写数据库并发较高时，不再直接写到数据库。而是先将数据缓存到Redis，然后定期将缓存中的数据批量写入数据库。由于redis时内存操作，所以写的效率大大提高，同时减少了DB操作，对数据库的压力减小</p><h4 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h4><ul><li>写缓存速度快，响应时间大大减少</li><li>降低数据库的写频率和写次数，大大减轻数据库压力</li><li>降低网络开销，提高数据传输效率</li></ul><h4 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h4><ul><li>实现相对复杂</li><li>依赖Redis可靠性</li><li>不支持事务和复杂业务</li></ul>]]></content:encoded>
      
      
      <category domain="https://www.yhblog.xyz/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/">微服务</category>
      
      
      
      <comments>https://www.yhblog.xyz/2024/07/13/%E9%AB%98%E5%B9%B6%E5%8F%91%E9%97%AE%E9%A2%98%E4%BC%98%E5%8C%96/#disqus_thread</comments>
      
    </item>
    
  </channel>
</rss>
