Haokang Mu https://muhaokang.github.io/ Haokang Mu 的个人网站,记录大数据技术笔记,关注大模型与数据应用。 zh-CN Thu, 29 Oct 2026 15:21:16 +0800 hkmu9248@gmail.com (Haokang Mu) 任务成功,数据为什么还是旧的?从调度依赖到数据就绪 https://muhaokang.github.io/data-readiness/ https://muhaokang.github.io/data-readiness/ Sun, 14 Sep 2025 08:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 日汇总任务显示成功,结果里的状态却还是昨天的;上游随后补齐数据,下游也没有自动变化。排查这种问题时,如果只看任务有没有报错,很容易错过真正的原因。 任务成功说明这次执行满足了程序和调度系统定义的成功条件。它是否读到了正确日期、正确版本且完整的数据,还要另外检查。 下面用订单与结算汇总构造一个例子。表名、时间和数量均为模拟数据,重点是看清链路中的判断条件... <p>日汇总任务显示成功,结果里的状态却还是昨天的;上游随后补齐数据,下游也没有自动变化。排查这种问题时,如果只看任务有没有报错,很容易错过真正的原因。</p> <p>任务成功说明这次执行满足了程序和调度系统定义的成功条件。它是否读到了正确日期、正确版本且完整的数据,还要另外检查。</p> <p>下面用订单与结算汇总构造一个例子。表名、时间和数量均为模拟数据,重点是看清链路中的判断条件。</p> <h2 id="1-先把时间线画出来">1. 先把时间线画出来</h2> <p>假设每天生成前一天的订单日汇总,需要订单明细和结算明细两份输入。9 月 14 日这次运行应处理业务日期 <code class="language-plaintext highlighter-rouge">2025-09-13</code>。</p> <table> <thead> <tr> <th>时间</th> <th>发生了什么</th> <th>当时可以读取的数据</th> </tr> </thead> <tbody> <tr> <td>08:50</td> <td>订单明细发布完成</td> <td>9 月 13 日订单数据</td> </tr> <tr> <td>09:00</td> <td>日汇总启动</td> <td>订单已齐,9 月 13 日结算数据还没发布</td> </tr> <tr> <td>09:03</td> <td>日汇总执行成功</td> <td>输出中的部分结算状态为空</td> </tr> <tr> <td>10:00</td> <td>结算明细发布完成</td> <td>9 月 13 日结算数据就绪</td> </tr> </tbody> </table> <p>如果日汇总使用左关联,暂时没有匹配结算记录的订单仍然可以输出。SQL 不一定报错,行数甚至可能与昨天接近。</p> <p>10 点以后,源表变了,已经落盘的日汇总通常不会随之重算。此时会同时看到“所有任务都是成功的”和“下游结果仍然不对”。</p> <p>这里至少有三个不同的时间概念:业务数据属于哪一天、任务实际在什么时候执行、某个输入版本什么时候发布。排查时应分别记录,不能用任务启动时间代替业务日期。Airflow 的数据区间与逻辑日期也明确区分了这些概念:逻辑日期表示数据区间的起点,并非实际执行时间。<a href="https://airflow.apache.org/docs/apache-airflow/2.10.5/core-concepts/dag-run.html#data-interval">官方说明</a></p> <h2 id="2-把下游需要什么写成明确条件">2. 把下游需要什么写成明确条件</h2> <p>“等上游好了再跑”不够具体。对这个日汇总,可以先约定四件事:</p> <table> <thead> <tr> <th>条件</th> <th>需要回答的问题</th> </tr> </thead> <tbody> <tr> <td>业务范围正确</td> <td>两个来源是否都覆盖目标业务日期?时区与业务日边界是否一致?</td> </tr> <tr> <td>输入版本确定</td> <td>下游读的是哪个已经发布的快照或批次?</td> </tr> <tr> <td>来源齐备</td> <td>订单、结算这两个必需来源是否都有就绪记录?</td> </tr> <tr> <td>约定校验通过</td> <td>记录数、业务主键、关键字段与对账结果是否满足约定?</td> </tr> </tbody> </table> <p>有了这些条件,调度依赖才有了清楚的目标。跨工作流等待也要对应到需要的数据区间,不能只挑“最近一个成功实例”。Airflow 的 <code class="language-plaintext highlighter-rouge">ExternalTaskSensor</code> 可以等待另一个工作流在指定逻辑日期的任务状态,但仍需要把逻辑日期映射到本业务真正需要的输入。<a href="https://airflow.apache.org/docs/apache-airflow/2.10.5/howto/operator/external_task_sensor.html">跨工作流依赖说明</a></p> <p>这个例子把“订单、结算都必须就绪”作为发布要求。实际链路也可以允许部分来源晚到,但必须明确输出是否为暂定结果、如何标记缺失、什么时候修正。不能让使用者从一个普通的空值猜测是哪种情况。</p> <h2 id="3-三个看似合理实际不够的检查">3. 三个看似合理、实际不够的检查</h2> <p><strong>分区存在,不代表装载完成。</strong></p> <p>任务可能先创建分区,再分批写入。下游一看到分区就读取,有机会读到空表或中间状态。更明确的方式是由上游发布完成标记,标记指向已经完成校验、可稳定读取的版本;下游读取那个版本,而不是一个还在变化的位置。</p> <p><strong>最新更新时间正常,不代表数据齐套。</strong></p> <p>假设本批应有 80 条记录,目前只有 50 条写入成功,其中一条更新时间是 09:59。<code class="language-plaintext highlighter-rouge">MAX(updated_at)</code> 看起来很新,却不能说明另外 30 条已经到达。时间指标适合检查新鲜度,覆盖度还需要来源清单、批次记录数或业务对账来验证。</p> <p><strong>结果为零,不一定是合法空批次。</strong></p> <p>没有权限、请求超时、日期条件写错、上游没有数据,都可能让调用方拿不到有效明细。先记录查询是否成功,再解释返回内容。只有来源明确声明该批次已经完成、预期记录数就是零,实际读取也为零,才符合本文的合法空批次约定。</p> <h2 id="4-一个可运行的三态检查">4. 一个可运行的三态检查</h2> <p>检查结果分成三种:</p> <ul> <li><code class="language-plaintext highlighter-rouge">PASS</code>:约定的检查都有证据支持,可以继续使用这批输入。</li> <li><code class="language-plaintext highlighter-rouge">BLOCK</code>:已经发现不满足约定的事实,例如业务日期错误或装载未完成。</li> <li><code class="language-plaintext highlighter-rouge">UNKNOWN</code>:证据不足,例如查询失败,或者没有独立的预期记录数。</li> </ul> <p>只有 <code class="language-plaintext highlighter-rouge">PASS</code> 才允许继续。<code class="language-plaintext highlighter-rouge">BLOCK</code> 与 <code class="language-plaintext highlighter-rouge">UNKNOWN</code> 都暂停消费,但后续动作不同:前者需要处理已知问题,后者需要补齐证据。如果两种情况同时存在,示例返回 <code class="language-plaintext highlighter-rouge">BLOCK</code>,同时保留所有原因。</p> <p>示例为每个来源保存以下字段:</p> <table> <thead> <tr> <th>字段</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td><code class="language-plaintext highlighter-rouge">business_date</code></td> <td>该快照对应的业务日期</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">publish_state</code></td> <td>上游是否已经发布完成</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">manifest_snapshot</code></td> <td>就绪清单声明的版本</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">observed_snapshot</code></td> <td>本次实际读取的版本</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">expected_rows</code></td> <td>来源清单独立提供的预期记录数</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">observed_rows</code></td> <td>在指定版本中实际读到的记录数</td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">query_ok</code></td> <td>读取这个版本是否成功</td> </tr> </tbody> </table> <p>这里的“独立”很重要。如果 <code class="language-plaintext highlighter-rouge">expected_rows</code> 也是对同一个不完整目标表执行 <code class="language-plaintext highlighter-rouge">COUNT(*)</code> 得到的,那么两者相等只是重复检查了同一份结果。</p> <p>已经成功读取的清单和随后对数据的查询是两份证据。即使后者失败,清单中已知的日期错误或未发布状态也不能被丢掉。对每个来源,核心判断可以用下面的伪代码表示;遍历来源与汇总原因的实现见<a href="/assets/examples/data_readiness.py">完整示例脚本</a>:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 先检查已经成功获得的清单证据,各项独立记录。 </span><span class="k">if</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">business_date</span> <span class="o">!=</span> <span class="n">target_date</span><span class="p">:</span> <span class="n">blocked</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"错误业务日期"</span><span class="p">)</span> <span class="k">if</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">publish_state</span> <span class="o">!=</span> <span class="s">"COMPLETE"</span><span class="p">:</span> <span class="n">blocked</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"批次还没有发布完成"</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">manifest_snapshot</span><span class="p">:</span> <span class="n">unknown</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"缺少清单版本号"</span><span class="p">)</span> <span class="n">expected_valid</span> <span class="o">=</span> <span class="p">(</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">expected_rows</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">expected_rows</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">expected_valid</span><span class="p">:</span> <span class="n">unknown</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"预期记录数不可用"</span><span class="p">)</span> <span class="c1"># 再检查数据;查询失败时保留前面已发现的问题。 </span><span class="k">if</span> <span class="ow">not</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">query_ok</span><span class="p">:</span> <span class="n">unknown</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"数据查询失败"</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">observed_snapshot</span><span class="p">:</span> <span class="n">unknown</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"缺少读取版本号"</span><span class="p">)</span> <span class="k">elif</span> <span class="p">(</span><span class="n">snapshot</span><span class="p">.</span><span class="n">manifest_snapshot</span> <span class="ow">and</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">manifest_snapshot</span> <span class="o">!=</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">observed_snapshot</span><span class="p">):</span> <span class="n">blocked</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"清单与读取版本不一致"</span><span class="p">)</span> <span class="n">observed_valid</span> <span class="o">=</span> <span class="p">(</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">observed_rows</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">observed_rows</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">observed_valid</span><span class="p">:</span> <span class="n">unknown</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"实际记录数不可用"</span><span class="p">)</span> <span class="k">elif</span> <span class="n">expected_valid</span> <span class="ow">and</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">expected_rows</span> <span class="o">!=</span> <span class="n">snapshot</span><span class="p">.</span><span class="n">observed_rows</span><span class="p">:</span> <span class="n">blocked</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="s">"记录数不符合约定"</span><span class="p">)</span> <span class="n">status</span> <span class="o">=</span> <span class="s">"BLOCK"</span> <span class="k">if</span> <span class="n">blocked</span> <span class="k">else</span> <span class="s">"UNKNOWN"</span> <span class="k">if</span> <span class="n">unknown</span> <span class="k">else</span> <span class="s">"PASS"</span> </code></pre></div></div> <p>下载脚本后直接运行,无需安装数据库或连接任何外部服务:</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 data_readiness.py </code></pre></div></div> <p>本次使用 Python 3.9.6 验证,脚本只依赖标准库。输出如下,每个场景都包含结果断言:</p> <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ready: PASS stale_partition: BLOCK partition_created: BLOCK partial_load: BLOCK wrong_snapshot: BLOCK missing_source: BLOCK query_failed: UNKNOWN empty_without_evidence: UNKNOWN valid_empty_batch: PASS mixed_evidence: BLOCK (both reasons retained) same_source_mixed_evidence: BLOCK (all known reasons retained) All 11 scenarios passed. </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">missing_source</code> 的前提是完整查询了就绪清单,确认必需来源没有记录。如果查询清单本身失败,应直接得到 <code class="language-plaintext highlighter-rouge">UNKNOWN</code>,不能把失败包装成空清单。<code class="language-plaintext highlighter-rouge">valid_empty_batch</code> 通过的原因则是清单明确声明了零条,且对应版本已经发布完成。</p> <p>最后一个场景中,同一来源的清单已经显示业务日期错误、批次未发布,随后读取数据又失败。因此结果为 <code class="language-plaintext highlighter-rouge">BLOCK</code>,同时保留查询失败的 <code class="language-plaintext highlighter-rouge">UNKNOWN</code> 原因;不会因为读数据失败就覆盖掉已经确认的问题。</p> <p>这只是一个最小示例。行数一致也不能证明内容正确:少一条、多一条可能恰好抵消,还可能存在金额错误、错误主键或来源间截止时点不一致。根据业务继续增加唯一性、范围覆盖、关键字段和金额对账,并保证校验和消费使用同一版本,才不会在“检查通过”之后又读到另一批数据。</p> <h2 id="5-修复时先恢复条件再决定补跑范围">5. 修复时先恢复条件,再决定补跑范围</h2> <p>回到最初的时间线,把日汇总固定推迟到 10:30 可能暂时有效,但如果上游某天延迟到 11 点,同样的问题还会出现。固定时间可以作为等待上限或运行窗口,数据依赖和就绪条件仍然需要表达清楚。</p> <p>更稳妥的处理顺序是:</p> <ol> <li>确认日汇总实际消费的业务日期、版本和来源,圈定受影响的输出。</li> <li>修正依赖关系或就绪检查,确保后续运行不会再次提前消费。</li> <li>等上游目标版本发布并校验通过,判断日汇总及其下游哪些结果需要重算。</li> <li>明确重算是覆盖分区、按业务键合并还是追加写入,避免一次修复造成新的重复。</li> <li>验证最终消费层,包括已生成的导出文件或缓存;只看重跑实例成功还不够。</li> </ol> <p>能否重复执行而不改变正确结果,是补跑前需要回答的问题。如果一个任务采用直接追加写入,就不能默认重跑一次只是“刷新一下”。</p> <p>数据链路的检查最终要落到一个具体问题:<strong>下游这次需要的那份数据,现在是否可以被稳定、完整地读取?</strong> 任务状态、分区、时间戳和记录数都是证据的一部分,把它们放在同一个业务日期和版本下核对,判断才有意义。</p> <hr /> <p>本文为历史专题补记,按 2025-09-14 归档,于 2026-10-29 整理并验证示例。</p> 去重不是 DISTINCT:业务主键、历史事件与最新状态 https://muhaokang.github.io/dedup-and-latest-state/ https://muhaokang.github.io/dedup-and-latest-state/ Sun, 16 Jun 2024 08:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 处理一张不断追加的事件表时,“按订单去重”听起来很明确,真正写 SQL 才会发现:重复到达的消息、同一订单的不同版本、取消后新建的订单,可能全挤在一张表里。 这些记录都能让订单号重复,但处理方式不同。把它们统一压成一行,行数可能变少,结果却未必正确。 下面用一组模拟订单说明三个问题:什么算同一件事,什么算同一个对象,以及哪条记录代表最新状态。 本文的指... <p>处理一张不断追加的事件表时,“按订单去重”听起来很明确,真正写 SQL 才会发现:重复到达的消息、同一订单的不同版本、取消后新建的订单,可能全挤在一张表里。</p> <p>这些记录都能让订单号重复,但处理方式不同。把它们统一压成一行,行数可能变少,结果却未必正确。</p> <p>下面用一组模拟订单说明三个问题:<strong>什么算同一件事,什么算同一个对象,以及哪条记录代表最新状态。</strong> 本文的指标是“当前有效订单数与金额”,不涉及收入确认、回款或结算。</p> <h2 id="1-先写清楚三种粒度">1. 先写清楚三种粒度</h2> <p>假设订单状态变化时,上游会产生一条带完整状态的事件。这个案例约定:</p> <table> <thead> <tr> <th>要回答的问题</th> <th>使用的键</th> <th>说明</th> </tr> </thead> <tbody> <tr> <td>这是不是同一个订单?</td> <td><code class="language-plaintext highlighter-rouge">tenant_id + order_id</code></td> <td>订单号只在租户内部唯一</td> </tr> <tr> <td>这是不是同一次事件的重放?</td> <td><code class="language-plaintext highlighter-rouge">tenant_id + event_id</code></td> <td>同一事件可能投递多次,事件编号保持不变</td> </tr> <tr> <td>哪个状态更新?</td> <td>订单内的 <code class="language-plaintext highlighter-rouge">source_version</code></td> <td>来源系统提供递增版本,每个版本对应一个完整状态</td> </tr> </tbody> </table> <p>另有一个 <code class="language-plaintext highlighter-rouge">request_id</code>,表示订单来自哪个业务申请。旧订单取消后,可以在同一申请下新建订单,所以它不能直接代替订单主键。</p> <p>这个区分决定了处理顺序:先识别重复投递,再保留不同状态版本,最后根据查询目的生成当前状态表。事件明细与当前状态表可以同时存在,各自服务不同的问题。</p> <h2 id="2-准备一组会暴露问题的数据">2. 准备一组会暴露问题的数据</h2> <p>模拟数据包含 10 条到达记录,分别覆盖下面的情况。<code class="language-plaintext highlighter-rouge">ACTIVE</code> 表示有效,<code class="language-plaintext highlighter-rouge">CANCELLED</code> 表示取消。</p> <table> <thead> <tr> <th>订单</th> <th style="text-align: right">金额</th> <th>状态变化</th> <th>特殊情况</th> </tr> </thead> <tbody> <tr> <td>T1 / O100</td> <td style="text-align: right">100</td> <td>v1 有效 → v2 取消</td> <td>v2 事件重复投递一次</td> </tr> <tr> <td>T1 / O101</td> <td style="text-align: right">100</td> <td>v1 有效</td> <td>和 O100 来自同一申请 R100,是取消后新建的另一笔订单</td> </tr> <tr> <td>T1 / O200</td> <td style="text-align: right">80</td> <td>v1 有效 → v2 取消</td> <td>v1 在 11:30 才到达,晚于 11:10 到达的 v2</td> </tr> <tr> <td>T1 / O300</td> <td style="text-align: right">50</td> <td>v1 有效 → v2 取消 → v3 有效</td> <td>v2、v3 的业务时间都记为 12:05:00</td> </tr> <tr> <td>T2 / O100</td> <td style="text-align: right">200</td> <td>v1 有效</td> <td>与 T1 的订单号相同,租户不同</td> </tr> </tbody> </table> <p>全量到达后,预期有效订单是 T1/O101、T1/O300、T2/O100,共 <strong>3 笔,金额 350</strong>。取消的 O100 和 O200 仍然保留在状态表中,只是不参与这个指标。</p> <p>完整建表、数据与断言放在<a href="/assets/examples/dedup_and_latest_state.py">可运行示例</a>里。下载文件后可以在独立环境运行:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> venv .venv <span class="nb">source</span> .venv/bin/activate python3 <span class="nt">-m</span> pip <span class="nb">install</span> <span class="s2">"duckdb==1.4.5"</span> python3 dedup_and_latest_state.py </code></pre></div></div> <p>本次使用 Python 3.9.6、DuckDB 1.4.5 验证。这是本次补记的验证环境,不代表 2024 年使用过这些版本。数据和查询都在内存中运行,不连接外部数据库。</p> <h2 id="3-先处理事件重放">3. 先处理事件重放</h2> <p>O100 的取消事件 E02 到达了两次,业务字段相同,<code class="language-plaintext highlighter-rouge">ingested_at</code> 不同。直接执行 <code class="language-plaintext highlighter-rouge">SELECT DISTINCT *</code>,仍然得到 10 行,因为到达时间也是参与比较的字段。</p> <p>如果按事件幂等键处理,则应该留下 9 个逻辑事件。但在保留某一条之前,要先确认同一个事件编号没有对应不同内容:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">event_id</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="k">DISTINCT</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">event_id</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">source_version</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">amount</span><span class="p">,</span> <span class="n">event_time</span> <span class="k">FROM</span> <span class="n">raw_events</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">payloads</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">event_id</span> <span class="k">HAVING</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">;</span> </code></pre></div></div> <p>这条检查应返回空集。它刻意不比较 <code class="language-plaintext highlighter-rouge">ingested_at</code>,因为重复投递允许有不同的到达时间。这里列出的字段构成了示例的完整业务载荷;真实事件还有其他业务字段时,也必须纳入一致性检查。</p> <p>检查通过后,再按事件编号保留第一次到达:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">VIEW</span> <span class="n">events</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">event_id</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">source_version</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">amount</span><span class="p">,</span> <span class="n">event_time</span><span class="p">,</span> <span class="n">ingested_at</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="o">*</span><span class="p">,</span> <span class="n">ROW_NUMBER</span><span class="p">()</span> <span class="n">OVER</span> <span class="p">(</span> <span class="k">PARTITION</span> <span class="k">BY</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">event_id</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">ingested_at</span> <span class="k">ASC</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">replay_rank</span> <span class="k">FROM</span> <span class="n">raw_events</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">ranked</span> <span class="k">WHERE</span> <span class="n">replay_rank</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> </code></pre></div></div> <p>这里选择第一次到达,只是为了保留一个代表记录。同一事件的业务载荷已经检查为一致,即使两次到达时间也相同,选中哪份副本都不影响业务状态。</p> <p>如果一个事件编号出现两种金额或状态,应先隔离冲突并核对来源。任取一条会隐藏问题。上游没有稳定事件编号时,也不能把包含到达时间的整行哈希直接当成幂等键,否则同一事件重放后仍会变成“新事件”。</p> <h2 id="4-再为每个订单选最新状态">4. 再为每个订单选最新状态</h2> <p>事件去重后,一笔订单仍然可以有多个版本。这是应该保留的历史。</p> <p>本例中,来源系统承诺每个订单的版本递增,且一个版本只代表一个逻辑事件。先检查这个约定:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">source_version</span> <span class="k">FROM</span> <span class="n">events</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">source_version</span> <span class="k">HAVING</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">;</span> </code></pre></div></div> <p>检查通过后,按完整订单主键分组、来源版本倒序,就能得到唯一的最新状态:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">VIEW</span> <span class="n">latest_state</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">source_version</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">amount</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="o">*</span><span class="p">,</span> <span class="n">ROW_NUMBER</span><span class="p">()</span> <span class="n">OVER</span> <span class="p">(</span> <span class="k">PARTITION</span> <span class="k">BY</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">order_id</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">source_version</span> <span class="k">DESC</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">state_rank</span> <span class="k">FROM</span> <span class="n">events</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">ranked</span> <span class="k">WHERE</span> <span class="n">state_rank</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> </code></pre></div></div> <p>这里的排序能够确定结果,是因为前置检查和来源约定保证了订单内的版本唯一。<code class="language-plaintext highlighter-rouge">ROW_NUMBER()</code> 本身不会替我们判断两个冲突版本谁更可信。它按窗口中的分组和排序规则编号,具体语义可以查阅 <a href="https://duckdb.org/docs/1.4/sql/functions/window_functions">DuckDB 窗口函数文档</a>。</p> <p>如果同一版本对应两个不同状态,补一个 <code class="language-plaintext highlighter-rouge">event_id DESC</code> 虽然能让结果看起来稳定,却不能证明选中的状态正确。应先解决版本冲突;如果来源只有秒级时间,没有可靠版本或顺序,需要明确额外的排序依据,或者把这批记录标记为顺序不确定。</p> <h2 id="5-先取最新再过滤有效状态">5. 先取最新,再过滤有效状态</h2> <p>得到 <code class="language-plaintext highlighter-rouge">latest_state</code> 后,指标计算很直接:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">active_orders</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">active_amount</span> <span class="k">FROM</span> <span class="n">latest_state</span> <span class="k">WHERE</span> <span class="n">status</span> <span class="o">=</span> <span class="s1">'ACTIVE'</span><span class="p">;</span> </code></pre></div></div> <p>结果是 <code class="language-plaintext highlighter-rouge">3 / 350.00</code>。如果把状态条件提前放进窗口计算内部,含义就变了:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 错误示例:计算的是“最近一次有效”,不是“最新状态有效”。</span> <span class="k">SELECT</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">source_version</span><span class="p">,</span> <span class="n">amount</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="o">*</span><span class="p">,</span> <span class="n">ROW_NUMBER</span><span class="p">()</span> <span class="n">OVER</span> <span class="p">(</span> <span class="k">PARTITION</span> <span class="k">BY</span> <span class="n">tenant_id</span><span class="p">,</span> <span class="n">order_id</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">source_version</span> <span class="k">DESC</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">state_rank</span> <span class="k">FROM</span> <span class="n">events</span> <span class="k">WHERE</span> <span class="n">status</span> <span class="o">=</span> <span class="s1">'ACTIVE'</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">ranked</span> <span class="k">WHERE</span> <span class="n">state_rank</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> </code></pre></div></div> <p>取消记录在排名之前就被删掉了,O100 和 O200 以前的有效版本重新排到第一名,最后错误地得到 <strong>5 笔、530.00</strong>。</p> <p>两个问题都可以有业务意义:“最近一次有效时是什么状态”和“当前是否有效”。但需要先确定正在回答哪一个,不能只看 SQL 是否去重成功。</p> <p><code class="language-plaintext highlighter-rouge">DISTINCT</code> 的作用同样取决于投影字段。<code class="language-plaintext highlighter-rouge">SELECT DISTINCT tenant_id, order_id, status</code> 可以列出不同状态组合,却没有表达哪种状态更晚。关于 <code class="language-plaintext highlighter-rouge">DISTINCT</code> 如何比较结果行,可参考 <a href="https://duckdb.org/docs/1.4/sql/query_syntax/select">DuckDB SELECT 文档</a>。</p> <h2 id="6-三个容易被样例掩盖的边界">6. 三个容易被样例掩盖的边界</h2> <p><strong>到达晚,不等于状态新。</strong> O200 的 v1 比 v2 晚到 20 分钟。如果按 <code class="language-plaintext highlighter-rouge">ingested_at DESC</code> 取最新,订单会从取消退回有效,结果变成 4 笔、430.00。<code class="language-plaintext highlighter-rouge">event_time</code> 表达事件发生时间,<code class="language-plaintext highlighter-rouge">ingested_at</code> 表达到达时间,<code class="language-plaintext highlighter-rouge">source_version</code> 表达本例来源系统的状态顺序,三者不能随意互换。</p> <p><strong>取消后重建,可能是新的业务对象。</strong> O100 与 O101 共享申请号 R100,但订单号不同。是否需要在申请层再合并,是另一个统计口径,不能在订单明细层顺手按 <code class="language-plaintext highlighter-rouge">request_id</code> 删除一笔。本例也保留了两个租户的 O100,避免因为省略租户键而合并不同对象。</p> <p><strong>状态可以反复,不能默认取消就是最终状态。</strong> O300 的 v2 取消后,v3 又变为有效。这里模拟的业务规则允许恢复,因此应保留 v3;其他系统可能禁止这种转移,需要按它自己的状态机检查。仅按 12:05:00 排序,无法区分本例的 v2 和 v3,可信版本才提供了先后关系。</p> <p>对于增量处理,还要把新事件与已有状态比较。来源版本较旧的晚到数据不能覆盖更高版本;事件明细可以追加,当前状态也可以保持不变。跨来源合并时,如果版本只在各自来源内有效,就需要把来源范围纳入键或明确冲突规则,不能直接比较两个系统的数字大小。</p> <h2 id="7-把结果和反例一起验证">7. 把结果和反例一起验证</h2> <p>示例脚本实际运行得到:</p> <table> <thead> <tr> <th>计算方法</th> <th>结果</th> </tr> </thead> <tbody> <tr> <td>原始记录 / 整行 DISTINCT / 事件幂等去重</td> <td>10 / 10 / 9 行</td> </tr> <tr> <td>先取最新状态,再筛有效订单</td> <td>3 笔 / 350.00</td> </tr> <tr> <td>先筛有效,再取最新</td> <td>5 笔 / 530.00</td> </tr> <tr> <td>把最近到达当成最新状态</td> <td>4 笔 / 430.00</td> </tr> </tbody> </table> <p>脚本还验证了四个变化:再次重放同一事件不会改变指标;同一事件编号出现不同载荷会被检测出来;同一订单版本出现冲突事件会被检测出来;晚到的旧版本不会把正确状态回退。</p> <p>这套方法依赖完整、可信的版本信息。本例的事件携带完整状态;如果输入是只包含变化字段的增量补丁,还需要按顺序还原状态,不能直接选最后一行。这里说的“当前”,也只是本次已收齐数据范围内的最新状态;上游尚有缺数时,应说明观测截止时间,不能把未到达当成不存在。</p> <p>写去重逻辑时,先写下实体键、事件键和版本规则,再分别检查输入、历史和输出。这样行数变化才有明确含义,也更容易定位究竟是重复投递、状态变化,还是来源数据出了问题。</p> <hr /> <p>本文为历史专题补记,按 2024-06-16 归档,于 2026-10-29 整理并验证示例。</p> SQL 没有报错,金额为什么多算了?从数据粒度到 JOIN 放大 https://muhaokang.github.io/sql-join-grain/ https://muhaokang.github.io/sql-join-grain/ Sun, 12 Nov 2023 08:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 一条 SQL 跑完了,行数也没有大得离谱,但汇总金额就是对不上。遇到这种情况,可以先把 SUM 放到一边,回答一个问题:执行到这一步时,一行数据究竟代表什么? 订单表的一行是一笔订单,明细表的一行是一项商品,附件表的一行是一个文件。把三张表关联起来以后,结果中的一行可能已经变成“某笔订单的一项商品与一个附件的组合”。此时继续累加订单金额,就会把同一个金... <p>一条 SQL 跑完了,行数也没有大得离谱,但汇总金额就是对不上。遇到这种情况,可以先把 <code class="language-plaintext highlighter-rouge">SUM</code> 放到一边,回答一个问题:<strong>执行到这一步时,一行数据究竟代表什么?</strong></p> <p>订单表的一行是一笔订单,明细表的一行是一项商品,附件表的一行是一个文件。把三张表关联起来以后,结果中的一行可能已经变成“某笔订单的一项商品与一个附件的组合”。此时继续累加订单金额,就会把同一个金额算多次。</p> <p>下面用一个可以直接运行的小例子,把这个过程拆开看。数据全部为模拟数据,不涉及实际业务系统。</p> <h2 id="1-先把要统计的对象说清楚">1. 先把要统计的对象说清楚</h2> <p>需求是:<strong>按区域统计至少有一个附件的订单数量、订单金额,以及这些订单的商品明细金额。</strong></p> <p>这里有三个边界:</p> <ul> <li>统计对象是订单,每笔订单只计一次。</li> <li>“有附件”是筛选条件,不要求把每个附件展开到结果里。</li> <li>商品明细金额要先按订单汇总,才能和订单金额放到同一行。</li> </ul> <p>先看输入数据。<code class="language-plaintext highlighter-rouge">orders</code> 每笔订单一行,主键是 <code class="language-plaintext highlighter-rouge">order_id</code>:</p> <table> <thead> <tr> <th>order_id</th> <th>region</th> <th style="text-align: right">order_amount</th> </tr> </thead> <tbody> <tr> <td>101</td> <td>north</td> <td style="text-align: right">100.00</td> </tr> <tr> <td>102</td> <td>north</td> <td style="text-align: right">100.00</td> </tr> <tr> <td>103</td> <td>south</td> <td style="text-align: right">80.00</td> </tr> </tbody> </table> <p><code class="language-plaintext highlighter-rouge">order_items</code> 每个明细一行,主键是 <code class="language-plaintext highlighter-rouge">item_id</code>。这里的 <code class="language-plaintext highlighter-rouge">item_amount</code> 已经是该明细的金额,不是单价:</p> <table> <thead> <tr> <th>item_id</th> <th>order_id</th> <th style="text-align: right">item_amount</th> </tr> </thead> <tbody> <tr> <td>11</td> <td>101</td> <td style="text-align: right">60.00</td> </tr> <tr> <td>12</td> <td>101</td> <td style="text-align: right">40.00</td> </tr> <tr> <td>21</td> <td>102</td> <td style="text-align: right">100.00</td> </tr> <tr> <td>31</td> <td>103</td> <td style="text-align: right">80.00</td> </tr> </tbody> </table> <p><code class="language-plaintext highlighter-rouge">attachments</code> 每个附件一行,主键是 <code class="language-plaintext highlighter-rouge">attachment_id</code>:</p> <table> <thead> <tr> <th>attachment_id</th> <th>order_id</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>101</td> </tr> <tr> <td>2</td> <td>101</td> </tr> <tr> <td>3</td> <td>102</td> </tr> </tbody> </table> <p>全部订单金额是 280 元,但订单 103 没有附件,不在本次统计范围内。因此,正确结果应当是:north 区域有 2 笔订单,订单金额 200 元,明细金额也是 200 元。</p> <p><strong>280 和 200 的差异来自筛选范围,不能直接当成数据丢失。</strong> 对账前必须先对齐统计对象。</p> <h2 id="2-直接关联金额在哪一步变多了">2. 直接关联,金额在哪一步变多了</h2> <p>下面这条 SQL 很容易写出来:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">joined_rows</span><span class="p">,</span> <span class="k">COUNT</span><span class="p">(</span><span class="k">DISTINCT</span> <span class="n">o</span><span class="p">.</span><span class="n">order_id</span><span class="p">)</span> <span class="k">AS</span> <span class="n">order_count</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">order_amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">order_amount</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">i</span><span class="p">.</span><span class="n">item_amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">item_amount</span> <span class="k">FROM</span> <span class="n">orders</span> <span class="n">o</span> <span class="k">JOIN</span> <span class="n">order_items</span> <span class="n">i</span> <span class="k">ON</span> <span class="n">o</span><span class="p">.</span><span class="n">order_id</span> <span class="o">=</span> <span class="n">i</span><span class="p">.</span><span class="n">order_id</span> <span class="k">JOIN</span> <span class="n">attachments</span> <span class="n">a</span> <span class="k">ON</span> <span class="n">o</span><span class="p">.</span><span class="n">order_id</span> <span class="o">=</span> <span class="n">a</span><span class="p">.</span><span class="n">order_id</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span><span class="p">;</span> </code></pre></div></div> <p>执行结果:</p> <table> <thead> <tr> <th>region</th> <th style="text-align: right">joined_rows</th> <th style="text-align: right">order_count</th> <th style="text-align: right">order_amount</th> <th style="text-align: right">item_amount</th> </tr> </thead> <tbody> <tr> <td>north</td> <td style="text-align: right">5</td> <td style="text-align: right">2</td> <td style="text-align: right">500.00</td> <td style="text-align: right">300.00</td> </tr> </tbody> </table> <p>订单数看起来是对的,两个金额却都错了。把聚合之前的数据展开,就能看到原因:</p> <table> <thead> <tr> <th>order_id</th> <th>item_id</th> <th>attachment_id</th> <th style="text-align: right">order_amount</th> <th style="text-align: right">item_amount</th> </tr> </thead> <tbody> <tr> <td>101</td> <td>11</td> <td>1</td> <td style="text-align: right">100.00</td> <td style="text-align: right">60.00</td> </tr> <tr> <td>101</td> <td>11</td> <td>2</td> <td style="text-align: right">100.00</td> <td style="text-align: right">60.00</td> </tr> <tr> <td>101</td> <td>12</td> <td>1</td> <td style="text-align: right">100.00</td> <td style="text-align: right">40.00</td> </tr> <tr> <td>101</td> <td>12</td> <td>2</td> <td style="text-align: right">100.00</td> <td style="text-align: right">40.00</td> </tr> <tr> <td>102</td> <td>21</td> <td>3</td> <td style="text-align: right">100.00</td> <td style="text-align: right">100.00</td> </tr> </tbody> </table> <p>订单 101 有 2 个明细和 2 个附件。两个关联都只按订单号匹配,所以它产生了 <code class="language-plaintext highlighter-rouge">2 × 2 = 4</code> 行。</p> <p>订单金额被重复 4 次,变成 400 元;两个明细也分别被重复 2 次,合计变成 200 元。再加上订单 102,就得到了 500 和 300。</p> <p>对于这种两个子表独立按同一订单键进行的内连接,一笔订单有 <code class="language-plaintext highlighter-rouge">m</code> 个明细和 <code class="language-plaintext highlighter-rouge">n</code> 个附件,就会形成 <code class="language-plaintext highlighter-rouge">m × n</code> 个组合。这不是子表本身一定有脏数据,而是关联后的粒度变了。如果关联条件还包含明细号,或者增加了其他过滤条件,则要按实际匹配关系重新分析,不能机械地套乘法。</p> <p><code class="language-plaintext highlighter-rouge">COUNT(DISTINCT order_id)</code> 只修正了订单数量,无法同时修正 <code class="language-plaintext highlighter-rouge">SUM</code>。这也是为什么只核对订单数不够。</p> <h2 id="3-为什么补一个-distinct-还不够">3. 为什么补一个 DISTINCT 还不够</h2> <p>一种尝试是把金额改成:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SUM</span><span class="p">(</span><span class="k">DISTINCT</span> <span class="n">o</span><span class="p">.</span><span class="n">order_amount</span><span class="p">)</span> </code></pre></div></div> <p>结果会变成 100 元。因为订单 101 和 102 的金额恰好都为 100,<code class="language-plaintext highlighter-rouge">DISTINCT</code> 消除的是相同的<strong>金额值</strong>,并不知道它们来自两笔不同订单。</p> <p>另一种写法是在聚合结果外加 <code class="language-plaintext highlighter-rouge">SELECT DISTINCT</code>。但 <code class="language-plaintext highlighter-rouge">GROUP BY region</code> 已经得到一行 north,金额在聚合时就算成了 500,外层去重不能把错误的总额还原。</p> <p>当然,先选出唯一的 <code class="language-plaintext highlighter-rouge">(order_id, order_amount)</code> 再汇总,在订单号确实唯一决定金额的前提下,也可以恢复订单粒度。关键在于选出的列是否都属于同一笔订单;一旦继续携带 <code class="language-plaintext highlighter-rouge">item_id</code> 或 <code class="language-plaintext highlighter-rouge">attachment_id</code>,结果仍然会展开。</p> <p>所以要先明确需要保留的记录,再决定如何去重。</p> <h2 id="4-只判断是否存在就使用-exists">4. 只判断是否存在,就使用 EXISTS</h2> <p>如果只要订单数和订单金额,根本不需要关联商品明细。附件只负责判断这笔订单是否进入统计范围:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">order_count</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">order_amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">order_amount</span> <span class="k">FROM</span> <span class="n">orders</span> <span class="n">o</span> <span class="k">WHERE</span> <span class="k">EXISTS</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">attachments</span> <span class="n">a</span> <span class="k">WHERE</span> <span class="n">a</span><span class="p">.</span><span class="n">order_id</span> <span class="o">=</span> <span class="n">o</span><span class="p">.</span><span class="n">order_id</span> <span class="p">)</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span><span class="p">;</span> </code></pre></div></div> <p>结果为 <code class="language-plaintext highlighter-rouge">north / 2 / 200.00</code>。同一订单有几个匹配附件,都只决定 <code class="language-plaintext highlighter-rouge">EXISTS</code> 是否成立,不会把左侧订单展开。</p> <p>DuckDB 的 <a href="https://duckdb.org/docs/1.4/sql/query_syntax/from">JOIN 文档</a>说明了半连接的语义:保留左侧存在匹配的行。Spark SQL 也提供 <a href="https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select-join.html">LEFT SEMI JOIN</a>。这里选择 <code class="language-plaintext highlighter-rouge">EXISTS</code> 表达筛选目的;本文实际执行环境是 DuckDB,没有据此推断其他引擎的性能。</p> <p>需要注意,<code class="language-plaintext highlighter-rouge">EXISTS</code> 也不会替左表去重。如果订单表本身有同一订单的多个版本,必须先确定要使用哪个版本。</p> <h2 id="5-需要右表指标就先对齐粒度">5. 需要右表指标,就先对齐粒度</h2> <p>完整需求还包含商品明细金额,可以先把商品明细聚合到订单粒度,再关联订单表:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="n">item_total</span> <span class="k">AS</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">order_id</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">item_amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">item_amount</span> <span class="k">FROM</span> <span class="n">order_items</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">order_id</span> <span class="p">)</span> <span class="k">SELECT</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span><span class="p">,</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">order_count</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">o</span><span class="p">.</span><span class="n">order_amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">order_amount</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">i</span><span class="p">.</span><span class="n">item_amount</span><span class="p">)</span> <span class="k">AS</span> <span class="n">item_amount</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">i</span><span class="p">.</span><span class="n">order_id</span> <span class="k">IS</span> <span class="k">NULL</span> <span class="k">THEN</span> <span class="mi">1</span> <span class="k">ELSE</span> <span class="mi">0</span> <span class="k">END</span><span class="p">)</span> <span class="k">AS</span> <span class="n">missing_item_orders</span> <span class="k">FROM</span> <span class="n">orders</span> <span class="n">o</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="n">item_total</span> <span class="n">i</span> <span class="k">ON</span> <span class="n">o</span><span class="p">.</span><span class="n">order_id</span> <span class="o">=</span> <span class="n">i</span><span class="p">.</span><span class="n">order_id</span> <span class="k">WHERE</span> <span class="k">EXISTS</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">attachments</span> <span class="n">a</span> <span class="k">WHERE</span> <span class="n">a</span><span class="p">.</span><span class="n">order_id</span> <span class="o">=</span> <span class="n">o</span><span class="p">.</span><span class="n">order_id</span> <span class="p">)</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">o</span><span class="p">.</span><span class="n">region</span><span class="p">;</span> </code></pre></div></div> <p>结果:</p> <table> <thead> <tr> <th>region</th> <th style="text-align: right">order_count</th> <th style="text-align: right">order_amount</th> <th style="text-align: right">item_amount</th> <th style="text-align: right">missing_item_orders</th> </tr> </thead> <tbody> <tr> <td>north</td> <td style="text-align: right">2</td> <td style="text-align: right">200.00</td> <td style="text-align: right">200.00</td> <td style="text-align: right">0</td> </tr> </tbody> </table> <p>这次的处理顺序是:商品明细按订单聚合,每个订单最多一行;再和订单表关联;用附件筛选订单;最后按区域汇总。</p> <p><code class="language-plaintext highlighter-rouge">missing_item_orders</code> 是特意保留的检查项。假设再加入一笔金额为 50 元、有附件但没有商品明细的订单 104,查询会得到:</p> <table> <thead> <tr> <th>region</th> <th style="text-align: right">order_count</th> <th style="text-align: right">order_amount</th> <th style="text-align: right">item_amount</th> <th style="text-align: right">missing_item_orders</th> </tr> </thead> <tbody> <tr> <td>north</td> <td style="text-align: right">3</td> <td style="text-align: right">250.00</td> <td style="text-align: right">200.00</td> <td style="text-align: right">1</td> </tr> </tbody> </table> <p>这个结果没有把缺明细的订单悄悄过滤掉,但也不能直接认定两个金额都完整。是否允许缺明细,需要由统计口径决定。</p> <p>在完整性尚未确认时,直接把缺失明细金额填成 0,会掩盖“没有取到数据”和“金额确实为零”的区别。另外,<code class="language-plaintext highlighter-rouge">SUM</code> 会忽略 NULL;没有任何匹配订单时,这条带 <code class="language-plaintext highlighter-rouge">GROUP BY</code> 的查询返回空结果,并不自动生成一行零值。这些情况都要单独处理。</p> <h2 id="6-换成真实数仓时还要检查什么">6. 换成真实数仓时,还要检查什么</h2> <p>这个例子省略了租户、来源系统、业务日期和币种。实际使用时,至少要补齐以下判断:</p> <ol> <li><strong>关联键是不是完整业务键。</strong> 订单号可能只在某个来源或租户内唯一,不能默认全局唯一。</li> <li><strong>两侧是否使用同一个统计范围。</strong> 明细取最新快照、主表取历史快照,会带来另一类不一致。</li> <li><strong>聚合后是否真的唯一。</strong> 如果要按 <code class="language-plaintext highlighter-rouge">(租户, 订单号)</code> 关联,预聚合也必须保留这组键。</li> <li><strong>过滤条件是否改变保留范围。</strong> <code class="language-plaintext highlighter-rouge">LEFT JOIN</code> 后在 <code class="language-plaintext highlighter-rouge">WHERE</code> 中要求右侧字段等于某值,会排除右侧缺失的记录。</li> <li><strong>金额是否具有可加性。</strong> 本例使用相同币种,并约定明细金额可直接相加。折扣、税额、退款和币种换算需要另行定义。</li> </ol> <p>排查时可以在每次关联后同时记录行数、唯一业务键数量、未匹配数量和关键金额。先定位是哪一步改变了粒度,再判断这个变化是否符合需求。只看最终总数,通常不容易找到问题出在哪里。</p> <h2 id="7-运行本文示例">7. 运行本文示例</h2> <p>下载 <a href="/assets/examples/sql_join_grain.py">完整 Python 示例</a>,其中包含建表、插入数据、错误写法、修复写法和结果断言。金额使用 <code class="language-plaintext highlighter-rouge">DECIMAL(12, 2)</code>。</p> <p>在下载目录中执行:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> venv .venv <span class="nb">source</span> .venv/bin/activate python3 <span class="nt">-m</span> pip <span class="nb">install </span><span class="nv">duckdb</span><span class="o">==</span>1.4.5 python3 sql_join_grain.py </code></pre></div></div> <p>本次验证使用 Python 3.9.6、DuckDB 1.4.5。脚本会检查金额 500/300 的错误结果、<code class="language-plaintext highlighter-rouge">SUM(DISTINCT)</code> 得到 100 的反例、修复后的 200/200,以及缺明细和无符合条件订单的情况。全部断言通过后输出 <code class="language-plaintext highlighter-rouge">PASS</code>。这些小数据验证用于说明计算语义,不是分布式引擎的性能测试。</p> <p>继续阅读:<a href="/dedup-and-latest-state/">去重与最新状态</a>、<a href="/data-readiness/">调度与数据就绪</a>。</p> <blockquote> <p>本文为历史专题补记,按 2023-11-12 归档,于 2026-10-29 整理并验证示例。文中的运行环境版本对应本次验证时间。</p> </blockquote> Prometheus概述 https://muhaokang.github.io/Prometheus%E6%A6%82%E8%BF%B0/ https://muhaokang.github.io/Prometheus%E6%A6%82%E8%BF%B0/ Thu, 05 May 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. Prometheus 的特点 Prometheus 是一个开源的完整监控解决方案,其对传统监控系统的测试和告警模型进行了彻底的颠覆,形成了基于中央化的规则计算、统一分析和告警的新模型。 相比于传统监控系统,Prometheus 具有以下优点: 1.1 易于管理 Prometheus 核心部分只有一个单独的二进制文件,不存在任何的第三方依赖(数据库... <h1 id="1-prometheus-的特点">1. Prometheus 的特点</h1> <p>Prometheus 是一个开源的完整监控解决方案,其对传统监控系统的测试和告警模型进行了彻底的颠覆,形成了基于中央化的规则计算、统一分析和告警的新模型。 相比于传统监控系统,Prometheus 具有以下优点:</p> <h2 id="11-易于管理">1.1 易于管理</h2> <ul> <li>Prometheus 核心部分只有一个单独的二进制文件,不存在任何的第三方依赖(数据库,缓存等等)。唯一需要的就是本地磁盘,因此不会有潜在级联故障的风险。</li> <li>Prometheus 基于 Pull 模型的架构方式,可以在任何地方(本地电脑,开发环境,测试环境)搭建我们的监控系统。</li> <li>对于一些复杂的情况,还可以使用 Prometheus 服务发现(Service Discovery)的能力动态管理监控目标。 <h2 id="12-监控服务的内部运行状态">1.2 监控服务的内部运行状态</h2> <p>Pometheus 鼓励用户监控服务的内部状态,基于 Prometheus 丰富的 Client 库,用户可以轻松的在应用程序中添加对 Prometheus 的支持,从而让用户可以获取服务和应用内部真正的运行状态。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1651749235833-f197af7c-d0b2-47dc-bee1-9e7d3a282a1c.png#clientId=ua4a51593-7502-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=298&amp;id=ucb2a5983&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=298&amp;originWidth=946&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=55110&amp;status=done&amp;style=none&amp;taskId=u1b8f4f92-d1a7-4b62-841f-3587df3834b&amp;title=&amp;width=946" alt="image.png" /></p> <h2 id="13-强大的数据模型">1.3 强大的数据模型</h2> <p>所有采集的监控数据均以指标(metric)的形式保存在<strong>内置的时间序列数据库</strong>当中 (<strong>TSDB</strong>)。所有的样本除了基本的指标名称以外,还包含一组用于描述该样本特征的标签。 如下所示:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http_request_status<span class="o">{</span><span class="nv">code</span><span class="o">=</span><span class="s1">'200'</span>,content_path<span class="o">=</span><span class="s1">'/api/path'</span>,environment<span class="o">=</span><span class="s1">'produment'</span><span class="o">}</span> <span class="o">=&gt;</span> <span class="o">[</span>value1@timestamp1,value2@timestamp2...] http_request_status<span class="o">{</span><span class="nv">code</span><span class="o">=</span><span class="s1">'200'</span>,content_path<span class="o">=</span><span class="s1">'/api/path2'</span>,environment<span class="o">=</span><span class="s1">'produment'</span><span class="o">}</span> <span class="o">=&gt;</span> <span class="o">[</span>value1@timestamp1,value2@timestamp2...] </code></pre></div> </div> <p>每一条时间序列由<strong>指标名称(Metrics Name)</strong>以及一组<strong>标签(Labels)</strong>唯一标识。每条时间序列按照时间的先后顺序存储一系列的样本值。</p> </li> <li>http_request_status:指标名称(Metrics Name)</li> <li>{code=’200’,content_path=’/api/path’,environment=’produment’}:表示维度的标签,基于这些 Labels 我们可以方便地对监控数据进行聚合,过滤,裁剪。</li> <li>[value1@timestamp1,value2@timestamp2…]:按照时间的先后顺序 存储的样本值。 <h2 id="14-强大的查询语言-promql">1.4 强大的查询语言 PromQL</h2> <p>Prometheus 内置了一个强大的数据查询语言 PromQL。 通过 PromQL 可以实现对监控数据的查询、聚合。同时 PromQL 也被应用于数据可视化(如 Grafana)以及告警当中。 通过 PromQL 可以轻松回答类似于以下问题:</p> </li> <li>在过去一段时间中 95%应用延迟时间的分布范围?</li> <li>预测在 4 小时后,磁盘空间占用大致会是什么情况?</li> <li>CPU 占用率前 5 位的服务有哪些?(过滤) <h2 id="15-高效">1.5 高效</h2> <p>对于监控系统而言,大量的监控任务必然导致有大量的数据产生。而 Prometheus 可以高效地处理这些数据,对于单一 Prometheus Server 实例而言它可以处理:</p> </li> <li>数以百万的监控指标</li> <li>每秒处理数十万的数据点 <h2 id="16-可扩展">1.6 可扩展</h2> <p>可以在每个数据中心、每个团队运行独立的 Prometheus Sevrer。Prometheus 对于联邦集群的支持,可以让多个 Prometheus 实例产生一个逻辑集群,当单实例 Prometheus Server 处理的任务量过大时,通过使用功能分区(sharding)+联邦集群(federation)可以对其进行扩展。</p> <h2 id="17-易于集成">1.7 易于集成</h2> <p>使用 Prometheus 可以快速搭建监控服务,并且可以非常方便地在应用程序中进行集成。目前支持:Java,JMX,Python,Go,Ruby,.Net,Node.js 等等语言的客户端 SDK,基于这些 SDK 可以快速让应用程序纳入到 Prometheus 的监控当中,或者开发自己的监控数据收集程序。 同时这些客户端收集的监控数据,不仅仅支持 Prometheus,还能支持 Graphite 这些其他的监控工具。 同时 Prometheus 还支持与其他的监控系统进行集成:Graphite,Statsd,Collected,Scollector, muini, Nagios 等。 Prometheus 社区还提供了大量第三方实现的监控数据采集支持:JMX,CloudWatch,EC2,MySQL,PostgresSQL,Haskell,Bash,SNMP,Consul,Haproxy,Mesos,Bind,CouchDB,Django,Memcached,RabbitMQ,Redis,RethinkDB,Rsyslog 等等。</p> <h2 id="18-可视化">1.8 可视化</h2> </li> <li>Prometheus Server 中自带的 Prometheus UI,可以方便地直接对数据进行查询,并且支持直接以图形化的形式展示数据。同时 Prometheus 还提供了一个独立的基于 Ruby On Rails 的 Dashboard 解决方案 Promdash。</li> <li>最新的 Grafana 可视化工具也已经提供了完整的 Prometheus 支持,基于 Grafana 可以创建更加精美的监控图标。</li> <li>基于 Prometheus 提供的 API 还可以实现自己的监控可视化 UI。 <h2 id="19-开放性">1.9 开放性</h2> <p>通常来说当我们需要监控一个应用程序时,一般需要该应用程序提供对相应监控系统协议的支持,因此应用程序会与所选择的监控系统进行绑定。为了减少这种绑定所带来的限制,对于决策者而言要么你就直接在应用中集成该监控系统的支持,要么就在外部创建单独的服务来适配不同的监控系统。 而对于 Prometheus 来说,使用 Prometheus 的 client library 的输出格式不止支持 Prometheus 的格式化数据,也可以输出支持其它监控系统的格式化数据,比如 Graphite。 因此你甚至可以在不使用 Prometheus 的情况下,采用 Prometheus 的 client library 来让 你的应用程序支持监控数据采集。</p> </li> </ul> <h1 id="2-prometheus-的架构">2. Prometheus 的架构</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1651749661875-1d4b580c-5735-4813-999e-96031a1e74bd.png#clientId=ua4a51593-7502-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=607&amp;id=u4c840b6f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=607&amp;originWidth=920&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=137611&amp;status=done&amp;style=none&amp;taskId=uff3ef4d8-0305-4410-8fd8-2807099d466&amp;title=&amp;width=920" alt="image.png" /></p> <h2 id="21-prometheus-生态圈组件">2.1 Prometheus 生态圈组件</h2> <ul> <li>Prometheus Server:主服务器,负责收集和存储时间序列数据</li> <li>client libraies:应用程序代码插桩,将监控指标嵌入到被监控应用程序中</li> <li>Pushgateway:推送网关,为支持 short-lived 作业提供一个推送网关</li> <li>exporter:专门为一些应用开发的数据摄取组件—exporter,例如:HAProxy、StatsD、Graphite 等等。</li> <li>Alertmanager:专门用于处理 alert 的组件 <h2 id="22-架构理解">2.2 架构理解</h2> <p>Prometheus 既然设计为一个维度存储模型,可以把它理解为一个 OLAP 系统。</p> <h4 id="1存储计算层">1、存储计算层</h4> </li> <li>Prometheus Server,里面包含了存储引擎和计算引擎。</li> <li>Retrieval 组件为取数组件,它会主动从 Pushgateway 或者 Exporter 拉取指标数据。</li> <li>Service discovery,可以动态发现要监控的目标。</li> <li>TSDB,数据核心存储与查询。</li> <li>HTTP server,对外提供 HTTP 服务。 <h4 id="2采集层">2、采集层</h4> <p>采集层分为两类,一类是生命周期较短的作业,还有一类是生命周期较长的作业。</p> </li> <li>短作业:直接通过 API,在退出时间指标推送给 Pushgateway。</li> <li>长作业:Retrieval 组件直接从 Job 或者 Exporter 拉取数据。 <h4 id="3应用层">3、应用层</h4> <p>应用层主要分为两种,一种是 AlertManager,另一种是数据可视化。</p> </li> <li>AlertManager</li> </ul> <p>对接 Pagerduty,是一套付费的监控报警系统。可实现短信报警、5 分钟无人 ack 打电话通知、仍然无人 ack,通知值班人员 Manager… Emial,发送邮件 … …</p> <ul> <li>数据可视化</li> </ul> <p>Prometheus build-in WebUI Grafana 其他基于 API 开发的客户端</p> <h1 id="3-prometheus-的安装">3. <strong>Prometheus 的安装</strong></h1> <h2 id="31-安装-prometheus-server">3.1 安装 Prometheus Server</h2> <p>Prometheus 基于 Golang 编写,编译后的软件包,不依赖于任何的第三方依赖。只需要下载对应平台的二进制包,解压并且添加基本的配置即可正常启动 Prometheus Server。</p> <h3 id="311-修改配置文件-prometheusyml">3.1.1 修改配置文件 prometheus.yml</h3> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">scrape_configs</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">prometheus'</span> <span class="na">static_configs</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">hadoop202:9090'</span><span class="pi">]</span> <span class="c1"># 添加 PushGateway 监控配置</span> <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">pushgateway'</span> <span class="na">static_configs</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">hadoop102:9091'</span><span class="pi">]</span> <span class="na">labels</span><span class="pi">:</span> <span class="na">instance</span><span class="pi">:</span> <span class="s">pushgateway</span> <span class="c1"># 添加 Node Exporter 监控配置</span> <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">node</span><span class="nv"> </span><span class="s">exporter'</span> <span class="na">static_configs</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">hadoop102:9100'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">hadoop103:9100'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">hadoop104:9100'</span><span class="pi">]</span> </code></pre></div></div> <p>配置说明: <strong>1、global 配置块</strong>:控制 Prometheus 服务器的全局配置</p> <ul> <li>scrape_interval:配置拉取数据的时间间隔,默认为 1 分钟。</li> <li>evaluation_interval:规则验证(生成 alert)的时间间隔,默认为 1 分钟。</li> </ul> <p><strong>2、rule_files 配置块</strong>:规则配置文件 <strong>3、scrape_configs 配置块</strong>:配置采集目标相关, prometheus 监视的目标。Prometheus 自身的运行信息可以通过 HTTP 访问,所以 Prometheus 可以监控自己的运行数据。</p> <ul> <li>job_name:监控作业的名称</li> <li>static_configs:表示静态目标配置,就是固定从某个 target 拉取数据</li> <li>targets : 指 定 监 控 的 目 标 , 其 实 就 是 从 哪 儿 拉 取 数 据 。 Prometheus 会 从</li> </ul> <p>http://hadoop102:9090/metrics 上拉取数据。 <em>Prometheus 是可以在运行时自动加载配置的。启动时需要添加:–web.enable-lifecycle</em></p> <h2 id="32-安装-pushgateway">3.2 安装 Pushgateway</h2> <p>Prometheus 在正常情况下是采用拉模式从产生 metric 的作业或者 exporter(比如专门监控主机的 NodeExporter)拉取监控数据。但是我们要监控的是 Flink on YARN 作业,想要让 Prometheus 自动发现作业的提交、结束以及自动拉取数据显然是比较困难的。 PushGateway 就是一个中转组件,通过配置 Flink on YARN 作业将 metric 推到 PushGateway,Prometheus 再从 PushGateway 拉取就可以了。</p> <h2 id="33-安装-node-exporter选择性安装">3.3 安装 Node Exporter(选择性安装)</h2> <p>在 Prometheus 的架构设计中,Prometheus Server 主要负责数据的收集,存储并且对外提供数据查询支持,而实际的监控样本数据的收集则是由 Exporter 完成。因此为了能够监控到某些东西,如主机的 CPU 使用率,我们需要使用到 Exporter。Prometheus 周期性的从 Exporter 暴露的 HTTP 服务地址(通常是/metrics)拉取监控样本数据。 Exporter 可以是一个相对开放的概念,其可以是一个独立运行的程序独立于监控目标以外,也可以是直接内置在监控目标中。只要能够向 Prometheus 提供标准格式的监控样本数据即可。 为了能够采集到主机的运行指标如 CPU, 内存,磁盘等信息。我们可以使用 Node Exporter。Node Exporter 同样采用 Golang 编写,并且不存在任何的第三方依赖,只需要下载,解压即可运行。可以从 https://prometheus.io/download/ 获取最新的 node exporter 版本的二进制包。</p> <p>启动并通过页面查看是否成功 执行./node_exporter 浏览器输入:http://hadoop102:9100/metrics,可以看到当前 node exporter 获取到的当前主机的所有监控数据。</p> <h3 id="331-节点分发">3.3.1 节点分发</h3> <p>将解压后的目录分发到要监控的节点</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xsync node_exporter-1.2.2 </code></pre></div></div> <p>修改 Prometheus 配置文件 prometheus.yml,在 之前已经添加过配置</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">hadoop202:9100'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">hadoop203:9100'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">hadoop204:9100'</span><span class="pi">]</span> </code></pre></div></div> <h3 id="332-设置为开机自启">3.3.2 设置为开机自启</h3> <p>创建 service 文件</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>vim /usr/lib/systemd/system/node_exporter.service <span class="o">[</span>Unit] <span class="nv">Description</span><span class="o">=</span>node_export <span class="nv">Documentation</span><span class="o">=</span>https://github.com/prometheus/node_exporter <span class="nv">After</span><span class="o">=</span>network.target <span class="o">[</span>Service] <span class="nv">Type</span><span class="o">=</span>simple <span class="nv">User</span><span class="o">=</span>mhk <span class="nv">ExecStart</span><span class="o">=</span> /opt/module/node_exporter-1.2.2/node_exporter <span class="nv">Restart</span><span class="o">=</span>on-failure <span class="o">[</span>Install] <span class="nv">WantedBy</span><span class="o">=</span>multi-user.target </code></pre></div></div> <p>分发文件</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /home/atguigu/bin/xsync /usr/lib/systemd/system/node_exporter.service </code></pre></div></div> <p>设为开机自启动(所有机器都执行)</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl <span class="nb">enable </span>node_exporter.service </code></pre></div></div> <p>启动服务(所有机器都执行)</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl start node_exporter.service </code></pre></div></div> <h2 id="34-安装-alertmanager选择性安装">3.4 安装 Alertmanager(选择性安装)</h2> <h3 id="341-上传安装包">3.4.1 上传安装包</h3> <p>上传 alertmanager-0.23.0.linux-amd64.tar.gz 到虚拟机的/opt/software 目录</p> <h3 id="342-解压安装包">3.4.2 解压安装包</h3> <p>解压到/opt/module 目录下</p> <h2 id="35-启-动-prometheus-server--pushgateway-和-alertmanager-">3.5 启 动 Prometheus Server 、 Pushgateway 和 **Alertmanager **</h2> <h3 id="351-prometheus-server-目录下执行启动命令">3.5.1 Prometheus Server 目录下执行启动命令</h3> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 prometheus-2.29.1]<span class="nv">$ </span><span class="nb">nohup</span> ./prometheus <span class="nt">--config</span>.file<span class="o">=</span>prometheus.yml <span class="o">&gt;</span> ./prometheus.log 2&gt;&amp;1 &amp; </code></pre></div></div> <h3 id="352-pushgateway-目录下执行启动命令">3.5.2 Pushgateway 目录下执行启动命令</h3> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 pushgateway-1.4.1]<span class="nv">$ </span><span class="nb">nohup</span> ./pushgateway <span class="nt">--web</span>.listen-address :9091 <span class="o">&gt;</span> ./pushgateway.log 2&gt;&amp;1 &amp; </code></pre></div></div> <h3 id="353-在-alertmanager-目录下启动">3.5.3 在 Alertmanager 目录下启动</h3> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 alertmanager-0.23.0]<span class="nv">$ </span><span class="nb">nohup</span> ./alertmanager <span class="nt">--config</span>.file<span class="o">=</span>alertmanager.yml <span class="o">&gt;</span> ./alertmanager.log 2&gt;&amp;1 &amp; </code></pre></div></div> <h3 id="354-打开-web-页面查看">3.5.4 打开 web 页面查看</h3> <ul> <li>浏览器输入:http://hadoop202:9090/</li> <li>点击 Status,选中 Targets:</li> </ul> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1651758595892-1ddb24ff-de6a-4e13-b35f-166de53f4d9c.png#clientId=ua4a51593-7502-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=319&amp;id=uc160755a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=319&amp;originWidth=856&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=74157&amp;status=done&amp;style=none&amp;taskId=u22799cd6-809a-4bb0-b629-9ea0652c6d6&amp;title=&amp;width=856" alt="image.png" /></p> <ul> <li>prometheus、pushgateway 和 node exporter 都是 up 状态,表示安装启动成功:</li> </ul> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1651758625322-56dc464e-cd7a-476a-a23b-f57d1b622272.png#clientId=ua4a51593-7502-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=363&amp;id=u521f58a9&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=363&amp;originWidth=797&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=188477&amp;status=done&amp;style=none&amp;taskId=u32870179-1239-49b7-865e-560d813d788&amp;title=&amp;width=797" alt="image.png" /></p> ClickHouse高级 https://muhaokang.github.io/ClickHouse-%E9%AB%98%E7%BA%A7/ https://muhaokang.github.io/ClickHouse-%E9%AB%98%E7%BA%A7/ Sat, 23 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1.**Explain 查看执行计划 ** 在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到,并且只能真正执行 sql,在执行日志里面查看。在 20.6 版本引入了原生的执行计划的语法。在 20.6.3 版本成为正式版本的功能。 本文档基于目前较新稳定版 21.7.3.14。 1.1... <h1 id="1explain-查看执行计划-">1.**Explain 查看执行计划 **</h1> <p>在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能可以看到,并且只能真正执行 sql,在执行日志里面查看。在 20.6 版本引入了原生的执行计划的语法。在 20.6.3 版本成为正式版本的功能。 <strong>本文档基于目前较新稳定版 21.7.3.14。</strong></p> <h2 id="11-基本语法">1.1 基本语法</h2> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="p">[</span><span class="n">AST</span> <span class="o">|</span> <span class="n">SYNTAX</span> <span class="o">|</span> <span class="n">PLAN</span> <span class="o">|</span> <span class="n">PIPELINE</span><span class="p">]</span> <span class="p">[</span><span class="n">setting</span> <span class="o">=</span> <span class="n">value</span><span class="p">,</span> <span class="p">...]</span> <span class="k">SELECT</span> <span class="p">...</span> <span class="p">[</span><span class="n">FORMAT</span> <span class="p">...]</span> </code></pre></div></div> <ul> <li>PLAN:用于查看执行计划,默认值。</li> </ul> <p>◼ header 打印计划中各个步骤的 head 说明,默认关闭,默认值 0; ◼ description 打印计划中各个步骤的描述,默认开启,默认值 1; ◼ actions 打印计划中各个步骤的详细信息,默认关闭,默认值 0。</p> <ul> <li>AST :用于查看语法树;</li> <li>SYNTAX:用于优化语法;</li> <li>PIPELINE:用于查看 PIPELINE 计划。</li> </ul> <p>◼ header 打印计划中各个步骤的 head 说明,默认关闭; ◼ graph 用 DOT 图形语言描述管道图,默认关闭,需要查看相关的图形需要配合 graphviz 查看; ◼ actions 如果开启了 graph,紧凑打印打,默认开启。 <em>注:PLAN 和 PIPELINE 还可以进行额外的显示设置,如上参数所示。</em></p> <h2 id="12-案例实操">1.2 案例实操</h2> <p>**1.2.1 新版本使用 EXPLAIN ** 可以再安装一个 20.6 以上版本,或者直接在官网的在线 demo,选择高版本进行测试。 官网在线测试链接:https://play.clickhouse.tech/?file=welcome</p> <p>1)查看 PLAIN 简单查询</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="k">SELECT</span> <span class="n">arrayJoin</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="k">NULL</span><span class="p">,</span> <span class="k">NULL</span><span class="p">])</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">8</span><span class="n">c3ab78b</span><span class="o">-</span><span class="mi">7147</span><span class="o">-</span><span class="mi">439</span><span class="n">e</span><span class="o">-</span><span class="mi">814</span><span class="n">e</span><span class="o">-</span><span class="mi">72</span><span class="n">c583ba1c2f</span> <span class="n">Connecting</span> <span class="k">to</span> <span class="n">localhost</span><span class="p">:</span><span class="mi">9000</span> <span class="k">as</span> <span class="k">user</span> <span class="k">default</span><span class="p">.</span> <span class="n">Connected</span> <span class="k">to</span> <span class="n">ClickHouse</span> <span class="n">server</span> <span class="k">version</span> <span class="mi">21</span><span class="p">.</span><span class="mi">7</span><span class="p">.</span><span class="mi">3</span> <span class="n">revision</span> <span class="mi">54449</span><span class="p">.</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">───────────────────────────────────────────────────────────────────┐</span> <span class="err">│</span> <span class="n">Expression</span> <span class="p">((</span><span class="n">Projection</span> <span class="o">+</span> <span class="k">Before</span> <span class="k">ORDER</span> <span class="k">BY</span><span class="p">))</span> <span class="err">│</span> <span class="err">│</span> <span class="n">SettingQuotaAndLimits</span> <span class="p">(</span><span class="k">Set</span> <span class="n">limits</span> <span class="k">and</span> <span class="n">quota</span> <span class="k">after</span> <span class="n">reading</span> <span class="k">from</span> <span class="k">storage</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ReadFromStorage</span> <span class="p">(</span><span class="n">SystemOne</span><span class="p">)</span> <span class="err">│</span> <span class="err">└───────────────────────────────────────────────────────────────────────────┘</span> </code></pre></div></div> <p>复杂 SQL 的执行计划</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="k">SELECT</span> <span class="k">database</span><span class="p">,</span> <span class="k">table</span><span class="p">,</span> <span class="k">count</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">AS</span> <span class="n">cnt</span> <span class="k">FROM</span> <span class="k">system</span><span class="p">.</span><span class="n">parts</span> <span class="k">WHERE</span> <span class="k">database</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'datasets'</span><span class="p">,</span> <span class="s1">'system'</span><span class="p">)</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="k">database</span><span class="p">,</span> <span class="k">table</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="k">database</span> <span class="k">ASC</span><span class="p">,</span> <span class="n">cnt</span> <span class="k">DESC</span> <span class="k">LIMIT</span> <span class="mi">2</span> <span class="k">BY</span> <span class="k">database</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">bd17ac72</span><span class="o">-</span><span class="mi">99</span><span class="n">e6</span><span class="o">-</span><span class="mi">4675</span><span class="o">-</span><span class="mi">9</span><span class="n">c50</span><span class="o">-</span><span class="n">f5a5d7b2d76e</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">─────────────────────────────────────────────────────────────────────────────────────┐</span> <span class="err">│</span> <span class="n">Expression</span> <span class="p">(</span><span class="n">Projection</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">LimitBy</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Expression</span> <span class="p">(</span><span class="k">Before</span> <span class="k">LIMIT</span> <span class="k">BY</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">MergingSorted</span> <span class="p">(</span><span class="n">Merge</span> <span class="n">sorted</span> <span class="n">streams</span> <span class="k">for</span> <span class="k">ORDER</span> <span class="k">BY</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">MergeSorting</span> <span class="p">(</span><span class="n">Merge</span> <span class="n">sorted</span> <span class="n">blocks</span> <span class="k">for</span> <span class="k">ORDER</span> <span class="k">BY</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">PartialSorting</span> <span class="p">(</span><span class="n">Sort</span> <span class="k">each</span> <span class="n">block</span> <span class="k">for</span> <span class="k">ORDER</span> <span class="k">BY</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Expression</span> <span class="p">(</span><span class="k">Before</span> <span class="k">ORDER</span> <span class="k">BY</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Aggregating</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Expression</span> <span class="p">(</span><span class="k">Before</span> <span class="k">GROUP</span> <span class="k">BY</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Filter</span> <span class="p">(</span><span class="k">WHERE</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">SettingQuotaAndLimits</span> <span class="p">(</span><span class="k">Set</span> <span class="n">limits</span> <span class="k">and</span> <span class="n">quota</span> <span class="k">after</span> <span class="n">reading</span> <span class="k">from</span> <span class="k">storage</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ReadFromStorage</span> <span class="p">(</span><span class="n">SystemParts</span><span class="p">)</span> <span class="err">│</span> <span class="err">└─────────────────────────────────────────────────────────────────────────────────────────────┘</span> </code></pre></div></div> <p>打开全部的参数的执行计划</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">header</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">actions</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">description</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">SELECT</span> <span class="n">number</span> <span class="k">FROM</span> <span class="k">system</span><span class="p">.</span><span class="n">numbers</span> <span class="k">LIMIT</span> <span class="mi">10</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">633</span><span class="n">e53c2</span><span class="o">-</span><span class="mi">573</span><span class="k">c</span><span class="o">-</span><span class="mi">450</span><span class="k">c</span><span class="o">-</span><span class="mi">8137</span><span class="o">-</span><span class="mi">554</span><span class="n">b79b9110b</span> <span class="n">Connecting</span> <span class="k">to</span> <span class="n">localhost</span><span class="p">:</span><span class="mi">9000</span> <span class="k">as</span> <span class="k">user</span> <span class="k">default</span><span class="p">.</span> <span class="n">Connected</span> <span class="k">to</span> <span class="n">ClickHouse</span> <span class="n">server</span> <span class="k">version</span> <span class="mi">21</span><span class="p">.</span><span class="mi">7</span><span class="p">.</span><span class="mi">3</span> <span class="n">revision</span> <span class="mi">54449</span><span class="p">.</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">───────────────────────────────────────────────────────────────────┐</span> <span class="err">│</span> <span class="n">Expression</span> <span class="p">((</span><span class="n">Projection</span> <span class="o">+</span> <span class="k">Before</span> <span class="k">ORDER</span> <span class="k">BY</span><span class="p">))</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Header</span><span class="p">:</span> <span class="n">number</span> <span class="n">UInt64</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Actions</span><span class="p">:</span> <span class="k">INPUT</span> <span class="p">::</span> <span class="mi">0</span> <span class="o">-&gt;</span> <span class="n">number</span> <span class="n">UInt64</span> <span class="p">:</span> <span class="mi">0</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Positions</span><span class="p">:</span> <span class="mi">0</span> <span class="err">│</span> <span class="err">│</span> <span class="n">SettingQuotaAndLimits</span> <span class="p">(</span><span class="k">Set</span> <span class="n">limits</span> <span class="k">and</span> <span class="n">quota</span> <span class="k">after</span> <span class="n">reading</span> <span class="k">from</span> <span class="k">storage</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Header</span><span class="p">:</span> <span class="n">number</span> <span class="n">UInt64</span> <span class="err">│</span> <span class="err">│</span> <span class="k">Limit</span> <span class="p">(</span><span class="n">preliminary</span> <span class="k">LIMIT</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Header</span><span class="p">:</span> <span class="n">number</span> <span class="n">UInt64</span> <span class="err">│</span> <span class="err">│</span> <span class="k">Limit</span> <span class="mi">10</span> <span class="err">│</span> <span class="err">│</span> <span class="k">Offset</span> <span class="mi">0</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ReadFromStorage</span> <span class="p">(</span><span class="n">SystemNumbers</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Header</span><span class="p">:</span> <span class="n">number</span> <span class="n">UInt64</span> <span class="err">│</span> <span class="err">└───────────────────────────────────────────────────────────────────────────┘</span> </code></pre></div></div> <p>2)AST 语法树</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">AST</span> <span class="k">SELECT</span> <span class="n">number</span> <span class="k">FROM</span> <span class="k">system</span><span class="p">.</span><span class="n">numbers</span> <span class="k">LIMIT</span> <span class="mi">10</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">af4f359b</span><span class="o">-</span><span class="n">c72e</span><span class="o">-</span><span class="mi">41</span><span class="n">e7</span><span class="o">-</span><span class="n">b1f4</span><span class="o">-</span><span class="mi">477</span><span class="n">d87597458</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">─────────────────────────────────────┐</span> <span class="err">│</span> <span class="n">SelectWithUnionQuery</span> <span class="p">(</span><span class="n">children</span> <span class="mi">1</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ExpressionList</span> <span class="p">(</span><span class="n">children</span> <span class="mi">1</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">SelectQuery</span> <span class="p">(</span><span class="n">children</span> <span class="mi">3</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ExpressionList</span> <span class="p">(</span><span class="n">children</span> <span class="mi">1</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Identifier</span> <span class="n">number</span> <span class="err">│</span> <span class="err">│</span> <span class="n">TablesInSelectQuery</span> <span class="p">(</span><span class="n">children</span> <span class="mi">1</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">TablesInSelectQueryElement</span> <span class="p">(</span><span class="n">children</span> <span class="mi">1</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">TableExpression</span> <span class="p">(</span><span class="n">children</span> <span class="mi">1</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">TableIdentifier</span> <span class="k">system</span><span class="p">.</span><span class="n">numbers</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Literal</span> <span class="n">UInt64_10</span> <span class="err">│</span> <span class="err">└─────────────────────────────────────────────┘</span> </code></pre></div></div> <p>3)SYNTAX 语法优化 先做一次查询</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'hello'</span><span class="p">,</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'world'</span><span class="p">,</span> <span class="s1">'mhk'</span><span class="p">))</span> <span class="k">FROM</span> <span class="n">numbers</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">f17854b0</span><span class="o">-</span><span class="mi">61</span><span class="n">ca</span><span class="o">-</span><span class="mi">4</span><span class="n">d25</span><span class="o">-</span><span class="mi">961</span><span class="n">a</span><span class="o">-</span><span class="mi">567</span><span class="n">db0fba1e7</span> <span class="err">┌─</span><span class="n">if</span><span class="p">(</span><span class="k">equals</span><span class="p">(</span><span class="n">number</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="s1">'hello'</span><span class="p">,</span> <span class="n">if</span><span class="p">(</span><span class="k">equals</span><span class="p">(</span><span class="n">number</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="s1">'world'</span><span class="p">,</span> <span class="s1">'mhk'</span><span class="p">))</span><span class="err">─┐</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">hello</span> <span class="err">│</span> <span class="err">│</span> <span class="n">world</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">│</span> <span class="n">mhk</span> <span class="err">│</span> <span class="err">└───────────────────────────────────────────────────────────────────────┘</span> </code></pre></div></div> <p>查看语法优化</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'hello'</span><span class="p">,</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'world'</span><span class="p">,</span> <span class="s1">'mhk'</span><span class="p">))</span> <span class="k">FROM</span> <span class="n">numbers</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">a706af9e</span><span class="o">-</span><span class="mi">986</span><span class="k">c</span><span class="o">-</span><span class="mi">48</span><span class="n">e2</span><span class="o">-</span><span class="mi">8446</span><span class="o">-</span><span class="mi">1</span><span class="n">ce411e5ef96</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">────────────────────────────────────────────────────────┐</span> <span class="err">│</span> <span class="k">SELECT</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'hello'</span><span class="p">,</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'world'</span><span class="p">,</span> <span class="s1">'mhk'</span><span class="p">))</span> <span class="err">│</span> <span class="err">│</span> <span class="k">FROM</span> <span class="n">numbers</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="err">│</span> <span class="err">└────────────────────────────────────────────────────────────────┘</span> </code></pre></div></div> <p>开启三元运算符优化</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SET</span> <span class="n">optimize_if_chain_to_multiif</span> <span class="o">=</span> <span class="mi">1</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">b9439d72</span><span class="o">-</span><span class="mi">9</span><span class="n">de7</span><span class="o">-</span><span class="mi">45</span><span class="n">b0</span><span class="o">-</span><span class="mi">83</span><span class="n">c7</span><span class="o">-</span><span class="mi">954034</span><span class="n">c897a7</span> <span class="n">Ok</span><span class="p">.</span> </code></pre></div></div> <p>再次查看语法优化</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'hello'</span><span class="p">,</span> <span class="n">if</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'world'</span><span class="p">,</span> <span class="s1">'mhk'</span><span class="p">))</span> <span class="k">FROM</span> <span class="n">numbers</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">7202</span><span class="n">a628</span><span class="o">-</span><span class="n">c87e</span><span class="o">-</span><span class="mi">403</span><span class="n">a</span><span class="o">-</span><span class="n">ab67</span><span class="o">-</span><span class="n">ac0a3e6c31e3</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">─────────────────────────────────────────────────────────┐</span> <span class="err">│</span> <span class="k">SELECT</span> <span class="n">multiIf</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'hello'</span><span class="p">,</span> <span class="n">number</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'world'</span><span class="p">,</span> <span class="s1">'mhk'</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="k">FROM</span> <span class="n">numbers</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="err">│</span> <span class="err">└─────────────────────────────────────────────────────────────────┘再次查看语法优化</span> </code></pre></div></div> <p>4)查看 PIPELINE</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">PIPELINE</span> <span class="k">SELECT</span> <span class="k">sum</span><span class="p">(</span><span class="n">number</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">numbers_mt</span><span class="p">(</span><span class="mi">100000</span><span class="p">)</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">number</span> <span class="o">%</span> <span class="mi">20</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">b02792e0</span><span class="o">-</span><span class="mi">0555</span><span class="o">-</span><span class="mi">4</span><span class="n">dce</span><span class="o">-</span><span class="mi">84</span><span class="n">a8</span><span class="o">-</span><span class="mi">353</span><span class="n">ededfc4cf</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">─────────────────────────┐</span> <span class="err">│</span> <span class="p">(</span><span class="n">Expression</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ExpressionTransform</span> <span class="err">│</span> <span class="err">│</span> <span class="p">(</span><span class="n">Aggregating</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">Resize</span> <span class="mi">2</span> <span class="err">→</span> <span class="mi">1</span> <span class="err">│</span> <span class="err">│</span> <span class="n">AggregatingTransform</span> <span class="err">×</span> <span class="mi">2</span> <span class="err">│</span> <span class="err">│</span> <span class="p">(</span><span class="n">Expression</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">ExpressionTransform</span> <span class="err">×</span> <span class="mi">2</span> <span class="err">│</span> <span class="err">│</span> <span class="p">(</span><span class="n">SettingQuotaAndLimits</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="p">(</span><span class="n">ReadFromStorage</span><span class="p">)</span> <span class="err">│</span> <span class="err">│</span> <span class="n">NumbersMt</span> <span class="err">×</span> <span class="mi">2</span> <span class="mi">0</span> <span class="err">→</span> <span class="mi">1</span> <span class="err">│</span> <span class="err">└─────────────────────────────────┘</span> </code></pre></div></div> <p>打开其他参数</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">PIPELINE</span> <span class="n">header</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">graph</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">SELECT</span> <span class="k">sum</span><span class="p">(</span><span class="n">number</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">numbers_mt</span><span class="p">(</span><span class="mi">10000</span><span class="p">)</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">number</span> <span class="o">%</span> <span class="mi">20</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">b2051401</span><span class="o">-</span><span class="n">c27e</span><span class="o">-</span><span class="mi">4</span><span class="n">f8c</span><span class="o">-</span><span class="n">bacf</span><span class="o">-</span><span class="mi">90061</span><span class="n">e4c6539</span> <span class="err">┌─</span><span class="k">explain</span><span class="err">─────────────────────────────────────┐</span> <span class="err">│</span> <span class="n">digraph</span> <span class="err">│</span> <span class="err">│</span> <span class="p">{</span> <span class="err">│</span> <span class="err">│</span> <span class="n">rankdir</span><span class="o">=</span><span class="nv">"LR"</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="p">{</span> <span class="n">node</span> <span class="p">[</span><span class="n">shape</span> <span class="o">=</span> <span class="n">box</span><span class="p">]</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n2</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">"Limit"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n1</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">"Numbers"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="n">subgraph</span> <span class="n">cluster_0</span> <span class="p">{</span> <span class="err">│</span> <span class="err">│</span> <span class="n">label</span> <span class="o">=</span><span class="nv">"Aggregating"</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">style</span><span class="o">=</span><span class="n">filled</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">color</span><span class="o">=</span><span class="n">lightgrey</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">node</span> <span class="p">[</span><span class="n">style</span><span class="o">=</span><span class="n">filled</span><span class="p">,</span><span class="n">color</span><span class="o">=</span><span class="n">white</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">{</span> <span class="n">rank</span> <span class="o">=</span> <span class="n">same</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n4</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">"AggregatingTransform"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="n">subgraph</span> <span class="n">cluster_1</span> <span class="p">{</span> <span class="err">│</span> <span class="err">│</span> <span class="n">label</span> <span class="o">=</span><span class="nv">"Expression"</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">style</span><span class="o">=</span><span class="n">filled</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">color</span><span class="o">=</span><span class="n">lightgrey</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">node</span> <span class="p">[</span><span class="n">style</span><span class="o">=</span><span class="n">filled</span><span class="p">,</span><span class="n">color</span><span class="o">=</span><span class="n">white</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">{</span> <span class="n">rank</span> <span class="o">=</span> <span class="n">same</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n5</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">"ExpressionTransform"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="n">subgraph</span> <span class="n">cluster_2</span> <span class="p">{</span> <span class="err">│</span> <span class="err">│</span> <span class="n">label</span> <span class="o">=</span><span class="nv">"Expression"</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">style</span><span class="o">=</span><span class="n">filled</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">color</span><span class="o">=</span><span class="n">lightgrey</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">node</span> <span class="p">[</span><span class="n">style</span><span class="o">=</span><span class="n">filled</span><span class="p">,</span><span class="n">color</span><span class="o">=</span><span class="n">white</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">{</span> <span class="n">rank</span> <span class="o">=</span> <span class="n">same</span><span class="p">;</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n3</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">"ExpressionTransform"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n2</span> <span class="o">-&gt;</span> <span class="n">n3</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">" │ │ number UInt64 UInt64(size = 0)"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n1</span> <span class="o">-&gt;</span> <span class="n">n2</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">" │ │ number UInt64 UInt64(size = 0)"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n4</span> <span class="o">-&gt;</span> <span class="n">n5</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">" │ │ modulo(number, 20) UInt8 UInt8(size = 0) │ │ sum(number) UInt64 UInt64(size = 0)"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="n">n3</span> <span class="o">-&gt;</span> <span class="n">n4</span> <span class="p">[</span><span class="n">label</span><span class="o">=</span><span class="nv">" │ │ number UInt64 UInt64(size = 0) │ │ modulo(number, 20) UInt8 UInt8(size = 0)"</span><span class="p">];</span> <span class="err">│</span> <span class="err">│</span> <span class="p">}</span> <span class="err">│</span> <span class="err">└─────────────────────────────────────────────┘</span> </code></pre></div></div> <h1 id="2-建表优化">2. 建表优化</h1> <h2 id="21-数据类型">2.1 数据类型</h2> <h3 id="211-时间字段的类型">2.1.1 时间字段的类型</h3> <p>建表时能用数值型或日期时间型表示的字段就不要用字符串,全 String 类型在以 Hive 为中心的数仓建设中常见,但 ClickHouse 环境不应受此影响。 虽然 ClickHouse 底层将 DateTime 存储为时间戳 Long 类型,但不建议存储 Long 类型, 因为 DateTime 不需要经过函数转换处理,执行效率高、可读性好。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_type2</span><span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="p">,</span> <span class="n">create_time</span> <span class="n">Int32</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">ReplacingMergeTree</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">toDate</span><span class="p">(</span><span class="n">create_time</span><span class="p">))</span> <span class="err">–</span><span class="o">-</span><span class="err">需要转换一次,否则报错</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <h3 id="212-空值存储类型">2.1.2 空值存储类型</h3> <p>官方已经指出 <strong>Nullable 类型几乎总是会拖累性能</strong>,因为存储 Nullable 列时需要创建一个额外的文件来存储 NULL 的标记,并且 Nullable 列无法被索引。因此除非极特殊情况,应直接使用字段默认值表示空,或者自行指定一个在业务中无意义的值(例如用-1 表示没有商品 ID)。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">t_null</span><span class="p">(</span><span class="n">x</span> <span class="n">Int8</span><span class="p">,</span> <span class="n">y</span> <span class="k">Nullable</span><span class="p">(</span><span class="n">Int8</span><span class="p">))</span> <span class="n">ENGINE</span> <span class="n">TinyLog</span><span class="p">;</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">t_null</span> <span class="p">(</span> <span class="nv">`x`</span> <span class="n">Int8</span><span class="p">,</span> <span class="nv">`y`</span> <span class="k">Nullable</span><span class="p">(</span><span class="n">Int8</span><span class="p">)</span> <span class="p">)</span> <span class="n">ENGINE</span> <span class="o">=</span> <span class="n">TinyLog</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">ad870b41</span><span class="o">-</span><span class="mi">0650</span><span class="o">-</span><span class="mi">4</span><span class="n">fe6</span><span class="o">-</span><span class="mi">8313</span><span class="o">-</span><span class="n">aabdc2693946</span> <span class="n">Ok</span><span class="p">.</span> <span class="mi">0</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">045</span> <span class="n">sec</span><span class="p">.</span> <span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">t_null</span> <span class="k">VALUES</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">NULL</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span> <span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">t_null</span> <span class="k">VALUES</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">33</span><span class="n">e450f9</span><span class="o">-</span><span class="mi">0</span><span class="n">c21</span><span class="o">-</span><span class="mi">483</span><span class="n">e</span><span class="o">-</span><span class="mi">99</span><span class="n">fd</span><span class="o">-</span><span class="n">a35d793f77f4</span> <span class="n">Ok</span><span class="p">.</span> <span class="mi">2</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">104</span> <span class="n">sec</span><span class="p">.</span> <span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">SELECT</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="k">FROM</span> <span class="n">t_null</span><span class="p">;</span> <span class="k">SELECT</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="k">FROM</span> <span class="n">t_null</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">99828183</span><span class="o">-</span><span class="mi">9860</span><span class="o">-</span><span class="mi">4</span><span class="n">c6b</span><span class="o">-</span><span class="n">bb45</span><span class="o">-</span><span class="mi">065729</span><span class="n">d6df38</span> <span class="err">┌─</span><span class="n">plus</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span><span class="err">─┐</span> <span class="err">│</span> <span class="err">ᴺᵁᴸᴸ</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">5</span> <span class="err">│</span> <span class="err">└────────────┘</span> <span class="mi">2</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">014</span> <span class="n">sec</span><span class="p">.</span> </code></pre></div></div> <p>查看存储的文件:(没有权限就用 root 用户)</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">root</span><span class="o">@</span><span class="n">hadoop102</span> <span class="n">t_null</span><span class="p">]</span><span class="o">#</span> <span class="n">pwd</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">clickhouse</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="k">default</span><span class="o">/</span><span class="n">t_null</span> <span class="p">[</span><span class="n">root</span><span class="o">@</span><span class="n">hadoop102</span> <span class="n">t_null</span><span class="p">]</span><span class="o">#</span> <span class="n">ll</span> <span class="err">总用量</span> <span class="mi">16</span> <span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="c1">----- 1 clickhouse clickhouse 91 4月 22 21:03 sizes.json</span> <span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="c1">----- 1 clickhouse clickhouse 28 4月 22 21:03 x.bin</span> <span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="c1">----- 1 clickhouse clickhouse 28 4月 22 21:03 y.bin</span> <span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="c1">----- 1 clickhouse clickhouse 28 4月 22 21:03 y.null.bin</span> </code></pre></div></div> <h2 id="22-分区和索引">2.2 分区和索引</h2> <p>分区粒度根据业务特点决定,不宜过粗或过细。一般选择<strong>按天分区</strong>,也可以指定为 Tuple(), 以单表一亿数据为例,分区大小控制在 10-30 个为最佳。</p> <p>必须指定索引列,ClickHouse 中的索引列即排序列,通过 order by 指定,一般在查询条件中经常被用来充当筛选条件的属性被纳入进来;可以是单一维度,也可以是组合维度的索引;通常需要满足高级列在前、查询频率大的在前原则;还有基数特别大的不适合做索引列,</p> <p>如用户表的 userid 字段;通常<strong>筛选后的数据满足在百万以内为最佳</strong>。</p> <h2 id="23-表参数">2.3 表参数</h2> <p>Index_granularity 是用来控制索引粒度的,默认是 8192,如非必须不建议调整。 如果表中不是必须保留全量历史数据,建议指定 TTL(生存时间值),可以免去手动过期历史数据的麻烦,TTL 也可以通过 alter table 语句随时修改。</p> <h2 id="24-写入和删除优化">2.4 写入和删除优化</h2> <p>(1)尽量不要执行单条或小批量删除和插入操作,这样会产生小分区文件,给后台 Merge 任务带来巨大压力 (2)不要一次写入太多分区,或数据写入太快,数据写入太快会导致 Merge 速度跟不上而报错,一般建议每秒钟发起 2-3 次写入操作,每次操作写入 2w~5w 条数据(依服务器性能而定) 写入过快报错,报错信息:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">1</span><span class="p">.</span> <span class="n">Code</span><span class="p">:</span> <span class="mi">252</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">displayText</span><span class="p">()</span> <span class="o">=</span> <span class="n">DB</span><span class="p">::</span><span class="n">Exception</span><span class="p">:</span> <span class="n">Too</span> <span class="n">many</span> <span class="n">parts</span><span class="p">(</span><span class="mi">304</span><span class="p">).</span> <span class="n">Merges</span> <span class="k">are</span> <span class="n">processing</span> <span class="n">significantly</span> <span class="n">slower</span> <span class="k">than</span> <span class="n">inserts</span> <span class="mi">2</span><span class="p">.</span> <span class="n">Code</span><span class="p">:</span> <span class="mi">241</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">displayText</span><span class="p">()</span> <span class="o">=</span> <span class="n">DB</span><span class="p">::</span><span class="n">Exception</span><span class="p">:</span> <span class="n">Memory</span> <span class="k">limit</span> <span class="p">(</span><span class="k">for</span> <span class="n">query</span><span class="p">)</span> <span class="n">exceeded</span><span class="p">:</span><span class="n">would</span> <span class="n">use</span> <span class="mi">9</span><span class="p">.</span><span class="mi">37</span> <span class="n">GiB</span> <span class="p">(</span><span class="n">attempt</span> <span class="k">to</span> <span class="k">allocate</span> <span class="n">chunk</span> <span class="k">of</span> <span class="mi">301989888</span> <span class="n">bytes</span><span class="p">),</span> <span class="n">maximum</span><span class="p">:</span> <span class="mi">9</span><span class="p">.</span><span class="mi">31</span> <span class="n">GiB</span> </code></pre></div></div> <p>处理方式: “ Too many parts 处理 ” :使用 WAL 预写日志,提高写入性能。 in_memory_parts_enable_wal 默认为 true 在服务器内存充裕的情况下增加内存配额,一般通过 max_memory_usage 来实现 在服务器内存不充裕的情况下,建议将超出部分内容分配到系统硬盘上,但会降低执行速度,一般通过 max_bytes_before_external_group_by、max_bytes_before_external_sort 参数来实现。</p> <h2 id="25-常见配置">2.5 常见配置</h2> <p>配置项主要在 config.xml 或 users.xml 中, 基本上都在 users.xml 里</p> <ul> <li>config.xml 的配置项</li> </ul> <p>https://clickhouse.tech/docs/en/operations/server-configuration-parameters/settings/</p> <ul> <li>users.xml 的配置项</li> </ul> <p>https://clickhouse.tech/docs/en/operations/settings/settings/</p> <h3 id="251-cpu-资源">2.5.1 CPU 资源</h3> <p>| 配置 | 描述 | | — | — | | background_pool_size | 后台线程池的大小,merge 线程就是在该线程中执行,该线程池不仅仅是给 merge 线程用的,默认值 16,允许的前提下建议改成 cpu 个数的 2 倍(线程数)。 | | background_schedule_pool_size | 执行后台任务(复制表、Kafka 流、DNS 缓存更新)的线程数。默认 128,建议改成 cpu 个数的 2 倍(线程数)。 | | background_distributed_schedule_pool_size | 设置为分布式发送执行后台任务的线程数,默认 16,建议改成 cpu个数的 2 倍(线程数)。 | | max_concurrent_queries | 最大并发处理的请求数(包含 select,insert 等),默认值 100,推荐 150(不够再加)~300。 | | max_threads | 设置单个查询所能使用的最大 cpu 个数,默认是 cpu 核数 |</p> <h3 id="252-内存资源">2.5.2 内存资源</h3> <p>| 配置 | 描述 | | — | — | | max_memory_usage | 此参数在 users.xml 中,表示单次 Query 占用内存最大值,该值可以设置的比较大,这样可以提升集群查询的上限。 保留一点给 OS,比如 128G 内存的机器,设置为 100GB。 | | max_bytes_before_external_group_by | 一般按照 max_memory_usage 的一半设置内存,当 group 使用内存超过阈值后会刷新到磁盘进行。 因为 clickhouse 聚合分两个阶段:查询并及建立中间数据、合并中间数据,结合上一项,建议 50GB。 | | max_bytes_before_external_sort | 当 order by 已使用 max_bytes_before_external_sort 内存就进行溢写磁盘(基于磁盘排序),如果不设置该值,那么当内存不够时直接抛错,设置了该值 order by 可以正常完成,但是速度相对存内存来 说肯定要慢点(实测慢的非常多,无法接受)。 | | max_table_size_to_drop | 此参数在 config.xml 中,应用于需要删除表或分区的情况,默认是50GB,意思是如果删除 50GB 以上的分区表会失败。建议修改为 0,这样不管多大的分区表都可以删除。 |</p> <h3 id="253-存储">2.5.3 存储</h3> <p>ClickHouse 不支持设置多数据目录,为了提升数据 io 性能,可以挂载虚拟券组,一个券组绑定多块物理磁盘提升读写性能,多数据查询场景 SSD 会比普通机械硬盘快 2-3 倍。</p> <h1 id="3-clickhouse-语法优化规则">3. ClickHouse 语法优化规则</h1> <p>ClickHouse 的 SQL 优化规则是基于 RBO(Rule Based Optimization),下面是一些优化规则</p> <h2 id="31-count-优化">3.1 COUNT 优化</h2> <p>在调用 count 函数时,如果使用的是 count() 或者 count(*),且没有 where 条件,则会直接使用 system.tables 的 total_rows,例如:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="k">SELECT</span> <span class="k">count</span><span class="p">()</span><span class="k">FROM</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span><span class="p">;</span> <span class="k">Union</span> <span class="n">Expression</span> <span class="p">(</span><span class="n">Projection</span><span class="p">)</span> <span class="n">Expression</span> <span class="p">(</span><span class="k">Before</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="k">and</span> <span class="k">SELECT</span><span class="p">)</span> <span class="n">MergingAggregated</span> <span class="n">ReadNothing</span> <span class="p">(</span><span class="n">Optimized</span> <span class="n">trivial</span> <span class="k">count</span><span class="p">)</span> </code></pre></div></div> <p>注意 Optimized trivial count ,这是对 count 的优化。 如果 count 具体的列字段,则不会使用此项优化:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="k">SELECT</span> <span class="k">count</span><span class="p">(</span><span class="n">CounterID</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span><span class="p">;</span> <span class="k">Union</span> <span class="n">Expression</span> <span class="p">(</span><span class="n">Projection</span><span class="p">)</span> <span class="n">Expression</span> <span class="p">(</span><span class="k">Before</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="k">and</span> <span class="k">SELECT</span><span class="p">)</span> <span class="n">Aggregating</span> <span class="n">Expression</span> <span class="p">(</span><span class="k">Before</span> <span class="k">GROUP</span> <span class="k">BY</span><span class="p">)</span> <span class="n">ReadFromStorage</span> <span class="p">(</span><span class="k">Read</span> <span class="k">from</span> <span class="n">MergeTree</span><span class="p">)</span> </code></pre></div></div> <h2 id="32-消除子查询重复字段">3.2 消除子查询重复字段</h2> <p>下面语句子查询中有两个重复的 id 字段,会被去重:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="n">a</span><span class="p">.</span><span class="n">UserID</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">VisitID</span><span class="p">,</span> <span class="n">a</span><span class="p">.</span><span class="n">URL</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">UserID</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">AS</span> <span class="n">a</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">UserID</span> <span class="k">as</span> <span class="n">HaHa</span><span class="p">,</span> <span class="n">VisitID</span> <span class="k">FROM</span> <span class="n">visits_v1</span><span class="p">)</span> <span class="k">AS</span> <span class="n">b</span> <span class="k">USING</span> <span class="p">(</span><span class="n">UserID</span><span class="p">)</span> <span class="k">limit</span> <span class="mi">3</span><span class="p">;</span> <span class="o">//</span><span class="err">返回优化语句:</span> <span class="k">SELECT</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">VisitID</span><span class="p">,</span> <span class="n">URL</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">UserID</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">AS</span> <span class="n">a</span> <span class="k">ALL</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">VisitID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">b</span> <span class="k">USING</span> <span class="p">(</span><span class="n">UserID</span><span class="p">)</span> <span class="k">LIMIT</span> <span class="mi">3</span> </code></pre></div></div> <h2 id="33-谓词下推">3.3 谓词下推</h2> <p>当 group by 有 having 子句,但是没有 with cube、with rollup 或者 with totals 修饰的时候,having 过滤会下推到 where 提前过滤。例如下面的查询,HAVING name 变成了 WHERE name,在 group by 之前过滤:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">UserID</span> <span class="k">HAVING</span> <span class="n">UserID</span> <span class="o">=</span> <span class="s1">'8585742290196126178'</span><span class="p">;</span> <span class="o">//</span><span class="err">返回优化语句</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">WHERE</span> <span class="n">UserID</span> <span class="o">=</span> <span class="err">\</span><span class="s1">'8585742290196126178</span><span class="se">\'</span><span class="s1"> GROUP BY UserID </span></code></pre></div></div> <p>子查询也支持谓词下推:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="p">)</span> <span class="k">WHERE</span> <span class="n">UserID</span> <span class="o">=</span> <span class="s1">'8585742290196126178'</span> <span class="o">//</span><span class="err">返回优化后的语句</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">WHERE</span> <span class="n">UserID</span> <span class="o">=</span> <span class="err">\</span><span class="s1">'8585742290196126178</span><span class="se">\'</span><span class="s1"> ) WHERE UserID = </span><span class="se">\'</span><span class="s1">8585742290196126178</span><span class="se">\'</span><span class="s1"> </span></code></pre></div></div> <p>再来一个复杂例子:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="p">)</span> <span class="k">UNION</span> <span class="k">ALL</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="p">)</span> <span class="p">)</span> <span class="k">WHERE</span> <span class="n">UserID</span> <span class="o">=</span> <span class="s1">'8585742290196126178'</span> <span class="o">//</span><span class="err">返回优化后的语句</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">WHERE</span> <span class="n">UserID</span> <span class="o">=</span> <span class="err">\</span><span class="s1">'8585742290196126178</span><span class="se">\'</span><span class="s1"> ) WHERE UserID = </span><span class="se">\'</span><span class="s1">8585742290196126178</span><span class="se">\'</span><span class="s1"> UNION ALL SELECT UserID FROM ( SELECT UserID FROM visits_v1 WHERE UserID = </span><span class="se">\'</span><span class="s1">8585742290196126178</span><span class="se">\'</span><span class="s1"> ) WHERE UserID = </span><span class="se">\'</span><span class="s1">8585742290196126178</span><span class="se">\'</span><span class="s1"> ) WHERE UserID = </span><span class="se">\'</span><span class="s1">8585742290196126178</span><span class="se">\'</span><span class="s1"> </span></code></pre></div></div> <h2 id="34-聚合计算外推">3.4 聚合计算外推</h2> <p>聚合函数内的计算,会外推,例如: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650718114457-45d2a547-f970-4524-b3d5-ac1bb6752000.png#clientId=u8279787c-d242-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=327&amp;id=uad21cf5e&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=327&amp;originWidth=435&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=53889&amp;status=done&amp;style=none&amp;taskId=ua339c514-5c07-4252-92ff-e1dd377f6e7&amp;title=&amp;width=435" alt="image.png" /></p> <h2 id="35-聚合函数消除">3.5 聚合函数消除</h2> <p>如果对聚合键,也就是 group by key 使用 min、max、any 聚合函数,则将函数消除, 例如:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="k">sum</span><span class="p">(</span><span class="n">UserID</span> <span class="o">*</span> <span class="mi">2</span><span class="p">),</span> <span class="k">max</span><span class="p">(</span><span class="n">VisitID</span><span class="p">),</span> <span class="k">max</span><span class="p">(</span><span class="n">UserID</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">UserID</span> <span class="o">//</span><span class="err">返回优化后的语句</span> <span class="k">SELECT</span> <span class="k">sum</span><span class="p">(</span><span class="n">UserID</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="k">max</span><span class="p">(</span><span class="n">VisitID</span><span class="p">),</span> <span class="n">UserID</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">UserID</span> </code></pre></div></div> <h2 id="36-删除重复的-order-by-key">3.6 删除重复的 order by key</h2> <p>例如下面的语句,重复的聚合键 id 字段会被去重:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">UserID</span> <span class="k">ASC</span><span class="p">,</span> <span class="n">UserID</span> <span class="k">ASC</span><span class="p">,</span> <span class="n">VisitID</span> <span class="k">ASC</span><span class="p">,</span> <span class="n">VisitID</span> <span class="k">ASC</span> <span class="o">//</span><span class="err">返回优化后的语句:</span> <span class="k">select</span> <span class="err">……</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">UserID</span> <span class="k">ASC</span><span class="p">,</span> <span class="n">VisitID</span> <span class="k">ASC</span> </code></pre></div></div> <h2 id="37-删除重复的-limit-by-key">3.7 删除重复的 limit by key</h2> <p>例如下面的语句,重复声明的 name 字段会被去重:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">LIMIT</span> <span class="mi">3</span> <span class="k">BY</span> <span class="n">VisitID</span><span class="p">,</span> <span class="n">VisitID</span> <span class="k">LIMIT</span> <span class="mi">10</span> <span class="o">//</span><span class="err">返回优化后的语句:</span> <span class="k">select</span> <span class="err">……</span> <span class="k">FROM</span> <span class="n">visits_v1</span> <span class="k">LIMIT</span> <span class="mi">3</span> <span class="k">BY</span> <span class="n">VisitID</span> <span class="k">LIMIT</span> <span class="mi">10</span> </code></pre></div></div> <h2 id="38-删除重复的-using-key">3.8 删除重复的 USING Key</h2> <p>例如下面的语句,重复的关联键 id 字段会被去重:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="n">a</span><span class="p">.</span><span class="n">UserID</span><span class="p">,</span> <span class="n">a</span><span class="p">.</span><span class="n">UserID</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">VisitID</span><span class="p">,</span> <span class="n">a</span><span class="p">.</span><span class="n">URL</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">UserID</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">AS</span> <span class="n">a</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="n">visits_v1</span> <span class="k">AS</span> <span class="n">b</span> <span class="k">USING</span> <span class="p">(</span><span class="n">UserID</span><span class="p">,</span> <span class="n">UserID</span><span class="p">)</span> <span class="o">//</span><span class="err">返回优化后的语句:</span> <span class="k">SELECT</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">VisitID</span><span class="p">,</span> <span class="n">URL</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">UserID</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">AS</span> <span class="n">a</span> <span class="k">ALL</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="n">visits_v1</span> <span class="k">AS</span> <span class="n">b</span> <span class="k">USING</span> <span class="p">(</span><span class="n">UserID</span><span class="p">)</span> </code></pre></div></div> <h2 id="39-标量替换">3.9 标量替换</h2> <p>如果子查询只返回一行数据,在被引用的时候用标量替换,例如下面语句中的 total_disk_usage 字段:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">WITH</span> <span class="p">(</span> <span class="k">SELECT</span> <span class="k">sum</span><span class="p">(</span><span class="n">bytes</span><span class="p">)</span> <span class="k">FROM</span> <span class="k">system</span><span class="p">.</span><span class="n">parts</span> <span class="k">WHERE</span> <span class="n">active</span> <span class="p">)</span> <span class="k">AS</span> <span class="n">total_disk_usage</span> <span class="k">SELECT</span> <span class="p">(</span><span class="k">sum</span><span class="p">(</span><span class="n">bytes</span><span class="p">)</span> <span class="o">/</span> <span class="n">total_disk_usage</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span> <span class="k">AS</span> <span class="n">table_disk_usage</span><span class="p">,</span> <span class="k">table</span> <span class="k">FROM</span> <span class="k">system</span><span class="p">.</span><span class="n">parts</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="k">table</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">table_disk_usage</span> <span class="k">DESC</span> <span class="k">LIMIT</span> <span class="mi">10</span><span class="p">;</span> <span class="o">//</span><span class="err">返回优化后的语句:</span> <span class="k">WITH</span> <span class="k">CAST</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="err">\</span><span class="s1">'UInt64</span><span class="se">\'</span><span class="s1">) AS total_disk_usage SELECT (sum(bytes) / total_disk_usage) * 100 AS table_disk_usage, table FROM system.parts GROUP BY table ORDER BY table_disk_usage DESC LIMIT 10 </span></code></pre></div></div> <h2 id="310-三元运算优化">3.10 三元运算优化</h2> <p>如果开启了 optimize_if_chain_to_multiif 参数,三元运算符会被替换成 multiIf 函数, 例如:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">EXPLAIN</span> <span class="n">SYNTAX</span> <span class="k">SELECT</span> <span class="n">number</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">?</span> <span class="s1">'hello'</span> <span class="p">:</span> <span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">?</span> <span class="s1">'world'</span> <span class="p">:</span> <span class="s1">'mhk'</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">numbers</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="n">settings</span> <span class="n">optimize_if_chain_to_multiif</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="o">//</span><span class="err">返回优化后的语句:</span> <span class="k">SELECT</span> <span class="n">multiIf</span><span class="p">(</span><span class="n">number</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="err">\</span><span class="s1">'hello</span><span class="se">\'</span><span class="s1">, number = 2, </span><span class="se">\'</span><span class="s1">world</span><span class="se">\'</span><span class="s1">, </span><span class="se">\'</span><span class="s1">mhk</span><span class="se">\'</span><span class="s1">) FROM numbers(10) SETTINGS optimize_if_chain_to_multiif = 1 </span></code></pre></div></div> <h1 id="4-查询优化">4. 查询优化</h1> <h2 id="41-单表查询">4.1 单表查询</h2> <h3 id="411-prewhere-替代-where">4.1.1 Prewhere 替代 where</h3> <p>Prewhere 和 where 语句的作用相同,用来过滤数据。不同之处在于 prewhere 只支持 *MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤之后再读取 select 声明的列字段来补全其余属性。</p> <p>当查询列明显多于筛选列时使用 Prewhere 可十倍提升查询性能,Prewhere 会自动优化执行过滤阶段的数据读取方式,降低 io 操作。</p> <p>在某些场合下,prewhere 语句比 where 语句处理的数据量更少性能更高。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span><span class="err">关闭</span> <span class="k">where</span> <span class="err">自动转</span> <span class="n">prewhere</span><span class="p">(</span><span class="err">默认情况下,</span> <span class="k">where</span> <span class="err">条件会自动优化成</span> <span class="n">prewhere</span><span class="p">)</span> <span class="k">set</span> <span class="n">optimize_move_to_prewhere</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="o">#</span> <span class="err">使用</span> <span class="k">where</span> <span class="k">select</span> <span class="n">WatchID</span><span class="p">,</span> <span class="n">JavaEnable</span><span class="p">,</span> <span class="n">Title</span><span class="p">,</span> <span class="n">GoodEvent</span><span class="p">,</span> <span class="n">EventTime</span><span class="p">,</span> <span class="n">EventDate</span><span class="p">,</span> <span class="n">CounterID</span><span class="p">,</span> <span class="n">ClientIP</span><span class="p">,</span> <span class="n">ClientIP6</span><span class="p">,</span> <span class="n">RegionID</span><span class="p">,</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">CounterClass</span><span class="p">,</span> <span class="n">OS</span><span class="p">,</span> <span class="n">UserAgent</span><span class="p">,</span> <span class="n">URL</span><span class="p">,</span> <span class="n">Referer</span><span class="p">,</span> <span class="n">URLDomain</span><span class="p">,</span> <span class="n">RefererDomain</span><span class="p">,</span> <span class="n">Refresh</span><span class="p">,</span> <span class="n">IsRobot</span><span class="p">,</span> <span class="n">RefererCategories</span><span class="p">,</span> <span class="n">URLCategories</span><span class="p">,</span> <span class="n">URLRegions</span><span class="p">,</span> <span class="n">RefererRegions</span><span class="p">,</span> <span class="n">ResolutionWidth</span><span class="p">,</span> <span class="n">ResolutionHeight</span><span class="p">,</span> <span class="n">ResolutionDepth</span><span class="p">,</span> <span class="n">FlashMajor</span><span class="p">,</span> <span class="n">FlashMinor</span><span class="p">,</span> <span class="n">FlashMinor2</span> <span class="k">from</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span> <span class="k">where</span> <span class="n">UserID</span><span class="o">=</span><span class="s1">'3198390223272470366'</span><span class="p">;</span> <span class="o">#</span> <span class="err">使用</span> <span class="n">prewhere</span> <span class="err">关键字</span> <span class="k">select</span> <span class="n">WatchID</span><span class="p">,</span> <span class="n">JavaEnable</span><span class="p">,</span> <span class="n">Title</span><span class="p">,</span> <span class="n">GoodEvent</span><span class="p">,</span> <span class="n">EventTime</span><span class="p">,</span> <span class="n">EventDate</span><span class="p">,</span> <span class="n">CounterID</span><span class="p">,</span> <span class="n">ClientIP</span><span class="p">,</span> <span class="n">ClientIP6</span><span class="p">,</span> <span class="n">RegionID</span><span class="p">,</span> <span class="n">UserID</span><span class="p">,</span> <span class="n">CounterClass</span><span class="p">,</span> <span class="n">OS</span><span class="p">,</span> <span class="n">UserAgent</span><span class="p">,</span> <span class="n">URL</span><span class="p">,</span> <span class="n">Referer</span><span class="p">,</span> <span class="n">URLDomain</span><span class="p">,</span> <span class="n">RefererDomain</span><span class="p">,</span> <span class="n">Refresh</span><span class="p">,</span> <span class="n">IsRobot</span><span class="p">,</span> <span class="n">RefererCategories</span><span class="p">,</span> <span class="n">URLCategories</span><span class="p">,</span> <span class="n">URLRegions</span><span class="p">,</span> <span class="n">RefererRegions</span><span class="p">,</span> <span class="n">ResolutionWidth</span><span class="p">,</span> <span class="n">ResolutionHeight</span><span class="p">,</span> <span class="n">ResolutionDepth</span><span class="p">,</span> <span class="n">FlashMajor</span><span class="p">,</span> <span class="n">FlashMinor</span><span class="p">,</span> <span class="n">FlashMinor2</span> <span class="k">from</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span> <span class="n">prewhere</span> <span class="n">UserID</span><span class="o">=</span><span class="s1">'3198390223272470366'</span><span class="p">;</span> </code></pre></div></div> <p>默认情况,我们肯定不会关闭 where 自动优化成 prewhere,但是某些场景即使开启优化,也不会自动转换成 prewhere,需要手动指定 prewhere:</p> <ul> <li>使用常量表达式</li> <li>使用默认值为 alias 类型的字段</li> <li>包含了 arrayJOIN,globalIn,globalNotIn 或者 indexHint 的查询</li> <li>select 查询的列字段和 where 的谓词相同</li> <li>使用了主键字段 <h3 id="412-数据采样">4.1.2 数据采样</h3> <p>通过采样运算可极大提升数据分析的性能</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">Title</span><span class="p">,</span><span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">PageViews</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="n">SAMPLE</span> <span class="mi">0</span><span class="p">.</span><span class="mi">1</span> <span class="o">#</span><span class="err">代表采样</span> <span class="mi">10</span><span class="o">%</span><span class="err">的数据</span><span class="p">,</span><span class="err">也可以是具体的条数</span> <span class="k">WHERE</span> <span class="n">CounterID</span> <span class="o">=</span><span class="mi">57</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">Title</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">PageViews</span> <span class="k">DESC</span> <span class="k">LIMIT</span> <span class="mi">1000</span> </code></pre></div> </div> <p>采样修饰符只有在 MergeTree engine 表中才有效,且在创建表时需要指定采样策略。</p> </li> </ul> <h3 id="413-列裁剪与分区裁剪">4.1.3 列裁剪与分区裁剪</h3> <p>数据量太大时应避免使用 select * 操作,查询的性能会与查询的字段大小和数量成线性 表换,字段越少,消耗的 io 资源越少,性能就会越高。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">反例:</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span><span class="p">;</span> <span class="err">正例:</span> <span class="k">select</span> <span class="n">WatchID</span><span class="p">,</span> <span class="n">JavaEnable</span><span class="p">,</span> <span class="n">Title</span><span class="p">,</span> <span class="n">GoodEvent</span><span class="p">,</span> <span class="n">EventTime</span><span class="p">,</span> <span class="n">EventDate</span><span class="p">,</span> <span class="n">CounterID</span><span class="p">,</span> <span class="n">ClientIP</span><span class="p">,</span> <span class="n">ClientIP6</span><span class="p">,</span> <span class="n">RegionID</span><span class="p">,</span> <span class="n">UserID</span> <span class="k">from</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span><span class="p">;</span> </code></pre></div></div> <p>分区裁剪就是只读取需要的分区,在过滤条件中指定。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="n">WatchID</span><span class="p">,</span> <span class="n">JavaEnable</span><span class="p">,</span> <span class="n">Title</span><span class="p">,</span> <span class="n">GoodEvent</span><span class="p">,</span> <span class="n">EventTime</span><span class="p">,</span> <span class="n">EventDate</span><span class="p">,</span> <span class="n">CounterID</span><span class="p">,</span> <span class="n">ClientIP</span><span class="p">,</span> <span class="n">ClientIP6</span><span class="p">,</span> <span class="n">RegionID</span><span class="p">,</span> <span class="n">UserID</span> <span class="k">from</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span> <span class="k">where</span> <span class="n">EventDate</span><span class="o">=</span><span class="s1">'2014-03-23'</span><span class="p">;</span> </code></pre></div></div> <h3 id="414-orderby-结合-wherelimit">4.1.4 orderby 结合 where、limit</h3> <p>千万以上数据集进行 order by 查询时需要搭配 where 条件和 limit 语句一起使用。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span><span class="err">正例:</span> <span class="k">SELECT</span> <span class="n">UserID</span><span class="p">,</span><span class="n">Age</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">WHERE</span> <span class="n">CounterID</span><span class="o">=</span><span class="mi">57</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">Age</span> <span class="k">DESC</span> <span class="k">LIMIT</span> <span class="mi">1000</span> <span class="o">#</span><span class="err">反例:</span> <span class="k">SELECT</span> <span class="n">UserID</span><span class="p">,</span><span class="n">Age</span> <span class="k">FROM</span> <span class="n">hits_v1</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">Age</span> <span class="k">DESC</span> </code></pre></div></div> <h3 id="415-避免构建虚拟列">4.1.5 避免构建虚拟列</h3> <p>如非必须,不要在结果集上构建虚拟列,虚拟列非常消耗资源浪费性能,可以考虑在前端进行处理,或者在表中构造实际字段进行额外存储。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">反例:</span> <span class="k">SELECT</span> <span class="n">Income</span><span class="p">,</span><span class="n">Age</span><span class="p">,</span><span class="n">Income</span><span class="o">/</span><span class="n">Age</span> <span class="k">as</span> <span class="n">IncRate</span> <span class="k">FROM</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span><span class="p">;</span> <span class="err">正例:拿到</span> <span class="n">Income</span> <span class="err">和</span> <span class="n">Age</span> <span class="err">后,考虑在前端进行处理,或者在表中构造实际字段进行额外存储</span> <span class="k">SELECT</span> <span class="n">Income</span><span class="p">,</span><span class="n">Age</span> <span class="k">FROM</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span><span class="p">;</span> </code></pre></div></div> <h3 id="416-uniqcombined-替代-distinct">4.1.6 uniqCombined 替代 distinct</h3> <p>性能可提升 10 倍以上,uniqCombined 底层采用类似 HyperLogLog 算法实现,能接收 2% 左右的数据误差,可直接使用这种去重方式提升查询性能。Count(distinct )会使用 uniqExact 精确去重。</p> <p>不建议在千万级不同数据上执行 distinct 去重查询,改为近似去重 uniqCombined</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">反例:</span> <span class="k">select</span> <span class="k">count</span><span class="p">(</span><span class="k">distinct</span> <span class="n">rand</span><span class="p">())</span> <span class="k">from</span> <span class="n">hits_v1</span><span class="p">;</span> <span class="err">正例:</span> <span class="k">SELECT</span> <span class="n">uniqCombined</span><span class="p">(</span><span class="n">rand</span><span class="p">())</span> <span class="k">from</span> <span class="n">datasets</span><span class="p">.</span><span class="n">hits_v1</span> </code></pre></div></div> <h3 id="417-其他注意事项">4.1.7 其他注意事项</h3> <p>**(1)查询熔断 ** 为了避免因个别慢查询引起的服务雪崩的问题,除了可以为单个查询设置超时以外,还可以配置周期熔断,在一个查询周期内,如果用户频繁进行慢查询操作超出规定阈值后将无法继续进行查询操作。</p> <p>**(2)关闭虚拟内存 ** 物理内存和虚拟内存的数据交换,会导致查询变慢,资源允许的情况下关闭虚拟内存。</p> <p>**(3)配置 join_use_nulls ** 为每一个账户添加 join_use_nulls 配置,左表中的一条记录在右表中不存在,右表的相应字段会返回该字段相应数据类型的默认值,而不是标准 SQL 中的 Null 值。</p> <p>**(4)批量写入时先排序 ** 批量写入数据时,必须控制每个批次的数据中涉及到的分区的数量,在写入之前最好对需要导入的数据进行排序。无序的数据或者涉及的分区太多,会导致 ClickHouse 无法及时对新导入的数据进行合并,从而影响查询性能。</p> <p>**(5)关注 CPU ** cpu 一般在 50%左右会出现查询波动,达到 70%会出现大范围的查询超时,cpu 是最关键的指标,要非常关注。</p> <h2 id="42-多表关联">4.2 多表关联</h2> <h3 id="421-准备表和数据">4.2.1 准备表和数据</h3> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#</span><span class="err">创建小表</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">visits_v2</span> <span class="n">ENGINE</span> <span class="o">=</span> <span class="n">CollapsingMergeTree</span><span class="p">(</span><span class="n">Sign</span><span class="p">)</span> <span class="k">PARTITION</span> <span class="k">BY</span> <span class="n">toYYYYMM</span><span class="p">(</span><span class="n">StartDate</span><span class="p">)</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="p">(</span><span class="n">CounterID</span><span class="p">,</span> <span class="n">StartDate</span><span class="p">,</span> <span class="n">intHash32</span><span class="p">(</span><span class="n">UserID</span><span class="p">),</span> <span class="n">VisitID</span><span class="p">)</span> <span class="n">SAMPLE</span> <span class="k">BY</span> <span class="n">intHash32</span><span class="p">(</span><span class="n">UserID</span><span class="p">)</span> <span class="n">SETTINGS</span> <span class="n">index_granularity</span> <span class="o">=</span> <span class="mi">8192</span> <span class="k">as</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">visits_v1</span> <span class="k">limit</span> <span class="mi">10000</span><span class="p">;</span> <span class="o">#</span><span class="err">创建</span> <span class="k">join</span> <span class="err">结果表:避免控制台疯狂打印数据</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">hits_v2</span> <span class="n">ENGINE</span> <span class="o">=</span> <span class="n">MergeTree</span><span class="p">()</span> <span class="k">PARTITION</span> <span class="k">BY</span> <span class="n">toYYYYMM</span><span class="p">(</span><span class="n">EventDate</span><span class="p">)</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="p">(</span><span class="n">CounterID</span><span class="p">,</span> <span class="n">EventDate</span><span class="p">,</span> <span class="n">intHash32</span><span class="p">(</span><span class="n">UserID</span><span class="p">))</span> <span class="n">SAMPLE</span> <span class="k">BY</span> <span class="n">intHash32</span><span class="p">(</span><span class="n">UserID</span><span class="p">)</span> <span class="n">SETTINGS</span> <span class="n">index_granularity</span> <span class="o">=</span> <span class="mi">8192</span> <span class="k">as</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="k">where</span> <span class="mi">1</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> </code></pre></div></div> <h3 id="422-用-in-代替-join">4.2.2 用 IN 代替 JOIN</h3> <p>当多表联查时,查询的数据仅从其中一张表出时,可考虑用 IN 操作而不是 JOIN</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">hits_v2</span> <span class="k">select</span> <span class="n">a</span><span class="p">.</span><span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="n">a</span> <span class="k">where</span> <span class="n">a</span><span class="p">.</span><span class="n">CounterID</span> <span class="k">in</span> <span class="p">(</span><span class="k">select</span> <span class="n">CounterID</span> <span class="k">from</span> <span class="n">visits_v1</span><span class="p">);</span> <span class="o">#</span><span class="err">反例:使用</span> <span class="k">join</span> <span class="k">insert</span> <span class="k">into</span> <span class="k">table</span> <span class="n">hits_v2</span> <span class="k">select</span> <span class="n">a</span><span class="p">.</span><span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="n">a</span> <span class="k">left</span> <span class="k">join</span> <span class="n">visits_v1</span> <span class="n">b</span> <span class="k">on</span> <span class="n">a</span><span class="p">.</span><span class="n">CounterID</span><span class="o">=</span><span class="n">b</span><span class="p">.</span><span class="n">CounterID</span><span class="p">;</span> </code></pre></div></div> <h3 id="423-大小表-join">4.2.3 大小表 JOIN</h3> <p>多表 join 时要满足小表在右的原则,右表关联时被加载到内存中与左表进行比较, ClickHouse 中无论是 Left join 、Right join 还是 Inner join 永远都是拿着右表中的每一条记录到左表中查找该记录是否存在,所以右表必须是小表。</p> <h3 id="424-注意谓词下推版本差异">4.2.4 注意谓词下推(版本差异)</h3> <p>ClickHouse 在 join 查询时不会主动发起谓词下推的操作,需要每个子查询提前完成过滤操作,需要注意的是,是否执行谓词下推,对性能影响差别很大(新版本中已经不存在此问题,但是需要注意谓词的位置的不同依然有性能的差异)</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">Explain</span> <span class="n">syntax</span> <span class="k">select</span> <span class="n">a</span><span class="p">.</span><span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="n">a</span> <span class="k">left</span> <span class="k">join</span> <span class="n">visits_v2</span> <span class="n">b</span> <span class="k">on</span> <span class="n">a</span><span class="p">.</span><span class="n">CounterID</span><span class="o">=</span><span class="n">b</span><span class="p">.</span><span class="n">CounterID</span> <span class="k">having</span> <span class="n">a</span><span class="p">.</span><span class="n">EventDate</span> <span class="o">=</span> <span class="s1">'2014-03-17'</span><span class="p">;</span> <span class="k">Explain</span> <span class="n">syntax</span> <span class="k">select</span> <span class="n">a</span><span class="p">.</span><span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="n">a</span> <span class="k">left</span> <span class="k">join</span> <span class="n">visits_v2</span> <span class="n">b</span> <span class="k">on</span> <span class="n">a</span><span class="p">.</span><span class="n">CounterID</span><span class="o">=</span><span class="n">b</span><span class="p">.</span><span class="n">CounterID</span> <span class="k">having</span> <span class="n">b</span><span class="p">.</span><span class="n">StartDate</span> <span class="o">=</span> <span class="s1">'2014-03-17'</span><span class="p">;</span> <span class="k">insert</span> <span class="k">into</span> <span class="n">hits_v2</span> <span class="k">select</span> <span class="n">a</span><span class="p">.</span><span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="n">a</span> <span class="k">left</span> <span class="k">join</span> <span class="n">visits_v2</span> <span class="n">b</span> <span class="k">on</span> <span class="n">a</span><span class="p">.</span><span class="n">CounterID</span><span class="o">=</span><span class="n">b</span><span class="p">.</span><span class="n">CounterID</span> <span class="k">where</span> <span class="n">a</span><span class="p">.</span><span class="n">EventDate</span> <span class="o">=</span> <span class="s1">'2014-03-17'</span><span class="p">;</span> <span class="k">insert</span> <span class="k">into</span> <span class="n">hits_v2</span> <span class="k">select</span> <span class="n">a</span><span class="p">.</span><span class="o">*</span> <span class="k">from</span> <span class="p">(</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">hits_v1</span> <span class="k">where</span> <span class="n">EventDate</span> <span class="o">=</span> <span class="s1">'2014-03-17'</span> <span class="p">)</span> <span class="n">a</span> <span class="k">left</span> <span class="k">join</span> <span class="n">visits_v2</span> <span class="n">b</span> <span class="k">on</span> <span class="n">a</span><span class="p">.</span><span class="n">CounterID</span><span class="o">=</span><span class="n">b</span><span class="p">.</span><span class="n">CounterID</span><span class="p">;</span> </code></pre></div></div> <h3 id="425-分布式表使用-global">4.2.5 分布式表使用 GLOBAL</h3> <p>两张分布式表上的 IN 和 JOIN 之前必须加上 GLOBAL 关键字,右表只会在接收查询请求的那个节点查询一次,并将其分发到其他节点上。如果不加 GLOBAL 关键字的话,每个节点都会单独发起一次对右表的查询,而右表又是分布式表,就导致右表一共会被查询 N²次(N是该分布式表的分片数量),这就是查询放大,会带来很大开销。</p> <h3 id="426-使用字典表">4.2.6 使用字典表</h3> <p>将一些需要关联分析的业务创建成字典表进行 join 操作,前提是字典表不宜太大,因为字典表会常驻内存</p> <h3 id="427-提前过滤">4.2.7 提前过滤</h3> <p>通过增加逻辑过滤可以减少数据扫描,达到提高执行速度及降低内存消耗的目的</p> <p>ck的Join</p> <ol> <li>原理: 右表加载到内存,再去匹配</li> <li>为什么join不行? 因为1</li> <li>非要使用join,怎么用比较好: 能先过滤的过滤,特别是右表 右边放小表 特殊场景可以考虑使用字典表 可以替换的话,尽量不要使用join,比如用 IN 实现 <h1 id="5-数据一致性">5. 数据一致性</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650722867114-c373e377-1355-4063-b0ef-141292fc4546.png#clientId=u8279787c-d242-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=224&amp;id=ufa586daa&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=224&amp;originWidth=777&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=104212&amp;status=done&amp;style=none&amp;taskId=u594a5647-5eed-4b4b-bea5-b4906b27545&amp;title=&amp;width=777" alt="image.png" /> 数据一致性: replacingMergeTree 不能保证查询时没重复,只能保证最终一致性 解决方案:</p> <ol> <li>手动执行: 生产环境不推荐</li> <li>通过SQL实现去重: group by ==》高级一点的用法,加标记字段</li> <li>使用final: 20.5之前的版本,final是单线程 20.5之后的版本,final可以是多线程,但是读取数据是串行的</li> <li>重复一点无所谓: 100万日活,统计出来100万1千</li> </ol> </li> </ol> <h1 id="6-物化视图">6. 物化视图</h1> <p>ClickHouse 的物化视图是一种查询结果的持久化,它确实是给我们带来了查询效率的提升。用户查起来跟表没有区别,它就是一张表,它也像是一张时刻在预计算的表,创建的过程它是用了一个特殊引擎,加上后来 as select,就是 create 一个 table as select 的写法。</p> <p>“查询结果集”的范围很宽泛,可以是基础表中部分数据的一份简单拷贝,也可以是多表 join 之后产生的结果或其子集,或者原始数据的聚合指标等等。所以,物化视图不会随着基础表的变化而变化,所以它也称为快照(snapshot)</p> <h2 id="61-概述">6.1 概述</h2> <h3 id="611-物化视图与普通视图的区别">6.1.1 物化视图与普通视图的区别</h3> <p><strong>普通视图不保存数据,保存的仅仅是查询语句</strong>,查询的时候还是从原表读取数据,可以将普通视图理解为是个子查询。<strong>物化视图则是把查询的结果根据相应的引擎存入到了磁盘或内存中</strong>,对数据重新进行了组织,你可以理解物化视图是完全的一张新表。</p> <h3 id="612-优缺点">6.1.2 优缺点</h3> <p>优点:查询速度<strong>快</strong>,要是把物化视图这些规则全部写好,它比原数据查询快了很多,总的行数少了,因为都预计算好了。</p> <p>缺点:它的本质是一个流式数据的使用场景,是累加式的技术,所以要用历史数据做去重、去核这样的分析,在物化视图里面是不太好用的。在某些场景的使用也是有限的。而且如果一张表加了好多物化视图,在写这张表的时候,就会消耗很多机器的资源,比如数据带宽占满、存储一下子增加了很多。</p> <h3 id="613-基本语法">6.1.3 基本语法</h3> <p>也是 create 语法,会创建一个隐藏的目标表来保存视图数据。也可以 TO 表名,保存到一张显式的表。没有加 TO 表名,表名默认就是 .inner.物化视图名</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="p">[</span><span class="n">MATERIALIZED</span><span class="p">]</span> <span class="k">VIEW</span> <span class="p">[</span><span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span><span class="p">]</span> <span class="p">[</span><span class="n">db</span><span class="p">.]</span><span class="k">table_name</span> <span class="p">[</span><span class="k">TO</span><span class="p">[</span><span class="n">db</span><span class="p">.]</span><span class="n">name</span><span class="p">]</span> <span class="p">[</span><span class="n">ENGINE</span> <span class="o">=</span> <span class="n">engine</span><span class="p">]</span> <span class="p">[</span><span class="n">POPULATE</span><span class="p">]</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="p">...</span> </code></pre></div></div> <p>populate:不建议添加,加上这个参数在创建视图的时候会尝试将历史所有数据重新走一遍并呈下来 **1)创建物化视图的限制 **</p> <ol> <li>必须指定物化视图的 engine 用于数据存储</li> <li>TO [db].[table]语法的时候,不得使用 POPULATE。</li> <li>查询语句(select)可以包含下面的子句: DISTINCT, GROUP BY, ORDER BY, LIMIT…</li> <li>物化视图的 alter 操作有些限制,操作起来不大方便。</li> <li>若物化视图的定义使用了 TO [db.]name 子语句,则可以将目标表的视图 卸载 DETACH 再装载 ATTACH</li> </ol> <p>**2)物化视图的数据更新 ** (1)物化视图创建好之后,若源表被写入新数据则物化视图也会同步更新 (2)POPULATE 关键字决定了物化视图的更新策略:</p> <ul> <li>若有 POPULATE 则在创建视图的过程会将源表已经存在的数据一并导入,类似于 create table … as</li> <li>若无 POPULATE 则物化视图在创建之后没有数据,只会在创建只有同步之后写入源表的数据</li> <li>clickhouse 官方并不推荐使用 POPULATE,因为在创建物化视图的过程中同时写入</li> </ul> <p>的数据不能被插入物化视图。 (3)物化视图不支持同步删除,若源表的数据不存在(删除了)则物化视图的数据仍然保留 (4)物化视图是一种特殊的数据表,可以用 show tables 查看 (5)物化视图数据的删除: (6)物化视图的删除:</p> ClickHouse表引擎 https://muhaokang.github.io/ClickHouse-%E8%A1%A8%E5%BC%95%E6%93%8E/ https://muhaokang.github.io/ClickHouse-%E8%A1%A8%E5%BC%95%E6%93%8E/ Fri, 22 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 表引擎的使用 表引擎是 ClickHouse 的一大特色。可以说, 表引擎决定了如何存储表的数据。包括: 数据的存储方式和位置,写到哪里以及从哪里读取数据。 支持哪些查询以及如何支持。 并发数据访问。 索引的使用(如果存在)。 是否可以执行多线程请求。 数据复制参数。 表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关参... <h1 id="1-表引擎的使用">1. 表引擎的使用</h1> <p>表引擎是 ClickHouse 的一大特色。可以说, 表引擎决定了如何存储表的数据。包括:</p> <ul> <li>数据的存储方式和位置,写到哪里以及从哪里读取数据。</li> <li>支持哪些查询以及如何支持。</li> <li>并发数据访问。</li> <li>索引的使用(如果存在)。</li> <li>是否可以执行多线程请求。</li> <li>数据复制参数。</li> </ul> <p>表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关参数。 特别注意:引擎的名称大小写敏感</p> <h1 id="2-tinylog">2. TinyLog</h1> <p>以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表, 生产环境上作用有限。可以用于平时练习测试用。 如:</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">create</span> <span class="k">table</span> <span class="n">t_tinylog</span> <span class="p">(</span> <span class="n">id</span> <span class="n">String</span><span class="p">,</span> <span class="n">name</span> <span class="n">String</span><span class="p">)</span> <span class="n">engine</span><span class="o">=</span><span class="n">TinyLog</span><span class="p">;</span> <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">t_tinylog</span> <span class="p">(</span> <span class="nv">`id`</span> <span class="n">String</span><span class="p">,</span> <span class="nv">`name`</span> <span class="n">String</span> <span class="p">)</span> <span class="n">ENGINE</span> <span class="o">=</span> <span class="n">TinyLog</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">1198</span><span class="n">d2f9</span><span class="o">-</span><span class="n">aa70</span><span class="o">-</span><span class="mi">4661</span><span class="o">-</span><span class="n">a6ab</span><span class="o">-</span><span class="n">a648d467e049</span> <span class="n">Ok</span><span class="p">.</span> <span class="mi">0</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">008</span> <span class="n">sec</span><span class="p">.</span> </code></pre></div></div> <h1 id="3-memory">3. Memory</h1> <p>内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。 读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过 10G/s)。</p> <p>一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概 1 亿行)的场景。 Version:0.9 StartHTML:0000000105 EndHTML:0000002532 StartFragment:0000000141 EndFragment:0000002492</p> <h1 id="4-mergetree">4. MergeTree</h1> <p>ClickHouse 中最强大的表引擎当属 MergeTree(合并树)引擎及该系列(*MergeTree)中的其他引擎,支持索引和分区,地位可以相当于 innodb 之于 Mysql。而且基于 MergeTree,还衍生除了很多小弟,也是非常有特色的引擎。 <strong>1)建表语句</strong></p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_mt</span><span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="n">create_time</span> <span class="nb">Datetime</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">MergeTree</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <p><strong>2)插入数据</strong></p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_mt</span> <span class="k">values</span> <span class="p">(</span><span class="mi">101</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">)</span> <span class="p">,</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 11:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_004'</span><span class="p">,</span><span class="mi">2500</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">12000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-02 12:00:00'</span><span class="p">);</span> </code></pre></div></div> <p>MergeTree 其实还有很多参数(绝大多数用默认值即可),但是三个参数是更加重要的, 也涉及了关于 MergeTree 的很多概念。</p> <h2 id="41-partition-by-分区可选">4.1 partition by 分区(可选)</h2> <p><strong>1)作用</strong> 学过 hive 的应该都不陌生,分区的目的主要是降低扫描的范围,优化查询速度</p> <p>**2)如果不填 ** 只会使用一个分区。</p> <p>**3)分区目录 ** MergeTree 是以列文件+索引文件+表定义文件组成的,但是如果设定了分区那么这些文件就会保存到不同的分区目录中。</p> <p>**4)并行 ** 分区后,面对涉及跨分区的查询统计,ClickHouse 会以分区为单位并行处理。</p> <p>**5)数据写入与分区合并 ** 任何一个批次的数据写入都会产生一个临时分区,不会纳入任何一个已有的分区。写入后的某个时刻(大概 10-15 分钟后),ClickHouse 会自动执行合并操作(等不及也可以手动通过 optimize 执行),把临时分区的数据,合并到已有分区中。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">optimize</span> <span class="k">table</span> <span class="n">xxxx</span> <span class="k">final</span><span class="p">;</span> </code></pre></div></div> <p>**6)例如 ** 再次执行上面的插入操作</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_mt</span> <span class="k">values</span> <span class="p">(</span><span class="mi">101</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">)</span> <span class="p">,</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 11:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_004'</span><span class="p">,</span><span class="mi">2500</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">12000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-02 12:00:00'</span><span class="p">);</span> </code></pre></div></div> <p>查看数据并没有纳入任何分区</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_mt</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_mt</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">05</span><span class="n">f4c4a0</span><span class="o">-</span><span class="n">fce7</span><span class="o">-</span><span class="mi">4</span><span class="n">bd3</span><span class="o">-</span><span class="mi">8</span><span class="n">d01</span><span class="o">-</span><span class="mi">14</span><span class="n">c87ebf7d79</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <p>手动 optimize 之后</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="n">optimize</span> <span class="k">table</span> <span class="n">t_order_mt</span> <span class="k">final</span><span class="p">;</span> <span class="n">OPTIMIZE</span> <span class="k">TABLE</span> <span class="n">t_order_mt</span> <span class="k">FINAL</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">5</span><span class="n">c63b117</span><span class="o">-</span><span class="mi">9150</span><span class="o">-</span><span class="mi">4245</span><span class="o">-</span><span class="mi">9</span><span class="n">bf4</span><span class="o">-</span><span class="n">b047ff37e64d</span> <span class="n">Ok</span><span class="p">.</span> <span class="mi">0</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">005</span> <span class="n">sec</span><span class="p">.</span> <span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_mt</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_mt</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">5</span><span class="n">db17a7a</span><span class="o">-</span><span class="n">fd16</span><span class="o">-</span><span class="mi">4</span><span class="n">ea4</span><span class="o">-</span><span class="mi">9</span><span class="n">bce</span><span class="o">-</span><span class="mi">1987</span><span class="n">adc0c020</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <h2 id="42-primary-key-主键可选">4.2 primary key 主键(可选)</h2> <p>ClickHouse 中的主键,和其他数据库不太一样,<strong>它只提供了数据的一级索引,但是却不是唯一约束。</strong>这就意味着是可以存在相同 primary key 的数据的。</p> <p>主键的设定主要依据是查询语句中的 where 条件。</p> <p>根据条件通过对主键进行某种形式的二分查找,能够定位到对应的 index granularity,避免了全表扫描。</p> <p>index granularity: 直接翻译的话就是索引粒度,指在稀疏索引中两个相邻索引对应数据的间隔。ClickHouse 中的 MergeTree 默认是 8192。官方不建议修改这个值,除非该列存在大量重复值,比如在一个分区中几万行才有一个不同数据。 <strong>稀疏索引:</strong> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650536950294-b18ba132-2a34-436e-a9f3-5d2273058049.png#clientId=u01ebed79-edcc-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=414&amp;id=u1f6c1fe6&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=414&amp;originWidth=751&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=249522&amp;status=done&amp;style=none&amp;taskId=u9c94c0c4-5bf8-405e-83b1-7d2e4cef002&amp;title=&amp;width=751" alt="image.png" /> 稀疏索引的好处就是可以用很少的索引数据,定位更多的数据,代价就是只能定位到索引粒度的第一行,然后再进行进行一点扫描。</p> <h2 id="43-order-by必选">4.3 order by(必选)</h2> <p>order by 设定了分区内的数据按照哪些字段顺序进行有序保存。 order by 是 MergeTree 中唯一一个必填项,甚至比 primary key 还重要,因为当用户不设置主键的情况,很多处理会依照 order by 的字段进行处理(比如后面会讲的去重和汇总)。 要求:主键必须是 order by 字段的前缀字段。 比如 order by 字段是 (id,sku_id) 那么主键必须是 id 或者(id,sku_id)</p> <h2 id="44-二级索引">4.4 二级索引</h2> <p>目前在 ClickHouse 的官网上二级索引的功能在 v20.1.2.4 之前是被标注为实验性的,在这个版本之后默认是开启的。</p> <p>**1)老版本使用二级索引前需要增加设置 ** 是否允许使用实验性的二级索引(v20.1.2.4 开始,这个参数已被删除,默认开启)</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span> <span class="n">allow_experimental_data_skipping_indices</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span> </code></pre></div></div> <p><strong>2)创建测试表</strong></p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_mt2</span><span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="n">create_time</span> <span class="nb">Datetime</span><span class="p">,</span> <span class="k">INDEX</span> <span class="n">a</span> <span class="n">total_amount</span> <span class="k">TYPE</span> <span class="n">minmax</span> <span class="n">GRANULARITY</span> <span class="mi">5</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">MergeTree</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <p>其中 GRANULARITY N 是设定二级索引对于一级索引粒度的粒度。</p> <p><strong>3)插入数据</strong></p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_mt2</span> <span class="k">values</span> <span class="p">(</span><span class="mi">101</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">)</span> <span class="p">,</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 11:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_004'</span><span class="p">,</span><span class="mi">2500</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">12000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-02 12:00:00'</span><span class="p">);</span> </code></pre></div></div> <p>**4)对比效果 ** 那么在使用下面语句进行测试,可以看出二级索引能够为非主键字段的查询发挥作用。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">[</span><span class="n">mhk</span><span class="o">@</span><span class="n">hadoop102</span> <span class="o">~</span><span class="p">]</span><span class="err">$</span> <span class="n">clickhouse</span><span class="o">-</span><span class="n">client</span> <span class="c1">--send_logs_level=trace &lt;&lt;&lt; 'select * from t_order_mt2 where total_amount &gt; toDecimal32(900., 2)';</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">734615</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="n">executeQuery</span><span class="p">:</span> <span class="p">(</span><span class="k">from</span> <span class="p">[::</span><span class="mi">1</span><span class="p">]:</span><span class="mi">47482</span><span class="p">,</span> <span class="k">using</span> <span class="n">production</span> <span class="n">parser</span><span class="p">)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_mt2</span> <span class="k">where</span> <span class="n">total_amount</span> <span class="o">&gt;</span> <span class="n">toDecimal32</span><span class="p">(</span><span class="mi">900</span><span class="p">.,</span> <span class="mi">2</span><span class="p">)</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">738641</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="n">InterpreterSelectQuery</span><span class="p">:</span> <span class="n">MergeTreeWhereOptimizer</span><span class="p">:</span> <span class="n">condition</span> <span class="nv">"total_amount &gt; toDecimal32(900., 2)"</span> <span class="n">moved</span> <span class="k">to</span> <span class="n">PREWHERE</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">739326</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Trace</span><span class="o">&gt;</span> <span class="n">ContextAccess</span> <span class="p">(</span><span class="k">default</span><span class="p">):</span> <span class="k">Access</span> <span class="k">granted</span><span class="p">:</span> <span class="k">SELECT</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">sku_id</span><span class="p">,</span> <span class="n">total_amount</span><span class="p">,</span> <span class="n">create_time</span><span class="p">)</span> <span class="k">ON</span> <span class="k">default</span><span class="p">.</span><span class="n">t_order_mt2</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">739672</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Trace</span><span class="o">&gt;</span> <span class="n">InterpreterSelectQuery</span><span class="p">:</span> <span class="n">FetchColumns</span> <span class="o">-&gt;</span> <span class="n">Complete</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">740449</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="k">default</span><span class="p">.</span><span class="n">t_order_mt2</span> <span class="p">(</span><span class="n">d745abc9</span><span class="o">-</span><span class="n">fa2e</span><span class="o">-</span><span class="mi">4</span><span class="n">e8d</span><span class="o">-</span><span class="mi">9745</span><span class="o">-</span><span class="n">abc9fa2e8e8d</span><span class="p">)</span> <span class="p">(</span><span class="n">SelectExecutor</span><span class="p">):</span> <span class="k">Key</span> <span class="n">condition</span><span class="p">:</span> <span class="k">unknown</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">740858</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="k">default</span><span class="p">.</span><span class="n">t_order_mt2</span> <span class="p">(</span><span class="n">d745abc9</span><span class="o">-</span><span class="n">fa2e</span><span class="o">-</span><span class="mi">4</span><span class="n">e8d</span><span class="o">-</span><span class="mi">9745</span><span class="o">-</span><span class="n">abc9fa2e8e8d</span><span class="p">)</span> <span class="p">(</span><span class="n">SelectExecutor</span><span class="p">):</span> <span class="n">MinMax</span> <span class="k">index</span> <span class="n">condition</span><span class="p">:</span> <span class="k">unknown</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">743255</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="k">default</span><span class="p">.</span><span class="n">t_order_mt2</span> <span class="p">(</span><span class="n">d745abc9</span><span class="o">-</span><span class="n">fa2e</span><span class="o">-</span><span class="mi">4</span><span class="n">e8d</span><span class="o">-</span><span class="mi">9745</span><span class="o">-</span><span class="n">abc9fa2e8e8d</span><span class="p">)</span> <span class="p">(</span><span class="n">SelectExecutor</span><span class="p">):</span> <span class="k">Index</span> <span class="nv">`a`</span> <span class="n">has</span> <span class="n">dropped</span> <span class="mi">1</span><span class="o">/</span><span class="mi">2</span> <span class="n">granules</span><span class="p">.</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">743363</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="k">default</span><span class="p">.</span><span class="n">t_order_mt2</span> <span class="p">(</span><span class="n">d745abc9</span><span class="o">-</span><span class="n">fa2e</span><span class="o">-</span><span class="mi">4</span><span class="n">e8d</span><span class="o">-</span><span class="mi">9745</span><span class="o">-</span><span class="n">abc9fa2e8e8d</span><span class="p">)</span> <span class="p">(</span><span class="n">SelectExecutor</span><span class="p">):</span> <span class="n">Selected</span> <span class="mi">2</span><span class="o">/</span><span class="mi">2</span> <span class="n">parts</span> <span class="k">by</span> <span class="k">partition</span> <span class="k">key</span><span class="p">,</span> <span class="mi">1</span> <span class="n">parts</span> <span class="k">by</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span> <span class="mi">2</span><span class="o">/</span><span class="mi">2</span> <span class="n">marks</span> <span class="k">by</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span> <span class="mi">1</span> <span class="n">marks</span> <span class="k">to</span> <span class="k">read</span> <span class="k">from</span> <span class="mi">1</span> <span class="n">ranges</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">743492</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="n">MergeTreeSelectProcessor</span><span class="p">:</span> <span class="n">Reading</span> <span class="mi">1</span> <span class="n">ranges</span> <span class="k">from</span> <span class="n">part</span> <span class="mi">20200601</span><span class="n">_1_1_0</span><span class="p">,</span> <span class="n">approx</span><span class="p">.</span> <span class="mi">5</span> <span class="k">rows</span> <span class="n">starting</span> <span class="k">from</span> <span class="mi">0</span> <span class="mi">101</span> <span class="n">sku_001</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="mi">102</span> <span class="n">sku_002</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="mi">102</span> <span class="n">sku_002</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="mi">102</span> <span class="n">sku_002</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="mi">102</span> <span class="n">sku_004</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">750132</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Information</span><span class="o">&gt;</span> <span class="n">executeQuery</span><span class="p">:</span> <span class="k">Read</span> <span class="mi">5</span> <span class="k">rows</span><span class="p">,</span> <span class="mi">160</span><span class="p">.</span><span class="mi">00</span> <span class="n">B</span> <span class="k">in</span> <span class="mi">0</span><span class="p">.</span><span class="mi">015278276</span> <span class="n">sec</span><span class="p">.,</span> <span class="mi">327</span> <span class="k">rows</span><span class="o">/</span><span class="n">sec</span><span class="p">.,</span> <span class="mi">10</span><span class="p">.</span><span class="mi">23</span> <span class="n">KiB</span><span class="o">/</span><span class="n">sec</span><span class="p">.</span> <span class="p">[</span><span class="n">hadoop102</span><span class="p">]</span> <span class="mi">2022</span><span class="p">.</span><span class="mi">04</span><span class="p">.</span><span class="mi">21</span> <span class="mi">11</span><span class="p">:</span><span class="mi">02</span><span class="p">:</span><span class="mi">48</span><span class="p">.</span><span class="mi">750215</span> <span class="p">[</span> <span class="mi">3080</span> <span class="p">]</span> <span class="p">{</span><span class="n">a6707ccc</span><span class="o">-</span><span class="mi">7</span><span class="n">da3</span><span class="o">-</span><span class="mi">4</span><span class="n">b07</span><span class="o">-</span><span class="mi">80</span><span class="n">a9</span><span class="o">-</span><span class="mi">15</span><span class="n">d42c6db6f6</span><span class="p">}</span> <span class="o">&lt;</span><span class="n">Debug</span><span class="o">&gt;</span> <span class="n">MemoryTracker</span><span class="p">:</span> <span class="n">Peak</span> <span class="n">memory</span> <span class="k">usage</span> <span class="p">(</span><span class="k">for</span> <span class="n">query</span><span class="p">):</span> <span class="mi">0</span><span class="p">.</span><span class="mi">00</span> <span class="n">B</span><span class="p">.</span> </code></pre></div></div> <h2 id="45-数据-ttl">4.5 数据 TTL</h2> <p>TTL 即 Time To Live,MergeTree 提供了可以管理数据表或者列的生命周期的功能。 **1)列级别 TTL ** (1)创建测试表</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_mt3</span><span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="n">TTL</span> <span class="n">create_time</span><span class="o">+</span><span class="n">interval</span> <span class="mi">10</span> <span class="k">SECOND</span><span class="p">,</span> <span class="n">create_time</span> <span class="nb">Datetime</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">MergeTree</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <p>(2)插入数据(注意:根据实际时间改变)</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_mt3</span> <span class="k">values</span> <span class="p">(</span><span class="mi">106</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2022-04-21 18:53:30'</span><span class="p">),</span> <span class="p">(</span><span class="mi">107</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2022-04-21 18:53:31'</span><span class="p">),</span> <span class="p">(</span><span class="mi">110</span><span class="p">,</span><span class="s1">'sku_003'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2022-04-21 18:53:32'</span><span class="p">);</span> </code></pre></div></div> <p>(3)手动合并,查看效果 到期后,指定的字段数据归 0 如果不成功重启ClickHouse试试</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_mt3</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_mt3</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">dd37debb</span><span class="o">-</span><span class="mi">0</span><span class="n">ae6</span><span class="o">-</span><span class="mi">4</span><span class="n">a21</span><span class="o">-</span><span class="mi">9</span><span class="n">ee8</span><span class="o">-</span><span class="n">f92659f483d7</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">110</span> <span class="err">│</span> <span class="n">sku_003</span> <span class="err">│</span> <span class="mi">0</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">13</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">106</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">0</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">12</span> <span class="mi">22</span><span class="p">:</span><span class="mi">52</span><span class="p">:</span><span class="mi">30</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">107</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">0</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">12</span> <span class="mi">22</span><span class="p">:</span><span class="mi">52</span><span class="p">:</span><span class="mi">30</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <p>**2)表级 TTL ** 下面的这条语句是数据会在 create_time 之后 10 秒丢失</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">alter</span> <span class="k">table</span> <span class="n">t_order_mt3</span> <span class="k">MODIFY</span> <span class="n">TTL</span> <span class="n">create_time</span> <span class="o">+</span> <span class="n">INTERVAL</span> <span class="mi">10</span> <span class="k">SECOND</span><span class="p">;</span> </code></pre></div></div> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_mt3</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_mt3</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">e775e83a</span><span class="o">-</span><span class="mi">02</span><span class="n">a5</span><span class="o">-</span><span class="mi">4</span><span class="n">f49</span><span class="o">-</span><span class="mi">87</span><span class="n">ad</span><span class="o">-</span><span class="mi">23999</span><span class="n">b19026f</span> <span class="n">ok</span><span class="p">.</span> <span class="mi">0</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">004</span> <span class="n">sec</span><span class="p">.</span> </code></pre></div></div> <p>涉及判断的字段必须是 Date 或者 Datetime 类型,推荐使用分区的日期字段。 能够使用的时间周期:</p> <ul> <li>SECOND</li> <li>MINUTE</li> <li>HOUR</li> <li>DAY</li> <li>WEEK</li> <li>MONTH</li> <li>QUARTER</li> <li>YEAR</li> </ul> <h1 id="5-replacingmergetree">5. ReplacingMergeTree</h1> <p>ReplacingMergeTree 是 MergeTree 的一个变种,它存储特性完全继承 MergeTree,只是多了一个去重的功能。 尽管 MergeTree 可以设置主键,但是 primary key 其实没有唯一约束的功能。如果你想处理掉重复的数据,可以借助这个 ReplacingMergeTree。 **1)去重时机 ** 数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预先作出计划。有一些数据可能仍未被处理。</p> <p>**2)去重范围 ** 如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。 所以 ReplacingMergeTree 能力有限, ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。</p> <p>**3)案例演示 ** (1)创建表</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_rmt</span><span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="p">,</span> <span class="n">create_time</span> <span class="nb">Datetime</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">ReplacingMergeTree</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <p>ReplacingMergeTree() 填入的参数为版本字段,重复数据保留版本字段值最大的。 如果不填版本字段,默认按照插入顺序保留最后一条。</p> <p>(2)向表中插入数据</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_rmt</span> <span class="k">values</span> <span class="p">(</span><span class="mi">101</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">)</span> <span class="p">,</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 11:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_004'</span><span class="p">,</span><span class="mi">2500</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">12000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-02 12:00:00'</span><span class="p">);</span> </code></pre></div></div> <p>(3)执行第一次查询</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_rmt</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_rmt</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">e88b72cc</span><span class="o">-</span><span class="n">a3b7</span><span class="o">-</span><span class="mi">44</span><span class="n">c3</span><span class="o">-</span><span class="n">a105</span><span class="o">-</span><span class="mi">390068</span><span class="n">c2cf04</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <p>(4)手动合并</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="n">OPTIMIZE</span> <span class="k">TABLE</span> <span class="n">t_order_rmt</span> <span class="k">FINAL</span><span class="p">;</span> <span class="n">OPTIMIZE</span> <span class="k">TABLE</span> <span class="n">t_order_rmt</span> <span class="k">FINAL</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">cc6fe5c5</span><span class="o">-</span><span class="mi">93</span><span class="n">b9</span><span class="o">-</span><span class="mi">4899</span><span class="o">-</span><span class="n">af8a</span><span class="o">-</span><span class="n">c2ff2afc6a91</span> <span class="n">Ok</span><span class="p">.</span> <span class="mi">0</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">005</span> <span class="n">sec</span><span class="p">.</span> </code></pre></div></div> <p>(5)再执行一次查询</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_rmt</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_rmt</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">392</span><span class="n">da567</span><span class="o">-</span><span class="n">ad3b</span><span class="o">-</span><span class="mi">43</span><span class="n">ab</span><span class="o">-</span><span class="mi">8</span><span class="n">fa9</span><span class="o">-</span><span class="n">de976f59d10c</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <p>**4)通过测试得到结论 **</p> <ul> <li>实际上是使用 order by 字段作为唯一键</li> <li>去重不能跨分区</li> <li>只有同一批插入(新版本)或合并分区时才会进行去重</li> <li>认定重复的数据保留,版本字段值最大的</li> <li>如果版本字段相同则按插入顺序保留最后一笔</li> </ul> <h1 id="6-summingmergetree">6. SummingMergeTree</h1> <p>对于不查询明细,只关心以维度进行汇总聚合结果的场景。如果只使用普通的MergeTree的话,无论是存储空间的开销,还是查询时临时聚合的开销都比较大。 ClickHouse 为了这种场景,提供了一种能够“预聚合”的引擎 SummingMergeTree **1)案例演示 ** (1)创建表</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_smt</span><span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="p">,</span> <span class="n">create_time</span> <span class="nb">Datetime</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">SummingMergeTree</span><span class="p">(</span><span class="n">total_amount</span><span class="p">)</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">sku_id</span> <span class="p">);</span> </code></pre></div></div> <p>(2)插入数据</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_smt</span> <span class="k">values</span> <span class="p">(</span><span class="mi">101</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 11:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_004'</span><span class="p">,</span><span class="mi">2500</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">12000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 13:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-02 12:00:00'</span><span class="p">);</span> </code></pre></div></div> <p>(3)执行第一次查询</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_smt</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_smt</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">a3c82de5</span><span class="o">-</span><span class="n">cc78</span><span class="o">-</span><span class="mi">4</span><span class="n">bf8</span><span class="o">-</span><span class="n">aaed</span><span class="o">-</span><span class="mi">96411</span><span class="n">d8caa0f</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">12000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">13</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <p>(4)手动合并</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="n">OPTIMIZE</span> <span class="k">TABLE</span> <span class="n">t_order_smt</span> <span class="k">FINAL</span><span class="p">;</span> </code></pre></div></div> <p>(5)再执行一次查询</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop102</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_smt</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_smt</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="n">a3c82de5</span><span class="o">-</span><span class="n">cc78</span><span class="o">-</span><span class="mi">4</span><span class="n">bf8</span><span class="o">-</span><span class="n">aaed</span><span class="o">-</span><span class="mi">96411</span><span class="n">d8caa0f</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">16000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">11</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> </code></pre></div></div> <p>**2)通过结果可以得到以下结论 **</p> <ul> <li>以 SummingMergeTree()中指定的列作为汇总数据列</li> <li>可以填写多列必须数字列,如果不填,以所有非维度列且为数字列的字段为汇总数据列</li> <li>以 order by 的列为准,作为维度列</li> <li>其他的列按插入顺序保留第一行</li> <li>不在一个分区的数据不会被聚合</li> <li>只有在同一批次插入(新版本)或分片合并时才会进行聚合</li> </ul> <p>**3)开发建议 ** 设计聚合表的话,唯一键值、流水号可以去掉,所有字段全部是维度、度量或者时间戳。</p> <p>**4)问题 ** 能不能直接执行以下 SQL 得到汇总值</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="n">total_amount</span> <span class="k">from</span> <span class="n">XXX</span> <span class="k">where</span> <span class="n">province_name</span><span class="o">=</span><span class="err">’’</span> <span class="k">and</span> <span class="n">create_date</span><span class="o">=</span><span class="err">’</span><span class="n">xxx</span><span class="err">’</span> </code></pre></div></div> <p>不行,可能会包含一些还没来得及聚合的临时明细</p> <p>如果要是获取汇总值,还是需要使用 sum 进行聚合,这样效率会有一定的提高,但本身 ClickHouse 是列式存储的,效率提升有限,不会特别明显。</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="k">sum</span><span class="p">(</span><span class="n">total_amount</span><span class="p">)</span> <span class="k">from</span> <span class="n">province_name</span><span class="o">=</span><span class="err">’’</span> <span class="k">and</span> <span class="n">create_date</span><span class="o">=</span><span class="err">‘</span><span class="n">xxx</span><span class="err">’</span> </code></pre></div></div> ClickHouse副本和分片集群 https://muhaokang.github.io/ClickHouse-%E5%89%AF%E6%9C%AC%E5%92%8C%E5%88%86%E7%89%87%E9%9B%86%E7%BE%A4/ https://muhaokang.github.io/ClickHouse-%E5%89%AF%E6%9C%AC%E5%92%8C%E5%88%86%E7%89%87%E9%9B%86%E7%BE%A4/ Fri, 22 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 副本 副本的目的主要是保障数据的高可用性,即使一台 ClickHouse 节点宕机,那么也可以从其他服务器获得相同的数据。 https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/replication/ 1.1 副本写入流程 1.2 配置步骤 (1)启动 zo... <h1 id="1-副本">1. 副本</h1> <p>副本的目的主要是保障数据的高可用性,即使一台 ClickHouse 节点宕机,那么也可以从其他服务器获得相同的数据。 <a href="https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/replication/">https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/replication/</a></p> <h2 id="11-副本写入流程">1.1 副本写入流程</h2> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650584988116-bf7b99ae-899a-47da-a7cc-18c16ee19f62.png#clientId=u7a1c0ee1-d60c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=290&amp;id=ub75bc128&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=290&amp;originWidth=739&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=57571&amp;status=done&amp;style=none&amp;taskId=u7fe0a75e-f1f5-45c3-9113-e3aa4f05202&amp;title=&amp;width=739" alt="image.png" /></p> <h2 id="12-配置步骤">1.2 配置步骤</h2> <p>(1)启动 zookeeper 集群 (2)在 hadoop102 的/etc/clickhouse-server/config.d 目录下创建一个名为 metrika.xml 的配置文件,内容如下: 注:也可以不创建外部文件,直接在 config.xml 中指定<zookeeper></zookeeper></p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span> <span class="nt">&lt;yandex&gt;</span> <span class="nt">&lt;zookeeper-servers&gt;</span> <span class="nt">&lt;node</span> <span class="na">index=</span><span class="s">"1"</span><span class="nt">&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop102<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>2181<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/node&gt;</span> <span class="nt">&lt;node</span> <span class="na">index=</span><span class="s">"2"</span><span class="nt">&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop103<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>2181<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/node&gt;</span> <span class="nt">&lt;node</span> <span class="na">index=</span><span class="s">"3"</span><span class="nt">&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop104<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>2181<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/node&gt;</span> <span class="nt">&lt;/zookeeper-servers&gt;</span> <span class="nt">&lt;/yandex&gt;</span> </code></pre></div></div> <p>(3)同步到 hadoop103 和 hadoop104 上</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[root@hadoop102 clickhouse-server]# xsync config.d/ </code></pre></div></div> <p>(4)在 hadoop102 的/etc/clickhouse-server/config.xml 中增加</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;zookeeper</span> <span class="na">incl=</span><span class="s">"zookeeper-servers"</span> <span class="na">optional=</span><span class="s">"true"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;include_from&gt;</span>/etc/clickhouse-server/config.d/metrika.xml<span class="nt">&lt;/include_from&gt;</span> </code></pre></div></div> <p>(5)同步到 hadoop103 和 hadoop104 上</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[root@hadoop102 clickhouse-server]# xsync config.xml </code></pre></div></div> <p>分别在 hadoop102 和 hadoop103 上启动 ClickHouse 服务 注意:因为修改了配置文件,如果以前启动了服务需要重启</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">sudo </span>clickhouse restart </code></pre></div></div> <p>注意:我们演示副本操作只需要在 hadoop102 和 hadoop103 两台服务器即可,上面的操作,我们 hadoop104 可以你不用同步,我们这里为了保证集群中资源的一致性,做了同步。 (6)在 hadoop102 和 hadoop103 上分别建表 副本只能同步数据,不能同步表结构,所以我们需要在每台机器上自己手动建表 ①hadoop102</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_rep2</span> <span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="n">create_time</span> <span class="nb">Datetime</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">ReplicatedMergeTree</span><span class="p">(</span><span class="s1">'/clickhouse/table/01/t_order_rep'</span><span class="p">,</span><span class="s1">'rep_102'</span><span class="p">)</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <p>②hadoop103</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">t_order_rep2</span> <span class="p">(</span> <span class="n">id</span> <span class="n">UInt32</span><span class="p">,</span> <span class="n">sku_id</span> <span class="n">String</span><span class="p">,</span> <span class="n">total_amount</span> <span class="nb">Decimal</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="n">create_time</span> <span class="nb">Datetime</span> <span class="p">)</span> <span class="n">engine</span> <span class="o">=</span><span class="n">ReplicatedMergeTree</span><span class="p">(</span><span class="s1">'/clickhouse/table/01/t_order_rep'</span><span class="p">,</span><span class="s1">'rep_103'</span><span class="p">)</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">toYYYYMMDD</span><span class="p">(</span><span class="n">create_time</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="n">sku_id</span><span class="p">);</span> </code></pre></div></div> <p>③参数解释 ReplicatedMergeTree 中, <strong>第一个参数</strong>是分片的 zk_path 一般按照:/clickhouse/table/{shard}/{table_name} 的格式写,如果只有一个分片就写 01 即可。 <strong>第二个参数</strong>是副本名称,相同的分片副本名称不能相同。</p> <p>(7)在 hadoop102 上执行 insert 语句</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">t_order_rep2</span> <span class="k">values</span> <span class="p">(</span><span class="mi">101</span><span class="p">,</span><span class="s1">'sku_001'</span><span class="p">,</span><span class="mi">1000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">102</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">103</span><span class="p">,</span><span class="s1">'sku_004'</span><span class="p">,</span><span class="mi">2500</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">104</span><span class="p">,</span><span class="s1">'sku_002'</span><span class="p">,</span><span class="mi">2000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-01 12:00:00'</span><span class="p">),</span> <span class="p">(</span><span class="mi">105</span><span class="p">,</span><span class="s1">'sku_003'</span><span class="p">,</span><span class="mi">600</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="s1">'2020-06-02 12:00:00'</span><span class="p">);</span> </code></pre></div></div> <p>(8)在 hadoop103 上执行 select,可以查询出结果,说明副本配置正确</p> <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hadoop103</span> <span class="p">:)</span> <span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t_order_rep2</span><span class="p">;</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">t_order_rep2</span> <span class="n">Query</span> <span class="n">id</span><span class="p">:</span> <span class="mi">4</span><span class="n">c800260</span><span class="o">-</span><span class="n">a5e1</span><span class="o">-</span><span class="mi">40</span><span class="n">ff</span><span class="o">-</span><span class="n">b7b1</span><span class="o">-</span><span class="mi">75</span><span class="n">a0eef9487b</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">105</span> <span class="err">│</span> <span class="n">sku_003</span> <span class="err">│</span> <span class="mi">600</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">02</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="err">┌──</span><span class="n">id</span><span class="err">─┬─</span><span class="n">sku_id</span><span class="err">──┬─</span><span class="n">total_amount</span><span class="err">─┬─────────</span><span class="n">create_time</span><span class="err">─┐</span> <span class="err">│</span> <span class="mi">101</span> <span class="err">│</span> <span class="n">sku_001</span> <span class="err">│</span> <span class="mi">1000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">102</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">103</span> <span class="err">│</span> <span class="n">sku_004</span> <span class="err">│</span> <span class="mi">2500</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">│</span> <span class="mi">104</span> <span class="err">│</span> <span class="n">sku_002</span> <span class="err">│</span> <span class="mi">2000</span><span class="p">.</span><span class="mi">00</span> <span class="err">│</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">01</span> <span class="mi">12</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span> <span class="err">│</span> <span class="err">└─────┴─────────┴──────────────┴─────────────────────┘</span> <span class="mi">5</span> <span class="k">rows</span> <span class="k">in</span> <span class="k">set</span><span class="p">.</span> <span class="n">Elapsed</span><span class="p">:</span> <span class="mi">0</span><span class="p">.</span><span class="mi">007</span> <span class="n">sec</span><span class="p">.</span> </code></pre></div></div> <h1 id="2-分片集群">2. 分片集群</h1> <p>副本虽然能够提高数据的可用性,降低丢失风险,但是每台服务器实际上必须容纳全量数据,对数据的横向扩容没有解决。 要解决数据水平切分的问题,需要引入分片的概念。通过分片把一份完整的数据进行切分,不同的分片分布到不同的节点上,再通过 Distributed 表引擎把数据拼接起来一同使用。 Distributed 表引擎本身不存储数据,有点类似于 MyCat 之于 MySql,成为一种中间件,通过分布式逻辑表来写入、分发、路由来操作多台节点不同分片的分布式数据。 注意:ClickHouse 的集群是表级别的,实际企业中,大部分做了高可用,但是没有用分片,避免降低查询性能以及操作集群的复杂性。</p> <h2 id="21-集群写入流程3-分片-2-副本共-6-个节点">2.1 集群写入流程(3 分片 2 副本共 6 个节点)</h2> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650586701399-bf474dfa-2d55-4c6a-b866-b1855df0a09f.png#clientId=u7a1c0ee1-d60c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=434&amp;id=u0f127260&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=434&amp;originWidth=872&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=92434&amp;status=done&amp;style=none&amp;taskId=u7dd33eb5-db5c-4a83-84b4-500414d0fc1&amp;title=&amp;width=872" alt="image.png" /></p> <h2 id="22-集群读取流程3-分片-2-副本共-6-个节点">2.2 集群读取流程(3 分片 2 副本共 6 个节点)</h2> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650586729181-7bd442d5-e64f-473a-b1dc-54a5c7e8d491.png#clientId=u7a1c0ee1-d60c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=449&amp;id=udf8baf32&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=449&amp;originWidth=824&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=82292&amp;status=done&amp;style=none&amp;taskId=ua7dc8487-6ff2-47a5-8323-03d9cdd8062&amp;title=&amp;width=824" alt="image.png" /></p> <h1 id="23-3-分片-2-副本共-6-个节点集群配置供参考">2.3 3 分片 2 副本共 6 个节点集群配置(供参考)</h1> <p>配置的位置还是在之前的/etc/clickhouse-server/config.d/metrika.xml,内容如下</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;yandex&gt;</span> <span class="nt">&lt;remote_servers&gt;</span> <span class="nt">&lt;gmall_cluster&gt;</span> <span class="c">&lt;!-- 集群名称--&gt;</span> <span class="nt">&lt;shard&gt;</span> <span class="c">&lt;!--集群的第一个分片--&gt;</span> <span class="nt">&lt;internal_replication&gt;</span>true<span class="nt">&lt;/internal_replication&gt;</span> <span class="c">&lt;!--该分片的第一个副本--&gt;</span> <span class="nt">&lt;replica&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop101<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>9000<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/replica&gt;</span> <span class="c">&lt;!--该分片的第二个副本--&gt;</span> <span class="nt">&lt;replica&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop102<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>9000<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/replica&gt;</span> <span class="nt">&lt;/shard&gt;</span> <span class="nt">&lt;shard&gt;</span> <span class="c">&lt;!--集群的第二个分片--&gt;</span> <span class="nt">&lt;internal_replication&gt;</span>true<span class="nt">&lt;/internal_replication&gt;</span> <span class="nt">&lt;replica&gt;</span> <span class="c">&lt;!--该分片的第一个副本--&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop103<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>9000<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/replica&gt;</span> <span class="nt">&lt;replica&gt;</span> <span class="c">&lt;!--该分片的第二个副本--&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop104<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>9000<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/replica&gt;</span> <span class="nt">&lt;/shard&gt;</span> <span class="nt">&lt;shard&gt;</span> <span class="c">&lt;!--集群的第三个分片--&gt;</span> <span class="nt">&lt;internal_replication&gt;</span>true<span class="nt">&lt;/internal_replication&gt;</span> <span class="nt">&lt;replica&gt;</span> <span class="c">&lt;!--该分片的第一个副本--&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop105<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>9000<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/replica&gt;</span> <span class="nt">&lt;replica&gt;</span> <span class="c">&lt;!--该分片的第二个副本--&gt;</span> <span class="nt">&lt;host&gt;</span>hadoop106<span class="nt">&lt;/host&gt;</span> <span class="nt">&lt;port&gt;</span>9000<span class="nt">&lt;/port&gt;</span> <span class="nt">&lt;/replica&gt;</span> <span class="nt">&lt;/shard&gt;</span> <span class="nt">&lt;/gmall_cluster&gt;</span> <span class="nt">&lt;/remote_servers&gt;</span> <span class="nt">&lt;/yandex&gt;</span> </code></pre></div></div> ClickHouse数据类型 https://muhaokang.github.io/ClickHouse-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/ https://muhaokang.github.io/ClickHouse-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/ Thu, 21 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 整型 固定长度的整型,包括有符号整型或无符号整型。 整型范围(-2^(n-1) ~ 2^(n-1)-1): Int8 - [-128 : 127] Int16 - [-32768 : 32767] Int32 - [-2147483648 : 2147483647] Int64 - [-9223372036854775808 : 92233720... <h1 id="1-整型">1. 整型</h1> <p>固定长度的整型,包括有符号整型或无符号整型。 整型范围(-2^(n-1) ~ 2^(n-1)-1): Int8 - [-128 : 127] Int16 - [-32768 : 32767] Int32 - [-2147483648 : 2147483647] Int64 - [-9223372036854775808 : 9223372036854775807] 无符号整型范围(0~(2^n)-1): UInt8 - [0 : 255] UInt16 - [0 : 65535] UInt32 - [0 : 4294967295] UInt64 - [0 : 18446744073709551615] **使用场景: 个数、数量、也可以存储型 id。 **</p> <h1 id="2-浮点型">2. 浮点型</h1> <p>Float32 - float Float64 – double 建议尽可能以整数形式存储数据。例如,将固定精度的数字转换为整数值,如时间用毫秒为单位表示,因为浮点型进行计算时可能引起四舍五入的误差。</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :<span class="o">)</span> <span class="k">select </span>1-0.9<span class="p">;</span> SELECT 1 - 0.9 Query <span class="nb">id</span>: e741974d-f149-4162-9147-01c3a9981677 ┌───────minus<span class="o">(</span>1, 0.9<span class="o">)</span>─┐ │ 0.09999999999999998 │ └─────────────────────┘ 1 rows <span class="k">in </span>set. Elapsed: 0.008 sec. </code></pre></div></div> <p>**使用场景:一般数据值比较小,不涉及大量的统计计算,精度要求不高的时候。比如 ** **保存商品的重量。 **</p> <h1 id="3-布尔型">3. 布尔型</h1> <p>没有单独的类型来存储布尔值。可以使用 UInt8 类型,取值限制为 0 或 1。</p> <h1 id="4-decimal-型">4. Decimal 型</h1> <p>有符号的浮点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。 有三种声明:</p> <ul> <li>Decimal32(s),相当于 Decimal(9-s,s),有效位数为 1~9</li> <li>Decimal64(s),相当于 Decimal(18-s,s),有效位数为 1~18</li> <li>Decimal128(s),相当于 Decimal(38-s,s),有效位数为 1~38</li> </ul> <p>s 标识小数位</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>123.123.123.123 decimal32(5) ===&gt; 整数+小数一共9位,小数部分有5位 123.12312 decimal64(5) ===&gt; 整数+小数一共18位,小数部分有5位 123.12312 </code></pre></div></div> <p>使用场景: 一般金额字段、汇率、利率等字段为了保证小数点精度,都使用 Decimal 进行存储。</p> <h1 id="5-字符串">5. 字符串</h1> <p>**1)String ** 字符串可以任意长度的。它可以包含任意的字节集,包含空字节。</p> <p>**2)FixedString(N) ** 固定长度 N 的字符串,N 必须是严格的正自然数。当服务端读取长度小于 N 的字符串时候,通过在字符串末尾添加空字节来达到 N 字节长度。 当服务端读取长度大于 N 的字符串时候,将返回错误消息。</p> <p>与 String 相比,极少会使用 FixedString,因为使用起来不是很方便。</p> <p>使用场景:名称、文字描述、字符型编码。 固定长度的可以保存一些定长的内容,比如一些编码,性别等但是考虑到一定的变化风险,带来收益不够明显,所以定长字符串使用意义有限。</p> <h1 id="6-枚举类型">6. 枚举类型</h1> <p>包括 Enum8 和 Enum16 类型。Enum 保存 ‘string’= integer 的对应关系。 Enum8 用 ‘String’= Int8 对描述。 Enum16 用 ‘String’= Int16 对描述。</p> <p>**1)用法演示 ** 创建一个带有一个枚举 Enum8(‘hello’ = 1, ‘world’ = 2) 类型的列</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :) CREATE TABLE t_enum :-] ( :-] x Enum8('hello' = 1, 'world' = 2) :-] ) :-] ENGINE = TinyLog; CREATE TABLE t_enum ( `x` Enum8('hello' = 1, 'world' = 2) ) ENGINE = TinyLog Query id: deb02552-6fb4-4836-b210-640211e9440b Ok. 0 rows in set. Elapsed: 0.005 sec. </code></pre></div></div> <p><strong>2)这个 x 列只能存储类型定义中列出的值:’hello’或’world’</strong></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :) select * from t_enum; SELECT * FROM t_enum Query id: 5a0532b8-d186-4402-9491-8bd688693005 ┌─x─────┐ │ hello │ │ world │ │ hello │ └───────┘ 3 rows in set. Elapsed: 0.003 sec. </code></pre></div></div> <p><strong>3)如果尝试保存任何其他值,ClickHouse 抛出异常</strong></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :) insert into t_enum values('a'); INSERT INTO t_enum VALUES Query id: bcbe515e-4247-4ecf-bb62-a6ca509d0aa0 Exception on client: Code: 36. DB::Exception: Unknown element 'a' for enum: data for INSERT was parsed from query Connecting to localhost:9000 as user default. Connected to ClickHouse server version 21.7.3 revision 54449. </code></pre></div></div> <p><strong>4)如果需要看到对应行的数值,则必须将 Enum 值转换为整数类型</strong></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :) SELECT CAST(x, 'Int8') FROM t_enum; SELECT CAST(x, 'Int8') FROM t_enum Query id: 68450b03-5d18-4e3d-99e3-5d42451caa25 ┌─CAST(x, 'Int8')─┐ │ 1 │ │ 2 │ │ 1 │ └─────────────────┘ 3 rows in set. Elapsed: 0.005 sec. </code></pre></div></div> <p>使用场景:对一些状态、类型的字段算是一种空间优化,也算是一种数据约束。但是实际使用中往往因为一些数据内容的变化增加一定的维护成本,甚至是数据丢失问题。所以谨慎使用。</p> <h1 id="7-时间类型">7. 时间类型</h1> <p>目前 ClickHouse 有三种时间类型</p> <ul> <li>Date 接受<strong>年-月-日</strong>的字符串比如 ‘2019-12-16’</li> <li>Datetime 接受<strong>年-月-日 时:分:秒</strong>的字符串比如 ‘2019-12-16 20:50:10’</li> <li>Datetime64 接受<strong>年-月-日 时:分:秒.亚秒</strong>的字符串比如‘2019-12-16 20:50:10.66’</li> </ul> <p>日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。 还有很多数据结构,可以参考官方文档:https://clickhouse.yandex/docs/zh/data_types/</p> <h1 id="8-数组">8. 数组</h1> <p><strong>Array(T):</strong>由 T 类型元素组成的数组。 T 可以是任意类型,包含数组类型。 但不推荐使用多维数组,ClickHouse 对多维数组的支持有限。例如,不能在 MergeTree 表中存储多维数组。 (1)创建数组方式 1,使用 array 函数</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :) SELECT array(1, 2) AS x, toTypeName(x) ; SELECT [1, 2] AS x, toTypeName(x) Query id: e0b7b306-a943-454d-9ac7-3118b4823d71 ┌─x─────┬─toTypeName(array(1, 2))─┐ │ [1,2] │ Array(UInt8) │ └───────┴─────────────────────────┘ 1 rows in set. Elapsed: 0.014 sec. </code></pre></div></div> <p>(2)创建数组方式 2:使用方括号</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :) SELECT [1, 2] AS x, toTypeName(x); SELECT [1, 2] AS x, toTypeName(x) Query id: cb94f85c-8946-4a36-8cdc-cc28d5c1e26d ┌─x─────┬─toTypeName([1, 2])─┐ │ [1,2] │ Array(UInt8) │ └───────┴────────────────────┘ 1 rows in set. Elapsed: 0.003 sec. </code></pre></div></div> ClickHouse安装部署 https://muhaokang.github.io/ClickHouse-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/ https://muhaokang.github.io/ClickHouse-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/ Thu, 21 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1 准备工作 1.1 确定防火墙处于关闭状态 1.2 CentOS 取消打开文件数限制 (1)在 hadoop102 的 /etc/security/limits.conf 文件的末尾加入以下内容 [mhk@hadoop102 ~]$ sudo vim /etc/security/limits.conf * soft nofile 65536 * ha... <h1 id="1-准备工作">1 准备工作</h1> <h2 id="11-确定防火墙处于关闭状态">1.1 确定防火墙处于关闭状态</h2> <h2 id="12-centos-取消打开文件数限制">1.2 CentOS 取消打开文件数限制</h2> <p>(1)在 hadoop102 的 /etc/security/limits.conf 文件的末尾加入以下内容</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo </span>vim /etc/security/limits.conf <span class="k">*</span> soft nofile 65536 <span class="k">*</span> hard nofile 65536 <span class="k">*</span> soft <span class="nb">nproc </span>131072 <span class="k">*</span> hard <span class="nb">nproc </span>131072 </code></pre></div></div> <p>(2)在 hadoop102 的/etc/security/limits.d/20-nproc.conf 文件的末尾加入以下内容</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo </span>vim /etc/security/limits.d/20-nproc.conf <span class="k">*</span> soft nofile 65536 <span class="k">*</span> hard nofile 65536 <span class="k">*</span> soft <span class="nb">nproc </span>131072 <span class="k">*</span> hard <span class="nb">nproc </span>131072 </code></pre></div></div> <p>(3)执行同步操作 xsync分发脚本</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span> <span class="c">#1. 判断参数个数</span> <span class="k">if</span> <span class="o">[</span> <span class="nv">$# </span><span class="nt">-lt</span> 1 <span class="o">]</span> <span class="k">then </span><span class="nb">echo </span>Not Enough Arguement! <span class="nb">exit</span><span class="p">;</span> <span class="k">fi</span> <span class="c">#2. 遍历集群所有机器</span> <span class="k">for </span>host <span class="k">in </span>hadoop102 hadoop103 hadoop104 <span class="k">do </span><span class="nb">echo</span> <span class="o">====================</span> <span class="nv">$host</span> <span class="o">====================</span> <span class="c">#3. 遍历所有目录,挨个发送</span> <span class="k">for </span>file <span class="k">in</span> <span class="nv">$@</span> <span class="k">do</span> <span class="c">#4. 判断文件是否存在</span> <span class="k">if</span> <span class="o">[</span> <span class="nt">-e</span> <span class="nv">$file</span> <span class="o">]</span> <span class="k">then</span> <span class="c">#5. 获取父目录</span> <span class="nv">pdir</span><span class="o">=</span><span class="si">$(</span><span class="nb">cd</span> <span class="nt">-P</span> <span class="si">$(</span><span class="nb">dirname</span> <span class="nv">$file</span><span class="si">)</span><span class="p">;</span> <span class="nb">pwd</span><span class="si">)</span> <span class="c">#6. 获取当前文件的名称</span> <span class="nv">fname</span><span class="o">=</span><span class="si">$(</span><span class="nb">basename</span> <span class="nv">$file</span><span class="si">)</span> ssh <span class="nv">$host</span> <span class="s2">"mkdir -p </span><span class="nv">$pdir</span><span class="s2">"</span> rsync <span class="nt">-av</span> <span class="nv">$pdir</span>/<span class="nv">$fname</span> <span class="nv">$host</span>:<span class="nv">$pdir</span> <span class="k">else </span><span class="nb">echo</span> <span class="nv">$file</span> does not exists! <span class="k">fi done done</span> </code></pre></div></div> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo</span> /home/mhk/bin/xsync /etc/security/limits.conf <span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo</span> /home/mhk/bin/xsync /etc/security/limits.d/20-nproc.conf </code></pre></div></div> <h2 id="13-安装依赖">1.3 安装依赖</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo </span>yum <span class="nb">install</span> <span class="nt">-y</span> libtool <span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo </span>yum <span class="nb">install</span> <span class="nt">-y</span> <span class="k">*</span>unixODBC<span class="k">*</span> </code></pre></div></div> <p>在 hadoop103、hadoop104 上执行以上操作</p> <h2 id="14-centos-取消-selinux">1.4 CentOS 取消 SELINUX</h2> <p>(1)修改/etc/selinux/config 中的 SELINUX=disabled</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo </span>vim /etc/selinux/config <span class="nv">SELINUX</span><span class="o">=</span>disabled 注意:别改错了 </code></pre></div></div> <p>(2)执行同步操作</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo</span> /home/atguigu/bin/xsync /etc/selinux/config </code></pre></div></div> <p>(3)重启三台服务器,修改内核级别的东西需要重启</p> <h1 id="2-单机安装">2 单机安装</h1> <p>官网:https://clickhouse.tech/ 下载地址:http://repo.red-soft.biz/repos/clickhouse/stable/el7/</p> <h2 id="21-在-hadoop102-的optsoftware-下创建-clickhouse-目录">2.1 在 hadoop102 的/opt/software 下创建 clickhouse 目录</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 software]<span class="nv">$ </span><span class="nb">mkdir </span>clickhouse </code></pre></div></div> <h2 id="22-将以下-4-个文件上传到-hadoop102-的-softwareclickhouse-目录下">2.2 将以下 4 个文件上传到 hadoop102 的 <strong>software/clickhouse 目录下</strong></h2> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650448886421-e4152b6b-965a-45fc-9a96-d22a517f2dbd.png#clientId=ud73da38b-4b8b-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=82&amp;id=u6f7518c4&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=82&amp;originWidth=214&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=14603&amp;status=done&amp;style=none&amp;taskId=uced851eb-9f9a-4014-a171-b54a9e227ad&amp;title=&amp;width=214" alt="image.png" /></p> <h2 id="23-将安装文件同步到-hadoop103hadoop104">2.3 将安装文件同步到 hadoop103、hadoop104</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 software]<span class="nv">$ </span>xsync clickhouse </code></pre></div></div> <h2 id="24-分别在三台机子上安装这-4-个-rpm-文件">2.4 分别在三台机子上安装这 4 个 rpm 文件</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop103 clickhouse]<span class="nv">$ </span><span class="nb">sudo </span>rpm <span class="nt">-ivh</span> <span class="k">*</span>.rpm <span class="o">[</span>mhk@hadoop103 clickhouse]<span class="nv">$ </span><span class="nb">sudo </span>rpm <span class="nt">-qa</span>|grep clickhouse clickhouse-server-21.7.3.14-2.noarch clickhouse-client-21.7.3.14-2.noarch clickhouse-common-static-dbg-21.7.3.14-2.x86_64 clickhouse-common-static-21.7.3.14-2.x86_64 </code></pre></div></div> <h2 id="25-修改配置文件">2.5 修改配置文件</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 clickhouse]<span class="nv">$ </span><span class="nb">sudo </span>vim /etc/clickhouse-server/config.xml </code></pre></div></div> <p>(1)把 <listen_host>::</listen_host> 的注释打开,这样的话才能让 ClickHouse 被除本机以外的服务器访问</p> <p>(2)分发配置文件</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /home/mhk/bin/xsync /etc/clickhouse-server/config.xml </code></pre></div></div> <p>在这个文件中,有 ClickHouse 的一些默认路径配置,比较重要的 数据文件路径:<path>/var/lib/clickhouse/</path> 日志文件路径:<log>/var/log/clickhouse-server/clickhouse-server.log</log></p> <h2 id="26-启动-server">2.6 启动 Server</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 bin]<span class="nv">$ </span><span class="nb">sudo </span>clickhouse start <span class="nb">chown</span> <span class="nt">--recursive</span> clickhouse <span class="s1">'/var/run/clickhouse-server/'</span> Will run su <span class="nt">-s</span> /bin/sh <span class="s1">'clickhouse'</span> <span class="nt">-c</span> <span class="s1">'/usr/bin/clickhouse-server --config-file /etc/clickhouse-server/config.xml --pid-file /var/run/clickhouse-server/clickhouse-server.pid --daemon'</span> Waiting <span class="k">for </span>server to start Waiting <span class="k">for </span>server to start Server started </code></pre></div></div> <h2 id="27-三台机器上关闭开机自启">2.7 三台机器上关闭开机自启</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 ~]<span class="nv">$ </span><span class="nb">sudo </span>systemctl disable clickhouse-server Removed symlink /etc/systemd/system/multi-user.target.wants/clickhouse-server.service. </code></pre></div></div> <h2 id="28-使用-client-连接-server">2.8 使用 client 连接 server</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 bin]<span class="nv">$ </span>clickhouse-client <span class="nt">-m</span> ClickHouse client version 21.7.3.14 <span class="o">(</span>official build<span class="o">)</span><span class="nb">.</span> Connecting to localhost:9000 as user default. Connected to ClickHouse server version 21.7.3 revision 54449. hadoop102 :<span class="o">)</span> <span class="nt">-m</span> 表示可以换行,以分号来识别语句的结束 远程访问 clickhouse-client <span class="nt">-h</span> hadoop103 <span class="nt">-p</span> 端口 clickhouse-client <span class="nt">--query</span> <span class="s2">"查询语句"</span> 类似于 hive <span class="nt">-e</span> <span class="s2">""</span> 写SQL,我不进去,给我返回结果 </code></pre></div></div> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hadoop102 :<span class="o">)</span> show databases<span class="p">;</span> SHOW DATABASES Query <span class="nb">id</span>: 0a19d7e7-274b-40b2-a25c-b0d173b6077c ┌─name────┐ │ default │ │ system │ └─────────┘ 2 rows <span class="k">in </span>set. Elapsed: 0.006 sec. hadoop102 :<span class="o">)</span> use system<span class="p">;</span> USE system Query <span class="nb">id</span>: 2bca7a0e-f42d-4777-942a-8fcddee70e69 Ok. 0 rows <span class="k">in </span>set. Elapsed: 0.004 sec. </code></pre></div></div> <h1 id="3-其他">3. 其他</h1> <p>文件所在地 bin/ ====&gt; /usr/bin/ conf/ ====&gt; /etc/clickhouse-server/ lib/ ====&gt; /var/lib/clickhouse/ log/ ====&gt; /var/log/clickhouse/</p> Elasticsearch优化 https://muhaokang.github.io/Elasticsearch-%E4%BC%98%E5%8C%96/ https://muhaokang.github.io/Elasticsearch-%E4%BC%98%E5%8C%96/ Wed, 20 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 硬件选择 Elasticsearch 的基础是 Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在 ES 的配置文件../config/elasticsearch.yml 中配置,如下: #----------------------------------- Paths----------------------------... <h1 id="1-硬件选择">1. 硬件选择</h1> <p>Elasticsearch 的基础是 Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在 ES 的配置文件../config/elasticsearch.yml 中配置,如下:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#----------------------------------- Paths------------------------------------</span> <span class="c1">#</span> <span class="c1"># Path to directory where to store the data (separate multiple locations by comma):</span> <span class="c1">#</span> <span class="c1">#path.data: /path/to/data</span> <span class="c1">#</span> <span class="c1"># Path to log files:</span> <span class="c1">#</span> <span class="c1">#path.logs: /path/to/logs</span> <span class="c1">#</span> </code></pre></div></div> <p>磁盘在现代服务器上通常都是瓶颈。Elasticsearch 重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。这里有一些优化磁盘 I/O 的技巧:</p> <ul> <li>使用 SSD。就像其他地方提过的, 他们比机械磁盘优秀多了。</li> <li>使用 RAID 0。原理是把连续的数据分散到多个磁盘中存取,这样每个磁盘并行读取可以大大提高效率。条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。</li> <li>另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面,可以并行的读写。</li> <li>不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。</li> </ul> <h1 id="2-分片策略">2 分片策略</h1> <h2 id="21-合理设置分片数">2.1 合理设置分片数</h2> <p>分片和副本的设计为 ES 提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且索引的分片完成分配后由于索引的路由机制,我们是不能重新修改分片数的。路由计算规则计算出分片的位置,如果修改了分片的数目,就很可能查不到</p> <p>可能有人会说,我不知道这个索引将来会变得多大,并且过后我也不能更改索引的大小,所以为了保险起见,还是给它设为 1000 个分片吧。但是需要知道的是,一个分片并不是没有代价的。需要了解:</p> <ul> <li> <p>一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。</p> </li> <li> <p>每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。</p> </li> <li> <p>用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。</p> </li> </ul> <p>一个业务索引具体需要分配多少分片可能需要架构师和技术人员对业务的增长有个预先的判断,横向扩展应当分阶段进行。为下一阶段准备好足够的资源。 只有当你进入到下一个阶段,你才有时间思考需要作出哪些改变来达到这个阶段。一般来说,我们遵循一些原则:</p> <ul> <li> <p>控制每个分片占用的硬盘容量不超过 ES 的最大 JVM 的堆空间设置(一般设置不超过 32G,参考下文的 JVM 设置原则),因此,如果索引的总容量在 500G 左右,那分片大小在 16 个左右即可;当然,最好同时考虑原则 2。 内存中是我们的操作,只有把内存的内容暂存到缓存中,用户才能查到,内存设的很大,操作系统的缓存就会变少</p> </li> <li> <p>考虑一下 node 数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了 1 个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以, 一般都设置分片数不超过节点数的 3 倍。</p> </li> <li> <p>主分片,副本和节点最大数之间数量,我们分配的时候可以参考以下关系:</p> </li> </ul> <p>节点数&lt;=主分片数*(副本数+1)</p> <h2 id="22-推迟分片分配">2.2 推迟分片分配</h2> <p>对于节点瞬时中断的问题,默认情况,集群会等待一分钟来查看节点是否会重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配。这样就可以减少 ES 在自动再平衡可用分片时所带来的极大开销。</p> <p>通过修改参数 delayed_timeout ,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">PUT</span><span class="w"> </span><span class="err">/_all/_settings</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"settings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"index.unassigned.node_left.delayed_timeout"</span><span class="p">:</span><span class="w"> </span><span class="s2">"5m"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h1 id="3-路由选择">3. 路由选择</h1> <p>当我们查询文档的时候,Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?它其实是通过下面这个公式来计算出来:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">shard</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">hash(routing)</span><span class="w"> </span><span class="err">%</span><span class="w"> </span><span class="err">number_of_primary_shards</span><span class="w"> </span></code></pre></div></div> <p>routing 默认值是文档的 id,也可以采用自定义值,比如用户 id。</p> <p>**不带 routing 查询 ** 在查询的时候因为不知道要查询的数据具体在哪个分片上,所以整个过程分为 2 个步骤</p> <ul> <li>分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。</li> <li>聚合: 协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。</li> </ul> <p>特别慢</p> <p>**带 routing 查询 ** 查询的时候,可以直接根据 routing 信息定位到某个分配查询,不需要查询所有的分配,经过协调节点排序。 向上面自定义的用户查询,如果 routing 设置为 userid 的话,就可以直接查询出数据来,效率提升很多。</p> <h1 id="4-写入速度优化">4. 写入速度优化</h1> <p>ES 的默认配置,是综合了数据可靠性、写入速度、搜索实时性等因素。实际使用时,我们需要根据公司要求,进行偏向性的优化。</p> <p>针对于搜索性能要求不高,但是对写入要求较高的场景,我们需要尽可能的选择恰当写优化策略。综合来说,可以考虑以下几个方面来提升写索引的性能:</p> <ul> <li>加大 Translog Flush ,目的是降低 Iops、Writeblock。</li> <li>增加 Index Refresh 间隔,目的是减少 Segment Merge 的次数。</li> <li>调整 Bulk 线程池和队列。</li> <li>优化节点间的任务分布。</li> <li>优化 Lucene 层的索引建立,目的是降低 CPU 及 IO。</li> </ul> <h2 id="41-批量数据提交">4.1 批量数据提交</h2> <p>ES 提供了 Bulk API 支持批量操作,当我们有大量的写任务时,可以使用 Bulk 来进行批量写入。</p> <p>通用的策略如下:Bulk 默认设置批量提交的数据量不能超过 100M。数据条数一般是根据文档的大小和服务器性能而定的,但是单次批处理的数据大小应从 5MB~15MB 逐渐增加,当性能没有提升时,把这个数据量作为最大值。</p> <h2 id="42-优化存储设备">4.2 优化存储设备</h2> <p>ES 是一种密集使用磁盘的应用,在段合并的时候会频繁操作磁盘,所以对磁盘要求较高,当磁盘速度提升之后,集群的整体性能会大幅度提高。</p> <h2 id="43-合理使用合并">4.3 合理使用合并</h2> <p>Lucene 以段的形式存储数据。当有新的数据写入索引时,Lucene 就会自动创建一个新的段。</p> <p>随着数据量的变化,段的数量会越来越多,需要落盘的文件也越来越多,消耗的多文件句柄数及 CPU 等资源就越多,查询效率就会下降。</p> <p>由于 Lucene 段合并的计算量庞大,会消耗大量的 I/O,所以 ES 默认采用较保守的策略,让后台定期进行段合并</p> <h2 id="44-减少-refresh-的次数">4.4 减少 Refresh 的次数</h2> <p>Lucene 在新增数据时,采用了延迟写入的策略,默认情况下索引的 refresh_interval 为 1 秒。</p> <p>Lucene 将待写入的数据先写到内存中,超过 1 秒(默认)时就会触发一次 Refresh,然后 Refresh 会把内存中的的数据刷新到操作系统的文件缓存系统中。</p> <p>如果我们对搜索的实效性要求不高,可以将 Refresh 周期延长,例如 30 秒。 文件缓存就不会频繁的刷新文件,落盘次数也会减少,提高写的效率。但这样就会消耗更多的内存</p> <p>这样还可以有效地减少段刷新次数,但这同时意味着需要消耗更多的 Heap 内存。</p> <h2 id="45-加大-flush-设置">4.5 加大 Flush 设置</h2> <p>Flush 的主要目的是把文件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到 512MB 或者 30 分钟时,会触发一次 Flush。</p> <p>index.translog.flush_threshold_size 参数的默认值是 512MB,我们进行修改。 加大参数值可以减少落盘的次数,提高效率 增加参数值意味着文件缓存系统中可能需要存储更多的数据,所以我们需要为操作系统的文件缓存系统留下足够的空间。</p> <h2 id="46-减少副本的数量">4.6 减少副本的数量</h2> <p>ES 为了保证集群的可用性,提供了 Replicas(副本)支持,然而每个副本也会执行分析、索引及可能的合并过程,所以 Replicas 的数量会严重影响写索引的效率。</p> <p>当写索引时,需要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。</p> <p>如 果 我 们 需 要 大 批 量 进 行 写 入 操 作 , 可 以 先 禁 止 Replica 复 制 , 设 置index.number_of_replicas: 0 关闭副本。在写入完成后,Replica 修改回正常的状态。先禁用再恢复</p> <h1 id="5-内存设置">5. 内存设置</h1> <p>ES 默认安装后设置的内存是 1GB,对于任何一个现实业务来说,这个设置都太小了。 如果是通过解压安装的 ES,则在 ES 安装文件中包含一个 jvm.option 文件,添加如下命令来设置 ES 的堆大小,Xms 表示堆的初始大小,Xmx 表示可分配的最大内存,都是 1GB。</p> <p>确保 Xmx 和 Xms 的大小是相同的,其目的是为了能够在 Java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源,可以减轻伸缩堆大小带来的压力。</p> <p>假设你有一个 64G 内存的机器,按照正常思维思考,你可能会认为把 64G 内存都给 ES 比较好,但现实是这样吗, 越大越好?虽然内存对 ES 来说是非常重要的,但是答案是否定的!</p> <p>内存中是我们的操作,只有把内存的内容暂存到缓存中,用户才能查到,内存设的很大,操作系统的缓存就会变少</p> <p>因为 ES 堆内存的分配需要满足以下两个原则:</p> <ul> <li>不要超过物理内存的 50%:Lucene 的设计目的是把底层 OS 里的数据缓存到内存中。</li> </ul> <p>Lucene 的段是分别存储到单个文件中的,这些文件都是不会变化的,所以很利于缓存,同时操作系统也会把这些段文件缓存起来,以便更快的访问。 如果我们设置的堆内存过大,Lucene 可用的内存将会减少,就会严重影响降低 Lucene 的全文本查询性能。</p> <ul> <li>堆内存的大小最好不要超过 32GB:在 Java 中,所有对象都分配在堆上,然后有一个 Klass Pointer 指针指向它的类元数据。</li> </ul> <p>这个指针在 64 位的操作系统上为 64 位,64 位的操作系统可以使用更多的内存(2^64)。在 32 位的系统上为 32 位,32 位的操作系统的最大寻址空间为 4GB(2^32)。 但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如 LLC, L1 等)之间移动数据的时候,会占用更多的带宽。 最终我们都会采用 31 G 设置 -Xms 31g -Xmx 31g</p> <p>假设你有个机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。 也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene</p> <h1 id="6-重要配置">6. 重要配置</h1> <p>| 参数名 | 参数值 | 说明 | | — | — | — | | cluster.name | es-cluster | 配置 ES 的集群名称,默认值是 ES,建议改成与所存数据相关的名称,ES 会自动发现在同一网段下的集群名称相同的节点 | | node.name | node-1 | 集群中的节点名,在同一个集群中不能重复。节点的名称一旦设置,就不能再改变了。当然,也可以设置 成 服 务 器 的 主 机 名 称 , 例 如 node.name:${HOSTNAME}。 | | node.master | true | 指定该节点是否有资格被选举成为 Master 节点,默认是 True,如果被设置为 True,则只是有资格成为Master 节点,具体能否成为 Master 节点,需要通过选举产生。 | | node.data | true | 指定该节点是否存储索引数据,默认为 True。数据的增、删、改、查都是在 Data 节点完成的。 | | index.number_of_shards | 1 | 设置都索引分片个数,默认是 1 片。也可以在创建索引时设置该值,具体设置为多大都值要根据数据量的大小来定。如果数据量不大,则设置成 1 时xi效率最高 | | index.number_of_replicas | 1 | 设置默认的索引副本个数,默认为 1 个。副本数越多,集群的可用性越好,但是写索引时需要同步的数据越多。 | | transport.tcp.compress | true | 设置在节点间传输数据时是否压缩,默认为 False, 不压缩 | | discovery.zen.minimum_master_nodes | 1 | 设置在选举 Master 节点时需要参与的最少的候选主节点数,默认为 1。如果使用默认值,则当网络不稳定时有可能会出现脑裂。 合理的数值为 (master_eligible_nodes/2)+1 ,其中 master_eligible_nodes 表示集群中的候选主节点数 | | discovery.zen.ping.timeout | 3s | 设置在集群中自动发现其他节点时 Ping 连接的超时时间,默认为 3 秒。 在较差的网络环境下需要设置得大一点,防止因误判该节点的存活状态而导致分片的转移 |</p> ClickHouse介绍 https://muhaokang.github.io/ClickHouse-%E4%BB%8B%E7%BB%8D/ https://muhaokang.github.io/ClickHouse-%E4%BB%8B%E7%BB%8D/ Wed, 20 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库(DBMS),使用 C++ 语言编写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报告。 1. ClickHouse的特点 1.1 列式存储 以下面的表为例 ID Name Age 1 张三 18 2 李四 22 3 王五 34 1)采用... <p>ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库(DBMS),使用 C++ 语言编写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报告。</p> <h1 id="1-clickhouse的特点">1. ClickHouse的特点</h1> <h2 id="11-列式存储">1.1 列式存储</h2> <p>以下面的表为例</p> <table> <thead> <tr> <th>ID</th> <th>Name</th> <th>Age</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>张三</td> <td>18</td> </tr> <tr> <td>2</td> <td>李四</td> <td>22</td> </tr> <tr> <td>3</td> <td>王五</td> <td>34</td> </tr> </tbody> </table> <p><strong>1)采用行式存储时,数据在磁盘上的组织结构为:</strong></p> <p>| 1 | 张三 | 18 | 2 | 李四 | 22 | 3 | 王五 | 34 | | — | — | — | — | — | — | — | — | — |</p> <p>好处是想查找某个人的所有的属性的时候,可以通过一个磁盘查找加顺序读取就可以。但是想查所有人的年龄时,需要不停的查找,或者全表扫描才行,遍历的很多数据都是不需要的。</p> <p><strong>2)采用列式存储时,数据在磁盘上的组织结构为:</strong></p> <p>| 1 | 2 | 3 | 张三 | 李四 | 王五 | 18 | 22 | 34 | | — | — | — | — | — | — | — | — | — |</p> <p>这时想查所有人的年龄只需把年龄那一列拿出来就可以了</p> <p>**3)列式储存的好处: **</p> <ul> <li>对于列的聚合,计数,求和等统计操作原因优于行式存储。</li> <li>由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重。</li> <li>由于数据压缩比更好,一方面节省了磁盘空间,另一方面对于 cache 也有了更大的发挥空间。</li> </ul> <p>坏处就是插入一个人的数据效率不如行式存储</p> <h2 id="12-dbms-的功能">1.2 DBMS 的功能</h2> <p>几乎覆盖了标准 SQL 的大部分语法,包括 DDL 和 DML,以及配套的各种函数,用户管理及权限管理,数据的备份与恢复。</p> <h2 id="13-多样化引擎">1.3 多样化引擎</h2> <p>ClickHouse 和 MySQL 类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎。目前包括合并树、日志、接口和其他四大类 20 多种引擎。</p> <h2 id="14-高吞吐写入能力">1.4 高吞吐写入能力</h2> <p>ClickHouse 采用类 LSM Tree的结构,数据写入后定期在后台 Compaction。通过类 LSM tree 的结构,ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台 compaction 时也是多个段 merge sort 后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力,即便在 HDD 上也有着优异的写入性能。 官方公开 benchmark 测试显示能够达到 50MB-200MB/s 的写入吞吐能力,按照每行 100Byte 估算,大约相当于 50W-200W 条/s 的写入速度。</p> <h2 id="15-数据分区与线程级并行">1.5 数据分区与线程级并行</h2> <p>ClickHouse 将数据划分为多个 partition,每个 partition 再进一步划分为多个 index granularity(索引粒度),然后通过多个 CPU核心分别处理其中的一部分来实现并行数据处理。</p> <p>在这种设计下,单条 Query 就能利用整机所有 CPU。极致的并行处理能力,极大的降低了查询延时。</p> <p>所以,ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多 cpu,就不利于同时并发多条查询。所以对于高 qps 的查询业务, ClickHouse 并不是强项。 ##</p> <h2 id="16-性能对比">1.6 性能对比</h2> <p>某网站精华帖,中对几款数据库做了性能对比。<br /> <strong>1)单表查询</strong> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650448259578-29517dd7-831b-45d5-bf08-ba0485e3f0c6.png#clientId=ua0875d5c-8ed7-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=220&amp;id=uc7b61c51&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=220&amp;originWidth=952&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=161076&amp;status=done&amp;style=none&amp;taskId=u57017cb3-c6c7-4464-8bd1-c1060d2e848&amp;title=&amp;width=952" alt="image.png" /> <strong>2)关联查询</strong> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650448285948-568a449c-7897-437a-97af-acf00728809c.png#clientId=ua0875d5c-8ed7-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=343&amp;id=u746cd365&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=343&amp;originWidth=950&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=232691&amp;status=done&amp;style=none&amp;taskId=u815296c3-5d80-4c47-99db-5f94d3140ab&amp;title=&amp;width=950" alt="image.png" /> 结论: ClickHouse 像很多 OLAP 数据库一样,单表查询速度由于关联查询,而且 ClickHouse 的两者差距更为明显。</p> Elasticsearch进阶 https://muhaokang.github.io/Elasticsearch-%E8%BF%9B%E9%98%B6/ https://muhaokang.github.io/Elasticsearch-%E8%BF%9B%E9%98%B6/ Tue, 19 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) # 1. 核心概念 ES与Mysql对照表 通过对比加深理解 Elasticsearch 索引(Index) 类型(Type) 文档(Docments) 字段(Fields)   | | | | |   关系数据库(MySQL) 数据库(DataBase) 表(Table) 行(Rows) 列(Columns) 1.1 索引(Index) 一个索引就是... <p>#</p> <h1 id="1-核心概念">1. 核心概念</h1> <p><strong>ES与Mysql对照表</strong> 通过对比加深理解</p> <table> <tbody> <tr> <td>Elasticsearch</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>索引(Index)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>类型(Type)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>文档(Docments)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>字段(Fields)</td> </tr> </tbody> </table> <table> <thead> <tr> <th> </th> </tr> </thead> <tbody> <tr> <td>|</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>|</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>|</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>|</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>|</td> </tr> </tbody> </table> <table> <tbody> <tr> <td> </td> </tr> <tr> <td>关系数据库(MySQL)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>数据库(DataBase)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>表(Table)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>行(Rows)</td> </tr> </tbody> </table> <table> <tbody> <tr> <td>列(Columns)</td> </tr> </tbody> </table> <table> <tbody> <tr> </tr> </tbody> </table> <h2 id="11-索引index">1.1 索引(Index)</h2> <p>一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。 能搜索的数据必须索引,这样的好处是可以提高查询速度,比如:新华字典前面的目录就是索引的意思,目录可以提高查询速度。 **Elasticsearch 索引的精髓:一切设计都是为了提高搜索的性能。 ** ##</p> <h2 id="12-类型type">1.2 类型(Type)</h2> <p>在一个索引中,你可以定义一种或多种类型。 一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。不同的版本,类型发生了不同的变化</p> <table> <thead> <tr> <th>版本</th> <th>Type</th> </tr> </thead> <tbody> <tr> <td>5.x</td> <td>支持多种type</td> </tr> <tr> <td>6.x</td> <td>只能有一种type</td> </tr> <tr> <td>7.x</td> <td>默认不再支持自定义索引类型(默认类型为:_doc)</td> </tr> </tbody> </table> <h2 id="13-文档document">1.3 文档(Document)</h2> <p>一个文档是一个可被索引的基础信息单元,也就是一条数据 比如:你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以 JSON(Javascript Object Notation)格式来表示,而 JSON 是一个到处存在的互联网数据交互格式。 在一个 index/type 里面,你可以存储任意多的文档。</p> <h2 id="14-字段field">1.4 字段(Field)</h2> <p>相当于是数据表的字段,对文档数据根据不同属性进行的分类标识。</p> <h2 id="15-映射mapping">1.5 映射(Mapping)</h2> <p>mapping 是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值、分析器、是否被索引等等。这些都是映射里面可以设置的,其它就是处理 ES 里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。</p> <h2 id="16-分片shards">1.6 分片(Shards)</h2> <p>一个索引可以存储超出单个节点硬件限制的大量数据。比如,一个具有 10 亿文档数据的索引占据 1TB 的磁盘空间,而任一节点都可能没有这样大的磁盘空间。或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch 提供了将索引划分成多份的能力,每一份就称之为分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点 上。 分片很重要,主要有两方面的原因: 1)允许你水平分割 / 扩展你的内容容量。 2)允许你在分片之上进行分布式的、并行的操作,进而提高性能/吞吐量。</p> <p>至于一个分片怎样分布,它的文档怎样聚合和搜索请求,是完全由 Elasticsearch 管理的,对于作为用户的你来说,这些都是透明的,无需过分关心。</p> <p>**被混淆的概念是,一个 Lucene 索引 我们在 Elasticsearch 称作分片 。 一个Elasticsearch 索引是分片的集合。 当 Elasticsearch 在索引中搜索的时候, 他发送查询到每一个属于索引的分片(Lucene 索引),然后合并每个分片的结果到一个全局的结果集。 **</p> <h2 id="17-副本replicas">1.7 副本(Replicas)</h2> <p>在一个网络 / 云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch 允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)。</p> <p>复制分片之所以重要,有两个主要原因:</p> <ul> <li>在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。</li> <li>扩展你的搜索量/吞吐量,因为搜索可以在所有的副本上并行运行。</li> </ul> <p>总之,每个索引可以被分成多个分片。一个索引也可以被复制 0 次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。默认情况下,Elasticsearch 中的每个索引被分片 1 个主分片和 1 个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有 1 个主分片和另外 1 个复制分片(1 个完全拷贝),这样的话每个索引总共就有 2 个分片,我们需要根据索引需要确定分片个数。</p> <h2 id="18-分配allocation">1.8 分配(Allocation)</h2> <p>将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。</p> <h1 id="2-系统架构">2. 系统架构</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650247396440-3ba18ef9-01b7-4780-ada2-6c058921ae08.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=390&amp;id=ua75c8f71&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=390&amp;originWidth=799&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=233807&amp;status=done&amp;style=none&amp;taskId=u5ab4b725-6f51-4e56-b26d-0f3f4495ca6&amp;title=&amp;width=799" alt="image.png" /> 一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。</p> <p>当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。</p> <p>作为用户,我们可以将请求发送到集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。</p> <h1 id="3-分布式集群">3. 分布式集群</h1> <p>我们在包含一个空节点的集群内创建名为 users 的索引,为了演示目的,我们将分配 3 个主分片和一份副本(每个主分片拥有一个副本分片)</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"settings"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"number_of_shards"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"number_of_replicas"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650267180096-325b8ce0-2b59-433e-95e4-53c70ce43b68.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=466&amp;id=ue69a15c4&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=466&amp;originWidth=852&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=141997&amp;status=done&amp;style=none&amp;taskId=u51d253cd-fa89-4249-b53f-17c1e7b96e7&amp;title=&amp;width=852" alt="image.png" /> 通过 elasticsearch-head 插件查看集群情况 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650267188083-7f34229e-5116-4b38-b74c-c1348a93e6a6.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=364&amp;id=u21bc6c16&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=364&amp;originWidth=904&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=144593&amp;status=done&amp;style=none&amp;taskId=u1d6525e4-20fe-4334-a34c-4402dca01a9&amp;title=&amp;width=904" alt="image.png" /> 集群健康值:green( 6 of 6 ) : 表示所有 6 个分片(包括 3 个主分片和 3 个副本分片)都在正常运行。 主分片为黑框框住的</p> <p>分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有 6 个分片(3 个主分片和 3 个副本分片)的索引可以最大扩容到 6 个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。</p> <p>**但是如果我们想要扩容超过 6 个节点怎么办呢? ** 主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够存储的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作——搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量。</p> <p>在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的 1 增加到 2</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"number_of_replicas"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650267477079-7d72aba9-ddde-45b3-80d2-d34a233eff88.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=414&amp;id=uf5ac81c8&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=414&amp;originWidth=851&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=109170&amp;status=done&amp;style=none&amp;taskId=ud1737c26-1fdc-4e88-ba92-55b4f763206&amp;title=&amp;width=851" alt="image.png" /></p> <p>users 索引现在拥有 9 个分片:3 个主分片和 6 个副本分片。 这意味着我们可以将集群扩容到 9 个节点,每个节点上一个分片。相比原来 3 个节点时,集群搜索性能可以提升 3 倍。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650267535561-49128fd0-0b41-4243-b0a4-a03551119be3.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=355&amp;id=u67bff2be&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=355&amp;originWidth=850&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=150924&amp;status=done&amp;style=none&amp;taskId=u34cbb9da-a03b-46b2-9934-71df299ef1b&amp;title=&amp;width=850" alt="image.png" /></p> <p>当然,如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少。 你需要增加更多的硬件资源来提升吞吐量。 但是更多的副本分片数提高了数据冗余量:按照上面的节点配置,我们可以在失去 2 个节点的情况下不丢失任何数据。</p> <h2 id="应对故障">应对故障</h2> <p>我们关闭第一个节点,这时集群的状态为:关闭了一个节点后的集群。</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>2022-04-18T15:47:11,989][INFO <span class="o">][</span>o.e.n.Node <span class="o">]</span> <span class="o">[</span>node-1] stopping ... <span class="o">[</span>2022-04-18T15:47:12,110][INFO <span class="o">][</span>o.e.x.w.WatcherService <span class="o">]</span> <span class="o">[</span>node-1] stopping watch service, reason <span class="o">[</span>shutdown initiated] <span class="o">[</span>2022-04-18T15:47:12,121][INFO <span class="o">][</span>o.e.x.w.WatcherLifeCycleService] <span class="o">[</span>node-1] watcher has stopped and shutdown <span class="o">[</span>2022-04-18T15:47:12,674][INFO <span class="o">][</span>o.e.n.Node <span class="o">]</span> <span class="o">[</span>node-1] stopped <span class="o">[</span>2022-04-18T15:47:12,675][INFO <span class="o">][</span>o.e.n.Node <span class="o">]</span> <span class="o">[</span>node-1] closing ... <span class="o">[</span>2022-04-18T15:47:12,800][INFO <span class="o">][</span>o.e.n.Node <span class="o">]</span> <span class="o">[</span>node-1] closed <span class="o">[</span>mhk@hadoop102 elasticsearch-7.8.0]<span class="nv">$ </span> </code></pre></div></div> <p>我们关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node-2 。在我们关闭 Node-1 的同时也失去了主分片 1 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为 red :不是所有主分片都在正常工作。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650268130385-fff10751-a5b9-4276-877f-58c7c486592e.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=345&amp;id=u32d508ee&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=345&amp;originWidth=876&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=140198&amp;status=done&amp;style=none&amp;taskId=ue5d6e0e1-fc7b-4c85-b23e-ccfea0e7b2a&amp;title=&amp;width=876" alt="image.png" /> 幸运的是,在其它节点上存在着这个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为yellow。这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。</p> <p><strong>为什么我们集群状态是 yellow 而不是 green 呢</strong>? 虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应 2 份副本分片,而此时只存在一份副本分片。 所以集群不能为 green 的状态,不过我们不必过于担心:如果我们同样关闭了 Node-2 ,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为Node-3 为每一个分片都保留着一份副本。</p> <p>如果我们重新启动 Node-1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态也将恢复成之前的状态。 如果 Node-1 依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。和之前的集群相比,只是 Master 节点切换了。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650268268929-b2619e89-8f6f-4153-b8ad-6ef236b21cd3.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=345&amp;id=uad9102f1&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=345&amp;originWidth=857&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=151193&amp;status=done&amp;style=none&amp;taskId=uab9f3ccc-67f0-4e07-bbfe-91319e8d796&amp;title=&amp;width=857" alt="image.png" /></p> <h1 id="4-路由计算">4. 路由计算</h1> <p>当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650269552278-b0752984-3079-4459-a529-0f4d8e21f4be.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=621&amp;id=uad869bc3&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=621&amp;originWidth=1275&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=360625&amp;status=done&amp;style=none&amp;taskId=u6b37b21f-0222-466a-bde8-35a7674d1a8&amp;title=&amp;width=1275" alt="image.png" /> 实际上,这个过程是根据下面这个公式决定的:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> shard <span class="o">=</span> <span class="nb">hash</span><span class="o">(</span>routing<span class="o">)</span> % number_of_primary_shards </code></pre></div></div> <p>hash( id ) % 主分片数量 = [ 0, 1, 2 ]</p> <p>routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。 routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求 的文档所在分片的位置。</p> <p>这就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。</p> <p>所有的文档 API( get 、 index 、 delete 、 bulk 、 update 以及 mget )都接受一个叫做 routing 的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。</p> <h1 id="5-分片控制">5. 分片控制</h1> <p>我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。例如将所有的请求发送到 Node-1,我们将其称为 <strong>协调节点</strong>(coordinating node) 。 如果访问的节点负载比较多,压力比较大,它会把请求抛给另外一个副本,这就是协调节点的含义</p> <p><strong>tips:当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点。</strong> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650269699572-6f1401be-a01f-48e8-b470-940c64f3af9a.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=593&amp;id=u0260f992&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=593&amp;originWidth=1307&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=429154&amp;status=done&amp;style=none&amp;taskId=u539cb65d-dfa8-4e9a-8fbd-ce116e2db2c&amp;title=&amp;width=1307" alt="image.png" /></p> <h2 id="51-写流程">5.1 写流程</h2> <p>新建、索引和删除 请求都是 写 操作, 必须在主分片上面完成之后才能被复制到相关的副本分片 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650270298307-f1599074-80d3-4d76-bbf8-59d02e2d645c.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=287&amp;id=ub6731714&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=287&amp;originWidth=610&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=65695&amp;status=done&amp;style=none&amp;taskId=u1990c824-fccd-4dd4-88ab-6596a1348c8&amp;title=&amp;width=610" alt="image.png" /> <strong>新建,索引和删除文档所需要的步骤顺序</strong>:</p> <ol> <li>客户端向 Node 1 发送新建、索引或者删除请求。</li> <li>节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。</li> <li>Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。</li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650270475923-fa9a6d31-a935-478d-b9f1-9f62a105cbdc.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=687&amp;id=u44adcb9f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=687&amp;originWidth=1250&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=471728&amp;status=done&amp;style=none&amp;taskId=u3b097aea-df84-4f37-b564-1da637215f7&amp;title=&amp;width=1250" alt="image.png" /> 在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为 Elasticsearch 已经很快</p> <h2 id="52-读流程">5.2 读流程</h2> <p>我们可以从主分片或者从其它任意副本分片检索文档 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650270538421-2161a830-6ba6-4b9c-aefd-baf64b0277f0.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=248&amp;id=u1cf5ca6c&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=248&amp;originWidth=672&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=75218&amp;status=done&amp;style=none&amp;taskId=u3803cae1-45b4-4d32-bfb6-b0d78023c3f&amp;title=&amp;width=672" alt="image.png" /> <strong>从主分片或者副本分片检索文档的步骤顺序</strong>:</p> <ol> <li>客户端向 Node 1 发送获取请求。</li> <li>节点使用文档的 _id 来确定文档属于分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2 。</li> <li>Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端。</li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650270818682-02b1b2e5-9313-4106-aca3-bc5839e6607a.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=588&amp;id=u61755a9f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=588&amp;originWidth=1291&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=455137&amp;status=done&amp;style=none&amp;taskId=u64ccfaec-2051-4a51-95d1-7ac2dd85e77&amp;title=&amp;width=1291" alt="image.png" /> 在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。</p> <h2 id="53-更新流程">5.3 更新流程</h2> <p>部分更新一个文档结合了先前说明的读取和写入流程: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650270880453-5fa9071e-4d12-4f22-b5d3-b7b10ab9b8f4.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=298&amp;id=ud3e13dae&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=298&amp;originWidth=741&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=86016&amp;status=done&amp;style=none&amp;taskId=u3ff5f0d3-8983-4951-a411-6eb63584caf&amp;title=&amp;width=741" alt="image.png" /> <strong>部分更新一个文档的步骤如下</strong>:</p> <ol> <li>客户端向 Node 1 发送更新请求。</li> <li>它将请求转发到主分片所在的 Node 3 。</li> <li>Node 3 从主分片检索文档,修改 _source 字段中的 JSON ,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤 3(抢占锁) ,超过 retry_on_conflict 次后放弃。</li> <li>如果 Node 3 成功地更新文档,它将新版本的文档并行转发到 Node 1 和 Node 2 上的副本分片,重新建立索引。一旦所有副本分片都返回成功, Node 3 向协调节点也返回成功,协调节点向客户端返回成功。</li> </ol> <p>当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果 Elasticsearch 仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。</p> <h2 id="54-多文档操作流程">5.4 多文档操作流程</h2> <p>mget 和 bulk API 的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。它将整个多文档请求分解成 每个分片 的多文档请求,并且将这些请求并行转发到每个参与节点。</p> <p>协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650272804961-d5246c4f-ed4f-4d29-a20c-545130278903.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=231&amp;id=CzKyF&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=231&amp;originWidth=689&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=76886&amp;status=done&amp;style=none&amp;taskId=u77f3d0e2-56cb-463e-81b3-f4dfc8b8ac8&amp;title=&amp;width=689" alt="image.png" /> <strong>用单个 mget 请求取回多个文档所需的步骤顺序</strong>:</p> <ol> <li>客户端向 Node 1 发送 mget 请求。</li> <li>Node 1 为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复, Node 1 构建响应并将其返回给客户端。</li> </ol> <p>可以对 docs 数组中每个文档设置 routing 参数。</p> <p><strong>bulk API</strong>, 允许在单个批量请求中执行多个创建、索引、删除和更新请求。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650273064245-aa07e51b-b2a8-47e6-b1da-1e44830e7b38.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=251&amp;id=uccccb8e7&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=251&amp;originWidth=562&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=75265&amp;status=done&amp;style=none&amp;taskId=ud9e7cf6e-0556-438b-913c-3576e789090&amp;title=&amp;width=562" alt="image.png" /> bulk API 按如下步骤顺序执行:</p> <ol> <li>客户端向 Node 1 发送 bulk 请求。</li> <li>Node 1 为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。</li> <li>主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。</li> </ol> <h1 id="6-分片原理">6. 分片原理</h1> <p>分片是 Elasticsearch 最小的工作单元。但是究竟什么是一个分片,它是如何工作的?</p> <p>传统的数据库每个字段存储单个值,但这对全文检索并不够。文本字段中的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值的能力。最好的支持是一个字段多个值需求的数据结构是倒排索引。</p> <h2 id="61-倒排索引">6.1 倒排索引</h2> <p>Elasticsearch 使用一种称为<strong>倒排索引</strong>的结构,它适用于快速的全文搜索。</p> <p>见其名,知其意,有倒排索引,肯定会对应有正向索引。正向索引(forward index),反向索引(inverted index)更熟悉的名字是倒排索引。</p> <p>所谓的正向索引,就是搜索引擎会将待搜索的文件都对应一个文件 ID,搜索时将这个 ID 和搜索关键字进行对应,形成 K-V 对,然后对关键字进行统计计数 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650278015517-015cd403-5769-437b-a3af-8f5512223548.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=212&amp;id=u3d3c68b6&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=212&amp;originWidth=484&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=101695&amp;status=done&amp;style=none&amp;taskId=u62e39a59-3874-41d5-b669-fcf4c04cf4c&amp;title=&amp;width=484" alt="image.png" /> 但是互联网上收录在搜索引擎中的文档的数目是个天文数字,这样的索引结构根本无法满足实时返回排名结果的要求。所以,搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650278054152-436995ee-504a-43b9-a45a-7b12708d4b84.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=231&amp;id=uf0282ef4&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=231&amp;originWidth=475&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=95361&amp;status=done&amp;style=none&amp;taskId=u22e58ef3-0ddc-4185-8cb4-e97fe646316&amp;title=&amp;width=475" alt="image.png" /> 一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。例如,假设我们有两个文档,每个文档的 content 域包含如下内容:</p> <ul> <li>The quick brown fox jumped over the lazy dog</li> <li>Quick brown foxes leap over lazy dogs in summer</li> </ul> <p>为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的 词(我们称它为 词条 或 tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650279445790-958e67dd-1ea9-4e92-9483-20900a0fd4bb.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=324&amp;id=u77738b13&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=324&amp;originWidth=188&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=52373&amp;status=done&amp;style=none&amp;taskId=u7517fa8e-cbc7-4a17-b573-d3da374364a&amp;title=&amp;width=188" alt="image.png" /> 现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650279494372-e1bf1428-e122-42ca-bcf3-6c84335b8029.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=163&amp;id=uca14c664&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=163&amp;originWidth=248&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=33149&amp;status=done&amp;style=none&amp;taskId=ub228a782-1f2d-41a8-9461-f04edbd96ce&amp;title=&amp;width=248" alt="image.png" /> 两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单相似性算法,那么我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。</p> <p>但是,我们目前的倒排索引有一些问题:</p> <ul> <li>Quick 和 quick 以独立的词条出现,然而用户可能认为它们是相同的词。</li> <li>fox 和 foxes 非常相似, 就像 dog 和 dogs ;他们有相同的词根。</li> <li>jumped 和 leap, 尽管没有相同的词根,但他们的意思很相近。他们是同义词。</li> </ul> <p>使用前面的索引搜索 +Quick +fox 不会得到任何匹配文档。(记住,+ 前缀表明这个词必须存在。)只有同时出现 Quick 和 fox 的文档才满足这个查询条件,但是第一个文档包含 quick fox ,第二个文档包含 Quick foxes 。</p> <p>我们的用户可以合理的期望两个文档与查询匹配。我们可以做的更好。 如果我们将词条规范为标准模式,那么我们可以找到与用户搜索的词条不完全一致,但具有足够相关性的文档。例如:</p> <ul> <li>Quick 可以小写化为 quick 。</li> <li>foxes 可以 词干提取 –变为词根的格式– 为 fox 。类似的, dogs 可以为提取为 dog 。</li> <li>jumped 和 leap 是同义词,可以索引为相同的单词 jump 。</li> </ul> <p>现在索引看上去像这样: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650279638827-079f17f5-2453-444f-989d-7674f072a6ad.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=306&amp;id=uf06996bf&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=306&amp;originWidth=226&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=51841&amp;status=done&amp;style=none&amp;taskId=u26b5949e-c82f-446b-a8e9-c8b1d594659&amp;title=&amp;width=226" alt="image.png" /> 这还远远不够。我们搜索 +Quick +fox 仍然 会失败,因为在我们的索引中,已经没有 Quick 了。但是,如果我们对搜索的字符串使用与 content 域相同的标准化规则,会变成查询 +quick +fox,这样两个文档都会匹配!分词和标准化的过程称为<strong>分析</strong> 这非常重要。你只能搜索在索引中出现的词条,所以索引文本和查询字符串必须标准化为相同的格式。</p> <p>词条:索引中最小存储和查询单元,英文当中就是一个单词,中文当中一般就是一个词组 词典:字典,是词条的集合,底层一般会用两种数据结构来实现,B+树和HashMap 倒排表:每条记录被称为一个倒排项。表示查询的词条在什么位置,出现的频率是多少。</p> <p>倒排索引的搜索过程:搜索的时候查询单词的词典,看看单词在不在词典当中,如果不在搜索就结束了,如果在词典当中的话,它要看一看单词在倒排表中的指针,通过倒排表去获取文档对应的ID的列表,然后拿着ID去对应数据</p> <h2 id="62-文档搜索">6.2 文档搜索</h2> <p>早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。 倒排索引被写入磁盘后是 不可改变的 : 它永远不会修改。</p> <p>不变性有重要的价值:</p> <ul> <li>不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。</li> <li>一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。 减少磁盘I/O</li> <li>其它缓存(像 filter 缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。</li> <li>写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量。</li> </ul> <p>当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。如果更新频率比较高,性能就会变差</p> <h2 id="63-动态更新索引">6.3 动态更新索引</h2> <p>如何在保留不变性的前提下实现倒排索引的更新?</p> <p>答案是: 用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。</p> <p>Elasticsearch 基于 Lucene, 这个 java 库引入了<strong>按段搜索</strong>的概念。 每一 段 本身都是一个倒排索引, 但索引在 Lucene 中除表示所有段的集合外, 还增加了提交点的概念 —— 一个列出了所有已知段的文件 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650282548051-3b83afca-7bfa-4285-85d1-f8926962de59.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=290&amp;id=ub4774baa&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=290&amp;originWidth=495&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=71640&amp;status=done&amp;style=none&amp;taskId=ud2c18628-96ea-46b9-9443-9c77a7e84d0&amp;title=&amp;width=495" alt="image.png" /></p> <p><strong>按段搜索</strong>会以如下流程执行:</p> <ol> <li>新文档被收集到内存索引缓存</li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650282605270-0570b85b-47c0-4769-a34e-fff3576b41cc.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=282&amp;id=ubb300751&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=282&amp;originWidth=313&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=62707&amp;status=done&amp;style=none&amp;taskId=u33567e7b-493c-4fba-b150-d6ce2e43b37&amp;title=&amp;width=313" alt="image.png" /></p> <ol> <li>不时地, 缓存被 提交</li> </ol> <p>(1) 一个新的段—— 一个追加的倒排索引 ——被写入磁盘。 (2) 一个新的包含新段名字的 提交点 被写入磁盘 (3) 磁盘进行 同步 —— 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件</p> <ol> <li> <p>新的段被开启,让它包含的文档可见以被搜索</p> </li> <li> <p>内存缓存被清空,等待接收新的文档</p> </li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650282678423-b0d71809-244f-4502-aed4-b86f2dc6f5a5.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=368&amp;id=ua86d712f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=368&amp;originWidth=489&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=100966&amp;status=done&amp;style=none&amp;taskId=uc29bbf20-c28b-450f-a8c2-2751a40edb8&amp;title=&amp;width=489" alt="image.png" /></p> <p>当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引。</p> <p>段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息。</p> <p>当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。</p> <p>文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。</p> <h2 id="64-近实时搜索">6.4 近实时搜索</h2> <p>es并非是实时的,而是准实时的,原因在于es的数据搜索是基于分段搜索,es中的每一段其实就是一个倒排索引,最新的数据更新会体现在最新的段中,而最新的段落盘之后es软件才能进行数据的搜索,这样的话,磁盘的I/O性能就极大的影响了查询的效率。但是反过来想想,全文搜索引擎的设计的目的不就是能快速的准确的获取有效的数据吗?所以,降低数据查询处理的延迟就显得尤为重要 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286110051-c697e07e-446e-454d-8e36-5bba04c5f3b7.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=643&amp;id=u3fd27832&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=643&amp;originWidth=1032&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=332925&amp;status=done&amp;style=none&amp;taskId=u4aefaf79-bed0-456f-aa78-02357333354&amp;title=&amp;width=1032" alt="image.png" /> 随着按段(per-segment)搜索的发展,一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被检索,但这样还是不够快。磁盘在这里成为了瓶颈。提交(Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。 但是 fsync 操作代价很大; 如果每次索引一个文档都去执行一次的话会造成很大的性能问题。</p> <p>我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着 fsync 要从整个过程中被移除。在 Elasticsearch 和磁盘之间是文件系统缓存。 像之前描述的一样, 在内存索引缓冲区中的文档会被写入到一个新的段中。 但是这里新段会被先写入到文件系统缓存 —— 这一步代价会比较低,稍后再被刷新到磁盘 —— 这一步代价比较高。不过只要文件已经在缓存中,就可以像其它文件一样被打开和读取了。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650282873933-ea32d30b-6dc8-49cb-8e4c-fb0f8b0ab6a2.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=306&amp;id=u59eb9b0f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=306&amp;originWidth=293&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=61500&amp;status=done&amp;style=none&amp;taskId=u8fb81a0c-c4cc-40d2-8468-e0d3a6501cd&amp;title=&amp;width=293" alt="image.png" /> Lucene 允许新段被写入和打开——使其包含的文档在未进行一次完整提交时便对搜索可见。这种方式比进行一次提交代价要小得多,并且在不影响性能的前提下可以被频繁地执行。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650282927550-e09ba76b-6432-4d53-9f8e-1b257d4ef9be.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=350&amp;id=u9aafdcc0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=350&amp;originWidth=374&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=75279&amp;status=done&amp;style=none&amp;taskId=u8fda0da8-5262-453c-91ed-2fd322a9927&amp;title=&amp;width=374" alt="image.png" /></p> <p>在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。</p> <p>这些行为可能会对新用户造成困惑: 他们索引了一个文档然后尝试搜索它,但却没有搜到。这个问题的解决办法是用 refresh API 执行一次手动刷新: /users/_refresh</p> <p>尽管刷新是比提交轻量很多的操作,它还是会有性能开销。当写测试的时候, 手动刷新很有用,但是不要 在生产环境下每次索引一个文档都去手动刷新。 相反,你的应用需要意识到 Elasticsearch 的近实时的性质,并接受它的不足。</p> <p>并不是所有的情况都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日志文件,你可能想优化索引速度而不是近实时搜索, 可以通过设置 refresh_interval , 降低每个索引的刷新频率</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"settings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"refresh_interval"</span><span class="p">:</span><span class="w"> </span><span class="s2">"30s"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>refresh_interval 可以在既存索引上进行动态更新。 在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span><span class="w"> </span><span class="err">关闭自动刷新</span><span class="w"> </span><span class="err">PUT</span><span class="w"> </span><span class="err">/users/_settings</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"refresh_interval"</span><span class="p">:</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="err">每一秒刷新</span><span class="w"> </span><span class="err">PUT</span><span class="w"> </span><span class="err">/users/_settings</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"refresh_interval"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1s"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286138793-6be1c972-081b-4a9c-85cf-6d17802773cd.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=748&amp;id=u01f4c3ba&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=748&amp;originWidth=980&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=312759&amp;status=done&amp;style=none&amp;taskId=ue138d535-65e9-4ec8-b32e-9f2f1743cd3&amp;title=&amp;width=980" alt="image.png" /></p> <h2 id="65-持久化变更">6.5 持久化变更</h2> <p>如果没有用 fsync 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证 Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。在 动态更新索引,我们说一次完整的提交会将段刷到磁盘,并写入一个包含所有段列表的提交点。Elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。</p> <p>即使通过每秒刷新(refresh)实现了近实时搜索,我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办?我们也不希望丢失掉这些数据。Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录</p> <p>整个流程如下:</p> <ol> <li>一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog</li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286279271-1f476646-4c0b-4c4d-8797-f35c0dc1d228.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=297&amp;id=u385aedb0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=297&amp;originWidth=349&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=85194&amp;status=done&amp;style=none&amp;taskId=u9dd8d12c-0cb3-4045-b976-5c62e27504e&amp;title=&amp;width=349" alt="image.png" /></p> <ol> <li>刷新(refresh)使分片每秒被刷新(refresh)一次: <ul> <li>这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。</li> <li>这个段被打开,使其可被搜索</li> <li>内存缓冲区被清空</li> </ul> </li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286386846-f1cdccf9-af07-436b-9768-ea0cdffff524.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=335&amp;id=u038bbf54&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=335&amp;originWidth=393&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=90248&amp;status=done&amp;style=none&amp;taskId=ua71b262f-cb92-472a-9026-129c65cdcfd&amp;title=&amp;width=393" alt="image.png" /></p> <ol> <li>这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志</li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286434301-0d9ad5df-cf07-4802-9e4f-1f40b99c0579.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=346&amp;id=uf97428ff&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=346&amp;originWidth=404&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=99301&amp;status=done&amp;style=none&amp;taskId=ud5a5d7ca-0c79-464b-ad43-d4fb2f5527e&amp;title=&amp;width=404" alt="image.png" /></p> <ol> <li>每隔一段时间—例如 translog 变得越来越大—索引被刷新(flush);一个新的 translog 被创建,并且一个全量提交被执行 <ul> <li>所有在内存缓冲区的文档都被写入一个新的段。</li> <li>缓冲区被清空。</li> <li>一个提交点被写入硬盘。</li> <li>文件系统缓存通过 fsync 被刷新(flush)。</li> <li>老的 translog 被删除。</li> </ul> </li> </ol> <p>translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。</p> <p>translog 也被用来提供实时 CRUD 。当你试着通过 ID 查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286544726-d9d2e473-4d76-413a-b89a-1160dfe732ae.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=401&amp;id=u05462253&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=401&amp;originWidth=485&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=125403&amp;status=done&amp;style=none&amp;taskId=u661dcf4e-2a3e-400c-944b-a5d2e611ecb&amp;title=&amp;width=485" alt="image.png" /> 执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush分片每 30 分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新</p> <p>你很少需要自己手动执行 flush 操作;通常情况下,自动刷新就足够了。这就是说,在重启节点或关闭索引之前执行 flush 有益于你的索引。当 Elasticsearch 尝试恢复或重新打开一个索引, 它需要重放 translog 中所有的操作,所以如果日志越短,恢复越快。</p> <p>translog 的目的是保证操作不会丢失,在文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失。默认 translog 是每 5 秒被 fsync 刷新到硬盘, 或者在每次写请求完成之后执行(e.g. index, delete, update, bulk)。这个过程在主分片和复制分片都会发生。最终, 基本上,这意味着在整个请求被 fsync 到主分片和复制分片的 translog 之前,你的客户端不会得到一个 200 OK 响应。</p> <p>在每次请求后都执行一个 fsync 会带来一些性能损失,尽管实践表明这种损失相对较小(特别是 bulk 导入,它在一次请求中平摊了大量文档的开销)。</p> <p>但是对于一些大容量的偶尔丢失几秒数据问题也并不严重的集群,使用异步的 fsync 还是比较有益的。比如,写入的数据被缓存到内存中,再每 5 秒执行一次 fsync 。如果你决定使用异步 translog 的话,你需要 保证 在发生 crash 时,丢失掉 sync_interval 时间段的数据也无所谓。请在决定前知晓这个特性。如果你不确定这个行为的后果,最好是使用默认的参数( “index.translog.durability”: “request” )来避免数据丢失。</p> <h2 id="66-段合并">6.6 段合并</h2> <p>由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。</p> <p>Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。</p> <p>段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。</p> <p>启动段合并不需要你做任何事。进行索引和搜索时会自动进行。</p> <ol> <li>当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。</li> <li>合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。</li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650286954875-901e548f-685a-43fb-914f-389bc243cfdc.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=242&amp;id=uff459ca0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=242&amp;originWidth=422&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=70650&amp;status=done&amp;style=none&amp;taskId=u2c8a55af-d705-4566-a8c7-bbb3de55565&amp;title=&amp;width=422" alt="image.png" /></p> <ol> <li>一旦合并结束,老的段被删除 <ul> <li>新的段被刷新(flush)到了磁盘。 写入一个包含新段且排除旧的和较小的段的新提交点。</li> <li>新的段被打开用来搜索。</li> <li>老的段被删除</li> </ul> </li> </ol> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650287071390-109c4ca5-dced-40c2-b636-1c65ca7acf33.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=250&amp;id=u5e12cd25&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=250&amp;originWidth=441&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=69732&amp;status=done&amp;style=none&amp;taskId=ud8b15396-7620-4ca9-ab0f-6649ac135df&amp;title=&amp;width=441" alt="image.png" /> 合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。</p> <h1 id="7-文档分析">7. 文档分析</h1> <p>分析 包含下面的过程:</p> <ul> <li>将一块文本分成适合于倒排索引的独立的 词条</li> <li>将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall</li> </ul> <p>分析器执行上面的工作。分析器实际上是将三个功能封装到了一个包里:</p> <ul> <li>字符过滤器</li> </ul> <p>首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉 HTML,或者将 &amp; 转化成 and。</p> <ul> <li>分词器</li> </ul> <p>其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。</p> <ul> <li>Token 过滤器</li> </ul> <p>最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。</p> <h2 id="71-内置分析器">7.1 内置分析器</h2> <p>Elasticsearch 还附带了可以直接使用的预包装的分析器。接下来我们会列出最重要的分析器。为了证明它们的差异,我们看看每个分析器会从下面的字符串得到哪些词条:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"Set the shape to semi-transparent by calling set_trans(5)" </code></pre></div></div> <ul> <li>标准分析器</li> </ul> <p>标准分析器是 Elasticsearch 默认使用的分析器。它是分析各种语言文本最常用的选择。 它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。 它会产生: set, the, shape, to, semi, transparent, by, calling, set_trans, 5</p> <ul> <li>简单分析器</li> </ul> <p>简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生: set, the, shape, to, semi, transparent, by, calling, set, trans</p> <ul> <li>空格分析器</li> </ul> <p>空格分析器在空格的地方划分文本。它会产生: Set, the, shape, to, semi-transparent, by, calling, set_trans(5)</p> <ul> <li>语言分析器</li> </ul> <p>特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如, 英语 分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的 词干 。 英语 分词器会产生下面的词条: set, shape, semi, transpar, call, set_tran, 5 注意看 transparent、 calling 和 set_trans 已经变为词根格式</p> <h2 id="72-分析器使用场景">7.2 分析器使用场景</h2> <p>当我们 索引 一个文档,它的全文域被分析成词条以用来创建倒排索引。 但是,当我们在全文域 搜索 的时候,我们需要将查询字符串通过 相同的分析过程 ,以保证我们搜索的词条格式与索引中的词条格式一致。</p> <p>全文查询,理解每个域是如何定义的,因此它们可以做正确的事:</p> <ul> <li>当你查询一个 全文 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。</li> <li>当你查询一个 精确值 域时,不会分析查询字符串,而是搜索你指定的精确值。</li> </ul> <h2 id="73-测试分析器">7.3 测试分析器</h2> <p>有些时候很难理解分词的过程和实际被存储到索引中的词条,特别是你刚接触Elasticsearch。为了理解发生了什么,你可以使用 analyze API 来看文本是如何被分析的。</p> <p>在消息体里,指定分析器和要分析的文本</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">GET</span><span class="w"> </span><span class="err">http://</span><span class="mf">10.211</span><span class="err">.</span><span class="mf">55.11</span><span class="err">:</span><span class="mi">9200</span><span class="err">/_analyze</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"analyzer"</span><span class="p">:</span><span class="w"> </span><span class="s2">"standard"</span><span class="p">,</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Text to analyze"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>结果中每个元素代表一个单独的词条:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"to"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"analyze"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>token 是实际存储到索引中的词条。 position 指明词条在原始文本中出现的位置。 start_offset 和 end_offset 指明字符在原始字符串中的位置。</p> <h2 id="74-指定分析器">7.4 指定分析器</h2> <p>当Elasticsearch在你的文档中检测到一个新的字符串域,它会自动设置其为一个全文 字符串 域,使用 标准 分析器对它进行分析。你不希望总是这样。可能你想使用一个不同的分析器,适用于你的数据使用的语言。有时候你想要一个字符串域就是一个字符串域——不使用分析,直接索引你传入的精确值,例如用户 ID 或者一个内部的状态域或标签。要做到这一点,我们必须手动指定这些域的映射。</p> <h2 id="75-ik-分词器">7.5 IK 分词器</h2> <p>首先我们通过 Postman 发送 **GET **请求查询分词效果</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="s2">"测试单词"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>ES 的默认分词器无法识别中文中测试、单词这样的词汇,而是简单的将每个字拆完分为一个词</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"测"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;IDEOGRAPHIC&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"试"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;IDEOGRAPHIC&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"单"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;IDEOGRAPHIC&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"词"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;IDEOGRAPHIC&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>这样的结果显然不符合我们的使用要求,所以我们需要下载 ES 对应版本的中文分词器。</p> <p>我们这里采用 IK 中文分词器,下载地址为: https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0 将解压后的后的文件夹放入 ES 根目录下的 plugins 目录下,重启 ES 即可使用。 我们这次加入新的查询参数”analyzer”:”ik_max_word”</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="s2">"中国人"</span><span class="p">,</span><span class="w"> </span><span class="nl">"analyzer"</span><span class="p">:</span><span class="s2">"ik_max_word"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <ul> <li>ik_max_word:会将文本做最细粒度的拆分</li> <li>ik_smart:会将文本做最粗粒度的拆分</li> </ul> <p>使用中文分词后的结果为:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"中国人"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_WORD"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"中国"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_WORD"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"国人"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_WORD"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="s2">"中国人"</span><span class="p">,</span><span class="w"> </span><span class="nl">"analyzer"</span><span class="p">:</span><span class="s2">"ik_smart"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"中国人"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_WORD"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>ES 中也可以进行扩展词汇,首先查询</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="s2">"弗雷尔卓德"</span><span class="p">,</span><span class="w"> </span><span class="nl">"analyzer"</span><span class="p">:</span><span class="s2">"ik_max_word"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"弗"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_CHAR"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"雷"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_CHAR"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"尔"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_CHAR"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"卓"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_CHAR"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"德"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CN_CHAR"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>仅仅可以得到每个字的分词结果,我们需要做的就是使分词器识别到弗雷尔卓德也是一个词语</p> <p>首先进入 ES 根目录中的 plugins 文件夹下的 ik 文件夹,进入 config 目录,创建 custom.dic 文件,写入弗雷尔卓德。同时打开 IKAnalyzer.cfg.xml 文件,将新建的 custom.dic 配置其中,重启 ES 服务器。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650331588132-60188d53-c5e4-4b1a-a7da-debf73660b5a.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=238&amp;id=uaeea03b5&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=238&amp;originWidth=620&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=172505&amp;status=done&amp;style=none&amp;taskId=uf9c53341-a76a-41ee-b73a-b9668668d2d&amp;title=&amp;width=620" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650331835112-ef2a1f95-baaf-45d4-aa2c-fc6a456e9136.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=564&amp;id=u6e75533a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=564&amp;originWidth=862&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=147012&amp;status=done&amp;style=none&amp;taskId=u0af2b3e1-7096-43bc-a559-a69e06cc35d&amp;title=&amp;width=862" alt="image.png" /></p> <h2 id="76-自定义分析器">7.6 自定义分析器</h2> <p>虽然 Elasticsearch 带有一些现成的分析器,然而在分析器上 Elasticsearch 真正的强大之处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单元过滤器来创建自定义的分析器。在 分析与分析器 我们说过,一个 分析器 就是在一个包里面组合了三种函数的一个包装器, 三种函数按照顺序被执行:</p> <ul> <li>**字符过滤器 **</li> </ul> <p>字符过滤器 用来 整理 一个尚未被分词的字符串。例如,如果我们的文本是 HTML 格式的,它会包含像 &lt;p&gt; 或者 &lt;div&gt; 这样的 HTML 标签,这些标签是我们不想索引的。我们可以使用 html 清除 字符过滤器 来移除掉所有的 HTML 标签,并且像把 Á 转换为相对应的 Unicode 字符 Á 这样,转换 HTML 实体。一个分析器可能有 0 个或者多个字符过滤器。</p> <ul> <li>**分词器 **</li> </ul> <p>一个分析器 必须 有一个唯一的分词器。 分词器把字符串分解成单个词条或者词汇单元。 标准 分析器里使用的 标准 分词器 把一个字符串根据单词边界分解成单个词条,并且移除掉大部分的标点符号,然而还有其他不同行为的分词器存在。 例如, 关键词 分词器 完整地输出 接收到的同样的字符串,并不做任何分词。 空格 分词器 只根据空格分割文本 。 正则 分词器 根据匹配正则表达式来分割文本 。</p> <ul> <li>**词单元过滤器 **</li> </ul> <p>经过分词,作为结果的 词单元流 会按照指定的顺序通过指定的词单元过滤器 。词单元过滤器可以修改、添加或者移除词单元。我们已经提到过 lowercase 和 stop 词过滤器 ,但是在 Elasticsearch 里面还有很多可供选择的词单元过滤器。词干过滤器 把单词 遏制 为 词干。 ascii_folding 过滤器移除变音符,把一个像 “très” 这样的词转换为 “tres” 。ngram 和 edge_ngram 词单元过滤器 可以产生 适合用于部分匹配或者自动补全的词单元。</p> <p>接下来,我们看看如何创建自定义的分析器:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span><span class="w"> </span><span class="err">PUT</span><span class="w"> </span><span class="err">http://</span><span class="mf">10.211</span><span class="err">.</span><span class="mf">55.11</span><span class="err">:</span><span class="mi">9200</span><span class="err">/my_index</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"settings"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"analysis"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"char_filter"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"&amp;_to_and"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mapping"</span><span class="p">,</span><span class="w"> </span><span class="nl">"mappings"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"&amp;=&gt; and "</span><span class="p">]</span><span class="w"> </span><span class="p">}},</span><span class="w"> </span><span class="nl">"filter"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"my_stopwords"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stop"</span><span class="p">,</span><span class="w"> </span><span class="nl">"stopwords"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"the"</span><span class="p">,</span><span class="w"> </span><span class="s2">"a"</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}},</span><span class="w"> </span><span class="nl">"analyzer"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"my_analyzer"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"custom"</span><span class="p">,</span><span class="w"> </span><span class="nl">"char_filter"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"html_strip"</span><span class="p">,</span><span class="w"> </span><span class="s2">"&amp;_to_and"</span><span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="nl">"tokenizer"</span><span class="p">:</span><span class="w"> </span><span class="s2">"standard"</span><span class="p">,</span><span class="w"> </span><span class="nl">"filter"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"lowercase"</span><span class="p">,</span><span class="w"> </span><span class="s2">"my_stopwords"</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}}</span><span class="w"> </span><span class="p">}}}</span><span class="w"> </span></code></pre></div></div> <p>索引被创建以后,使用 analyze API 来 测试这个新的分析器</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"text"</span><span class="p">:</span><span class="s2">"The quick &amp; brown fox"</span><span class="p">,</span><span class="w"> </span><span class="nl">"analyzer"</span><span class="p">:</span><span class="w"> </span><span class="s2">"my_analyzer"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"quick"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"and"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"brown"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">17</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"token"</span><span class="p">:</span><span class="w"> </span><span class="s2">"fox"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w"> </span><span class="nl">"end_offset"</span><span class="p">:</span><span class="w"> </span><span class="mi">21</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;ALPHANUM&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nl">"position"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h1 id="8-文档处理">8 文档处理</h1> <h2 id="81-文档冲突">8.1 文档冲突</h2> <p>当我们使用 index API 更新文档 ,可以一次性读取原始文档,做我们的修改,然后重新索引整个文档。 最近的索引请求将获胜:无论最后哪一个文档被索引,都将被唯一存储在 Elasticsearch 中。如果其他人同时更改这个文档,他们的更改将丢失。 全量更新还好,如果是局部更新,你把别人这个数据改了,别人把你那个数据改了,这就会有冲突</p> <p>很多时候这是没有问题的。也许我们的主数据存储是一个关系型数据库,我们只是将数据复制到Elasticsearch 中并使其可被搜索。 也许两个人同时更改相同的文档的几率很小。或者对于我们的业务来说偶尔丢失更改并不是很严重的问题。</p> <p>但有时丢失了一个变更就是 非常严重的 。试想我们使用 Elasticsearch 存储我们网上商城商品库存的数量, 每次我们卖一个商品的时候,我们在 Elasticsearch 中将库存数量减少。有一天,管理层决定做一次促销。突然地,我们一秒要卖好几个商品。 假设有两个 web 程序并行运行,每一个都同时处理所有商品的销售 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650332883197-9ddd1300-fa52-4b73-82c0-62d9188a193a.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=356&amp;id=u65a6238a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=356&amp;originWidth=270&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=59965&amp;status=done&amp;style=none&amp;taskId=u8ab55748-e67e-441a-81fe-bf4ff9ea89c&amp;title=&amp;width=270" alt="image.png" /> web_1 对 stock_count 所做的更改已经丢失,因为 web_2 不知道它的 stock_count 的拷贝已经过期。 结果我们会认为有超过商品的实际数量的库存,因为卖给顾客的库存商品并不存在,我们将让他们非常失望。 变更越频繁,读数据和更新数据的间隙越长,也就越可能丢失变更。</p> <p>在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失:</p> <ul> <li>**悲观并发控制 **</li> </ul> <p>这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改。 性能比较低,加锁阻塞其他用户的访问</p> <ul> <li>**乐观并发控制 **</li> </ul> <p>Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。 然而,如果源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。 例如,可以重试更新、使用新的数据、或者将相关情况报告给用户。</p> <h2 id="82-乐观并发控制">8.2 乐观并发控制</h2> <p>Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许 顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本。</p> <p>当我们之前讨论 index ,GET 和 delete 请求时,我们指出每个文档都有一个 _version(版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。</p> <p>我们可以利用 version 号来确保 应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。</p> <p>老的版本 es 使用 version,但是新版本不支持了,会报下面的错误,提示我们用 if_seq_no 和 if_primary_term</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"error"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"root_cause"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"action_request_validation_exception"</span><span class="p">,</span><span class="w"> </span><span class="nl">"reason"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"action_request_validation_exception"</span><span class="p">,</span><span class="w"> </span><span class="nl">"reason"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">400</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h2 id="83-外部系统版本控制">8.3 外部系统版本控制</h2> <p>一个常见的设置是使用其它数据库作为主要的数据存储,使用 Elasticsearch 做数据检索, 这意味着主数据库的所有更改发生时都需要被复制到 Elasticsearch ,如果多个进程负责这一数据同步,你可能遇到类似于之前描述的并发问题。</p> <p>如果你的主数据库已经有了版本号 —— 或一个能作为版本号的字段值比如 timestamp —— 那么你就可以在 Elasticsearch 中通过增加 version_type=external 到查询字符串的方式重用这些相同的版本号, 版本号必须是大于零的整数, 且小于 9.2E+18 — 一个 Java 中 long 类型的正值。</p> <p>外部版本号的处理方式和我们之前讨论的内部版本号的处理方式有些不同,Elasticsearch 不是检查当前 _version 和请求中指定的版本号是否相同, 而是检查当前 _version 是否 小于 指定的版本号。 如果请求成功,外部的版本号作为文档的新 _version 进行存储。</p> <p>当前版本为5<img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650333867644-f3dada70-52ca-43aa-8fb5-a1162878e156.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=473&amp;id=u3d82536c&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=473&amp;originWidth=841&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=142476&amp;status=done&amp;style=none&amp;taskId=ue547d0ae-f531-4cbb-925b-47344a111e7&amp;title=&amp;width=841" alt="image.png" /></p> <p>使用version=1去查找就会报错 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650333999725-5ac1af4a-1b06-401e-be16-1bcd8d435646.png#clientId=u64207e38-56dd-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=486&amp;id=u9669e7bc&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=486&amp;originWidth=946&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=233802&amp;status=done&amp;style=none&amp;taskId=uc5bd544c-9ca7-4c98-afeb-b6551384d77&amp;title=&amp;width=946" alt="image.png" /> 外部版本号不仅在索引和删除请求是可以指定,而且在 创建 新文档时也可以指定。</p> Elasticsearch集群部署 https://muhaokang.github.io/Elasticsearch-%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2/ https://muhaokang.github.io/Elasticsearch-%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2/ Mon, 18 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 事先提醒 首先准备安装包,然后重新解压 万万不可将hadoop102节点上的 elasticsearch-7.8.0/ 分发到其他节点,这样会导致集群中只有hadoop102这一个节点,因为我们本来部署单点模式的时候用了es做了好多次案例,es会存储data 2. 解压压缩包 [mhk@hadoop102 software]$ tar -zxvf... <h1 id="1-事先提醒">1. 事先提醒</h1> <p>首先准备安装包,然后重新解压 万万不可将hadoop102节点上的 elasticsearch-7.8.0/ 分发到其他节点,这样会导致集群中只有hadoop102这一个节点,因为我们本来部署单点模式的时候用了es做了好多次案例,es会存储data</p> <h1 id="2-解压压缩包">2. 解压压缩包</h1> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 software]<span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-zxvf</span> elasticsearch-7.8.0-linux-x86_64.tar.gz <span class="nt">-C</span> /opt/module/ </code></pre></div></div> <p>分发</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 module]<span class="nv">$ </span>xsync elasticsearch-7.8.0/ </code></pre></div></div> <h1 id="3-修改配置文件">3. 修改配置文件</h1> <p>修改/opt/module/es/config/elasticsearch.yml 文件,分发文件</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#集群名称</span> <span class="na">cluster.name</span><span class="pi">:</span> <span class="s">cluster-es</span> <span class="c1">#节点名称,每个节点的名称不能重复</span> <span class="na">node.name</span><span class="pi">:</span> <span class="s">node-1</span> <span class="c1">#ip 地址,每个节点的地址不能重复</span> <span class="na">network.host</span><span class="pi">:</span> <span class="s">hadoop102</span> <span class="c1">#是不是有资格主节点</span> <span class="na">node.master</span><span class="pi">:</span> <span class="no">true</span> <span class="na">node.data</span><span class="pi">:</span> <span class="no">true</span> <span class="na">http.port</span><span class="pi">:</span> <span class="m">9200</span> <span class="c1"># head 插件需要这打开这两个配置</span> <span class="na">http.cors.allow-origin</span><span class="pi">:</span> <span class="s2">"</span><span class="s">*"</span> <span class="na">http.cors.enabled</span><span class="pi">:</span> <span class="no">true</span> <span class="na">http.max_content_length</span><span class="pi">:</span> <span class="s">200mb</span> <span class="c1">#es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master</span> <span class="na">cluster.initial_master_nodes</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">node-1"</span><span class="pi">]</span> <span class="c1">#es7.x 之后新增的配置,节点发现</span> <span class="na">discovery.seed_hosts</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">hadoop102:9300"</span><span class="pi">,</span><span class="s2">"</span><span class="s">hadoop103:9300"</span><span class="pi">,</span><span class="s2">"</span><span class="s">hadoop104:9300"</span><span class="pi">]</span> <span class="na">gateway.recover_after_nodes</span><span class="pi">:</span> <span class="m">2</span> <span class="na">network.tcp.keep_alive</span><span class="pi">:</span> <span class="no">true</span> <span class="na">network.tcp.no_delay</span><span class="pi">:</span> <span class="no">true</span> <span class="na">transport.tcp.compress</span><span class="pi">:</span> <span class="no">true</span> <span class="c1">#集群内同时启动的数据任务个数,默认是 2 个</span> <span class="na">cluster.routing.allocation.cluster_concurrent_rebalance</span><span class="pi">:</span> <span class="m">16</span> <span class="c1">#添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个</span> <span class="na">cluster.routing.allocation.node_concurrent_recoveries</span><span class="pi">:</span> <span class="m">16</span> <span class="c1">#初始化数据恢复时,并发恢复线程的个数,默认 4 个</span> <span class="na">cluster.routing.allocation.node_initial_primaries_recoveries</span><span class="pi">:</span> <span class="m">16</span> </code></pre></div></div> <p>分发后,hadoop102,hadoop103两台节点修改名称和地址</p> <p>修改/etc/security/limits.conf ,分发文件</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">*</span> <span class="err"> </span> <span class="s">soft </span><span class="err"> </span><span class="s">nofile </span><span class="err"> </span><span class="m">65536</span> <span class="err">* </span> <span class="s">hard </span><span class="err"> </span><span class="s">nofile </span><span class="err"> </span><span class="m">65536</span> </code></pre></div></div> <p>修改/etc/security/limits.d/20-nproc.conf,分发文件</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 在文件末尾中增加下面内容</span> <span class="err">*</span> <span class="err"> </span><span class="s">soft </span><span class="err"> </span><span class="s">nofile </span><span class="err"> </span><span class="m">65536</span> <span class="err">*</span> <span class="err"> </span><span class="s">hard </span><span class="err"> </span><span class="s">nofile </span><span class="err"> </span><span class="m">65536</span> <span class="err">*</span> <span class="err"> </span><span class="s">hard </span><span class="err"> </span><span class="s">nproc </span><span class="err"> </span><span class="m">4096</span> <span class="c1"># 注:* 带表 Linux 所有用户名称</span> </code></pre></div></div> <p>修改/etc/sysctl.conf</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">vm.max_map_count=655360</span> </code></pre></div></div> <p>重新加载</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">sysctl -p</span> </code></pre></div></div> <p>#</p> <h1 id="4-启动软件">4. 启动软件</h1> <p>分别在不同节点上启动 ES 软件</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">cd /opt/module/elasticsearch-7.8.0</span> <span class="c1">#启动</span> <span class="s">bin/elasticsearch</span> <span class="c1">#后台启动</span> <span class="s">bin/elasticsearch -d</span> </code></pre></div></div> <h1 id="5-测试集群">5. 测试集群</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650243878054-0039055f-bd88-4d08-8b2c-7687ec90c273.png#clientId=u460153c8-e799-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=447&amp;id=u817cff25&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=447&amp;originWidth=855&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=102908&amp;status=done&amp;style=none&amp;taskId=u63a951be-61d3-4bb8-8950-10f9933b849&amp;title=&amp;width=855" alt="image.png" /></p> Elasticsearch JavaAPI操作 https://muhaokang.github.io/Elasticsearch-JavaAPI%E6%93%8D%E4%BD%9C/ https://muhaokang.github.io/Elasticsearch-JavaAPI%E6%93%8D%E4%BD%9C/ Mon, 18 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 创建Maven项目,添加依赖 &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;org.elasticsearch&lt;/groupId&gt; &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt; &lt;version&gt;7.8.... <h1 id="1-创建maven项目添加依赖">1. 创建Maven项目,添加依赖</h1> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependencies&gt;</span> <span class="nt">&lt;dependency&gt;</span> <span class="nt">&lt;groupId&gt;</span>org.elasticsearch<span class="nt">&lt;/groupId&gt;</span> <span class="nt">&lt;artifactId&gt;</span>elasticsearch<span class="nt">&lt;/artifactId&gt;</span> <span class="nt">&lt;version&gt;</span>7.8.0<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;/dependency&gt;</span> <span class="c">&lt;!-- elasticsearch 的客户端 --&gt;</span> <span class="nt">&lt;dependency&gt;</span> <span class="nt">&lt;groupId&gt;</span>org.elasticsearch.client<span class="nt">&lt;/groupId&gt;</span> <span class="nt">&lt;artifactId&gt;</span>elasticsearch-rest-high-level-client<span class="nt">&lt;/artifactId&gt;</span> <span class="nt">&lt;version&gt;</span>7.8.0<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;/dependency&gt;</span> <span class="c">&lt;!-- elasticsearch 依赖 2.x 的 log4j --&gt;</span> <span class="nt">&lt;dependency&gt;</span> <span class="nt">&lt;groupId&gt;</span>org.apache.logging.log4j<span class="nt">&lt;/groupId&gt;</span> <span class="nt">&lt;artifactId&gt;</span>log4j-api<span class="nt">&lt;/artifactId&gt;</span> <span class="nt">&lt;version&gt;</span>2.8.2<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;/dependency&gt;</span> <span class="nt">&lt;dependency&gt;</span> <span class="nt">&lt;groupId&gt;</span>org.apache.logging.log4j<span class="nt">&lt;/groupId&gt;</span> <span class="nt">&lt;artifactId&gt;</span>log4j-core<span class="nt">&lt;/artifactId&gt;</span> <span class="nt">&lt;version&gt;</span>2.8.2<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;/dependency&gt;</span> <span class="nt">&lt;dependency&gt;</span> <span class="nt">&lt;groupId&gt;</span>com.fasterxml.jackson.core<span class="nt">&lt;/groupId&gt;</span> <span class="nt">&lt;artifactId&gt;</span>jackson-databind<span class="nt">&lt;/artifactId&gt;</span> <span class="nt">&lt;version&gt;</span>2.9.9<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;/dependency&gt;</span> <span class="c">&lt;!-- junit 单元测试 --&gt;</span> <span class="nt">&lt;dependency&gt;</span> <span class="nt">&lt;groupId&gt;</span>junit<span class="nt">&lt;/groupId&gt;</span> <span class="nt">&lt;artifactId&gt;</span>junit<span class="nt">&lt;/artifactId&gt;</span> <span class="nt">&lt;version&gt;</span>4.12<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;/dependency&gt;</span> <span class="nt">&lt;/dependencies&gt;</span> </code></pre></div></div> <h1 id="2-创建客户端对象">2. 创建客户端对象</h1> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Client</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <h1 id="3-索引操作">3. 索引操作</h1> <h2 id="31-创建索引">3.1 创建索引</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Index_Create</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//创建索引</span> <span class="nc">CreateIndexRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CreateIndexRequest</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="nc">CreateIndexResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">indices</span><span class="o">().</span><span class="na">create</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="c1">//响应状态</span> <span class="kt">boolean</span> <span class="n">acknowledged</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">isAcknowledged</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"索引操作:"</span> <span class="o">+</span> <span class="n">acknowledged</span><span class="o">);</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650183816531-d2cc52ac-6661-4351-b2c2-079af13266f3.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=152&amp;id=u9a5210b3&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=152&amp;originWidth=262&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=30537&amp;status=done&amp;style=none&amp;taskId=u1afff7e4-f4b6-4068-99db-f069e6be1c4&amp;title=&amp;width=262" alt="image.png" /> 前往postman查看有没有创建成功 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650183895823-f46ba04b-f6d8-4f00-82b7-0367a5cd451d.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=466&amp;id=u1b727264&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=466&amp;originWidth=857&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=130301&amp;status=done&amp;style=none&amp;taskId=u9c153486-06c5-4244-8165-81085eaa4eb&amp;title=&amp;width=857" alt="image.png" /></p> <h2 id="32-查看索引">3.2 查看索引</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Index_Search</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//查询索引</span> <span class="nc">GetIndexRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GetIndexRequest</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="nc">GetIndexResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">indices</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="c1">//响应状态</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getAliases</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getMappings</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getSettings</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650184254886-3ad437b8-5069-4fef-bd2f-bb7a1f4f9189.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=156&amp;id=u7f7cdcc3&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=156&amp;originWidth=1364&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=55130&amp;status=done&amp;style=none&amp;taskId=u38223c33-2f8c-435f-9258-afb96b31195&amp;title=&amp;width=1364" alt="image.png" /></p> <h2 id="33-删除索引">3.3 删除索引</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Index_Delete</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//查询索引</span> <span class="nc">DeleteIndexRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DeleteIndexRequest</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="nc">AcknowledgedResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">indices</span><span class="o">().</span><span class="na">delete</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="c1">//响应状态</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">isAcknowledged</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>前往postman确认 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650184528596-c296d47d-100f-4033-a30c-ea8e54a4759e.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=432&amp;id=u1c0e6314&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=432&amp;originWidth=857&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=110333&amp;status=done&amp;style=none&amp;taskId=ufcc411fa-3a61-43d7-ac6f-f0764003376&amp;title=&amp;width=857" alt="image.png" /> 已经被删除</p> <h1 id="4-文档操作">4. 文档操作</h1> <h2 id="41-新增文档">4.1 新增文档</h2> <p>创建数据模型</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Users</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">sex</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">Integer</span> <span class="n">age</span><span class="o">;</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">name</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getSex</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">sex</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setSex</span><span class="o">(</span><span class="nc">String</span> <span class="n">sex</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">sex</span> <span class="o">=</span> <span class="n">sex</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">getAge</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">age</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAge</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Insert</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//插入数据</span> <span class="nc">IndexRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">IndexRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1001"</span><span class="o">);</span> <span class="nc">Users</span> <span class="n">users</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Users</span><span class="o">();</span> <span class="n">users</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="s">"zhangsan"</span><span class="o">);</span> <span class="n">users</span><span class="o">.</span><span class="na">setAge</span><span class="o">(</span><span class="mi">22</span><span class="o">);</span> <span class="n">users</span><span class="o">.</span><span class="na">setSex</span><span class="o">(</span><span class="s">"男"</span><span class="o">);</span> <span class="c1">//向ES插入数据,必须将数据转换成JSON格式</span> <span class="nc">ObjectMapper</span> <span class="n">mapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ObjectMapper</span><span class="o">();</span> <span class="nc">String</span> <span class="n">usersJson</span> <span class="o">=</span> <span class="n">mapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="n">users</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">usersJson</span><span class="o">,</span> <span class="nc">XContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">);</span> <span class="nc">IndexResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">index</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getResult</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650185316904-3c444319-f8f0-49c0-86e1-e96e9133a98f.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=106&amp;id=ud9f38235&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=106&amp;originWidth=254&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=19218&amp;status=done&amp;style=none&amp;taskId=uebf654c0-c56e-4a0d-bb21-05ac8ead076&amp;title=&amp;width=254" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650185379566-551d1112-a830-4350-ae3f-d53c785399f1.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=609&amp;id=u8c7918ee&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=609&amp;originWidth=848&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=145223&amp;status=done&amp;style=none&amp;taskId=ueceebec0-ad4a-4bd2-b11f-3a5daf37341&amp;title=&amp;width=848" alt="image.png" /></p> <h2 id="42-修改文档">4.2 修改文档</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Update</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//修改数据</span> <span class="nc">UpdateRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UpdateRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1001"</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">doc</span><span class="o">(</span><span class="nc">XContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">,</span><span class="s">"name"</span><span class="o">,</span><span class="s">"李四"</span><span class="o">);</span> <span class="nc">UpdateResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">update</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getResult</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650185689525-582bd844-0e06-44ad-b9b2-61b77470f095.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=140&amp;id=u672201ec&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=140&amp;originWidth=270&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=35133&amp;status=done&amp;style=none&amp;taskId=ucef688e9-0cf3-4208-94c7-38919469772&amp;title=&amp;width=270" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650185717490-d1e3ad06-ddc5-44d4-8f78-5ade462964bf.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=614&amp;id=u52119739&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=614&amp;originWidth=892&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=151461&amp;status=done&amp;style=none&amp;taskId=u29622791-c247-4372-a8e1-b45444d0c51&amp;title=&amp;width=892" alt="image.png" /></p> <h2 id="43-查询文档">4.3 查询文档</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Get</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//查询数据</span> <span class="nc">GetRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GetRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1001"</span><span class="o">);</span> <span class="nc">GetResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650186106311-cbe9950f-66c9-4acb-8be6-5ad0f23582d1.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=128&amp;id=u49e995bb&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=128&amp;originWidth=317&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=26659&amp;status=done&amp;style=none&amp;taskId=uaf9b2f6d-5e98-4af4-bf5e-62bee0273e9&amp;title=&amp;width=317" alt="image.png" /></p> <h2 id="44-删除文档">4.4 删除文档</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Delete</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="nc">DeleteRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DeleteRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1001"</span><span class="o">);</span> <span class="nc">DeleteResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">delete</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650186304855-39fecc60-91f2-4c20-85dd-ef070855df21.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=158&amp;id=u4817785f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=158&amp;originWidth=1077&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=43808&amp;status=done&amp;style=none&amp;taskId=uae403df3-f58a-4b36-8763-a5187f85c81&amp;title=&amp;width=1077" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650186617326-60f806ce-57bc-4e1a-9162-cc8daa6c738e.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=488&amp;id=u714d4c9a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=488&amp;originWidth=855&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=106668&amp;status=done&amp;style=none&amp;taskId=u36f90e11-69bb-45b7-a813-26d0df649ca&amp;title=&amp;width=855" alt="image.png" /></p> <h2 id="45-批量新增">4.5 批量新增</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Insert_Batch</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//批量插入数据</span> <span class="nc">BulkRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BulkRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">IndexRequest</span><span class="o">().</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1001"</span><span class="o">).</span><span class="na">source</span><span class="o">(</span><span class="nc">XContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">,</span><span class="s">"name"</span><span class="o">,</span><span class="s">"张三"</span><span class="o">));</span> <span class="n">request</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">IndexRequest</span><span class="o">().</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1002"</span><span class="o">).</span><span class="na">source</span><span class="o">(</span><span class="nc">XContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">,</span><span class="s">"name"</span><span class="o">,</span><span class="s">"李四"</span><span class="o">));</span> <span class="n">request</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">IndexRequest</span><span class="o">().</span><span class="na">index</span><span class="o">(</span><span class="s">"users"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1003"</span><span class="o">).</span><span class="na">source</span><span class="o">(</span><span class="nc">XContentType</span><span class="o">.</span><span class="na">JSON</span><span class="o">,</span><span class="s">"name"</span><span class="o">,</span><span class="s">"王五"</span><span class="o">));</span> <span class="nc">BulkResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">bulk</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getItems</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650186873748-5ef31e90-643b-46c2-ad6a-dd572158ece9.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=168&amp;id=u77c1ebab&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=168&amp;originWidth=538&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=43423&amp;status=done&amp;style=none&amp;taskId=uaed466f9-f537-4ff2-a852-ae2d4c22981&amp;title=&amp;width=538" alt="image.png" /></p> <h2 id="46-批量删除">4.6 批量删除</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Delete_Batch</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//批量删除数据</span> <span class="nc">BulkRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BulkRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">DeleteRequest</span><span class="o">().</span><span class="na">index</span><span class="o">(</span><span class="s">"user"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1001"</span><span class="o">));</span> <span class="n">request</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">DeleteRequest</span><span class="o">().</span><span class="na">index</span><span class="o">(</span><span class="s">"user"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1002"</span><span class="o">));</span> <span class="n">request</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">DeleteRequest</span><span class="o">().</span><span class="na">index</span><span class="o">(</span><span class="s">"user"</span><span class="o">).</span><span class="na">id</span><span class="o">(</span><span class="s">"1003"</span><span class="o">));</span> <span class="nc">BulkResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">bulk</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getItems</span><span class="o">());</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650187801034-f6ee3f41-e6e1-4479-bc5c-1e5e0d557b89.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=157&amp;id=u4aa7bdc7&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=157&amp;originWidth=521&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=41566&amp;status=done&amp;style=none&amp;taskId=uc74f95dc-3e5b-4d6b-8904-b95c252c352&amp;title=&amp;width=521" alt="image.png" /></p> <h2 id="47-全量查询">4.7 全量查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ESTest_Doc_Query</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="c1">//创建ES客户端</span> <span class="nc">RestHighLevelClient</span> <span class="n">esClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RestHighLevelClient</span><span class="o">(</span> <span class="nc">RestClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpHost</span><span class="o">(</span><span class="s">"hadoop102"</span><span class="o">,</span><span class="mi">9200</span><span class="o">))</span> <span class="o">);</span> <span class="c1">//全量查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">().</span><span class="na">query</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">matchAllQuery</span><span class="o">()));</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> <span class="c1">//关闭ES客户端</span> <span class="n">esClient</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650189384212-4e018894-bb73-4bc3-b695-a807928d31bf.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=242&amp;id=u76598ca5&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=242&amp;originWidth=441&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=67473&amp;status=done&amp;style=none&amp;taskId=u9901cc81-9fb5-4831-90b4-1cf5dc5ce2a&amp;title=&amp;width=441" alt="image.png" /></p> <h2 id="48-条件查询">4.8 条件查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件termQuery</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">().</span><span class="na">query</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">termQuery</span><span class="o">(</span><span class="s">"age"</span><span class="o">,</span><span class="mi">30</span><span class="o">)));</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650194676495-fb444355-e4d0-4573-ab6b-fe34bb57d122.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=147&amp;id=u8103eab7&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=147&amp;originWidth=344&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=35204&amp;status=done&amp;style=none&amp;taskId=u28111a02-7224-44bb-900f-bc1ad8a5414&amp;title=&amp;width=344" alt="image.png" /></p> <h2 id="49-分页查询">4.9 分页查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">().</span><span class="na">query</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">matchAllQuery</span><span class="o">());</span> <span class="c1">//(当前页码-1)* 每页显示的条数</span> <span class="n">builder</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="n">builder</span><span class="o">.</span><span class="na">size</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span> <span class="c1">//每页两条数据</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650194954705-7407ac78-7fc5-41ef-bbc1-2e843ce69c29.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=147&amp;id=u5d62caa0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=147&amp;originWidth=335&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=35969&amp;status=done&amp;style=none&amp;taskId=ucd05ecfb-3593-4262-a835-851239551c4&amp;title=&amp;width=335" alt="image.png" /></p> <h2 id="410-查询排序">4.10 查询排序</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">().</span><span class="na">query</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">matchAllQuery</span><span class="o">());</span> <span class="n">builder</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="s">"age"</span><span class="o">,</span> <span class="nc">SortOrder</span><span class="o">.</span><span class="na">DESC</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650195272387-9b7b2db6-15d0-4692-a6e6-7fab9e3dee75.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=228&amp;id=u8b0a405e&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=228&amp;originWidth=428&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=65523&amp;status=done&amp;style=none&amp;taskId=u0d773968-bdb8-49aa-9b40-aa55b6b1f0e&amp;title=&amp;width=428" alt="image.png" /></p> <h2 id="411-过滤字段">4.11 过滤字段</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">().</span><span class="na">query</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">matchAllQuery</span><span class="o">());</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">excludes</span> <span class="o">=</span> <span class="o">{};</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">includes</span> <span class="o">=</span> <span class="o">{</span><span class="s">"name"</span><span class="o">};</span> <span class="n">builder</span><span class="o">.</span><span class="na">fetchSource</span><span class="o">(</span><span class="n">includes</span><span class="o">,</span><span class="n">excludes</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650195509585-e8c06695-9c3e-49c6-b237-0a1d98c719ce.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=226&amp;id=ub3fa1fce&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=226&amp;originWidth=332&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=49881&amp;status=done&amp;style=none&amp;taskId=u604bb4b0-a6fe-44cb-a6d2-9a7383e01bc&amp;title=&amp;width=332" alt="image.png" /></p> <h2 id="411-组合查询">4.11 组合查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//6. 组合查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">();</span> <span class="nc">BoolQueryBuilder</span> <span class="n">boolQueryBuilder</span> <span class="o">=</span> <span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">boolQuery</span><span class="o">();</span> <span class="c1">// boolQueryBuilder.must(QueryBuilders.matchQuery("age",30));</span> <span class="c1">// boolQueryBuilder.must(QueryBuilders.matchQuery("sex","男"));</span> <span class="c1">// boolQueryBuilder.mustNot(QueryBuilders.matchQuery("sex","男"));</span> <span class="n">boolQueryBuilder</span><span class="o">.</span><span class="na">should</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">matchQuery</span><span class="o">(</span><span class="s">"age"</span><span class="o">,</span><span class="mi">30</span><span class="o">));</span> <span class="n">boolQueryBuilder</span><span class="o">.</span><span class="na">should</span><span class="o">(</span><span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">matchQuery</span><span class="o">(</span><span class="s">"age"</span><span class="o">,</span><span class="mi">40</span><span class="o">));</span> <span class="n">builder</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="n">boolQueryBuilder</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650203258060-224e5d64-dd19-4ced-9b07-29416b4c985c.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=216&amp;id=u559fd42f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=216&amp;originWidth=368&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=60729&amp;status=done&amp;style=none&amp;taskId=u2d312d52-b9ca-449e-aab8-d6113099de8&amp;title=&amp;width=368" alt="image.png" /></p> <h2 id="412-范围查询">4.12 范围查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//7. 范围查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">();</span> <span class="nc">RangeQueryBuilder</span> <span class="n">rangeQuery</span> <span class="o">=</span> <span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">rangeQuery</span><span class="o">(</span><span class="s">"age"</span><span class="o">);</span> <span class="n">rangeQuery</span><span class="o">.</span><span class="na">gte</span><span class="o">(</span><span class="mi">30</span><span class="o">);</span> <span class="n">rangeQuery</span><span class="o">.</span><span class="na">lte</span><span class="o">(</span><span class="mi">40</span><span class="o">);</span> <span class="n">builder</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="n">rangeQuery</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650203466989-c8596755-efa6-481d-be49-5c66cc331000.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=253&amp;id=ufc39ad6d&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=253&amp;originWidth=383&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=67284&amp;status=done&amp;style=none&amp;taskId=u8265ac81-1c76-43ca-9bc4-e0fb2dc2417&amp;title=&amp;width=383" alt="image.png" /></p> <h2 id="413-模糊查询">4.13 模糊查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//7. 模糊查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">();</span> <span class="nc">FuzzyQueryBuilder</span> <span class="n">fuzziness</span> <span class="o">=</span> <span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">fuzzyQuery</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="s">"王五"</span><span class="o">).</span><span class="na">fuzziness</span><span class="o">(</span><span class="nc">Fuzziness</span><span class="o">.</span><span class="na">ONE</span><span class="o">);</span><span class="c1">//差一个字符</span> <span class="n">builder</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="n">fuzziness</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650203904064-4fd2c21c-e001-4127-9a97-7ad15ed84113.png#clientId=u07a2aa4f-bdc4-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=184&amp;id=uda951b44&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=184&amp;originWidth=355&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=48437&amp;status=done&amp;style=none&amp;taskId=u48ff9f02-ec88-487e-a55b-7fafdb9947a&amp;title=&amp;width=355" alt="image.png" /></p> <h2 id="414-高亮查询">4.14 高亮查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//8. 高亮查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">();</span> <span class="nc">TermQueryBuilder</span> <span class="n">termQueryBuilder</span> <span class="o">=</span> <span class="nc">QueryBuilders</span><span class="o">.</span><span class="na">termQuery</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="s">"张三"</span><span class="o">);</span> <span class="nc">HighlightBuilder</span> <span class="n">highlightBuilder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HighlightBuilder</span><span class="o">();</span> <span class="n">highlightBuilder</span><span class="o">.</span><span class="na">preTags</span><span class="o">(</span><span class="s">"&lt;font color ='red'&gt;"</span><span class="o">);</span> <span class="n">highlightBuilder</span><span class="o">.</span><span class="na">postTags</span><span class="o">(</span><span class="s">"&lt;/font&gt;"</span><span class="o">);</span> <span class="n">highlightBuilder</span><span class="o">.</span><span class="na">field</span><span class="o">(</span><span class="s">"name"</span><span class="o">);</span> <span class="n">builder</span><span class="o">.</span><span class="na">highlighter</span><span class="o">(</span><span class="n">highlightBuilder</span><span class="o">);</span> <span class="n">builder</span><span class="o">.</span><span class="na">query</span><span class="o">(</span><span class="n">termQueryBuilder</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <h2 id="415-聚合查询">4.15 聚合查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//9. 聚合查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">();</span> <span class="nc">AggregationBuilder</span> <span class="n">aggregationBuilder</span> <span class="o">=</span> <span class="nc">AggregationBuilders</span><span class="o">.</span><span class="na">max</span><span class="o">(</span><span class="s">"maxAge"</span><span class="o">).</span><span class="na">field</span><span class="o">(</span><span class="s">"age"</span><span class="o">);</span> <span class="n">builder</span><span class="o">.</span><span class="na">aggregation</span><span class="o">(</span><span class="n">aggregationBuilder</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <h2 id="416-分组查询">4.16 分组查询</h2> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">//10. 分组查询</span> <span class="nc">SearchRequest</span> <span class="n">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchRequest</span><span class="o">();</span> <span class="n">request</span><span class="o">.</span><span class="na">indices</span><span class="o">(</span><span class="s">"users"</span><span class="o">);</span> <span class="c1">//构造查询条件</span> <span class="nc">SearchSourceBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchSourceBuilder</span><span class="o">();</span> <span class="nc">AggregationBuilder</span> <span class="n">aggregationBuilder</span> <span class="o">=</span> <span class="nc">AggregationBuilders</span><span class="o">.</span><span class="na">terms</span><span class="o">(</span><span class="s">"ageGroup"</span><span class="o">).</span><span class="na">field</span><span class="o">(</span><span class="s">"age"</span><span class="o">);</span> <span class="n">builder</span><span class="o">.</span><span class="na">aggregation</span><span class="o">(</span><span class="n">aggregationBuilder</span><span class="o">);</span> <span class="n">request</span><span class="o">.</span><span class="na">source</span><span class="o">(</span><span class="n">builder</span><span class="o">);</span> <span class="nc">SearchResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">esClient</span><span class="o">.</span><span class="na">search</span><span class="o">(</span><span class="n">request</span><span class="o">,</span> <span class="nc">RequestOptions</span><span class="o">.</span><span class="na">DEFAULT</span><span class="o">);</span> <span class="nc">SearchHits</span> <span class="n">hits</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getHits</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">getTook</span><span class="o">());</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hits</span><span class="o">.</span><span class="na">getTotalHits</span><span class="o">());</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SearchHit</span> <span class="n">hit</span> <span class="o">:</span> <span class="n">hits</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">hit</span><span class="o">.</span><span class="na">getSourceAsString</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> Elasticsearch安装部署 https://muhaokang.github.io/Elasticsearch-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/ https://muhaokang.github.io/Elasticsearch-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/ Sun, 17 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 下载软件 Elasticsearch 的官方地址:https://www.elastic.co/cn/ 我选择 7.8.0 版本 2. 安装软件 2.1 将软件从本地上传到Linux [mhk@hadoop102 software]$ ll -rw-rw-r--. 1 mhk mhk 319112561 3月 16 16:22 elasticse... <h1 id="1-下载软件">1. 下载软件</h1> <p>Elasticsearch 的官方地址:https://www.elastic.co/cn/ <br /> 我选择 7.8.0 版本 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650104297306-9d9763e3-68c5-4ed5-8ad5-7d5bb0e00abc.png#clientId=ue5a300bb-fb59-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=237&amp;id=uf5902fd6&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=237&amp;originWidth=948&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=62248&amp;status=done&amp;style=none&amp;taskId=u01cae41f-05ff-402d-b0e1-3618d575b74&amp;title=&amp;width=948" alt="image.png" /></p> <h1 id="2-安装软件">2. 安装软件</h1> <h2 id="21-将软件从本地上传到linux">2.1 将软件从本地上传到Linux</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 software]<span class="nv">$ </span>ll <span class="nt">-rw-rw-r--</span><span class="nb">.</span> 1 mhk mhk 319112561 3月 16 16:22 elasticsearch-7.8.0-linux-x86_64.tar.gz </code></pre></div></div> <h2 id="22-解压缩到指定路径">2.2 解压缩到指定路径</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 software]<span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-zxvf</span> elasticsearch-7.8.0-linux-x86_64.tar.gz <span class="nt">-C</span> /opt/module/ </code></pre></div></div> <h2 id="23-运行bin目录下的启动脚本">2.3 运行bin目录下的启动脚本</h2> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 elasticsearch-7.8.0]<span class="nv">$ </span>bin/elasticsearch ... <span class="o">[</span>2022-04-16T16:36:09,824][INFO <span class="o">][</span>o.e.h.AbstractHttpServerTransport] <span class="o">[</span>hadoop102] publish_address <span class="o">{</span>127.0.0.1:9200<span class="o">}</span>, bound_addresses <span class="o">{[</span>::1]:9200<span class="o">}</span>, <span class="o">{</span>127.0.0.1:9200<span class="o">}</span> <span class="o">[</span>2022-04-16T16:36:09,826][INFO <span class="o">][</span>o.e.n.Node <span class="o">]</span> <span class="o">[</span>hadoop102] started <span class="o">[</span>2022-04-16T16:36:11,078][INFO <span class="o">][</span>o.e.l.LicenseService <span class="o">]</span> <span class="o">[</span>hadoop102] license <span class="o">[</span>7ee9cff5-bb62-4949-b1b9-86535854a73e] mode <span class="o">[</span>basic] - valid <span class="o">[</span>2022-04-16T16:36:11,081][INFO <span class="o">][</span>o.e.x.s.s.SecurityStatusChangeListener] <span class="o">[</span>hadoop102] Active license is now <span class="o">[</span>BASIC]<span class="p">;</span> Security is disabled <span class="o">[</span>2022-04-16T16:36:11,391][INFO <span class="o">][</span>o.e.g.GatewayService <span class="o">]</span> <span class="o">[</span>hadoop102] recovered <span class="o">[</span>0] indices into cluster_state </code></pre></div></div> <h2 id="24-配置远程访问">2.4 配置远程访问</h2> <p>打开 config/ 目录下的 elasticsearch.yml,修改配置</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 elasticsearch-7.8.0]<span class="nv">$ </span><span class="nb">cd </span>config/ <span class="o">[</span>mhk@hadoop102 config]<span class="nv">$ </span>ll 总用量 40 <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 199 4月 16 16:24 elasticsearch.keystore <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 2813 4月 16 17:16 elasticsearch.yml <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 2301 6月 15 2020 jvm.options drwxr-x---. 2 mhk mhk 6 6月 15 2020 jvm.options.d <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 17419 6月 15 2020 log4j2.properties <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 473 6月 15 2020 role_mapping.yml <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 197 6月 15 2020 roles.yml <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 0 6月 15 2020 <span class="nb">users</span> <span class="nt">-rw-rw----</span><span class="nb">.</span> 1 mhk mhk 0 6月 15 2020 users_roles <span class="o">[</span>mhk@hadoop102 config]<span class="nv">$ </span>vim elasticsearch.yml network.host: 0.0.0.0 </code></pre></div></div> <p>再次运行启动脚本,发现报错</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: <span class="o">[</span>2] bootstrap checks failed <span class="o">[</span>1]: max file descriptors <span class="o">[</span>4096] <span class="k">for </span>elasticsearch process is too low, increase to at least <span class="o">[</span>65535] <span class="o">[</span>2]: max virtual memory areas vm.max_map_count <span class="o">[</span>65530] is too low, increase to at least <span class="o">[</span>262144] <span class="o">[</span>3]: the default discovery settings are unsuitable <span class="k">for </span>production use<span class="p">;</span> at least one of <span class="o">[</span>discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured ERROR: Elasticsearch did not <span class="nb">exit </span>normally - check the logs at /opt/module/elasticsearch-7.8.0/logs/elasticsearch.log </code></pre></div></div> <p>针对这三点,逐个击破 <strong>1、Linux系统的soft、hard值配置过低,至少65535</strong></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim /etc/security/limits.conf <span class="c">#* soft core 0</span> <span class="c">#* hard rss 10000</span> <span class="k">*</span> soft nofile 65535 <span class="k">*</span> hard nofile 65535 </code></pre></div></div> <p>建议重启虚拟机</p> <p><strong>2、Linux系统vm.max_map_count值配置过低,至少262144</strong></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim /etc/sysctl.conf vm.max_map_count<span class="o">=</span>262144 <span class="c">#sysctl -p 生效</span> <span class="o">[</span>mhk@hadoop102 etc]<span class="nv">$ </span>sysctl <span class="nt">-p</span> vm.max_map_count <span class="o">=</span> 262144 </code></pre></div></div> <p><strong>3、缺少默认配置</strong> 至少需要配置 discovery.seed_hosts、discovery.seed_providers、cluster.initial_master_nodes 中的一个参数.</p> <ul> <li>discovery.seed_hosts:  集群主机列表</li> <li>discovery.seed_providers: 基于配置文件配置集群主机列表</li> <li>cluster.initial_master_nodes: 启动时初始化的参与选主的node,生产环境必填 ```shell vim config/elasticsearch.yml</li> </ul> <p>#添加配置 discovery.seed_hosts: [“hadoop102”]</p> <p>cluster.initial_master_nodes: [“hadoop102”]</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## 2.5 重启软件,访问web界面 [http://10.211.55.11:9200/](http://10.211.55.11:9200/) ```shell [mhk@hadoop102 elasticsearch-7.8.0]$ bin/elasticsearch ... [2022-04-16T17:46:23,701][INFO ][o.e.h.AbstractHttpServerTransport] [hadoop102] publish_address {10.211.55.11:9200}, bound_addresses {[::]:9200} [2022-04-16T17:46:23,725][INFO ][o.e.n.Node ] [hadoop102] started [2022-04-16T17:46:25,348][INFO ][o.e.l.LicenseService ] [hadoop102] license [7ee9cff5-bb62-4949-b1b9-86535854a73e] mode [basic] - valid [2022-04-16T17:46:25,352][INFO ][o.e.x.s.s.SecurityStatusChangeListener] [hadoop102] Active license is now [BASIC]; Security is disabled [2022-04-16T17:46:25,496][INFO ][o.e.g.GatewayService ] [hadoop102] recovered [0] indices into cluster_state </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/25452040/1650105666436-348a2353-ebc1-4d33-9208-0bd4e31d51f2.jpeg#clientId=ue5a300bb-fb59-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=347&amp;id=u4d6e7bef&amp;margin=%5Bobject%20Object%5D&amp;name=D6AC1022D571B06B1EF0514B661464AA.jpg&amp;originHeight=347&amp;originWidth=573&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=61398&amp;status=done&amp;style=none&amp;taskId=u20db2931-a53b-47a4-b49f-89190919777&amp;title=&amp;width=573" alt="D6AC1022D571B06B1EF0514B661464AA.jpg" /></p> Elasticsearch基本操作 https://muhaokang.github.io/Elasticsearch-%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/ https://muhaokang.github.io/Elasticsearch-%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/ Sun, 17 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1.**RESTful ** REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可... <h1 id="1restful-">1.**RESTful **</h1> <p>REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。</p> <p>在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。</p> <p>在 REST 样式的 Web 服务中,每个资源都有一个地址。资源本身都是方法调用的目标,方法列表对所有资源都是一样的。这些方法都是标准方法,包括 HTTP GET、POST、PUT、DELETE,还可能包括 HEAD 和 OPTIONS。简单的理解就是,如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以及对资源进行的操作(增删改查)。</p> <h1 id="2-客户端安装">2. 客户端安装</h1> <p>如果直接通过浏览器向 Elasticsearch 服务器发请求,那么需要在发送的请求中包含HTTP 标准的方法,而 HTTP 的大部分特性且仅支持 GET 和 POST 方法。所以为了能方便地进行客户端的访问,可以使用 Postman 软件</p> <p>Postman 是一款强大的网页调试工具,提供功能强大的 Web API 和 HTTP 请求调试。软件功能强大,界面简洁明晰、操作方便快捷,设计得很人性化。Postman 中文版能够发送任何类型的 HTTP 请求 (GET, HEAD, POST, PUT..),不仅能够表单提交,且可以附带任意类型请求体。</p> <p>Postman 官网:https://www.getpostman.com Postman 下载:https://www.getpostman.com/apps</p> <h1 id="3-数据格式">3. 数据格式</h1> <p>Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650106282634-8c401896-25e7-41a2-83c2-e9f15348456e.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=226&amp;id=uf1b6a949&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=226&amp;originWidth=808&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=93303&amp;status=done&amp;style=none&amp;taskId=u0c8da7d8-60d8-40c2-bf4a-92846268d2f&amp;title=&amp;width=808" alt="image.png" /> ES 里的 Index 可以看做一个库,而 Types 相当于表,Documents 则相当于表的行。 这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个 type,Elasticsearch 7.X 中, Type 的概念已经被删除了。 6 用 JSON 作为文档序列化的格式,比如一条用户信息:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">{</span> <span class="s2">"name"</span> : <span class="s2">"John"</span>, <span class="s2">"sex"</span> : <span class="s2">"Male"</span>, <span class="s2">"age"</span> : 25, <span class="s2">"birthDate"</span>: <span class="s2">"1990/05/01"</span>, <span class="s2">"about"</span> : <span class="s2">"I love to go rock climbing"</span>, <span class="s2">"interests"</span>: <span class="o">[</span> <span class="s2">"sports"</span>, <span class="s2">"music"</span> <span class="o">]</span> <span class="o">}</span> </code></pre></div></div> <h1 id="4-http-操作">4. HTTP 操作</h1> <h2 id="41-索引操作">4.1 索引操作</h2> <h3 id="1-创建索引">1) 创建索引</h3> <p>对比关系型数据库,创建索引就等同于创建数据库 在 Postman 中,向 ES 服务器发 **PUT **请求 :<a href="http://10.211.55.11:9200/shopping">http://10.211.55.11:9200/shopping</a> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650108031938-eeec96bc-4ba2-4266-bfaa-3e91839f7282.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=608&amp;id=uf94f184a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=608&amp;originWidth=861&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=118802&amp;status=done&amp;style=none&amp;taskId=ueed6472c-3cb9-458c-a3b0-de6d4f988cb&amp;title=&amp;width=861" alt="image.png" /></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">{</span> <span class="s2">"acknowledged"</span>【响应结果】: <span class="nb">true</span>, <span class="c"># true 操作成功</span> <span class="s2">"shards_acknowledged"</span>【分片结果】: <span class="nb">true</span>, <span class="c"># 分片操作成功</span> <span class="s2">"index"</span>【索引名称】: <span class="s2">"shopping"</span> <span class="o">}</span> <span class="c"># 注意:创建索引库的分片数默认 1 片,在 7.0.0 之前的 Elasticsearch 版本中,默认 5 片</span> </code></pre></div></div> <p>如果重复添加索引,会返回错误信息 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650108498721-9b38ddef-f590-42a0-83e0-23059b6b8352.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=277&amp;id=u47f0b2f8&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=277&amp;originWidth=766&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=144491&amp;status=done&amp;style=none&amp;taskId=u694c9aee-46c7-4576-a153-156043d8e27&amp;title=&amp;width=766" alt="image.png" /></p> <h3 id="2-查看所有索引">2) 查看所有索引</h3> <p>在 Postman 中,向 ES 服务器发 **GET **请求 :<a href="http://10.211.55.11:9200/_cat/indices?v">http://10.211.55.11:9200/_cat/indices?v</a></p> <p>这里请求路径中的_cat 表示查看的意思,indices 表示索引,所以整体含义就是查看当前 ES 服务器中的所有索引,就好像 MySQL 中的 show tables 的感觉,服务器响应结果如下 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650108641911-fafc2b7b-d50e-4c14-b07e-12664c6416e8.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=495&amp;id=u23a7bb97&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=495&amp;originWidth=850&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=101513&amp;status=done&amp;style=none&amp;taskId=u79ed1a4f-e52b-4458-9dcb-45a6453a657&amp;title=&amp;width=850" alt="image.png" /></p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>health status index uuid pri rep docs.count docs.deleted store.size pri.store.size yellow open shopping ohQY_Mb0Rm2UG-Z9J0ZhMg 1 1 0 0 208b 208b </code></pre></div></div> <p>| health | 当前服务器健康状态:<strong>green</strong>(集群完整) <strong>yellow</strong>(单点正常、集群不完整) red(单点不正常) | | — | — | | status | 索引打开、关闭状态 | | index | 索引名 | | uuid | 索引统一编号 | | pri | 主分片数量 | | rep | 副本数量 | | docs.count | 可用文档数量 | | docs.deleted | 文档删除状态(逻辑删除) | | store.size | 主分片和副分片整体占空间大小 | | pri.store.size | 主分片占空间大小 |</p> <h3 id="3-查看单个索引">3) 查看单个索引</h3> <p>在 Postman 中,向 ES 服务器发 **GET **请求 : <a href="http://10.211.55.11:9200/shopping">http://10.211.55.11:9200/shopping</a></p> <p>查看索引向 ES 服务器发送的请求路径和创建索引是一致的。但是 HTTP 方法不一致。这里可以体会一下 RESTful 的意义, 请求后,服务器响应结果如下: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650109165674-a1c66990-a7fc-48ae-9717-7464764f90ed.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=614&amp;id=ue4bbe693&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=614&amp;originWidth=881&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=145158&amp;status=done&amp;style=none&amp;taskId=u3911cd3b-5d8c-4910-b892-2a31a6744a0&amp;title=&amp;width=881" alt="image.png" /></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="s2">"shopping"</span><span class="err">【索引名】:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"aliases"</span><span class="err">【别名】:</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="s2">"mappings"</span><span class="err">【映射】:</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="s2">"settings"</span><span class="err">【设置】:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"index"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引】:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"creation_date"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">创建时间】:</span><span class="w"> </span><span class="s2">"1650108007285"</span><span class="p">,</span><span class="w"> </span><span class="s2">"number_of_shards"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">主分片数量】:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w"> </span><span class="s2">"number_of_replicas"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">副分片数量】:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w"> </span><span class="s2">"uuid"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">唯一标识】:</span><span class="w"> </span><span class="s2">"ohQY_Mb0Rm2UG-Z9J0ZhMg"</span><span class="p">,</span><span class="w"> </span><span class="s2">"version"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">版本】:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"created"</span><span class="p">:</span><span class="w"> </span><span class="s2">"7080099"</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="s2">"provided_name"</span><span class="err">【设置</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">索引</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">名称】:</span><span class="w"> </span><span class="s2">"shopping"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h3 id="4-删除索引">4) 删除索引</h3> <p>在 Postman 中,向 ES 服务器发 <strong>DELETE **请求 :<a href="http://10.211.55.11:9200/shopping">http://10.211.55.11:9200/shopping</a> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650114661772-c14dd498-3dbe-4619-9cf6-b050b052ebec.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=519&amp;id=ua446acca&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=519&amp;originWidth=858&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=93369&amp;status=done&amp;style=none&amp;taskId=u766abe91-37d5-42de-8961-3f23f49ae15&amp;title=&amp;width=858" alt="image.png" /> 重新访问索引时,服务器返回响应:</strong>索引不存在** <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650115411382-3131d3cb-ad24-463f-aad5-557bb0e406cf.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=618&amp;id=u5852c1da&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=618&amp;originWidth=855&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=151701&amp;status=done&amp;style=none&amp;taskId=u1cd64cb4-c20f-46ac-bc85-d0b3662bf05&amp;title=&amp;width=855" alt="image.png" /></p> <h2 id="42-文档操作">4.2 文档操作</h2> <h3 id="1-创建文档">1) 创建文档</h3> <p>索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式 在 Postman 中,向 ES 服务器发 <strong>POST **请求 :http://10.211.55.11:9200/shopping</strong>/_doc ** 请求体内容为:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="s2">"小米手机"</span><span class="p">,</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"小米"</span><span class="p">,</span><span class="w"> </span><span class="nl">"images"</span><span class="p">:</span><span class="s2">"http://www.gulixueyuan.com/xm.jpg"</span><span class="p">,</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="mf">3999.00</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650115683911-69e81188-4636-4f22-ae46-11ac92cddd20.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=417&amp;id=u18f3bc33&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=417&amp;originWidth=866&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=134780&amp;status=done&amp;style=none&amp;taskId=u3b506108-1fe8-4c97-a7fd-4b6eef0adcd&amp;title=&amp;width=866" alt="image.png" /> 此处发送请求的方式必须为 <strong>POST</strong>,不能是 <strong>PUT</strong>,否则会发生错误 PUT请求是幂等性的,POST请求不是幂等性的,每一次POST都返回不同的ID <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650115828658-95158cd9-e1e5-44d7-a536-2a923088a4c1.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=156&amp;id=uc0e5b092&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=156&amp;originWidth=815&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=67519&amp;status=done&amp;style=none&amp;taskId=u8dd500bf-e040-4921-928f-9ca91b9251b&amp;title=&amp;width=815" alt="image.png" /></p> <p>服务器响应结果如下: <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650115785649-496792db-d909-44b1-b543-3489fde30d19.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=347&amp;id=u5870bc56&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=347&amp;originWidth=849&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=112550&amp;status=done&amp;style=none&amp;taskId=u8fbee92d-0292-40f4-b4de-690b7d185f2&amp;title=&amp;width=849" alt="image.png" /></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="s2">"_index"</span><span class="err">【索引】:</span><span class="w"> </span><span class="s2">"shopping"</span><span class="p">,</span><span class="w"> </span><span class="s2">"_type"</span><span class="err">【类型-文档】:</span><span class="w"> </span><span class="s2">"_doc"</span><span class="p">,</span><span class="w"> </span><span class="s2">"_id"</span><span class="err">【唯一标识】:</span><span class="w"> </span><span class="s2">"1ZOPMoABrqtNwT9MR10m"</span><span class="p">,</span><span class="w"> </span><span class="err">#可以类比为</span><span class="w"> </span><span class="err">MySQL</span><span class="w"> </span><span class="err">中的主键,随机生成</span><span class="w"> </span><span class="s2">"_version"</span><span class="err">【版本】:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s2">"result"</span><span class="err">【结果】:</span><span class="w"> </span><span class="s2">"created"</span><span class="p">,</span><span class="w"> </span><span class="err">#这里的</span><span class="w"> </span><span class="err">create</span><span class="w"> </span><span class="err">表示创建成功</span><span class="w"> </span><span class="s2">"_shards"</span><span class="err">【分片】:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"total"</span><span class="err">【分片</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">总数】:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="s2">"successful"</span><span class="err">【分片</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">成功】:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s2">"failed"</span><span class="err">【分片</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">失败】:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"_seq_no"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"_primary_term"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>上面的数据创建后,由于没有指定数据唯一性标识(ID),默认情况下,ES 服务器会随机生成一个。 如果想要自定义唯一性标识,需要在创建时指定:http://10.211.55.11:9200/shopping/_doc/<strong>1001</strong> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650116267023-567f5569-befa-483c-b9e0-8f1dc604f1b6.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=626&amp;id=ued4363d2&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=626&amp;originWidth=849&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=196176&amp;status=done&amp;style=none&amp;taskId=u8feb13ae-2f1f-4438-838b-0955749a282&amp;title=&amp;width=849" alt="image.png" /> 此处需要注意:如果增加数据时明确数据主键,那么请求方式也可以为 PUT</p> <h3 id="2-查看文档">2) 查看文档</h3> <p>查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询 在 Postman 中,向 ES 服务器发 <strong>GET **请求 :http://10.211.55.11:9200/shopping</strong>/_doc/1001** <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650116389541-77fd9101-24d2-45bd-a013-7a122c95f2d7.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=619&amp;id=u7a1ffdb0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=619&amp;originWidth=890&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=213425&amp;status=done&amp;style=none&amp;taskId=u437bebb8-f916-4c6e-833c-dfbcd4aafaf&amp;title=&amp;width=890" alt="image.png" /></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="s2">"_index"</span><span class="err">【索引】:</span><span class="w"> </span><span class="s2">"shopping"</span><span class="p">,</span><span class="w"> </span><span class="s2">"_type"</span><span class="err">【文档类型】:</span><span class="w"> </span><span class="s2">"_doc"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1001"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_version"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"_seq_no"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"_primary_term"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s2">"found"</span><span class="err">【查询结果】:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="err">表示查找到,</span><span class="kc">false</span><span class="w"> </span><span class="err">表示未查找到</span><span class="w"> </span><span class="s2">"_source"</span><span class="err">【文档源信息】:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"小米手机"</span><span class="p">,</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="w"> </span><span class="s2">"小米"</span><span class="p">,</span><span class="w"> </span><span class="nl">"images"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://www.gulixueyuan.com/xm.jpg"</span><span class="p">,</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">3999.00</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>全部查询:向 ES 服务器发 <strong>GET **请求 :http://10.211.55.11:9200/shopping</strong>/_search**</p> <h3 id="3-修改文档">3) 修改文档</h3> <p>和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖 在 Postman 中,向 ES 服务器发 <strong>POST **请求 :http://10.211.55.11:9200/shopping</strong>/_doc/1001 ** 请求体内容为:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="s2">"华为手机"</span><span class="p">,</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"华为"</span><span class="p">,</span><span class="w"> </span><span class="nl">"images"</span><span class="p">:</span><span class="s2">"http://www.gulixueyuan.com/hw.jpg"</span><span class="p">,</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="mf">4999.00</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650116923039-cccc696a-8e1c-4187-94ae-ce09c1a8bf65.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=622&amp;id=ue1c34ede&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=622&amp;originWidth=865&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=193471&amp;status=done&amp;style=none&amp;taskId=udefb90ec-faaf-44b6-9b91-9203d359a5a&amp;title=&amp;width=865" alt="image.png" /></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shopping"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"_doc"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1001"</span><span class="p">,</span><span class="w"> </span><span class="s2">"_version"</span><span class="err">【版本】:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="s2">"result"</span><span class="err">【结果】:</span><span class="w"> </span><span class="s2">"updated"</span><span class="p">,</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="err">updated</span><span class="w"> </span><span class="err">表示数据被更新</span><span class="w"> </span><span class="nl">"_shards"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"successful"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"failed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"_seq_no"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"_primary_term"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h3 id="4-修改字段">4) 修改字段</h3> <p>局部修改:非幂等性,每次修改的内容都不一样,使用POST请求 <a href="http://10.211.55.11:9200/shopping/_update/1001">http://10.211.55.11:9200/shopping/_update/1001</a> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650117324522-020b4a85-5b7e-476f-9818-bf985ff64311.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=612&amp;id=uc60f7e4a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=612&amp;originWidth=874&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=176955&amp;status=done&amp;style=none&amp;taskId=u3945162d-fb71-4bfe-9546-4cddd1eef8d&amp;title=&amp;width=874" alt="image.png" /> 根据唯一性标识,查询文档数据,文档数据已经更新 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650117483405-383929bb-1a29-4fc7-a070-a85c49e993d5.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=619&amp;id=u43f6f0fe&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=619&amp;originWidth=852&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=166689&amp;status=done&amp;style=none&amp;taskId=u5a0a6da8-9649-4dd7-89b9-bf5967f4ddb&amp;title=&amp;width=852" alt="image.png" /></p> <h3 id="5-删除文档">5) 删除文档</h3> <p>删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。 在 Postman 中,向 ES 服务器发 <strong>DELETE **请求 :http://10.211.55.11:9200/shopping</strong>/_doc/1001** <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650117601189-ce89396b-4b9a-4cf9-95fa-d9193bf9c35a.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=613&amp;id=ua2134545&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=613&amp;originWidth=862&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=151098&amp;status=done&amp;style=none&amp;taskId=ue790e738-845a-4ede-821c-05e9c73b162&amp;title=&amp;width=862" alt="image.png" /></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"_index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shopping"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"_doc"</span><span class="p">,</span><span class="w"> </span><span class="nl">"_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1001"</span><span class="p">,</span><span class="w"> </span><span class="s2">"_version"</span><span class="err">【版本】:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="err">#对数据的操作,都会更新版本</span><span class="w"> </span><span class="s2">"result"</span><span class="err">【结果】:</span><span class="w"> </span><span class="s2">"deleted"</span><span class="p">,</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="err">deleted</span><span class="w"> </span><span class="err">表示数据被标记为删除</span><span class="w"> </span><span class="nl">"_shards"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"successful"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"failed"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"_seq_no"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"_primary_term"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>如果删除一个并不存在的文档 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650117744330-6cbe8d87-46de-4280-881e-c3a0dd70a8b0.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=616&amp;id=u6b33d49c&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=616&amp;originWidth=858&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=153624&amp;status=done&amp;style=none&amp;taskId=uefda654f-7955-4ecd-b4ef-166507a42de&amp;title=&amp;width=858" alt="image.png" /> not_found 表示未查找到</p> <h3 id="6-条件删除文档">6) 条件删除文档</h3> <p>一般删除数据都是根据文档的唯一性标识进行删除,实际操作时,也可以根据条件对多条数 据进行删除 首先分别增加多条数据:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="s2">"小米手机"</span><span class="p">,</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"小米"</span><span class="p">,</span><span class="w"> </span><span class="nl">"images"</span><span class="p">:</span><span class="s2">"http://www.gulixueyuan.com/xm.jpg"</span><span class="p">,</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="mf">4000.00</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="s2">"华为手机"</span><span class="p">,</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"华为"</span><span class="p">,</span><span class="w"> </span><span class="nl">"images"</span><span class="p">:</span><span class="s2">"http://www.gulixueyuan.com/hw.jpg"</span><span class="p">,</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="mf">4000.00</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650118725347-49b174d9-cbd9-48c3-aed9-83091e81bfdb.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=280&amp;id=u47e307b0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=280&amp;originWidth=851&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=92200&amp;status=done&amp;style=none&amp;taskId=ubd0b31be-d73e-4485-8093-b5c8052ec50&amp;title=&amp;width=851" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650118704541-7e835f39-956b-44de-92a1-34b076e69894.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=266&amp;id=u625cd02a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=266&amp;originWidth=844&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=88224&amp;status=done&amp;style=none&amp;taskId=uca0b7b60-8907-4c8e-93e1-dfef9929cd7&amp;title=&amp;width=844" alt="image.png" /><br /> 向 ES 服务器发 <strong>POST **请求 :http://10.211.55.11:9200/shopping</strong>/_delete_by_query** <strong>请求的内容为</strong></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="mf">4000.00</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650118973050-f200849a-1f86-47f2-a73c-81c89b85556d.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=619&amp;id=u277a1676&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=619&amp;originWidth=860&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=181248&amp;status=done&amp;style=none&amp;taskId=u7e1f8130-933d-45bb-9326-da6c2ae1bda&amp;title=&amp;width=860" alt="image.png" /></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="s2">"took"</span><span class="err">【耗时】:</span><span class="w"> </span><span class="mi">821</span><span class="p">,</span><span class="w"> </span><span class="s2">"timed_out"</span><span class="err">【是否超时】:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"> </span><span class="s2">"total"</span><span class="err">【总数】:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="s2">"deleted"</span><span class="err">【删除数量】:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"batches"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"version_conflicts"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"noops"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"retries"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"bulk"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"search"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"throttled_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"requests_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">-1.0</span><span class="p">,</span><span class="w"> </span><span class="nl">"throttled_until_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nl">"failures"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h3 id="7-条件查询">7) 条件查询</h3> <p><strong>查询种类是小米的请求</strong> <strong>GET操作(请求路径):</strong><a href="http://10.211.55.11:9200/shopping/_search?q=catagory:">http://10.211.55.11:9200/shopping/_search?q=catagory:</a>小米 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650119185493-7b264e36-7bc5-4d7b-b4e3-253dd4201d5f.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=604&amp;id=uad85bb4b&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=604&amp;originWidth=859&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=145558&amp;status=done&amp;style=none&amp;taskId=u7c29b04d-3e67-43ba-8090-b94be49dd6f&amp;title=&amp;width=859" alt="image.png" /></p> <p><strong>请求体</strong> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650119351422-8c0ec2e3-0574-4599-b988-c1c943ec579c.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=610&amp;id=u0ac67933&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=610&amp;originWidth=869&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=170067&amp;status=done&amp;style=none&amp;taskId=u71284db6-8ca8-432b-8e5b-cac860c482c&amp;title=&amp;width=869" alt="image.png" /></p> <h3 id="8-多条件查询">8) 多条件查询</h3> <p>同时成立:<strong>must</strong></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"bool"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"must"</span><span class="p">:[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"cateaory"</span><span class="p">:</span><span class="s2">"小米"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="s2">"2999.00"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650157055379-d81b252d-882e-4f50-a4b4-2157dfcd6aae.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=542&amp;id=ud2783ea2&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=542&amp;originWidth=861&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=156893&amp;status=done&amp;style=none&amp;taskId=u2d1cd282-3553-4957-a186-8680f1f4cbc&amp;title=&amp;width=861" alt="image.png" /></p> <p>任何一个都满足条件:<strong>should</strong></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"bool"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"should"</span><span class="p">:[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"cateaory"</span><span class="p">:</span><span class="s2">"小米"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"cateaory"</span><span class="p">:</span><span class="s2">"苹果"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650157188681-73d2acae-175c-41aa-a37f-470bcbedd57d.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=508&amp;id=ucd6787d4&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=508&amp;originWidth=858&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=157079&amp;status=done&amp;style=none&amp;taskId=ub1caaf33-c27e-4055-b067-9d09ec29346&amp;title=&amp;width=858" alt="image.png" /></p> <p>范围操作:<strong>filter</strong> <strong>range</strong></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"bool"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"should"</span><span class="p">:[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"cateaory"</span><span class="p">:</span><span class="s2">"小米"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"cateaory"</span><span class="p">:</span><span class="s2">"苹果"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="nl">"filter"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"range"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"price"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"gt"</span><span class="p">:</span><span class="mi">2000</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650157458071-2409c1cf-b3b4-4a74-9f26-05ed84499643.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=570&amp;id=u42652187&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=570&amp;originWidth=847&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=165256&amp;status=done&amp;style=none&amp;taskId=uc861a855-cc94-4874-924b-2e8ccc06d8c&amp;title=&amp;width=847" alt="image.png" /> ###</p> <h3 id="9-全文检索">9) 全文检索</h3> <p>当保存文档数据时,es会将数据文字进行分词拆解操作,并将拆解后的数据保存到倒排索引当中,那么即使使用文字的一部分,仍可以查询到数据,这种检索的方式称之为全文检索。es会将查询内容进行分词,在倒排索引中进行匹配</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"米"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650157917758-e34070d8-9e25-4db9-a07b-1fb0f2288669.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=598&amp;id=u103cb014&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=598&amp;originWidth=859&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=200840&amp;status=done&amp;style=none&amp;taskId=ubcc436af-b3e3-4c00-8db1-c8a67e8f149&amp;title=&amp;width=859" alt="image.png" /></p> <p>完全匹配:<strong>match_phrase</strong></p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"match_phrase"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"米华"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650158084451-922b2000-614d-419d-8ce7-c9099a1e9043.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=575&amp;id=u899dc3ce&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=575&amp;originWidth=886&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=173002&amp;status=done&amp;style=none&amp;taskId=u9c2ad199-caac-4ef8-b46a-35f99e4832c&amp;title=&amp;width=886" alt="image.png" /></p> <p>高亮查询</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"match_phrase"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:</span><span class="s2">"小米"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"highlight"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"fields"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"category"</span><span class="p">:{}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650158321048-9e8f345b-c6fe-44ad-b476-956f55f7e239.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=575&amp;id=u14b51b03&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=575&amp;originWidth=865&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=191414&amp;status=done&amp;style=none&amp;taskId=ue5fb9995-8250-4cd5-a210-754f30b8410&amp;title=&amp;width=865" alt="image.png" /></p> <h3 id="10-聚合操作">10) 聚合操作</h3> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"aggs"</span><span class="p">:{</span><span class="w"> </span><span class="err">//聚合操作</span><span class="w"> </span><span class="nl">"price_group"</span><span class="p">:{</span><span class="w"> </span><span class="err">//统计结果的名称,任意取</span><span class="w"> </span><span class="nl">"terms"</span><span class="p">:{</span><span class="w"> </span><span class="err">//分组</span><span class="w"> </span><span class="nl">"field"</span><span class="p">:</span><span class="s2">"price"</span><span class="w"> </span><span class="err">//分组字段</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650158630603-9fcde9af-e0f9-41c5-8d1b-b9addd997f64.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=612&amp;id=uf83287d9&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=612&amp;originWidth=854&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=198073&amp;status=done&amp;style=none&amp;taskId=u22c161ee-26e0-4c82-a01d-10ebc8f0f17&amp;title=&amp;width=854" alt="image.png" /></p> <p>不显示原始数据</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"aggs"</span><span class="p">:{</span><span class="w"> </span><span class="err">//聚合操作</span><span class="w"> </span><span class="nl">"price_group"</span><span class="p">:{</span><span class="w"> </span><span class="err">//统计结果的名称,任意取</span><span class="w"> </span><span class="nl">"terms"</span><span class="p">:{</span><span class="w"> </span><span class="err">//分组</span><span class="w"> </span><span class="nl">"field"</span><span class="p">:</span><span class="s2">"price"</span><span class="w"> </span><span class="err">//分组字段</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"size"</span><span class="p">:</span><span class="mi">0</span><span class="w"> </span><span class="err">//不用显示原始数据</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650158829629-be063b87-2a05-4b0e-9664-942200150eda.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=275&amp;id=uc3d8d997&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=275&amp;originWidth=617&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=84401&amp;status=done&amp;style=none&amp;taskId=ufc234aab-6499-40f8-8a0e-64bbb0a6b28&amp;title=&amp;width=617" alt="image.png" /></p> <p>求平均值</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"aggs"</span><span class="p">:{</span><span class="w"> </span><span class="err">//聚合操作</span><span class="w"> </span><span class="nl">"price_avg"</span><span class="p">:{</span><span class="w"> </span><span class="err">//统计结果的名称,任意取</span><span class="w"> </span><span class="nl">"avg"</span><span class="p">:{</span><span class="w"> </span><span class="err">//平均值</span><span class="w"> </span><span class="nl">"field"</span><span class="p">:</span><span class="s2">"price"</span><span class="w"> </span><span class="err">//分组字段</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"size"</span><span class="p">:</span><span class="mi">0</span><span class="w"> </span><span class="err">//不用显示原始数据</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650158887334-ace2f9f1-5c9a-4c8e-8f6b-d79741d11820.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=144&amp;id=ue0a58173&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=144&amp;originWidth=331&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=31119&amp;status=done&amp;style=none&amp;taskId=u1af87de4-1b17-431f-ad0b-be5b3016131&amp;title=&amp;width=331" alt="image.png" /></p> <h2 id="43-映射关系">4.3 映射关系</h2> <p>有了索引库,等于有了数据库中的 database。</p> <p>接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构(table)。</p> <p>创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。</p> <h3 id="1-创建映射">1) 创建映射</h3> <p>在 Postman 中,向 ES 服务器发 <strong>PUT **请求 :http://10.211.55.11:9200/user</strong>/_mapping ** 请求体内容为:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"sex"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"keyword"</span><span class="p">,</span><span class="w"> </span><span class="err">//不能够被分词</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"tel"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"keyword"</span><span class="p">,</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="err">//不能够被索引</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650159817033-9d7113cc-dba0-4a7d-aa3b-e94830125763.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=422&amp;id=uff2070e6&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=422&amp;originWidth=856&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=129305&amp;status=done&amp;style=none&amp;taskId=u0769dd00-ecbc-46f3-8da0-0b4b4b618f7&amp;title=&amp;width=856" alt="image.png" /></p> <p>映射数据说明:</p> <ul> <li>字段名:任意填写,下面指定许多属性,例如:title、subtitle、images、price</li> <li>type:类型,Elasticsearch 中支持的数据类型非常丰富,说几个关键的:</li> </ul> <p>String 类型,又分两种: text:可分词 keyword:不可分词,数据会作为完整字段进行匹配 <br /> Numerical:数值类型,分两类 基本数据类型:long、integer、short、byte、double、float、half_float 浮点数的高精度类型:scaled_float Date:日期类型 Array:数组类型 Object:对象</p> <ul> <li>index:是否索引,默认为 true,也就是说你不进行任何配置,所有字段都会被索引。</li> </ul> <p>true:字段会被索引,则可以用来进行搜索 false:字段不会被索引,不能用来搜索</p> <ul> <li>store:是否将数据进行独立存储,默认为 false</li> </ul> <p>原始的文本会存储在_source 里面,默认情况下其他提取出来的字段都不是独立存储的,是从_source 里面提取出来的。当然你也可以独立的存储某个字段,只要设置 “store”: true 即可,获取独立存储的字段要比从_source 中解析快得多,但是也会占用更多的空间,所以要根据实际业务需求来设置。</p> <ul> <li>analyzer:分词器</li> </ul> <h3 id="2-查看映射">2) 查看映射</h3> <p>在 Postman 中,向 ES 服务器发 **GET **请求:<a href="http://10.211.55.11:9200/user/_mapping">http://10.211.55.11:9200/user/_mapping</a> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650160158293-bbb7a039-e333-40a3-9db6-ccf8dbbf9567.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=348&amp;id=ucad1394a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=348&amp;originWidth=589&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=79124&amp;status=done&amp;style=none&amp;taskId=u20c6236f-054d-49ed-9259-1f117aa26bc&amp;title=&amp;width=589" alt="image.png" /></p> <h3 id="3-查询数据">3) 查询数据</h3> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"query"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"match"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="s2">"小"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650160737757-a83824c4-c349-4e04-838e-82828d7c3c16.png#clientId=uc1b60b7a-85e5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=341&amp;id=u53aa2714&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=341&amp;originWidth=504&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=75299&amp;status=done&amp;style=none&amp;taskId=u4dcdb3b8-e7b7-4efd-88a5-3941eb2115d&amp;title=&amp;width=504" alt="image.png" /></p> HBase优化 https://muhaokang.github.io/HBase-%E4%BC%98%E5%8C%96/ https://muhaokang.github.io/HBase-%E4%BC%98%E5%8C%96/ Sat, 16 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 高可用 在 HBase 中 HMaster 负责监控 HRegionServer 的生命周期,均衡 RegionServer 的负载,如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以 HBase 支持对 HMaster 的高可用配置。 HBase自带高可用,比如可以在hadoop1... <h1 id="1-高可用">1. 高可用</h1> <p>在 HBase 中 HMaster 负责监控 HRegionServer 的生命周期,均衡 RegionServer 的负载,如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以 HBase 支持对 HMaster 的高可用配置。</p> <p>HBase自带高可用,比如可以在hadoop102和hadoop103上同时单点启动master,此时hadoop103上的master就是backup master</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 hbase-1.3.1]<span class="nv">$ </span>bin/hbase-daemon.sh start master <span class="o">[</span>mhk@hadoop103 hbase-1.3.1]<span class="nv">$ </span>bin/hbase-daemon.sh start master </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650069425340-b423a334-b6cb-4e36-981f-9d2f850eff70.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=220&amp;id=u13850056&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=220&amp;originWidth=943&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=38578&amp;status=done&amp;style=none&amp;taskId=u83a1eaec-b617-4cbb-b2d9-9bbbddcb51d&amp;title=&amp;width=943" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650069528768-1c325051-11da-4f73-88b6-931749d11b9e.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=181&amp;id=u474f28a1&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=181&amp;originWidth=476&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=33790&amp;status=done&amp;style=none&amp;taskId=u917dbe5a-6db2-4654-9a78-36bb1aab885&amp;title=&amp;width=476" alt="image.png" /> <strong>配置自动高可用</strong> 1.关闭 HBase 集群(如果没有开启则跳过此步)</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 hbase-1.3.1]<span class="nv">$ </span>bin/stop-hbase.sh </code></pre></div></div> <p>2.在 conf 目录下创建 backup-masters 文件</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 hbase-1.3.1]<span class="nv">$ </span><span class="nb">touch </span>conf/backup-masters </code></pre></div></div> <p>3.在 backup-masters 文件中配置高可用 HMaster 节点</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 conf]<span class="nv">$ </span><span class="nb">cat </span>backup-masters hadoop103 hadoop104 </code></pre></div></div> <p>4.将整个 conf 目录 scp 到其他节点</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 hbase-1.3.1]<span class="nv">$ </span>xsync conf/ </code></pre></div></div> <p>5.启动集群</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 hbase-1.3.1]<span class="nv">$ </span>bin/start-hbase.sh starting master, logging to /opt/module/hbase-1.3.1/bin/../logs/hbase-mhk-master-hadoop102.out hadoop103: starting regionserver, logging to /opt/module/hbase-1.3.1/bin/../logs/hbase-mhk-regionserver-hadoop103.out hadoop104: starting regionserver, logging to /opt/module/hbase-1.3.1/bin/../logs/hbase-mhk-regionserver-hadoop104.out hadoop102: starting regionserver, logging to /opt/module/hbase-1.3.1/bin/../logs/hbase-mhk-regionserver-hadoop102.out hadoop104: starting master, logging to /opt/module/hbase-1.3.1/bin/../logs/hbase-mhk-master-hadoop104.out hadoop103: starting master, logging to /opt/module/hbase-1.3.1/bin/../logs/hbase-mhk-master-hadoop103.out </code></pre></div></div> <p>6.打开页面测试查看 http://hadooo102:16010 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650070058849-985b4d40-aca8-4721-978f-df8dcc8c353e.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=252&amp;id=u33a05fff&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=252&amp;originWidth=887&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=51332&amp;status=done&amp;style=none&amp;taskId=u7bcfc528-81d3-4f00-ac58-0ca8b9617fc&amp;title=&amp;width=887" alt="image.png" /></p> <h1 id="2-预分区">2. 预分区</h1> <p>每一个 region 维护着 StartRow 与 EndRow,如果加入的数据符合某个 Region 维护的RowKey 范围,则该数据交给这个 Region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase 性能。 预分区和什么有关?未来发展的数据量+机器规模 一般生产环境中,一台机器放2~3个region</p> <p>1.手动设定预分区</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:001:0&gt; create <span class="s1">'staff1'</span>,<span class="s1">'info'</span>,<span class="s1">'partition1'</span>,SPLITS <span class="o">=&gt;</span> <span class="o">[</span><span class="s1">'1000'</span>,<span class="s1">'2000'</span>,<span class="s1">'3000'</span>,<span class="s1">'4000'</span><span class="o">]</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650070716788-65c782d9-db7b-4376-9f2d-a6cb92080bba.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=168&amp;id=u7b157120&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=168&amp;originWidth=1342&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=71667&amp;status=done&amp;style=none&amp;taskId=ub9e3177c-7979-4cad-9e08-f4b8c3a7f95&amp;title=&amp;width=1342" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650070828558-5bec547c-5b31-47b7-b8cc-cfc6568fe8af.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=407&amp;id=ua8aa970c&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=407&amp;originWidth=1320&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=168705&amp;status=done&amp;style=none&amp;taskId=u15c3e3b5-6b48-4759-a2fb-332a5f494ae&amp;title=&amp;width=1320" alt="image.png" /> 2.生成 16 进制序列预分区</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:002:0&gt; create <span class="s1">'staff2'</span>,<span class="s1">'info'</span>,<span class="s1">'partition2'</span>,<span class="o">{</span>NUMREGIONS <span class="o">=&gt;</span> 15, SPLITALGO <span class="o">=&gt;</span> <span class="s1">'HexStringSplit'</span><span class="o">}</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650071556937-bc0e45a1-9ecf-499f-b4a5-c7283b428ce3.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=211&amp;id=u96e8db95&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=211&amp;originWidth=1351&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=102007&amp;status=done&amp;style=none&amp;taskId=u2a217e75-fd52-41aa-bad9-3ae6bbdb8f9&amp;title=&amp;width=1351" alt="image.png" /> <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650071609928-7cc630f7-ac2b-450c-a022-b3a2e641c2e0.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=600&amp;id=u3403d398&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=600&amp;originWidth=1395&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=279546&amp;status=done&amp;style=none&amp;taskId=ud2df4c72-be2f-4dab-9e00-b46082ebd66&amp;title=&amp;width=1395" alt="image.png" /><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650071630634-43ce71d8-0817-46ed-b75b-ed71bd0a5511.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=440&amp;id=u85569ada&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=440&amp;originWidth=1381&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=222784&amp;status=done&amp;style=none&amp;taskId=u2fb7daae-8fcf-492b-8af3-f8b00c430cd&amp;title=&amp;width=1381" alt="image.png" /></p> <p>3.按照文件中设置的规则预分区 创建 splits.txt 文件内容如下: aaaa bbbb cccc dddd 然后执行:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:005:0&gt; create <span class="s1">'staff3'</span>,<span class="s1">'partition3'</span>,SPLITS_FILE <span class="o">=&gt;</span> <span class="s1">'spilts.txt'</span> </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1650072446302-c7210b1b-b16a-4860-9451-922de91ca994.png#clientId=u510c73fa-4151-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=429&amp;id=u4f6f39e4&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=429&amp;originWidth=1331&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=158831&amp;status=done&amp;style=none&amp;taskId=u97af0363-45f9-46ac-8007-d2b47c36cfb&amp;title=&amp;width=1331" alt="image.png" /></p> <p>4.使用 JavaAPI 创建预分区</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//自定义算法,产生一系列 hash 散列值存储在二维数组中 </span> <span class="kt">byte</span><span class="o">[][]</span> <span class="n">splitKeys</span> <span class="o">=</span> <span class="n">某个散列值函数</span> <span class="c1">//创建 HbaseAdmin 实例 </span> <span class="nc">HBaseAdmin</span> <span class="n">hAdmin</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HBaseAdmin</span><span class="o">(</span><span class="nc">HbaseConfiguration</span><span class="o">.</span><span class="na">create</span><span class="o">());</span> <span class="c1">//创建 HTableDescriptor 实例 </span> <span class="nc">HTableDescriptor</span> <span class="n">tableDesc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HTableDescriptor</span><span class="o">(</span><span class="n">tableName</span><span class="o">);</span> <span class="c1">//通过 HTableDescriptor 实例和散列值二维数组创建带有预分区的 Hbase 表 </span> <span class="n">hAdmin</span><span class="o">.</span><span class="na">createTable</span><span class="o">(</span><span class="n">tableDesc</span><span class="o">,</span> <span class="n">splitKeys</span><span class="o">);</span> </code></pre></div></div> <h1 id="3-rowkey-设计">3. RowKey 设计</h1> <p>散列性 唯一性 长度原则 一条数据的唯一标识就是 RowKey,那么这条数据存储于哪个分区,取决于 RowKey 处于哪个一个预分区的区间内,设计 RowKey 的主要目的 ,就是让数据均匀的分布于所有的region 中,在一定程度上防止数据倾斜。接下来我们就谈一谈 RowKey 常用的设计方案。</p> <p>1.生成随机数、hash、散列值 比如: 原 本 rowKey 为 1001 的 , SHA1 后 变 成 : dd01903921ea24941c26a48f2cec24e0bb0e8cc7 原 本 rowKey 为 3001 的 , SHA1 后 变 成 : 49042c54de64a1e9bf0b33e00245660ef92dc7bd 原 本 rowKey 为 5001 的 , SHA1 后 变 成 : 7b61dec07e02c188790670af43e717f0f46e8913 在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的 rowKey 来 Hash 后作为每个分区的临界值。</p> <p>2.字符串反转 20170524000001 转成 10000042507102 20170524000002 转成 20000042507102 这样也可以在一定程度上散列逐步 put 进来的数据。</p> <p>3.字符串拼接 20170524000001_a12e 20170524000001_93i7</p> <p>Rowkey设计时需要遵循三大原则: 1) <strong>唯一性原则</strong> rowkey在设计上保证其唯一性。rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。</p> <p>2)<strong>长度原则</strong> rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以byte[] 形式保存,一般设计成定长。<strong>建议越短越好,不要超过16个字节</strong>,原因如下:数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。<strong>目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性</strong>。</p> <p><strong>3)散列原则</strong> 如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率</p> <p><strong>加盐</strong>:这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点</p> <p><strong>哈希</strong>:哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据</p> <p><strong>反转</strong>:第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题</p> <p><strong>时间戳反转</strong>:一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用Long.Max_Value - timestamp 追加到key的末尾,例如[key][reverse_timestamp] ,[key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计rowkey的时候,可以这样设计[userId反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转][000000000000],stopRow是[userId反转][<strong>Long.Max_Value - timestamp</strong>]如果需要查询某段时间的操作记录,startRow是[user反转][Long.Max_Value - 起始时间],stopRow是[userId反转][Long.Max_Value - 结束时间]</p> <h1 id="4-内存优化">4. 内存优化</h1> <p>HBase 操作过程中需要大量的内存开销,毕竟 Table 是可以缓存在内存中的,一般会分配整个可用内存的 70%给 HBase 的 Java 堆。但是不建议分配非常大的堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,一般 16~48G 内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。</p> <h1 id="5-基础优化">5. 基础优化</h1> <p>1.允许在 HDFS 的文件中追加内容 hdfs-site.xml、hbase-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:dfs.support.append 解释:开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化。默认值为 true。 </code></pre></div></div> <p>2.优化 DataNode 允许的最大文件打开数 hdfs-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:dfs.datanode.max.transfer.threads 解释:HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作, 设置为 4096 或者更高。默认值:4096 </code></pre></div></div> <p>3.优化延迟高的数据操作的等待时间 hdfs-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:dfs.image.transfer.timeout 解释:如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把 该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被 timeout 掉。 </code></pre></div></div> <p>4.优化数据的写入效率 mapred-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性: mapreduce.map.output.compress mapreduce.map.output.compress.codec 解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。 第一个属性值修改为true, 第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec 或者其他压缩方式。 </code></pre></div></div> <p>5.设置 RPC 监听数量 hbase-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:Hbase.regionserver.handler.count 解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写 请求较多时,增加此值。 </code></pre></div></div> <p>6.优化 HStore 文件大小 hbase-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:hbase.hregion.max.filesize 解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值, 因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间 过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile。 </code></pre></div></div> <p>7.优化 HBase 客户端缓存 hbase-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:hbase.client.write.buffer 解释:用于指定 Hbase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内存, 反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的。 </code></pre></div></div> <p>8.指定 scan.next 扫描 HBase 所获取的行数 hbase-site.xml</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>属性:hbase.client.scanner.caching 解释:用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。 </code></pre></div></div> <p>9.flush、compact、split 机制 当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;compact 机制则是把 flush 出来的小文件合并成大的 Storefile 文件。split 则是当 Region 达到阈值,会把过大的 Region 一分为二。</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>涉及属性: 即:128M 就是 Memstore 的默认阈值 hbase.hregion.memstore.flush.size:134217728 即:这个参数的作用是当单个 HRegion 内所有的 Memstore 大小总和超过指定值时,flush 该 HRegion 的所有 memstore。RegionServer 的 flush 是通过将请求添加一个队列,模拟生 产消费模型来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求 时,可能会导致内存陡增,最坏的情况是触发 OOM。 hbase.regionserver.global.memstore.upperLimit:0.4 hbase.regionserver.global.memstore.lowerLimit:0.38 即:当 MemStore 使用内存总量达到hbase.regionserver.global.memstore.upperLimit 指定值时, 将会有多个 MemStores flush 到文件中,MemStore flush 顺序是按照大小降序执行的,直到 刷新到 MemStore 使用内存略小于 lowerLimit </code></pre></div></div> HBase进阶 https://muhaokang.github.io/HBase-%E8%BF%9B%E9%98%B6/ https://muhaokang.github.io/HBase-%E8%BF%9B%E9%98%B6/ Thu, 14 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1、架构原理 **1)StoreFile ** 保存实际数据的物理文件,StoreFile 以 HFile 的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。 **2)MemStore ** 写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 M... <h1 id="1架构原理">1、架构原理</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649765666046-fa2b9bc8-792d-4a3b-a3fd-7b58fd57c2d7.png#clientId=u0a08b26d-cf7c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=461&amp;id=ud74a502f&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=461&amp;originWidth=957&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=298242&amp;status=done&amp;style=none&amp;taskId=uc2999ec5-2cd9-4189-9671-6190fc0fd2c&amp;title=&amp;width=957" alt="image.png" /> **1)StoreFile ** 保存实际数据的物理文件,StoreFile 以 HFile 的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。</p> <p>**2)MemStore ** 写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好序后,等到达刷写时机才会刷写到 HFile,每次刷写都会形成一个新的 HFile。</p> <p>**3)WAL ** 由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的 概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做 Write-Ahead logfile 的文件 中,然后再写入 MemStore 中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。</p> <h1 id="2写流程">2、写流程</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649765983537-01f92d16-5171-454a-b0b9-23cce07bda2b.png#clientId=u0a08b26d-cf7c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=472&amp;id=u7379b4bb&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=472&amp;originWidth=956&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=248236&amp;status=done&amp;style=none&amp;taskId=u90f2b004-cceb-4acc-a5c5-90dc6d32a93&amp;title=&amp;width=956" alt="image.png" /> 写流程: 1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>zk: localhost:2181<span class="o">(</span>CONNECTED<span class="o">)</span> 1] <span class="nb">ls</span> / <span class="o">[</span>admin, brokers, cluster, config, consumers, controller_epoch, hbase, isr_change_notification, latest_producer_id_block, locks, servers, spark, zookeeper] <span class="o">[</span>zk: localhost:2181<span class="o">(</span>CONNECTED<span class="o">)</span> 2] <span class="nb">ls</span> /hbase <span class="o">[</span>backup-masters, draining, flush-table-proc, hbaseid, master, meta-region-server, namespace, online-snapshot, recovering-regions, region-in-transition, replication, rs, running, splitWAL, switch, table, table-lock] <span class="o">[</span>zk: localhost:2181<span class="o">(</span>CONNECTED<span class="o">)</span> 3] get /hbase/meta-region-server �regionserver:16020�<span class="o">}</span>6��PBUF hadoop104�<span class="o">}</span>���ˁ0 </code></pre></div></div> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649833360415-062d67e3-e1ce-4a3e-9c30-86f3d1e40a37.png#clientId=u6889a29a-5fa5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=203&amp;id=u569ed608&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=283&amp;originWidth=863&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=49087&amp;status=done&amp;style=none&amp;taskId=uf705c4bc-6960-4d83-b24d-0144c3ee450&amp;title=&amp;width=618" alt="image.png" /></p> <p>2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649833629399-ba8a4804-a9f4-4507-81b4-8c98923a780c.png#clientId=u6889a29a-5fa5-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=196&amp;id=u0cb7c7f2&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=196&amp;originWidth=833&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=207398&amp;status=done&amp;style=none&amp;taskId=u9bb408f3-9add-4bba-ad2a-d9a1cda1cdb&amp;title=&amp;width=833" alt="image.png" /> 扫描hbase:meta这张表,发现student表是由hadoop102这台机器上的Region Server维护的</p> <p>3)与目标 Region Server 进行通讯;</p> <p>4)将数据顺序写入(追加)到 WAL;</p> <p>5)将数据写入对应的 MemStore,数据会在 MemStore 进行排序;</p> <p>6)向客户端发送 ack;</p> <p>7)等达到 MemStore 的刷写时机后,将数据刷写到 HFile。</p> <h1 id="3memstore-flush">3、MemStore Flush</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649766087232-1f6c5670-209a-4202-b15b-ee3de2b0aded.png#clientId=u0a08b26d-cf7c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=439&amp;id=u0f0894e8&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=439&amp;originWidth=645&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=129392&amp;status=done&amp;style=none&amp;taskId=u86a98e9f-2df7-4c7f-adda-3d8c6b2d211&amp;title=&amp;width=645" alt="image.png" /> <br /> <strong>MemStore 刷写时机: ** 1、当某个 memstroe 的大小达到了 **hbase.hregion.memstore.flush.size(默认值 128M)</strong>, 其所在 region 的所有 memstore 都会刷写。 当 memstore 的大小达到了 <em>*hbase.hregion.memstore.flush.size(默认值 128M)</em> hbase.hregion.memstore.block.multiplier(默认值 4) **时,会阻止继续往该 memstore 写数据。</p> <p>2、当 region server 中 memstore 的总大小达到 <strong>java_heapsize <em>hbase.regionserver.global.memstore.size(默认值 0.4)</em>hbase.regionserver.global.memstore.size.lower.limit(默认值 0.95)</strong>, region 会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写。直到 region server 中所有 memstore 的总大小减小到上述值以下。</p> <p>当 region server 中 memstore 的总大小达到 <em>*java_heapsize</em>hbase.regionserver.global.memstore.size(默认值 0.4) ** 时,会阻止继续往所有的 memstore 写数据。</p> <p>3、到达自动刷写的时间,也会触发 memstore flush。自动刷新的时间间隔由该属性进行配置 <strong>hbase.regionserver.optionalcacheflushinterval(默认 1 小时)</strong>。</p> <p>4、当 WAL 文件的数量超过 <strong>hbase.regionserver.max.logs</strong>,region 会按照时间顺序依次进 行刷写,直到 WAL 文件数量减小到 **hbase.regionserver.max.log **以下(该属性名已经废弃,现无需手动设置,最大值为 32)。</p> <h1 id="4读流程">4、读流程</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649768302818-9d3401a4-9bc7-43ae-872d-3c34d4e60c34.png#clientId=u0a08b26d-cf7c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=461&amp;id=u005da675&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=461&amp;originWidth=955&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=215961&amp;status=done&amp;style=none&amp;taskId=u4a210d9f-2ced-4f18-908b-68291530c81&amp;title=&amp;width=955" alt="image.png" /> **读流程 ** 1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。</p> <p>2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。</p> <p>3)与目标 Region Server 进行通讯;</p> <p>4)分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。</p> <p>5) 将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到 Block Cache。</p> <p>6)将合并后的最终结果返回给客户端。</p> <h1 id="5storefile-compaction">5、StoreFile Compaction</h1> <p>由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp) 和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,因此查询时需要遍历所有的 HFile。为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。</p> <p>Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。Minor Compaction 会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但<strong>不会</strong>清理过期和删除的数据。 Major Compaction 会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且<strong>会</strong>清理掉过期 和删除的数据。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649769906795-1acd2ab2-1002-4ee0-91b4-18670378b55c.png#clientId=u0a08b26d-cf7c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=488&amp;id=u4fa04373&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=488&amp;originWidth=949&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=203571&amp;status=done&amp;style=none&amp;taskId=uec985e30-988e-4909-9f4a-fdd9352ae65&amp;title=&amp;width=949" alt="image.png" /></p> <h1 id="6region-split">6、Region Split</h1> <p>默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑,HMaster 有可能会将某个 Region 转移给其他的 Region Server。</p> <p>Region Split 时机: 1、当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize, 该 Region 就会进行拆分(0.94 版本之前)。</p> <p>2、当 1 个 region 中 的 某 个 Store 下所有 StoreFile 的 总 大 小 超 过 Min(R^2 * “hbase.hregion.memstore.flush.size”,hbase.hregion.max.filesize”),该 Region 就会进行拆分,其 中 R 为当前 Region Server 中属于该 Table 的个数(0.94 版本之后)。 <img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649770070047-d1ae27aa-9b02-40a0-a9d4-84adb7865940.png#clientId=u0a08b26d-cf7c-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=438&amp;id=u945894e0&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=438&amp;originWidth=935&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=253216&amp;status=done&amp;style=none&amp;taskId=u4f37a973-e6cb-493f-be3e-7b29488cf65&amp;title=&amp;width=935" alt="image.png" /></p> HBase Shell操作 https://muhaokang.github.io/HBase-Shell-%E6%93%8D%E4%BD%9C/ https://muhaokang.github.io/HBase-Shell-%E6%93%8D%E4%BD%9C/ Wed, 13 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. 基本操作 **1.进入 HBase 客户端命令行 ** [mhk@hadoop102 hbase-1.3.1]$ bin/hbase shell **2.查看帮助命令 ** hbase(main):001:0&gt; help HBase Shell, version 1.3.1, r930b9a55528fe45d8edce7af42fef2d... <h1 id="1-基本操作">1. 基本操作</h1> <p>**1.进入 HBase 客户端命令行 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>mhk@hadoop102 hbase-1.3.1]<span class="nv">$ </span>bin/hbase shell </code></pre></div></div> <p>**2.查看帮助命令 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:001:0&gt; <span class="nb">help </span>HBase Shell, version 1.3.1, r930b9a55528fe45d8edce7af42fef2d35e77677a, Thu Apr 6 19:36:54 PDT 2017 Type <span class="s1">'help "COMMAND"'</span>, <span class="o">(</span>e.g. <span class="s1">'help "get"'</span> <span class="nt">--</span> the quotes are necessary<span class="o">)</span> <span class="k">for </span><span class="nb">help </span>on a specific command. Commands are grouped. Type <span class="s1">'help "COMMAND_GROUP"'</span>, <span class="o">(</span>e.g. <span class="s1">'help "general"'</span><span class="o">)</span> <span class="k">for </span><span class="nb">help </span>on a <span class="nb">command </span>group. COMMAND GROUPS: Group name: general Commands: status, table_help, version, <span class="nb">whoami </span>Group name: ddl Commands: alter, alter_async, alter_status, create, describe, disable, disable_all, drop, drop_all, <span class="nb">enable</span>, enable_all, exists, get_table, is_disabled, is_enabled, list, locate_region, show_filters Group name: namespace Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables Group name: dml Commands: append, count, delete, deleteall, get, get_counter, get_splits, incr, put, scan, <span class="nb">truncate</span>, truncate_preserve Group name: tools Commands: assign, balance_switch, balancer, balancer_enabled, catalogjanitor_enabled, catalogjanitor_run, catalogjanitor_switch, close_region, compact, compact_rs, flush, major_compact, merge_region, move, normalize, normalizer_enabled, normalizer_switch, <span class="nb">split</span>, splitormerge_enabled, splitormerge_switch, trace, unassign, wal_roll, zk_dump Group name: replication Commands: add_peer, append_peer_tableCFs, disable_peer, disable_table_replication, enable_peer, enable_table_replication, get_peer_config, list_peer_configs, list_peers, list_replicated_tables, remove_peer, remove_peer_tableCFs, set_peer_tableCFs, show_peer_tableCFs Group name: snapshots Commands: clone_snapshot, delete_all_snapshot, delete_snapshot, delete_table_snapshots, list_snapshots, list_table_snapshots, restore_snapshot, snapshot Group name: configuration Commands: update_all_config, update_config Group name: quotas Commands: list_quotas, set_quota Group name: security Commands: grant, list_security_capabilities, revoke, user_permission Group name: procedures Commands: abort_procedure, list_procedures Group name: visibility labels Commands: add_labels, clear_auths, get_auths, list_labels, set_auths, set_visibility SHELL USAGE: Quote all names <span class="k">in </span>HBase Shell such as table and column names. Commas delimit <span class="nb">command </span>parameters. Type &lt;RETURN&gt; after entering a <span class="nb">command </span>to run it. Dictionaries of configuration used <span class="k">in </span>the creation and alteration of tables are Ruby Hashes. They look like this: <span class="o">{</span><span class="s1">'key1'</span> <span class="o">=&gt;</span> <span class="s1">'value1'</span>, <span class="s1">'key2'</span> <span class="o">=&gt;</span> <span class="s1">'value2'</span>, ...<span class="o">}</span> and are opened and closed with curley-braces. Key/values are delimited by the <span class="s1">'=&gt;'</span> character combination. Usually keys are predefined constants such as NAME, VERSIONS, COMPRESSION, etc. Constants <span class="k">do </span>not need to be quoted. Type <span class="s1">'Object.constants'</span> to see a <span class="o">(</span>messy<span class="o">)</span> list of all constants <span class="k">in </span>the environment. If you are using binary keys or values and need to enter them <span class="k">in </span>the shell, use double-quote<span class="s1">'d hexadecimal representation. For example: hbase&gt; get '</span>t1<span class="s1">', "key\x03\x3f\xcd" hbase&gt; get '</span>t1<span class="s1">', "key\003\023\011" hbase&gt; put '</span>t1<span class="s1">', "test\xef\xff", '</span>f1:<span class="s1">', "\x01\x33\x40" The HBase shell is the (J)Ruby IRB with the above HBase-specific commands added. For more on the HBase Shell, see http://hbase.apache.org/book.html </span></code></pre></div></div> <p>**3.查看当前数据库中有哪些表 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:002:0&gt; list TABLE 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.6870 seconds <span class="o">=&gt;</span> <span class="o">[]</span> </code></pre></div></div> <h1 id="2-表的操作">2. 表的操作</h1> <p>**1.创建表 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:003:0&gt; create <span class="s1">'student'</span>,<span class="s1">'info'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>2.6380 seconds <span class="o">=&gt;</span> Hbase::Table - student hbase<span class="o">(</span>main<span class="o">)</span>:005:0&gt; create <span class="s1">'stu'</span>,<span class="s1">'info1'</span>,<span class="s1">'info2'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>2.3180 seconds <span class="o">=&gt;</span> Hbase::Table - stu hbase<span class="o">(</span>main<span class="o">)</span>:006:0&gt; list TABLE stu student 2 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0240 seconds <span class="o">=&gt;</span> <span class="o">[</span><span class="s2">"stu"</span>, <span class="s2">"student"</span><span class="o">]</span> hbase<span class="o">(</span>main<span class="o">)</span>:011:0&gt; create_namespace <span class="s1">'mhk'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>1.2260 seconds hbase<span class="o">(</span>main<span class="o">)</span>:012:0&gt; list_namespace NAMESPACE default hbase mhk 3 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.1410 seconds hbase<span class="o">(</span>main<span class="o">)</span>:013:0&gt; create <span class="s1">'mhk:stu'</span>,<span class="s1">'info'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>2.2640 seconds <span class="o">=&gt;</span> Hbase::Table - mhk:stu hbase<span class="o">(</span>main<span class="o">)</span>:014:0&gt; list TABLE mhk:stu stu student 3 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0250 seconds <span class="o">=&gt;</span> <span class="o">[</span><span class="s2">"mhk:stu"</span>, <span class="s2">"stu"</span>, <span class="s2">"student"</span><span class="o">]</span> </code></pre></div></div> <p>**2.插入数据到表 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:003:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1001'</span>,<span class="s1">'info:sex'</span>,<span class="s1">'male'</span> hbase<span class="o">(</span>main<span class="o">)</span>:004:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1001'</span>,<span class="s1">'info:age'</span>,<span class="s1">'18'</span> hbase<span class="o">(</span>main<span class="o">)</span>:005:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1002'</span>,<span class="s1">'info:name'</span>,<span class="s1">'Janna'</span> hbase<span class="o">(</span>main<span class="o">)</span>:006:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1002'</span>,<span class="s1">'info:sex'</span>,<span class="s1">'female'</span> hbase<span class="o">(</span>main<span class="o">)</span>:007:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1002'</span>,<span class="s1">'info:age'</span>,<span class="s1">'20'</span> </code></pre></div></div> <p>**3.扫描查看表数据 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:019:0&gt; scan <span class="s1">'student'</span> ROW COLUMN+CELL 1001 <span class="nv">column</span><span class="o">=</span>info:age, <span class="nv">timestamp</span><span class="o">=</span>1649732463800, <span class="nv">value</span><span class="o">=</span>18 1001 <span class="nv">column</span><span class="o">=</span>info:sex, <span class="nv">timestamp</span><span class="o">=</span>1649729775042, <span class="nv">value</span><span class="o">=</span>male 1002 <span class="nv">column</span><span class="o">=</span>info:age, <span class="nv">timestamp</span><span class="o">=</span>1649732716233, <span class="nv">value</span><span class="o">=</span>20 1002 <span class="nv">column</span><span class="o">=</span>info:name, <span class="nv">timestamp</span><span class="o">=</span>1649732473758, <span class="nv">value</span><span class="o">=</span>Janna 1002 <span class="nv">column</span><span class="o">=</span>info:sex, <span class="nv">timestamp</span><span class="o">=</span>1649732706857, <span class="nv">value</span><span class="o">=</span>female 2 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.1560 seconds hbase<span class="o">(</span>main<span class="o">)</span>:020:0&gt; scan <span class="s1">'student'</span>,<span class="o">{</span>STARTROW <span class="o">=&gt;</span> <span class="s1">'1001'</span>, STOPROW <span class="o">=&gt;</span> <span class="s1">'1001'</span><span class="o">}</span> ROW COLUMN+CELL 1001 <span class="nv">column</span><span class="o">=</span>info:age, <span class="nv">timestamp</span><span class="o">=</span>1649732463800, <span class="nv">value</span><span class="o">=</span>18 1001 <span class="nv">column</span><span class="o">=</span>info:sex, <span class="nv">timestamp</span><span class="o">=</span>1649729775042, <span class="nv">value</span><span class="o">=</span>male 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0550 seconds 左闭右开 hbase<span class="o">(</span>main<span class="o">)</span>:008:0&gt; scan <span class="s1">'student'</span>,<span class="o">{</span>STARTROW <span class="o">=&gt;</span> <span class="s1">'1001'</span>,STOPROW <span class="o">=&gt;</span><span class="s1">'1002'</span><span class="o">}</span> ROW COLUMN+CELL 1001 <span class="nv">column</span><span class="o">=</span>info:age, <span class="nv">timestamp</span><span class="o">=</span>1649738076812, <span class="nv">value</span><span class="o">=</span>18 1001 <span class="nv">column</span><span class="o">=</span>info:sex, <span class="nv">timestamp</span><span class="o">=</span>1649738063585, <span class="nv">value</span><span class="o">=</span>male 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.2100 seconds hbase<span class="o">(</span>main<span class="o">)</span>:021:0&gt; scan <span class="s1">'student'</span>,<span class="o">{</span>STARTROW <span class="o">=&gt;</span> <span class="s1">'1001'</span><span class="o">}</span> ROW COLUMN+CELL 1001 <span class="nv">column</span><span class="o">=</span>info:age, <span class="nv">timestamp</span><span class="o">=</span>1649732463800, <span class="nv">value</span><span class="o">=</span>18 1001 <span class="nv">column</span><span class="o">=</span>info:sex, <span class="nv">timestamp</span><span class="o">=</span>1649729775042, <span class="nv">value</span><span class="o">=</span>male 1002 <span class="nv">column</span><span class="o">=</span>info:age, <span class="nv">timestamp</span><span class="o">=</span>1649732716233, <span class="nv">value</span><span class="o">=</span>20 1002 <span class="nv">column</span><span class="o">=</span>info:name, <span class="nv">timestamp</span><span class="o">=</span>1649732473758, <span class="nv">value</span><span class="o">=</span>Janna 1002 <span class="nv">column</span><span class="o">=</span>info:sex, <span class="nv">timestamp</span><span class="o">=</span>1649732706857, <span class="nv">value</span><span class="o">=</span>female 2 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.2290 seconds </code></pre></div></div> <p>**4.查看表结构 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:001:0&gt; describe <span class="s1">'student'</span> Table student is ENABLED student COLUMN FAMILIES DESCRIPTION <span class="o">{</span>NAME <span class="o">=&gt;</span> <span class="s1">'info'</span>, BLOOMFILTER <span class="o">=&gt;</span> <span class="s1">'ROW'</span>, VERSIONS <span class="o">=&gt;</span> <span class="s1">'1'</span>, IN_MEMORY <span class="o">=&gt;</span> <span class="s1">'false'</span>, KEEP_DELETED_CELLS <span class="o">=&gt;</span> <span class="s1">'FA LSE'</span>, DATA_BLOCK_ENCODING <span class="o">=&gt;</span> <span class="s1">'NONE'</span>, TTL <span class="o">=&gt;</span> <span class="s1">'FOREVER'</span>, COMPRESSION <span class="o">=&gt;</span> <span class="s1">'NONE'</span>, MIN_VERSIONS <span class="o">=&gt;</span> <span class="s1">'0'</span>, BLOC KCACHE <span class="o">=&gt;</span> <span class="s1">'true'</span>, BLOCKSIZE <span class="o">=&gt;</span> <span class="s1">'65536'</span>, REPLICATION_SCOPE <span class="o">=&gt;</span> <span class="s1">'0'</span><span class="o">}</span> 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>1.0330 seconds </code></pre></div></div> <p>**5.更新指定字段的数据 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:022:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1001'</span>,<span class="s1">'info:name'</span>,<span class="s1">'Nick'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0470 seconds hbase<span class="o">(</span>main<span class="o">)</span>:023:0&gt; put <span class="s1">'student'</span>,<span class="s1">'1001'</span>,<span class="s1">'info:age'</span>,<span class="s1">'100'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0330 seconds </code></pre></div></div> <p>**6.查看“指定行”或“指定列族:列”的数据 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:002:0&gt; get <span class="s1">'student'</span>,<span class="s1">'1001'</span> COLUMN CELL info:age <span class="nv">timestamp</span><span class="o">=</span>1649733079650, <span class="nv">value</span><span class="o">=</span>100 info:name <span class="nv">timestamp</span><span class="o">=</span>1649733071082, <span class="nv">value</span><span class="o">=</span>Nick info:sex <span class="nv">timestamp</span><span class="o">=</span>1649729775042, <span class="nv">value</span><span class="o">=</span>male 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.3120 seconds hbase<span class="o">(</span>main<span class="o">)</span>:003:0&gt; get <span class="s1">'student'</span>,<span class="s1">'1001'</span>,<span class="s1">'info:name'</span> COLUMN CELL info:name <span class="nv">timestamp</span><span class="o">=</span>1649733071082, <span class="nv">value</span><span class="o">=</span>Nick 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0660 seconds </code></pre></div></div> <p>**7.统计表数据行数 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:004:0&gt; count <span class="s1">'student'</span> 2 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0860 seconds <span class="o">=&gt;</span> 2 </code></pre></div></div> <p>**8.删除数据 ** 删除某 rowkey 的全部数据:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> deleteall <span class="s1">'student'</span>,<span class="s1">'1001'</span> </code></pre></div></div> <p>删除某 rowkey 的某一列数据:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:006:0&gt; delete <span class="s1">'student'</span>,<span class="s1">'1002'</span>,<span class="s1">'info:sex'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0570 seconds </code></pre></div></div> <p>**9.清空表数据 **</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:007:0&gt; <span class="nb">truncate</span> <span class="s1">'student'</span> Truncating <span class="s1">'student'</span> table <span class="o">(</span>it may take a <span class="k">while</span><span class="o">)</span>: - Disabling table... - Truncating table... 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>4.7150 seconds </code></pre></div></div> <p>提示:清空表的操作顺序为先 disable,然后再 truncate。</p> <p>**10.删除表 ** 首先需要先让该表为 disable 状态:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:008:0&gt; disable <span class="s1">'student'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>2.3030 seconds </code></pre></div></div> <p>然后才能 drop 这个表:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:009:0&gt; drop <span class="s1">'student'</span> 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>1.3620 seconds </code></pre></div></div> <p>提示:如果直接 drop 表,会报错:ERROR: Table student is enabled. Disable it first.</p> <p>**11.变更表信息 ** 将 info 列族中的数据存放 3 个版本:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hbase<span class="o">(</span>main<span class="o">)</span>:001:0&gt; describe <span class="s1">'student'</span> Table student is ENABLED student COLUMN FAMILIES DESCRIPTION <span class="o">{</span>NAME <span class="o">=&gt;</span> <span class="s1">'info'</span>, BLOOMFILTER <span class="o">=&gt;</span> <span class="s1">'ROW'</span>, VERSIONS <span class="o">=&gt;</span> <span class="s1">'1'</span>, IN_MEMORY <span class="o">=&gt;</span> <span class="s1">'false'</span>, KEEP_DELETED_CELLS <span class="o">=&gt;</span> <span class="s1">'FA LSE'</span>, DATA_BLOCK_ENCODING <span class="o">=&gt;</span> <span class="s1">'NONE'</span>, TTL <span class="o">=&gt;</span> <span class="s1">'FOREVER'</span>, COMPRESSION <span class="o">=&gt;</span> <span class="s1">'NONE'</span>, MIN_VERSIONS <span class="o">=&gt;</span> <span class="s1">'0'</span>, BLOC KCACHE <span class="o">=&gt;</span> <span class="s1">'true'</span>, BLOCKSIZE <span class="o">=&gt;</span> <span class="s1">'65536'</span>, REPLICATION_SCOPE <span class="o">=&gt;</span> <span class="s1">'0'</span><span class="o">}</span> 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>1.1010 seconds hbase<span class="o">(</span>main<span class="o">)</span>:002:0&gt; alter <span class="s1">'student'</span>,<span class="o">{</span><span class="nv">NAME</span><span class="o">=&gt;</span><span class="s1">'info'</span>,VERSIONS<span class="o">=&gt;</span>3<span class="o">}</span> Updating all regions with the new schema... 0/1 regions updated. 1/1 regions updated. Done. 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>3.4050 seconds hbase<span class="o">(</span>main<span class="o">)</span>:003:0&gt; describe <span class="s1">'student'</span> Table student is ENABLED student COLUMN FAMILIES DESCRIPTION <span class="o">{</span>NAME <span class="o">=&gt;</span> <span class="s1">'info'</span>, BLOOMFILTER <span class="o">=&gt;</span> <span class="s1">'ROW'</span>, VERSIONS <span class="o">=&gt;</span> <span class="s1">'3'</span>, IN_MEMORY <span class="o">=&gt;</span> <span class="s1">'false'</span>, KEEP_DELETED_CELLS <span class="o">=&gt;</span> <span class="s1">'FA LSE'</span>, DATA_BLOCK_ENCODING <span class="o">=&gt;</span> <span class="s1">'NONE'</span>, TTL <span class="o">=&gt;</span> <span class="s1">'FOREVER'</span>, COMPRESSION <span class="o">=&gt;</span> <span class="s1">'NONE'</span>, MIN_VERSIONS <span class="o">=&gt;</span> <span class="s1">'0'</span>, BLOC KCACHE <span class="o">=&gt;</span> <span class="s1">'true'</span>, BLOCKSIZE <span class="o">=&gt;</span> <span class="s1">'65536'</span>, REPLICATION_SCOPE <span class="o">=&gt;</span> <span class="s1">'0'</span><span class="o">}</span> 1 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.0530 seconds hbase<span class="o">(</span>main<span class="o">)</span>:004:0&gt; get <span class="s1">'student'</span>,<span class="s1">'1001'</span>,<span class="o">{</span><span class="nv">COLUMN</span><span class="o">=&gt;</span><span class="s1">'info:name'</span>,VERSIONS<span class="o">=&gt;</span>3<span class="o">}</span> COLUMN CELL 0 row<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.3190 seconds </code></pre></div></div> HBase简介 https://muhaokang.github.io/HBase-%E7%AE%80%E4%BB%8B/ https://muhaokang.github.io/HBase-%E7%AE%80%E4%BB%8B/ Tue, 12 Apr 2022 00:00:00 +0800 hkmu9248@gmail.com (Haokang Mu) 1. HBase定义 HBase 是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库。 2. HBase数据模型 逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。 但从 HBase 的底层物理存储结构(K-V)来看,HBase 更像是一个 multi-dimensional map。 2.1 HBase逻辑结... <h1 id="1-hbase定义">1. HBase定义</h1> <p>HBase 是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库。</p> <h1 id="2-hbase数据模型">2. HBase数据模型</h1> <p>逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。 但从 HBase 的底层物理存储结构(K-V)来看,HBase 更像是一个 multi-dimensional map。</p> <h2 id="21-hbase逻辑结构">2.1 HBase逻辑结构</h2> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649695211846-23fa490f-08fd-406f-b0f5-aeb419e64306.png#clientId=u66838846-09bb-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=470&amp;id=uade5d78a&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=470&amp;originWidth=957&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=107174&amp;status=done&amp;style=none&amp;taskId=u9074c8d7-9db6-47d5-9598-e3b08de6334&amp;title=&amp;width=957" alt="image.png" /></p> <h2 id="22-hbase-物理存储结构">2.2 HBase 物理存储结构</h2> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649695269124-cd97064c-ae89-4a2b-b67a-941ed697a1cf.png#clientId=u66838846-09bb-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=449&amp;id=u73676993&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=449&amp;originWidth=938&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=94146&amp;status=done&amp;style=none&amp;taskId=u389806e5-8ac3-4fe2-b5c7-838b64c55f7&amp;title=&amp;width=938" alt="image.png" /></p> <h2 id="23-数据模型">2.3 数据模型</h2> <p>**1)Name Space ** 命名空间,类似于关系型数据库的 DatabBase 概念,每个命名空间下有多个表。HBase 有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表, default 表是用户默认使用的命名空间。</p> <p>**2)Region ** 类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往 HBase 写入数据时,字段可以动态、按需指定。因此,和关 系型数据库相比,HBase 能够轻松应对字段变更的场景。</p> <p><strong>3)Row ** HBase 表中的每行数据都由一个 **RowKey **和多个 **Column</strong>(列)组成,数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重 要。</p> <p>**4)Column ** HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。</p> <p>**5)Time Stamp ** 用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会 自动为其加上该字段,其值为写入 HBase 的时间。</p> <p>**6)Cell ** 由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存贮。</p> <h1 id="3-hbase基本架构">3. HBase基本架构</h1> <p><img src="https://cdn.nlark.com/yuque/0/2022/png/25452040/1649695459866-22e28095-8586-478e-a1d9-6eaaf1cfb697.png#clientId=u66838846-09bb-4&amp;crop=0&amp;crop=0&amp;crop=1&amp;crop=1&amp;from=paste&amp;height=480&amp;id=uc47cd4b4&amp;margin=%5Bobject%20Object%5D&amp;name=image.png&amp;originHeight=480&amp;originWidth=962&amp;originalType=binary&amp;ratio=1&amp;rotation=0&amp;showTitle=false&amp;size=82002&amp;status=done&amp;style=none&amp;taskId=u2dc2fe54-50d9-4519-877c-510f7bf8d2b&amp;title=&amp;width=962" alt="image.png" /></p> <p>**架构角色: ** **1)Region Server ** Region Server 为 Region 的管理者,其实现类为 HRegionServer,主要作用如下: 对于数据的操作:get, put, delete; 对于 Region 的操作:splitRegion、compactRegion。</p> <p>**2)Master ** Master 是所有 Region Server 的管理者,其实现类为 HMaster,主要作用如下: 对于表的操作:create, delete, alter 对于 RegionServer的操作:分配 regions到每个RegionServer,监控每个 RegionServer的状态,负载均衡和故障转移。</p> <p>**3)Zookeeper ** HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。</p> <p>**4)HDFS ** HDFS 为 HBase 提供最终的底层数据存储服务,同时为 HBase 提供高可用的支持。</p>