星爷的博客
2022-11-15T15:54:30+00:00
http://www.readingnotes.site
星爷
longxingwei@gmail.com
Hive On Spark 的一些经验
2019-05-06T00:00:00+00:00
http://www.readingnotes.site/posts/Hive On Spark 的一些经验
<p>Hive 在现在数仓中被广泛使用,然而,Hive On MR 速度太慢,随着Spark SQL的兴起,似乎Spark SQL 才是未来。但对很多公司来说,由于历史包袱,无法将HQL 编写的任务轻松迁移到Spark SQL,在这种情况下,Cloudera 发起了Hive On Spark,将 Spark 作为Hive的计算引擎,提高Hive的查询性能,同时,兼容了老的HQL 任务。</p>
<p>在我们的生产环境中,采用了Hive On Spark 作为 Hive的计算引擎,下面是一些经验值,主要是Yarn 和 Spark 相关。</p>
<p><strong>假设集群节点16核64G</strong>。</p>
<h1 id="yarn">Yarn</h1>
<p>Yarn 集群主要需要设置分配和计算的 cores 数 和内存大小。一般会预留一部分资源给操作系统、DataNode、NodeManager,在这里我们设置:</p>
<ul>
<li>yarn.nodemanager.resource.cpu-vcores=12</li>
<li>yarn.nodemanager.resource.memory-mb=48G</li>
</ul>
<h1 id="spark">Spark</h1>
<h2 id="driver">Driver</h2>
<p>Driver 需要配置内存相关的两个参数:</p>
<ul>
<li>spark.driver.memory:spark driver 能申请的最大JVM 堆内存</li>
<li>spark.driver.memoryOverhead:spark driver 从yarn 申请的堆外内存</li>
</ul>
<p>这俩参数共同决定了 driver 的内存大小,一般来说,driver 内存大小并不直接影响性能,根据网上经验来看(非亲测),假设yarn.nodemanager.resource.memory-mb=M,driver 内存位N</p>
<ul>
<li>M > 50G,则 N = 12G</li>
<li>12G < M <= 50G, 则 N=4G</li>
<li>1G < M <=12G,则N=1G</li>
<li>M <= 1G,则 N=256M</li>
</ul>
<p>在我们的场景中,设置N为4G,设置spark.driver.memoryOverhead=500Mb,spark.driver.memory=3.5G</p>
<h2 id="executor">Executor</h2>
<h3 id="spark-executor-内存与cpu">Spark Executor 内存与CPU</h3>
<p>Executor 设置包括内存和CPU。</p>
<p>HDFS 在某些情况下无法很好的处理并发写,所以,过多的 core 数,可能导致竞争。同时,executor的内存,内存大的时候可以减少OOM,但同时,也会增加 GC 压力。</p>
<p>一般推荐将 core 设置为4、5、6,由于48可以被4整除,此时,每个node 可以分配到3个executor,每个execuror 可以分配到16G内存。设置参数:</p>
<ul>
<li>spark.executor.cores=4</li>
</ul>
<p>内存分堆内内存和堆外内存,一般建议堆外内存占15%到20%。我们设置</p>
<ul>
<li>spark.executor.memory=12G</li>
<li>spark.executor.memoryOverhead=4G</li>
</ul>
<p>这两个参数只是我们设置的平台级别的参数,每个Spark 任务可以自行调整相应参数。</p>
<h3 id="spark-executor-动态资源申请">Spark Executor 动态资源申请</h3>
<p>对于开发人员来说,并不清楚需要申请多少资源,所以,并不推荐在提交任务时指定executor-number 数量,推荐的做法是动态申请。</p>
<p>现在来看,动态资源申请并不是一个可选项,而是必选项,一是有效利用资源,二是,Spark 与 MR 不同,MR 用完资源就释放了,而Spark 在会话终止前都不会释放(比如在命令行里跑Spark SQL,不从终端退出,是不释放资源的,即使任务已经跑完了),静态资源分配会导致后面的任务无资源可用,即使在集群空闲的情况下。</p>
<blockquote>
<p>spark.dynamicAllocation.enabled = true</p>
<p>spark.dynamicAllocation.minExecutors = 1</p>
<p>spark.dynamicAllocation.maxExecutors = 1000</p>
<p>spark.dynamicAllocation.executorIdleTimeout = 100s</p>
<p>spark.dynamicAllocation.cachedExecutorIdleTimeout = 600s</p>
</blockquote>
<p>注意,在开启动态资源申请时,一定要开启 Spark Shuffle Service。</p>
<h1 id="其他">其他</h1>
<p>另外,推荐另一篇<a href="https://mp.weixin.qq.com/s/ITwwTDkWVwToshjHQEp5Dg">博文</a> 。发现我要写的他都几乎写了,我没用到的,他也写了。</p>
<p>博客评论要FQ,可以公众号交流。</p>
<p><img src="../../wxqr.jpg" alt="follow me" /></p>
Flink(7)——flink standalone ha cluster 安装与配置详解
2019-03-18T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(7)——Flink Standalone HA Cluster 安装与配置详解
<p>本文基于 Flink 1.7。</p>
<p>本文介绍搭建standalone HA 集群的过程,并且简单介绍核心配置。</p>
<h2 id="requirements">Requirements</h2>
<ul>
<li>Java 1.8+</li>
<li>ssh</li>
</ul>
<p>注意:集群之间所有节点 无密SSH跳转与保持相同的目录结构将有助于使用Flink 提供的脚本。</p>
<h2 id="版本选择">版本选择</h2>
<p><a href="https://flink.apache.org/downloads.html">下载</a> 合适版本的Flink安装包。Flink 并不要求一定要用到Hadoop 生态组件,如果要用到Hadoop 生态组件,建议下载于Hadoop 绑定的版本,在我们的场景中,用的是Hadoop2.6,所以这里选择<code class="language-plaintext highlighter-rouge">Apache Flink 1.7.2 with Hadoop® 2.6</code>。一般来说,Flink 的运行模式Yarn、以及savepoint/checkpoint 会用到HDFS,所以,采用与Hadoop 绑定的版本更常见一点。</p>
<h2 id="配置flink">配置Flink</h2>
<p>我们这里配置Flink Standalone HA集群,集群包含5台机器,分别是testhadoop[1-5],其中testhadoop[1-2]上运行Jobmanager,testhadoop[3-5]上运行Taskmanager。</p>
<p>配置 <strong>./conf/masters</strong> 文件,配置了Jobmanager,其中8081端口是Flink 的Rest 端口,该端口在下面的 ./conf/flink-conf.yaml 中配置。</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat </span>masters
testhadoop1:8081
testhadoop2:8081
</code></pre></div></div>
<p>配置 <strong>./conf/slaves</strong> 文件,配置Taskmanagers 地址。</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat </span>slaves
testhadoop3
testhadoop4
testhadoop5
</code></pre></div></div>
<p>配置 <strong>./conf/flink-conf.yaml</strong> 文件,注意,下面仅列出了对配置文件的更改,主要是HA方面的,该配置文件还有一些默认配置没有列出,具体的可以参考配置文件。</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">jobmanager.rpc.address=testhadoop1 // Standalone HA Cluster模式下会被masters文件覆盖</span>
<span class="na">high-availability</span><span class="pi">:</span> <span class="s">zookeeper</span>
<span class="s">high-availability.zookeeper.path.root</span><span class="pi">:</span> <span class="s">/flink_test</span>
<span class="s">high-availability.cluster-id</span><span class="pi">:</span> <span class="s">cluster_test</span>
<span class="s">high-availability.storageDir</span><span class="pi">:</span> <span class="s">hdfs:///flink_test/ha/</span>
<span class="s">high-availability.zookeeper.quorum</span><span class="pi">:</span> <span class="s">testhadoop1:2181</span>
</code></pre></div></div>
<h2 id="启动集群">启动集群</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/start-cluster.sh
</code></pre></div></div>
<p><img src="../../assets/img/2019/flink-standalone-ha-start.png" alt="启动HA" /></p>
<p>可以看到,我们启动了一个HA集群,集群有2个master节点,分别位于testhadoop[1-2]上,启动了<code class="language-plaintext highlighter-rouge">standalonesession</code> 守护进程 ; 在testhadoop[3-5] 上,启动了<code class="language-plaintext highlighter-rouge">taskexecutor</code>守护进程。我们用浏览器打开testhadoop1:8081 ,如下图所示:</p>
<p><img src="../../assets/img/2019/flink-standalone-ha-overview.png" alt="overview" /></p>
<h2 id="配置说明">配置说明</h2>
<p>下表介绍 <code class="language-plaintext highlighter-rouge">./conf/flink-conf.yaml</code> 中重要的配置项,具体完整版参考<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/ops/config.html">配置页面</a>。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>默认值</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>jobmanager.rpc.address</td>
<td>localhost</td>
<td style="text-align: left">仅 standalone 模式需要,表明jobmanager 所在host,如果是HA集群,用./bin/start_cluster.sh 时,该配置会被master文件覆盖。</td>
</tr>
<tr>
<td>jobmanager.heap.size</td>
<td>1024m</td>
<td style="text-align: left">Jobmanager的JVM heap size</td>
</tr>
<tr>
<td>taskmanager.heap.size</td>
<td>1024m</td>
<td style="text-align: left">Taskmanager的JVM heap size</td>
</tr>
<tr>
<td>taskmanager.numberOfTaskSlots</td>
<td>1</td>
<td style="text-align: left">每个Taskmanager的slot数,每个slot 可以运行一个并行pipeline。如果该值大于1,则可以运行多个并行pipeline。优点是可以充分利用多核CPU,缺点是各个slot会平分可用的内存,通常来说,这个值被设置为机器的物理CPU 核数。</td>
</tr>
<tr>
<td>parallelism.default</td>
<td>1</td>
<td style="text-align: left">Flink 任务默认的并行度</td>
</tr>
<tr>
<td>high-availability</td>
<td>“NONE”</td>
<td style="text-align: left">默认为NONE,但这并不适用于生产,所以,需要用zookeeper,</td>
</tr>
<tr>
<td>high-availability.zookeeper.path.root</td>
<td> </td>
<td style="text-align: left">Flink 在 zookeeper中存储的root 地址</td>
</tr>
<tr>
<td>high-availability.cluster-id</td>
<td>“/default”</td>
<td style="text-align: left">Flink 集群的ID,用于区分不同集群,仅standalone 模式需要,Yarn/Mesos 可以自动推断。</td>
</tr>
<tr>
<td>high-availability.storageDir</td>
<td> </td>
<td style="text-align: left">HA 模式下,Flink 存储元数据的文件系统URI,我们常用HDFS 路径</td>
</tr>
<tr>
<td>high-availability.zookeeper.quorum</td>
<td> </td>
<td style="text-align: left">HA 用到的zookeeper的地址</td>
</tr>
<tr>
<td>rest.port</td>
<td> </td>
<td style="text-align: left">rest 端口</td>
</tr>
<tr>
<td>state.backend</td>
<td> </td>
<td style="text-align: left">checkpoint的后端存储,支持jobmanager、filesystem和rocksdb,常用rocksdb</td>
</tr>
<tr>
<td>state.checkpoints.dir</td>
<td> </td>
<td style="text-align: left">checkpoint 的存储目录,比如HDFS 目录</td>
</tr>
<tr>
<td>state.savepoints.dir</td>
<td> </td>
<td style="text-align: left">savepoint 的存储目录,比如HDFS 目录</td>
</tr>
</tbody>
</table>
<h2 id="总结">总结</h2>
<p>本文主要介绍Flink 的standalone 集群安装和配置,standalone 集群可以快速搭建,也能满足HA的要求。但是,在实际生产环境,可能更多的还是使用基于Yarn等资源管理器的Flink 集群。</p>
<p>到此,Flink 基础知识已经全部介绍,看到这里应该已经可以写一些简单的Flink 应用了。</p>
<p><a href="https://mp.weixin.qq.com/s/yQer9fQAyZXTaRdRhEyCDQ">Flink(0)——基于flink的流计算</a></p>
<p><a href="https://mp.weixin.qq.com/s/8ICLIEzuGvDuzgOddXwTGg">Flink(1)——基于flink sql的流计算平台设计</a></p>
<p><a href="https://mp.weixin.qq.com/s/4ySScrUpXTJoCtRP0feitg">Flink(2)——Apache Flink 介绍</a></p>
<p><a href="https://mp.weixin.qq.com/s/sJa3yGENCaDHd-LHz5jFsg">Flink(3)——Event Time 与 Watermark</a></p>
<p><a href="https://mp.weixin.qq.com/s/jvveJR99vKQ11Jlr9mkMqA">Flink(4)——Source 介绍与实践</a></p>
<p><a href="https://mp.weixin.qq.com/s/_W5uws9lFQn61VS7manFRQ">Flink(5)——sink 介绍与实践</a></p>
<p><a href="https://mp.weixin.qq.com/s/KZckw0wCuRt2O_NhmwbFkA">Flink(6)——flink table & sql 介绍</a></p>
<p><a href="https://mp.weixin.qq.com/s/es59kyYtiErllUlbXW1U7Q">Flink(7)——Flink Standalone Cluster 安装与配置详解</a></p>
Hive的数据抽样
2019-03-12T00:00:00+00:00
http://www.readingnotes.site/posts/Hive的数据抽样
<p>最近在做Hive的数据抽样,基于以下考虑:</p>
<ul>
<li>效率:数据量大的时候,可以给Hive 的使用者提供抽样数据,供他们开发、测试,提高效率。</li>
<li>安全:有些场景,不便于提供全量数据给开发者,但是又不能影响建模效果,这时,就需要随机抽样数据给开发者。</li>
</ul>
<p>要求:</p>
<ul>
<li>随机</li>
<li>抽取数据量可控</li>
<li>分区:分区信息需要保留,数据整体随机,分区内也要随机</li>
</ul>
<h1 id="1-抽样方案">1. 抽样方案</h1>
<h2 id="11-方案一block-sampling">1.1 方案一:Block Sampling</h2>
<p>Hive 本身提供了抽样函数,使用TABLESAMPLE 抽取指定的 <strong>行数/比例/大小</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">XXX</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span> <span class="n">TABLESAMPLE</span><span class="p">(</span><span class="mi">1000</span> <span class="k">ROWS</span><span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">XXX</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span> <span class="n">TABLESAMPLE</span> <span class="p">(</span><span class="mi">20</span> <span class="n">PERCENT</span><span class="p">);</span> <span class="o">//</span> <span class="err">测试未生效</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">XXX</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span> <span class="n">TABLESAMPLE</span><span class="p">(</span><span class="mi">1</span><span class="n">M</span><span class="p">);</span> <span class="o">//</span> <span class="err">测试未生效</span>
</code></pre></div></div>
<p>缺点:<strong>不随机</strong>。该方法实际上是按照文件中的顺序返回数据,对分区表,从头开始抽取,可能造成只有前面几个分区的数据。</p>
<p>优点:速度快。</p>
<h2 id="12-方案二分桶表抽样-smapling-bucketized-table">1.2 方案二:分桶表抽样 (Smapling Bucketized Table)</h2>
<p>利用分桶表,随机分到多个桶里,然后抽取指定的一个桶。举例:随机分到10个桶,抽取第一个桶。</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">XXX</span> <span class="k">AS</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span> <span class="n">TABLESAMPLE</span> <span class="p">(</span><span class="n">BUCKET</span> <span class="mi">1</span> <span class="k">OUT</span> <span class="k">OF</span> <span class="mi">10</span> <span class="k">ON</span> <span class="n">rand</span><span class="p">());</span>
</code></pre></div></div>
<p>优点:随机,测试发现,速度比方法3的<code class="language-plaintext highlighter-rouge">rand()</code>快。</p>
<h2 id="13-方案三随机抽样-rand">1.3 方案三:随机抽样 rand</h2>
<p>原理:利用<code class="language-plaintext highlighter-rouge">rand()</code>函数进行抽取,<code class="language-plaintext highlighter-rouge">rand()</code> 返回一个0到1之间的double 值。</p>
<p><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">XXX</span> <span class="k">AS</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">rand</span><span class="p">()</span>
<span class="k">limit</span> <span class="mi">10000</span>
</code></pre></div></div>
<p>此时,可以提供真正的随机抽样,但是,需要在单个<code class="language-plaintext highlighter-rouge">reducer</code>中进行总排序,<strong>速度慢</strong>。</p>
<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">XXX</span> <span class="k">AS</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span>
<span class="n">SORT</span> <span class="k">BY</span> <span class="n">rand</span><span class="p">()</span>
<span class="k">limit</span> <span class="mi">10000</span>
</code></pre></div></div>
<p>Hive 提供了<code class="language-plaintext highlighter-rouge">sort by</code>,<code class="language-plaintext highlighter-rouge">sort by</code> 提供了单个<code class="language-plaintext highlighter-rouge">reducer</code> 内的排序功能,但不保证整体有序,上面的语句是不保证随机性的。</p>
<p><strong>法3</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">XXX</span> <span class="k">AS</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span>
<span class="k">where</span> <span class="n">rand</span><span class="p">()</span><span class="o"><</span><span class="mi">0</span><span class="p">.</span><span class="mi">002</span>
<span class="n">distribute</span> <span class="k">by</span> <span class="n">rand</span><span class="p">()</span>
<span class="n">sort</span> <span class="k">by</span> <span class="n">rand</span><span class="p">()</span>
<span class="k">limit</span> <span class="mi">10000</span><span class="p">;</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">where</code> 条件首先进行一次<code class="language-plaintext highlighter-rouge">map</code> 端的优化,减少<code class="language-plaintext highlighter-rouge">reducer</code> 需要处理的数据量,提高速度。<code class="language-plaintext highlighter-rouge">distribute by</code> 将数据随机分布,然后在每个<code class="language-plaintext highlighter-rouge">reducer</code>内进行随机排序,最终取10000条数据(如果数据量不足,可以提高<code class="language-plaintext highlighter-rouge">where</code>条件的<code class="language-plaintext highlighter-rouge">rand</code>过滤值)</p>
<p>缺点:<strong>速度慢</strong></p>
<p>优点:随机</p>
<p><strong>法4</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">XXX</span> <span class="k">AS</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span>
<span class="k">where</span> <span class="n">rand</span><span class="p">()</span><span class="o"><</span><span class="mi">0</span><span class="p">.</span><span class="mi">002</span>
<span class="k">cluster</span> <span class="k">by</span> <span class="n">rand</span><span class="p">()</span>
<span class="k">limit</span> <span class="mi">10000</span><span class="p">;</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">cluster by</code> 的功能是 <code class="language-plaintext highlighter-rouge">distribute by</code> 和 <code class="language-plaintext highlighter-rouge">sort by</code>的功能相结合,在上面的例子中,<code class="language-plaintext highlighter-rouge">distribute by rand() sort by rand()</code> 进行了两次随机,<code class="language-plaintext highlighter-rouge">cluster by rand()</code> 仅一次随机,那么,会影响最终的抽样结果吗?</p>
<h1 id="2-分区">2. 分区</h1>
<p>但是,上面的方法,会<strong>丢失掉分区信息</strong>!</p>
<p>所以,需要结合动态分区:</p>
<ul>
<li>step1: create table</li>
<li>step2: 利用动态分区,插入select 出来的结果。</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span> <span class="n">hive</span><span class="p">.</span><span class="k">exec</span><span class="p">.</span><span class="k">dynamic</span><span class="p">.</span><span class="n">partition</span><span class="o">=</span><span class="k">true</span><span class="p">;</span>
<span class="k">set</span> <span class="n">hive</span><span class="p">.</span><span class="k">exec</span><span class="p">.</span><span class="k">dynamic</span><span class="p">.</span><span class="n">partition</span><span class="p">.</span><span class="k">mode</span><span class="o">=</span><span class="n">nonstrict</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="k">TABLE</span> <span class="n">XXX</span> <span class="n">partition</span><span class="p">(</span><span class="n">thedate</span><span class="p">)</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">YYY</span> <span class="n">TABLESAMPLE</span> <span class="p">(</span><span class="n">BUCKET</span> <span class="mi">1</span> <span class="k">OUT</span> <span class="k">OF</span> <span class="mi">10</span> <span class="k">ON</span> <span class="n">rand</span><span class="p">());</span>
</code></pre></div></div>
<p>博客评论被墙,关注公众号交流。</p>
<p><img src="../../wxqr.jpg" alt="" /></p>
Flink(6)——flink table & sql 介绍
2019-03-06T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(6)——Flink Table & SQL 介绍
<p>本文基于 Flink 1.7。</p>
<p>随着 Hadoop 的发展,有了Hive,使用HQL 即可完成原来繁琐的Map Reduce 程序。</p>
<p>随着 Spark的发展,引入了 Spark SQL。</p>
<p>随着 Flink 版本的更迭,Flink 也提供了Flink SQL,以及 Table APIs。</p>
<p>注意:截止 Flink 1.7,Table API 和 SQL 还没有完成,有些操作还不支持。</p>
<h1 id="1-基本概念">1. 基本概念</h1>
<h2 id="11-why">1.1 Why</h2>
<p>那么,为什么要推出Table APIs和SQL?</p>
<p>首先,来看下Flink 的编程模型。如下图所示(图片来源于官网),DataStream API 和 DataSet API 是分开的,但是对于应用开发者来说,为什么要关注这一点?对于相同的数据,批处理与流计算居然要写两套代码,简直不可思议。Table APIs和SQL的推出,实现了流处理和批处理的统一。<img src="../../assets/img/2018/flink-abstract.png" alt="" /></p>
<p>其次,降低了学习和使用门槛,基于 DataStream/DataSet APIs 的 Scala 或 Java 程序开发,对于BI/分析师来说,还是有一定门槛的,而SQL 则简单太多了。</p>
<h2 id="12-dynamic-tables">1.2 Dynamic Tables</h2>
<p>Dynamic Tables 是 Flink Table API 和 SQL的核心概念,与大家熟知的Static Tables 相比,Dynamic Tables 随着时间一直在变化。可以查询Dynamic Table,查询Dynamic Table 时会产生一个持续的查询,持续的查询不会终止,产生的结果也是Dynamic Table,根据输入,输出也会不断变化。熟悉关系型数据库的可以将Dynamic Tables的查询跟关系型数据库里查询物化视图对比起来,需要注意的是,Dynamic Tables 是一个逻辑概念,不需要(全部)物化。</p>
<p>另外,需要注意,在Dynamic Table上的持续查询的结果语义上是跟在Dynamic Table的快照上执行查询相同。</p>
<p><img src="../../assets/img/2019/flink_dynamic_table.png" alt="dynamic table & continuous query & stream" /></p>
<p>如上图所示:</p>
<ul>
<li>Stream 转化为 Dynamic Table</li>
<li>在Dynamic Table 上执行查询,得到的结果是一个新的Dynamic Table</li>
<li>最终的Dynamic Table 结果,被转化为 Stream</li>
</ul>
<h2 id="13-update-queries-vs-append-queries">1.3 Update Queries VS Append Queries</h2>
<p>Append Queries:只会对查询结果进行追加的查询。</p>
<p>Update Queries:会更新查询结果的查询,一般需要维护更多的state。</p>
<h2 id="14-查询限制">1.4 查询限制</h2>
<p>有些 Stream 上的查询需要花费巨大的代价:</p>
<ul>
<li>需要维护的state 太大。持续查询可能会运行非常长的时间,处理的数据量会非常大,对于一些需要更新原来结果的查询,需要维护原来的结果,维护的state会很大。</li>
<li>更新计算代价高昂:输入数据的一小点变化,可能有些查询需要重新计算大量的数据,这种计算就不适合做持续查询。</li>
</ul>
<h2 id="15-table-到-stream-的转化">1.5 Table 到 Stream 的转化</h2>
<p>就像普通的数据库Table 一样,Dynamic Table也支持 <code class="language-plaintext highlighter-rouge">insert</code>、<code class="language-plaintext highlighter-rouge">update</code>、<code class="language-plaintext highlighter-rouge">delete</code>等对它的更新。当需要将Dynamic Table 转化为 Stream 或者输出到外部系统时,需要对这些更新进行<code class="language-plaintext highlighter-rouge">encode</code>。</p>
<ul>
<li>Append-only Stream:仅有Insert 更新的Dynamic Table,可通过emit 插入的数据行转化为stream。</li>
<li>Retract Stream:Retract Stream 是支持 add 消息 和 retract 消息两类消息的流。将insert 编码为add 消息、将delete 编码为retract 消息、将update 编码为对之前消息的retarct 消息和对新消息的add消息。</li>
<li>Upsert Stream:Upsert Stream 是支持upsert 消息和delete消息两类消息的流。如果一个Dynamic Table需要转化为一个Upsert Stream,这个Table 必须要有<code class="language-plaintext highlighter-rouge">unique key</code>,可以将insert/update编码为upsert 消息,将delete 编码为delete消息。Upsert Stream 与 Retract Stream的主要区别是update 操作只需要一条消息,所以会更高效。</li>
</ul>
<p>Append-only Stream 和 Retarct Stream 支持将Dynamic Table 转化为DataStream。</p>
<h1 id="2-实战">2. 实战</h1>
<p>下面引入一个简单的例子,从stream开始,转化为 Table,然后查询Table,最后将Table 转化为Stream。</p>
<p>从例子可以很容易的看出,Stream 和 Table APIs / SQL 可以很容易的混用,这也给我们带来了极大的便利性。</p>
<h2 id="21-引入依赖">2.1 引入依赖</h2>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.apache.flink<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flink-table_2.11<span class="nt"></artifactId></span>
<span class="nt"><version></span>${flink.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="c"><!-- for batch query --></span>
<span class="nt"><groupId></span>org.apache.flink<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flink-scala_${scala.binary.version}<span class="nt"></artifactId></span>
<span class="nt"><version></span>${flink.version}<span class="nt"></version></span>
<span class="c"><!-- 上线时用provided,避免build的jar包太大,更避免冲突 --></span>
<span class="c"><!--<scope>provided</scope>--></span>
<span class="c"><!-- IDEA 里用compile,否则in-ide execution会失败 --></span>
<span class="nt"><scope></span>compile<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="c"><!-- for streaming query --></span>
<span class="nt"><groupId></span>org.apache.flink<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flink-streaming-scala_${scala.binary.version}<span class="nt"></artifactId></span>
<span class="nt"><version></span>${flink.version}<span class="nt"></version></span>
<span class="c"><!-- 上线时用provided,避免build的jar包太大,更避免冲突 --></span>
<span class="c"><!--<scope>provided</scope>--></span>
<span class="c"><!-- IDEA 里用compile,否则in-ide execution会失败 --></span>
<span class="nt"><scope></span>compile<span class="nt"></scope></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<h2 id="22-隐式转换">2.2 隐式转换</h2>
<p>Flink 的 Scala Table APIs用了隐式转换,所以,需要import 进来。</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">import</span> <span class="nn">org.apache.flink.table.api.scala._</span>
<span class="k">import</span> <span class="nn">org.apache.flink.api.scala._</span>
</code></pre></div></div>
<h2 id="23-创建-tableenvironment">2.3 创建 TableEnvironment</h2>
<p>TableEnvironment 是 Table APIs 和 SQL 的核心,可以用于:</p>
<ul>
<li>注册Table</li>
<li>执行SQL 查询</li>
<li>注册UDF</li>
<li>将DataStream / DataSet 转化为 Table</li>
<li>维护一个到ExecutionEnvironment或StreamExecutionEnvironment的引用。</li>
</ul>
<p>Table 总是绑定到一个 TableEnvironment的,在使用时,在同一个SQL中不能联合使用不同TableEnvironment的表,比如<code class="language-plaintext highlighter-rouge">join</code>或<code class="language-plaintext highlighter-rouge">union</code>。</p>
<p>下面创建一个用于Stream的StreamTableEnvironment。另外,创建一个简单的stream。</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// 创建StreamTableEnvironment</span>
<span class="k">val</span> <span class="nv">senv</span> <span class="k">=</span> <span class="nv">StreamExecutionEnvironment</span><span class="o">.</span><span class="py">getExecutionEnvironment</span>
<span class="k">val</span> <span class="nv">stableEnv</span><span class="k">:</span> <span class="kt">StreamTableEnvironment</span> <span class="o">=</span> <span class="nv">TableEnvironment</span><span class="o">.</span><span class="py">getTableEnvironment</span><span class="o">(</span><span class="n">senv</span><span class="o">)</span>
<span class="c1">// 创建一个用于实验的 Stream[ObjPrice]</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">ObjPrice</span> <span class="o">(</span><span class="n">name</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">price</span><span class="k">:</span> <span class="kt">Long</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">stream</span><span class="k">:</span> <span class="kt">DataStream</span><span class="o">[</span><span class="kt">ObjPrice</span><span class="o">]</span> <span class="k">=</span> <span class="nv">senv</span><span class="o">.</span><span class="py">fromCollection</span><span class="o">(</span><span class="nc">List</span><span class="o">(</span><span class="nc">ObjPrice</span><span class="o">(</span><span class="s">"car"</span><span class="o">,</span> <span class="mi">100000</span><span class="o">),</span> <span class="nc">ObjPrice</span><span class="o">(</span><span class="s">"house"</span><span class="o">,</span> <span class="mi">2000000</span><span class="o">),</span> <span class="nc">ObjPrice</span><span class="o">(</span><span class="s">"book"</span><span class="o">,</span> <span class="mi">100</span><span class="o">),</span> <span class="nc">ObjPrice</span><span class="o">(</span><span class="s">"car"</span><span class="o">,</span> <span class="mi">900210</span><span class="o">)))</span>
</code></pre></div></div>
<h2 id="24-将stream-转化为-table">2.4 将Stream 转化为 Table</h2>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">val</span> <span class="nv">sTable1Rename</span><span class="k">:</span> <span class="kt">Table</span> <span class="o">=</span> <span class="nv">stableEnv</span><span class="o">.</span><span class="py">fromDataStream</span><span class="o">(</span><span class="n">stream</span><span class="o">,</span> <span class="ss">'myName</span><span class="o">,</span> <span class="ss">'myPrice</span><span class="o">)</span>
</code></pre></div></div>
<p>将上面的stream 转化为 Table,同时对字段进行重命名。</p>
<h2 id="25-查询-table">2.5 查询 Table</h2>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// 采用Table API 的方式进行查询</span>
<span class="k">val</span> <span class="nv">sTableResult</span><span class="k">:</span> <span class="kt">Table</span> <span class="o">=</span> <span class="n">sTable1Rename</span>
<span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="ss">'myPrice</span> <span class="o">></span> <span class="mi">1000</span><span class="o">)</span>
<span class="o">.</span><span class="py">groupBy</span><span class="o">(</span><span class="ss">'myName</span><span class="o">)</span>
<span class="o">.</span><span class="py">select</span><span class="o">(</span><span class="ss">'myName</span><span class="o">,</span> <span class="ss">'myPrice</span><span class="o">.</span><span class="py">sum</span> <span class="n">as</span> <span class="ss">'mySumPrice</span><span class="o">)</span>
</code></pre></div></div>
<h2 id="26-将-table-转化为-stream">2.6 将 Table 转化为 Stream</h2>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">val</span> <span class="nv">sResultDataStream</span><span class="k">:</span> <span class="kt">DataStream</span><span class="o">[(</span><span class="kt">Boolean</span>, <span class="kt">ObjPrice</span><span class="o">)]</span> <span class="k">=</span> <span class="nv">stableEnv</span><span class="o">.</span><span class="py">toRetractStream</span><span class="o">[</span><span class="kt">ObjPrice</span><span class="o">](</span><span class="n">sTableResult</span><span class="o">)</span>
</code></pre></div></div>
<h1 id="3-总结">3. 总结</h1>
<p>本文仅涉及一些基础知识和最常见的使用,其他的比如注册 <code class="language-plaintext highlighter-rouge">Table</code> / <code class="language-plaintext highlighter-rouge">TableSink</code> / <code class="language-plaintext highlighter-rouge">TableSource</code> / <code class="language-plaintext highlighter-rouge">External Catalog</code> 、数据类型与Table Schema的映射、查询优化等并不涉及,可以参考<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.7/dev/table/common.html">官网</a> 进行查阅。</p>
<p>为了方便交流,请扫描下方二维码关注我。</p>
<p><img src="../../wxqr.jpg" alt="QR" /></p>
Flink(5)——sink 介绍与实践
2019-02-28T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(5)——Sink 介绍与实践
<p>本文基于Apache Flink 1.7。</p>
<p>结合上一篇文章,Source 是 Flink 程序的输入,Sink 就是 Flink 程序处理完Source后数据的输出,比如将输出写到文件、sockets、外部系统、或者仅仅是显示(在大数据生态中,很多类似的,比如Flume里也是对应的Source/Channel/Sink),Flink 提供了多种数据输出方式,下面逐一介绍。</p>
<h1 id="概念">概念</h1>
<h2 id="flink-预定义-sinks">Flink 预定义 Sinks</h2>
<ul>
<li>基于文件的:如 <code class="language-plaintext highlighter-rouge">writeAsText()</code>、<code class="language-plaintext highlighter-rouge">writeAsCsv()</code>、<code class="language-plaintext highlighter-rouge">writeUsingOutputFormat</code>、<code class="language-plaintext highlighter-rouge">FileOutputFormat</code>。</li>
<li>写到socket: <code class="language-plaintext highlighter-rouge">writeToSocket</code>。</li>
<li>用于显示的:<code class="language-plaintext highlighter-rouge">print</code>、<code class="language-plaintext highlighter-rouge">printToErr</code>。</li>
<li>自定义Sink: <code class="language-plaintext highlighter-rouge">addSink</code>。</li>
</ul>
<p>对于<code class="language-plaintext highlighter-rouge">write*</code> 来说,主要用于测试程序,Flink 没有实现这些方法的检查点机制,也就没有 <strong>exactly-once</strong> 支持。所以,为了保证 <strong>exactly-once</strong> ,需要使用 <strong>flink-connector-filesystem</strong>,同时,自定义的<code class="language-plaintext highlighter-rouge">addSink</code> 也可以支持。</p>
<h2 id="connectors">Connectors</h2>
<p>connectors 用于给接入第三方数据提供接口,现在支持的connectors 包括:</p>
<ul>
<li>Apache Kafka</li>
<li>Apache Cassandra</li>
<li>Elasticsearch</li>
<li>Hadoop FileSystem</li>
<li>RabbitMQ</li>
<li>Apache NiFi</li>
</ul>
<p>另外,通过 <a href="https://bahir.apache.org/">Apache Bahir</a>,可以支持Apache ActiveMQ、Apache Flume、Redis、Akka之类的Sink。</p>
<h2 id="容错">容错</h2>
<p>为了保证端到端的 <strong>exactly-once</strong>,Sink 需要实现checkpoint 机制,下图(图片来自于官网)所示的Sink 实现了这点。<img src="./../../assets/img/2019/flink-exactly-once-sink.png" alt="exactly-once sinks" />.</p>
<h1 id="实战">实战</h1>
<h2 id="elasticsearch-connector">Elasticsearch Connector</h2>
<p>下面我们将使用 Elasticsearch Connector 作为Sink 为例示范Sink的使用。Elasticsearch Connector 提供了<strong>at least once</strong> 语义支持,at lease once 支持需要用到Flink的checkpoint 机制。</p>
<p>要使用Elasticsearch Connector 需要根据Elasticsearch 版本添加依赖,如下图所示(图片来自官网)。<img src="../../assets/img/2019/flink-es-mvn.png" alt="es connector maven dependency" /></p>
<p>在这里,我们使用的Elasticsearch 版本是5.6.9,Scala 版本2.11。</p>
<p>添加如下依赖:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.apache.flink<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flink-connector-elasticsearch5_2.11<span class="nt"></artifactId></span>
<span class="nt"><version></span>${flink.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>先看ElasticsearchSink 源码,我们需要定义 ElasticsearchSinkFunction<T> 以及可选的 ActionRequestFailureHandler,ActionRequestFailureHandler 用来处理失败的请求。</T></p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">public</span> <span class="k">class</span> <span class="nc">ElasticsearchSink</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="k">extends</span> <span class="nc">ElasticsearchSinkBase</span><span class="o"><</span><span class="n">T</span><span class="o">,</span> <span class="nc">TransportClient</span><span class="o">></span> <span class="o">{</span>
<span class="k">private</span> <span class="n">static</span> <span class="k">final</span> <span class="n">long</span> <span class="n">serialVersionUID</span> <span class="k">=</span> <span class="mi">1L</span><span class="o">;</span>
<span class="n">public</span> <span class="nc">ElasticsearchSink</span><span class="o">(</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">userConfig</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">InetSocketAddress</span><span class="o">></span> <span class="n">transportAddresses</span><span class="o">,</span> <span class="nc">ElasticsearchSinkFunction</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="n">elasticsearchSinkFunction</span><span class="o">)</span> <span class="o">{</span>
<span class="nf">this</span><span class="o">(</span><span class="n">userConfig</span><span class="o">,</span> <span class="n">transportAddresses</span><span class="o">,</span> <span class="n">elasticsearchSinkFunction</span><span class="o">,</span> <span class="k">new</span> <span class="nc">NoOpFailureHandler</span><span class="o">());</span>
<span class="o">}</span>
<span class="n">public</span> <span class="nc">ElasticsearchSink</span><span class="o">(</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">userConfig</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">InetSocketAddress</span><span class="o">></span> <span class="n">transportAddresses</span><span class="o">,</span> <span class="nc">ElasticsearchSinkFunction</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="n">elasticsearchSinkFunction</span><span class="o">,</span> <span class="nc">ActionRequestFailureHandler</span> <span class="n">failureHandler</span><span class="o">)</span> <span class="o">{</span>
<span class="nf">super</span><span class="o">(</span><span class="k">new</span> <span class="nc">Elasticsearch5ApiCallBridge</span><span class="o">(</span><span class="n">transportAddresses</span><span class="o">),</span> <span class="n">userConfig</span><span class="o">,</span> <span class="n">elasticsearchSinkFunction</span><span class="o">,</span> <span class="n">failureHandler</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>下面看完整的例子:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">learn.sourcesAndsinks</span>
<span class="k">import</span> <span class="nn">java.net.</span><span class="o">{</span><span class="nc">InetAddress</span><span class="o">,</span> <span class="nc">InetSocketAddress</span><span class="o">}</span>
<span class="k">import</span> <span class="nn">java.util</span>
<span class="k">import</span> <span class="nn">org.apache.flink.api.common.functions.RuntimeContext</span>
<span class="k">import</span> <span class="nn">org.apache.flink.streaming.connectors.elasticsearch.</span><span class="o">{</span><span class="nc">ElasticsearchSinkFunction</span><span class="o">,</span> <span class="nc">RequestIndexer</span><span class="o">}</span>
<span class="k">import</span> <span class="nn">org.apache.flink.streaming.api.TimeCharacteristic</span>
<span class="k">import</span> <span class="nn">org.apache.flink.streaming.api.scala.</span><span class="o">{</span><span class="nc">StreamExecutionEnvironment</span><span class="o">,</span> <span class="k">_</span><span class="o">}</span>
<span class="k">import</span> <span class="nn">org.apache.flink.streaming.connectors.elasticsearch.util.IgnoringFailureHandler</span>
<span class="k">import</span> <span class="nn">org.apache.flink.streaming.connectors.elasticsearch5.ElasticsearchSink</span>
<span class="k">import</span> <span class="nn">org.elasticsearch.action.index.IndexRequest</span>
<span class="k">import</span> <span class="nn">org.elasticsearch.client.Requests</span>
<span class="k">object</span> <span class="nc">BasicSinks</span> <span class="o">{</span>
<span class="k">def</span> <span class="nf">main</span><span class="o">(</span><span class="n">args</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">String</span><span class="o">])</span><span class="k">:</span> <span class="kt">Unit</span> <span class="o">=</span> <span class="o">{</span>
<span class="k">val</span> <span class="nv">env</span> <span class="k">=</span> <span class="nv">StreamExecutionEnvironment</span><span class="o">.</span><span class="py">getExecutionEnvironment</span>
<span class="nv">env</span><span class="o">.</span><span class="py">setStreamTimeCharacteristic</span><span class="o">(</span><span class="nv">TimeCharacteristic</span><span class="o">.</span><span class="py">ProcessingTime</span><span class="o">)</span>
<span class="c1">// 定义stream </span>
<span class="k">val</span> <span class="nv">stream</span><span class="k">:</span> <span class="kt">DataStream</span><span class="o">[</span><span class="kt">String</span><span class="o">]</span> <span class="k">=</span> <span class="nv">env</span><span class="o">.</span><span class="py">fromCollection</span><span class="o">(</span><span class="nc">List</span><span class="o">(</span><span class="s">"aaa"</span><span class="o">,</span> <span class="s">"bbb"</span><span class="o">,</span> <span class="s">"ccc"</span><span class="o">))</span>
<span class="c1">// Elasticsearch 相关配置,ES 用 docker 起的,所以cluster.name 是默认的docker-cluster</span>
<span class="k">val</span> <span class="nv">config</span> <span class="k">=</span> <span class="k">new</span> <span class="nv">util</span><span class="o">.</span><span class="py">HashMap</span><span class="o">[</span><span class="kt">String</span>, <span class="kt">String</span><span class="o">]()</span>
<span class="nv">config</span><span class="o">.</span><span class="py">put</span><span class="o">(</span><span class="s">"cluster.name"</span><span class="o">,</span> <span class="s">"docker-cluster"</span><span class="o">)</span>
<span class="nv">config</span><span class="o">.</span><span class="py">put</span><span class="o">(</span><span class="s">"bulk.flush.max.actions"</span><span class="o">,</span> <span class="s">"1"</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">transportAddress</span> <span class="k">=</span> <span class="k">new</span> <span class="nv">util</span><span class="o">.</span><span class="py">ArrayList</span><span class="o">[</span><span class="kt">InetSocketAddress</span><span class="o">]()</span>
<span class="nv">transportAddress</span><span class="o">.</span><span class="py">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">InetSocketAddress</span><span class="o">(</span><span class="nv">InetAddress</span><span class="o">.</span><span class="py">getByName</span><span class="o">(</span><span class="s">"127.0.0.1"</span><span class="o">),</span> <span class="mi">9300</span><span class="o">))</span>
<span class="nv">stream</span><span class="o">.</span><span class="py">addSink</span><span class="o">(</span><span class="k">new</span> <span class="nc">ElasticsearchSink</span><span class="o">(</span>
<span class="n">config</span><span class="o">,</span>
<span class="n">transportAddress</span><span class="o">,</span>
<span class="k">new</span> <span class="nc">ElasticsearchSinkFunction</span><span class="o">[</span><span class="kt">String</span><span class="o">]</span> <span class="o">{</span>
<span class="k">def</span> <span class="nf">createIndexRequest</span><span class="o">(</span><span class="n">element</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span><span class="k">:</span> <span class="kt">IndexRequest</span> <span class="o">=</span> <span class="o">{</span>
<span class="k">val</span> <span class="nv">json</span> <span class="k">=</span> <span class="k">new</span> <span class="nv">util</span><span class="o">.</span><span class="py">HashMap</span><span class="o">[</span><span class="kt">String</span>, <span class="kt">String</span><span class="o">]()</span>
<span class="nv">json</span><span class="o">.</span><span class="py">put</span><span class="o">(</span><span class="s">"data"</span><span class="o">,</span> <span class="n">element</span><span class="o">)</span>
<span class="k">return</span> <span class="nv">Requests</span><span class="o">.</span><span class="py">indexRequest</span><span class="o">()</span>
<span class="o">.</span><span class="py">index</span><span class="o">(</span><span class="s">"my-index"</span><span class="o">)</span>
<span class="o">.</span><span class="n">`type`</span><span class="o">(</span><span class="s">"my-type"</span><span class="o">)</span>
<span class="o">.</span><span class="py">source</span><span class="o">(</span><span class="n">json</span><span class="o">)</span>
<span class="o">}</span>
<span class="k">def</span> <span class="nf">process</span><span class="o">(</span><span class="n">element</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">ctx</span><span class="k">:</span> <span class="kt">RuntimeContext</span><span class="o">,</span> <span class="n">indexer</span><span class="k">:</span> <span class="kt">RequestIndexer</span><span class="o">)</span> <span class="k">=</span> <span class="o">{</span>
<span class="nv">indexer</span><span class="o">.</span><span class="py">add</span><span class="o">(</span><span class="nf">createIndexRequest</span><span class="o">(</span><span class="n">element</span><span class="o">))</span>
<span class="o">}</span>
<span class="o">},</span>
<span class="c1">// 忽略错误,示例用,不建议用于生产环境</span>
<span class="k">new</span> <span class="nc">IgnoringFailureHandler</span><span class="o">()</span>
<span class="o">))</span>
<span class="nv">env</span><span class="o">.</span><span class="py">execute</span><span class="o">()</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>如下图所示,是上面程序的结果。<img src="../../assets/img/2019/flink-es-result.png" alt="es result" /></p>
<p>上面实现了一个基础的Elasticsearch Sink,为了保证数据完整性,需要添加一些重试策略,这些主要跟 Elasticsearch 相关。</p>
<blockquote>
<p>ES flush 相关配置</p>
<p>bulk.flush.max.actions</p>
<p>bulk.flush.max.size.mb</p>
<p>bulk.flush.interval.ms</p>
<p>ES 错误重试配置</p>
<p>bulk.flush.backoff.enable</p>
<p>bulk.flush.backoff.type</p>
<p>bulk.flush.backoff.delay</p>
<p>bulk.flush.backoff.retries</p>
</blockquote>
<p>如果在此基础上还需要处理Elasticsearch 的报错,可以自己实现ActionRequestFailureHandler 方法。</p>
<h1 id="总结">总结</h1>
<p>本文主要以 Flink Elasticsearch Connector 为例讲了Flink 里的Sink,后面会对Source 和 Sink 进行源码解读。</p>
<p>看到这里,请扫描下方二维码关注我,Happy Friday !</p>
<p><img src="../../wxqr.jpg" alt="QR" /></p>
Flink(4)——source 介绍与实践
2018-12-20T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(4)——Source 介绍与实践
<p>本文转自个人微信公众号,<a href="https://mp.weixin.qq.com/s/jvveJR99vKQ11Jlr9mkMqA">原文链接</a>。本博客评论系统需要梯子,大家关注下公众号方便交流。</p>
<p>本文基于Apache Flink 1.7。</p>
<p>Source 就是Flink 程序的数据输入,Flink 提供了多种数据输入方式,下面逐一介绍。</p>
<h1 id="概念">概念</h1>
<h2 id="flink预定义sources">Flink预定义Sources</h2>
<p>Flink 预定义了多种Sources。</p>
<ul>
<li>基于文件的,如<code class="language-plaintext highlighter-rouge">readTextFile(path)</code>、<code class="language-plaintext highlighter-rouge">readFile(fileInputFormat, path)</code>等;</li>
<li>基于socket的,如<code class="language-plaintext highlighter-rouge">socketTextStream</code>;</li>
<li>基于collections and iterators的,如<code class="language-plaintext highlighter-rouge">fromCollection(Seq)</code>、<code class="language-plaintext highlighter-rouge">fromElements(elements: _*)</code>等,常用于开发测试。</li>
</ul>
<h2 id="connectors">Connectors</h2>
<p>connectors 用于给接入第三方数据提供接口,现在支持的connectors 包括:</p>
<ul>
<li>Apache Kafka</li>
<li>RabbitMQ</li>
<li>Apache NiFi</li>
<li>Twtter Streaming API</li>
<li>Amazon Kinesis Streams</li>
</ul>
<p>另外,通过 <a href="https://bahir.apache.org/">Apache Bahir</a>,可以支持ActiveMQ/Netty之类的Source。</p>
<h2 id="async-io-api">Async I/O API</h2>
<p>Flink 提供了外部数据存储的异步I/O API。流计算中经常需要与外部存储系统交互,比如取某个表的数据以便跟流中数据进行关联,一般来说,如果用同步I/O的方式,会造成系统中出现大的等待时间,影响吞吐和延迟。为了解决这个问题,异步I/O可以并发处理多个请求,提高吞吐,减少延迟,如下图所示。</p>
<p><img src="../../assets/img/2018/flink-async-io.png" alt="Async I/O" /></p>
<h2 id="queryable-state">Queryable State</h2>
<p>如果Flink 应用需要将大量数据写到外部存储,这时候很容易产生I/O 瓶颈,如果需要写的数据是读少写多的数据,那么是否可以让外部应用自己来拉取数据呢?Queryable State 就是这个用途,提供了接口给外部应用,允许外部应用根据需要查询Flink state,现阶段Queryable State 还是Beta版,期待ing。</p>
<h1 id="容错">容错</h1>
<p>Flink 提供了容错机制,以便Jobs从Failure恢复并继续执行,Flink 提供source的 exactly-once需要source的支持,如下图所示(注:图片来源于Flink 官网):</p>
<p><img src="../../assets/img/2018/flink-source-exactly-once.png" alt="Source 容错" /></p>
<h1 id="实战">实战</h1>
<h2 id="预定义sources">预定义Sources</h2>
<p>预定义的Source比较简单,在程序开发、调试阶段,可以采用基于Collection的Source,举例来说:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">env</span> <span class="k">=</span> <span class="nv">StreamExecutionEnvironment</span><span class="o">.</span><span class="py">getExecutionEnvironment</span>
<span class="nv">env</span><span class="o">.</span><span class="py">setStreamTimeCharacteristic</span><span class="o">(</span><span class="nv">TimeCharacteristic</span><span class="o">.</span><span class="py">ProcessingTime</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">stream</span> <span class="k">=</span> <span class="nv">env</span><span class="o">.</span><span class="py">fromCollection</span><span class="o">(</span><span class="nc">List</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">4</span><span class="o">,</span><span class="mi">5</span><span class="o">))</span>
<span class="nv">stream</span><span class="o">.</span><span class="py">print</span>
<span class="nv">env</span><span class="o">.</span><span class="py">execute</span>
</code></pre></div></div>
<h2 id="kafka-connector">Kafka Connector</h2>
<p>Connectors 就以最常用的Kafka Connectors来说。Flink 提供了Flink Kafka Consumer 读取Kafka topics的数据,Flink Kafka Consumer 集成了Flink的checkpoint 机制以提供<strong>exactly-once</strong> 语义,不仅以来Kafka Consumer 的offset 追踪,同时将这些信息存到checkpoint。</p>
<p>Kafka Connector 在Flink 1.7.0后有大的改动,但还处于beta阶段,所以,下面还是以<strong>flink-connector-kafka-0.11_2.11</strong> 为例,而且,我们在生产环境也是用的这个版本。大家也可以用最新的<strong>flink-connector-kafka_2.11</strong>,这是一个通用版本,兼容0.10.0后边的版本。</p>
<p>首先,在项目中import Flink Kafka Connector。</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.apache.flink<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flink-connector-kafka-0.11_${scala.binary.version}<span class="nt"></artifactId></span>
<span class="nt"><version></span>${flink.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>代码如下:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** 注释1
* 启动checkpoint(可选)
* env.enableCheckpointing(5000); //checkpoint every 5000 msecs
*/</span>
<span class="k">val</span> <span class="nv">properties</span> <span class="k">=</span> <span class="k">new</span> <span class="nc">Properties</span><span class="o">();</span>
<span class="nv">properties</span><span class="o">.</span><span class="py">setProperty</span><span class="o">(</span><span class="s">"bootstrap.servers"</span><span class="o">,</span> <span class="s">"127.0.0.1:9092"</span><span class="o">);</span>
<span class="nv">properties</span><span class="o">.</span><span class="py">setProperty</span><span class="o">(</span><span class="s">"group.id"</span><span class="o">,</span> <span class="s">"groupXXX"</span><span class="o">);</span>
<span class="cm">/** 注释2
* 下面配置读取kafka topic partition 的起始偏移量(可选)
* consumer.setStartFromEarliest();
* consumer.setStartFromLatest();
* consumer.setStartFromTimestamp(...);
* consumer.setStartFromGroupOffsets(); // 默认
*/</span>
<span class="k">val</span> <span class="nv">consumer</span> <span class="k">=</span> <span class="k">new</span> <span class="nc">FlinkKafkaConsumer011</span><span class="o">[](</span><span class="s">"topic_name"</span><span class="o">,</span> <span class="nc">SimpleStringSchema</span><span class="o">,</span> <span class="n">properties</span><span class="o">)</span>
<span class="cm">/** 注释3
* Watermark (可选)
* consumer.assignTimestampsAndWatermarks(new CustomWatermarkEmitter());
*/</span>
<span class="k">val</span> <span class="nv">stream</span> <span class="k">=</span> <span class="nv">env</span><span class="o">.</span><span class="py">addSource</span><span class="o">(</span><span class="n">consumer</span><span class="o">)</span>
</code></pre></div></div>
<p><strong>1. 构造函数</strong> ,需要三个参数:</p>
<ul>
<li>topic 名</li>
<li>反序列化方法</li>
<li>Kafka consumer的Properties</li>
</ul>
<p><strong>2. checkpoint</strong>,从注释1 可以看出,Flink Kafka Consumer 可以启动checkpoint机制,会周期性的给Kafka offsets 和 Flink的其它states 做 checkpoints,当Job 失败时,Flink 读取checkpoint里最新的state并从对应offset 开始消费数据恢复运算。</p>
<p><strong>3. 起始偏移量</strong>,从注释2可以看出,Flink Kafka Consumer 允许设置读取 Kafka Partition的起始偏移量,而且,允许不同Partitions 分别进行设置。但要注意,设置起始偏移量<strong>不适用</strong>于两种情况:</p>
<ul>
<li>Job 从failure 自动恢复。</li>
<li>手动从某savepoint 启动任务。</li>
</ul>
<p><strong>4. Kafka Topic 和Partition 自发现</strong>,比如构建Kafka Consumer时,topic名可以是正则表达式,这时候,如果有符合该正则的新的topic 加入到Kafka 集群,可以被自动发现;另外,如果对Kafka Topic 进行RePartition,也可以自动发现,使用不多,可以自行查阅文档。</p>
<p><strong>5. Kafka Consumer与Watermark</strong> ,从注释3可以看出,结合<a href="https://mp.weixin.qq.com/s/sJa3yGENCaDHd-LHz5jFsg">上篇文章</a>,可以给数据设置方法以便给数据带上watermark。</p>
<h1 id="总结">总结</h1>
<p>本文主要以 Flink Kafka Connector 为例讲了Flink 里的Sources,主要是考虑Kafka广泛使用在实时系统中,甚至可以说是标配,后边将开始讲解Sink以及Flink SQL。</p>
<p>看到这里,请扫描下方二维码关注我,Happy Friday !</p>
<p><img src="../../wxqr.jpg" alt="wx" /></p>
Flink(3)——apache flink event time 与 watermark
2018-12-10T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(3)——Apache Flink Event Time 与 Watermark
<p>本文转自个人微信公众号,<a href="https://mp.weixin.qq.com/s/sJa3yGENCaDHd-LHz5jFsg">原文链接</a>。</p>
<p>如 <a href="https://mp.weixin.qq.com/s/4ySScrUpXTJoCtRP0feitg">上篇</a> 所述,Flink 里时间包括Event Time、Processing Time 和 Ingestion Time 三种类型。</p>
<ul>
<li>Processing Time:Processing Time 是算子处理某个数据时到系统时间。Processing Time 是最简单的时间,提供了最好的性能和最低的延迟,但是,在分布式环境中,Processing Time具有不确定性,多次运行的结果可能出现不一致。</li>
<li>Ingestion Time:Ingestion Time 是数据进入Flink 集群的时间,Source Operator 给数据加上时间戳。</li>
<li>Event Time:Event Time是数据在设备上产生时的时间,一般都嵌入到了数据记录中,相比于其他两种,Event Time 更具有业务意义, 取决于数据而不是系统。举例来说,重跑历史数据时,如果根据Processing Time 重跑,可能会造成结果不一致,而根据Event Time 重跑,结果是一致的。</li>
</ul>
<p>由于Event Time 更能表达业务需求,所以,Event Time 应用更为广泛,但使用Event Time 也会存在一些问题。</p>
<h1 id="1-问题乱序与延迟">1. 问题:乱序与延迟</h1>
<p>乱序与延迟是实时系统中最常见的问题。比如说,在实时系统中广泛使用的消息队列,很难保证端到端的全局有序,从而导致进入 Flink 集群的数据是无序的;然后,由于洪峰的存在,比如秒杀或者重跑历史数据,很容易造成数据在消息队列堆积,从而造成延迟。</p>
<h1 id="2-解决方案">2. 解决方案</h1>
<p>采用Event Time的流计算处理器,需要评估Event Time进展,比如当窗口结束时,需要通知 Operator 关闭窗口并开始计算。</p>
<h2 id="21-watermark">2.1 Watermark</h2>
<p>Apache Flink 采用watermark来处理,watermark 带有一个时间戳,作为数据流的一部分随数据流流动,<code class="language-plaintext highlighter-rouge">Watermark(t)</code> 表示event time 小于等于 <code class="language-plaintext highlighter-rouge">t</code> 的都已经到达,如下图所示。</p>
<p><img src="../../assets/img/2018/flink-watermark.png" alt="Watermark" /></p>
<h3 id="211-生成watermark">2.1.1 生成Watermark</h3>
<h4 id="2111-方法1-source-中生成">2.1.1.1 方法1 Source 中生成</h4>
<p>在source中,直接生成watermark,不过,source生成的watermark 优先级比较低,可以被方法2中的覆盖掉。具体的定义在一篇讲Source & Sink 时详述。</p>
<h4 id="2112-方法2-timestamp-assigner">2.1.1.2 方法2 Timestamp Assigner</h4>
<p>Timestamp Assigner 输入数据流,产生一个新的数据流,新数据流带有产生的watermark,如果原数据流本身就有watermark,则覆盖原watermark。Timestamp Assigner 一般紧跟在source后,但不是必须的,但是必须在第一个event time 操作前。</p>
<p>Timestamp Assigner 分两种:</p>
<ul>
<li><strong><em>Periodic</em></strong>: 周期性(一定时间间隔或一定数据量)产生watermark。</li>
<li><strong><em>Punctuated</em></strong>: 间断的 watermark,一般根据event 决定是否产生新watermark。</li>
</ul>
<p><strong>Periodic</strong></p>
<p>直接看源码(注释太明白,不舍得删)。</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* A {@code TimestampAssigner} assigns event time timestamps to elements.
* These timestamps are used by all functions that operate on event time,
* for example event time windows.
*
* <p>Timestamps are represented in milliseconds since the Epoch
* (midnight, January 1, 1970 UTC).
*
* @param <T> The type of the elements to which this assigner assigns timestamps.
*/</span>
<span class="n">public</span> <span class="n">interface</span> <span class="nc">TimestampAssigner</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="k">extends</span> <span class="nc">Function</span> <span class="o">{</span>
<span class="cm">/**
* Assigns a timestamp to an element, in milliseconds since the Epoch.
*
* <p>The method is passed the previously assigned timestamp of the element.
* That previous timestamp may have been assigned from a previous assigner,
* by ingestion time. If the element did not carry a timestamp before, this value is
* {@code Long.MIN_VALUE}.
*
* @param element The element that the timestamp will be assigned to.
* @param previousElementTimestamp The previous internal timestamp of the element,
* or a negative value, if no timestamp has been assigned yet.
* @return The new timestamp.
*/</span>
<span class="n">long</span> <span class="nf">extractTimestamp</span><span class="o">(</span><span class="n">T</span> <span class="n">element</span><span class="o">,</span> <span class="n">long</span> <span class="n">previousElementTimestamp</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* The {@code AssignerWithPeriodicWatermarks} assigns event time timestamps to elements,
* and generates low watermarks that signal event time progress within the stream.
* These timestamps and watermarks are used by functions and operators that operate
* on event time, for example event time windows.
*
* <p>Use this class to generate watermarks in a periodical interval.
* At most every {@code i} milliseconds (configured via
* {@link ExecutionConfig#getAutoWatermarkInterval()}), the system will call the
* {@link #getCurrentWatermark()} method to probe for the next watermark value.
* The system will generate a new watermark, if the probed value is non-null
* and has a timestamp larger than that of the previous watermark (to preserve
* the contract of ascending watermarks).
*
* <p>The system may call the {@link #getCurrentWatermark()} method less often than every
* {@code i} milliseconds, if no new elements arrived since the last call to the
* method.
*
* <p>Timestamps and watermarks are defined as {@code longs} that represent the
* milliseconds since the Epoch (midnight, January 1, 1970 UTC).
* A watermark with a certain value {@code t} indicates that no elements with event
* timestamps {@code x}, where {@code x} is lower or equal to {@code t}, will occur any more.
*
* @param <T> The type of the elements to which this assigner assigns timestamps.
*
* @see org.apache.flink.streaming.api.watermark.Watermark
*/</span>
<span class="n">public</span> <span class="n">interface</span> <span class="nc">AssignerWithPeriodicWatermarks</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="k">extends</span> <span class="nc">TimestampAssigner</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="o">{</span>
<span class="cm">/**
* Returns the current watermark. This method is periodically called by the
* system to retrieve the current watermark. The method may return {@code null} to
* indicate that no new Watermark is available.
*
* <p>The returned watermark will be emitted only if it is non-null and its timestamp
* is larger than that of the previously emitted watermark (to preserve the contract of
* ascending watermarks). If the current watermark is still
* identical to the previous one, no progress in event time has happened since
* the previous call to this method. If a null value is returned, or the timestamp
* of the returned watermark is smaller than that of the last emitted one, then no
* new watermark will be generated.
*
* <p>The interval in which this method is called and Watermarks are generated
* depends on {@link ExecutionConfig#getAutoWatermarkInterval()}.
*
* @see org.apache.flink.streaming.api.watermark.Watermark
* @see ExecutionConfig#getAutoWatermarkInterval()
*
* @return {@code Null}, if no watermark should be emitted, or the next watermark to emit.
*/</span>
<span class="nd">@Nullable</span>
<span class="nc">Watermark</span> <span class="nf">getCurrentWatermark</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>可以看出,自定义的Assigner 需要实现<code class="language-plaintext highlighter-rouge">AssignerWithPeriodicWatermarks</code> 接口,其中<code class="language-plaintext highlighter-rouge">getCurrentWatermark</code> 产生新的watermark,如果返回非空且大于原来的watermark,则生成了新的watermark;另外,<code class="language-plaintext highlighter-rouge">extractTimestamp</code> 用于给数据加上时间戳,这个时间戳在后续所有基于event time的计算中使用。以下面的代码为例,假设数据可能乱序,但最多延迟3.5秒。</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="cm">/**
* This generator generates watermarks assuming that elements arrive out of order,
* but only to a certain degree. The latest elements for a certain timestamp t will arrive
* at most n milliseconds after the earliest elements for timestamp t.
*/</span>
<span class="k">class</span> <span class="nc">BoundedOutOfOrdernessGenerator</span> <span class="k">extends</span> <span class="nc">AssignerWithPeriodicWatermarks</span><span class="o">[</span><span class="kt">MyEvent</span><span class="o">]</span> <span class="o">{</span>
<span class="k">val</span> <span class="nv">maxOutOfOrderness</span> <span class="k">=</span> <span class="mi">3500L</span> <span class="c1">// 3.5 seconds</span>
<span class="k">var</span> <span class="n">currentMaxTimestamp</span><span class="k">:</span> <span class="kt">Long</span> <span class="o">=</span> <span class="k">_</span>
<span class="k">override</span> <span class="k">def</span> <span class="nf">extractTimestamp</span><span class="o">(</span><span class="n">element</span><span class="k">:</span> <span class="kt">MyEvent</span><span class="o">,</span> <span class="n">previousElementTimestamp</span><span class="k">:</span> <span class="kt">Long</span><span class="o">)</span><span class="k">:</span> <span class="kt">Long</span> <span class="o">=</span> <span class="o">{</span>
<span class="nv">element</span><span class="o">.</span><span class="py">getCreationTime</span><span class="o">()</span>
<span class="o">}</span>
<span class="k">override</span> <span class="k">def</span> <span class="nf">getCurrentWatermark</span><span class="o">()</span><span class="k">:</span> <span class="kt">Watermark</span> <span class="o">=</span> <span class="o">{</span>
<span class="c1">// return the watermark as current highest timestamp minus the out-of-orderness bound</span>
<span class="k">new</span> <span class="nc">Watermark</span><span class="o">(</span><span class="n">currentMaxTimestamp</span> <span class="o">-</span> <span class="n">maxOutOfOrderness</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ExecutionConfig.setAutoWatermarkInterval(...) </code> 定义了watermark产生的时间间隔,单位是毫秒。</p>
<p><strong>Punctuated</strong></p>
<p>根据event来确定是否需要产生新的watermark,定义Punctuated Assigner 需要实现<code class="language-plaintext highlighter-rouge">AssignerWithPunctuatedWatermarks</code>接口,包括函数<code class="language-plaintext highlighter-rouge">extractTimestamp</code>,<code class="language-plaintext highlighter-rouge">checkAndGetNextWatermark</code>,其中<code class="language-plaintext highlighter-rouge">extractTimestamp</code> 同Periodic Assigner,首先调用;然后调用<code class="language-plaintext highlighter-rouge">checkAndGetNextWatermark</code> ,用于确定是否需要产生新的watermark,当<code class="language-plaintext highlighter-rouge">checkAndGetNextWatermark</code> 产生一个非空且大于上一个watermark时就产生了新的watermark。举个例子如下:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">PunctuatedAssigner</span> <span class="k">extends</span> <span class="nc">AssignerWithPunctuatedWatermarks</span><span class="o">[</span><span class="kt">MyEvent</span><span class="o">]</span> <span class="o">{</span>
<span class="k">override</span> <span class="k">def</span> <span class="nf">extractTimestamp</span><span class="o">(</span><span class="n">element</span><span class="k">:</span> <span class="kt">MyEvent</span><span class="o">,</span> <span class="n">previousElementTimestamp</span><span class="k">:</span> <span class="kt">Long</span><span class="o">)</span><span class="k">:</span> <span class="kt">Long</span> <span class="o">=</span> <span class="o">{</span>
<span class="nv">element</span><span class="o">.</span><span class="py">getCreationTime</span>
<span class="o">}</span>
<span class="k">override</span> <span class="k">def</span> <span class="nf">checkAndGetNextWatermark</span><span class="o">(</span><span class="n">lastElement</span><span class="k">:</span> <span class="kt">MyEvent</span><span class="o">,</span> <span class="n">extractedTimestamp</span><span class="k">:</span> <span class="kt">Long</span><span class="o">)</span><span class="k">:</span> <span class="kt">Watermark</span> <span class="o">=</span> <span class="o">{</span>
<span class="nf">if</span> <span class="o">(</span><span class="nv">lastElement</span><span class="o">.</span><span class="py">hasWatermarkMarker</span><span class="o">())</span> <span class="k">new</span> <span class="nc">Watermark</span><span class="o">(</span><span class="n">extractedTimestamp</span><span class="o">)</span> <span class="k">else</span> <span class="kc">null</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="212--flink-预定义timestamp-assigner">2.1.2 Flink 预定义Timestamp Assigner</h3>
<p>为了便于使用,Apache Flink 提供了两种预定义的Timestamp Assigner:</p>
<ul>
<li>
<p><strong>AscendingTimestampExtractor</strong>: 这是<code class="language-plaintext highlighter-rouge">AssignerWithPeriodicWatermarks</code> 的最简单的情况,数据流是按时间戳升序到达Flink的,这种情况下,数据里的时间戳就可以作为watermark</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">withTimestampsAndWatermarks</span> <span class="k">=</span> <span class="nv">stream</span><span class="o">.</span><span class="py">assignAscendingTimestamps</span><span class="o">(</span> <span class="nv">_</span><span class="o">.</span><span class="py">getCreationTime</span> <span class="o">)</span>
</code></pre></div> </div>
</li>
<li>
<p><strong>BoundedOutOfOrdernessTimestampExtractor</strong>: 这也是一个<code class="language-plaintext highlighter-rouge">AssignerWithPeriodicWatermarks</code> 的实现,表示已知数据的最大延迟。</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">withTimestampsAndWatermarks</span> <span class="k">=</span> <span class="nv">stream</span><span class="o">.</span><span class="py">assignTimestampsAndWatermarks</span><span class="o">(</span><span class="k">new</span> <span class="nc">BoundedOutOfOrdernessTimestampExtractor</span><span class="o">[</span><span class="kt">MyEvent</span><span class="o">](</span><span class="nv">Time</span><span class="o">.</span><span class="py">seconds</span><span class="o">(</span><span class="mi">10</span><span class="o">))(</span> <span class="nv">_</span><span class="o">.</span><span class="py">getCreationTime</span> <span class="o">))</span>
</code></pre></div> </div>
</li>
</ul>
<p>这两种Timestamp Assigner 一是可以直接使用,二是可以作为学习的代码示例。</p>
<h2 id="latency">Latency</h2>
<p>即使采用watermark 技术,对于watermark(t) 也可能存在时间戳小于t却没有到达的数据,在现实中,延迟可能是无上限的,这种情况下,不可能无限等待下去;另外,即使延迟有限,但如果让watermark 延迟太多也不好,因为延迟太多可能就失去了实时的意义。所以,必须要作出选择。</p>
<p>默认情况下,延迟超过watermark的数据会被丢弃,但 Flink 允许在窗口操作上指定最大延迟,我们用N表示支持的最大延迟(N默认为0),对于窗口 [start_time, end_time)] ,数据迟于 watermark(t) 但先于end_time+N到达的,仍然会添加到窗口中再次触发计算。为了支持这种情况,Flink 需要保持这个窗口state 到时间戳 end_time + N ,当时间到达end_time+N后,Flink 删除窗口和state。</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">stream</span>
<span class="o">.</span><span class="py">keyBy</span><span class="o">(<</span><span class="n">key</span> <span class="n">selector</span><span class="o">>)</span>
<span class="o">.</span><span class="py">window</span><span class="o">(<</span><span class="n">window</span> <span class="n">assigner</span><span class="o">>)</span>
<span class="o">.</span><span class="py">allowedLateness</span><span class="o">(<</span><span class="n">time</span><span class="o">>)</span>
<span class="o">.<</span><span class="n">windowed</span> <span class="n">transformation</span><span class="o">>(<</span><span class="n">window</span> <span class="n">function</span><span class="o">>)</span>
</code></pre></div></div>
<h1 id="3-总结">3. 总结</h1>
<p>本文主要介绍Flink 中Event Time 和Watermark。由于Event Time 具有业务意义,且具有确定性,所以Event Time 应用广泛,但由于在现实中存在延迟和乱序问题,Flink 采用了 Watermark 来解决这个问题。</p>
<p>扫描下方二维码关注我。</p>
<p><img src="../../wxqr.jpg" alt="wx" /></p>
Flink(2)——apache flink 介绍
2018-11-20T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(2)——Apache Flink 介绍
<p>本文转自个人微信公众号,<a href="https://mp.weixin.qq.com/s/4ySScrUpXTJoCtRP0feitg">原文链接</a>。</p>
<blockquote>
<p>Apache Flink is a framework and distributed processing engine for stateful computations over <em>unbounded and bounded</em> data streams. Flink has been designed to run in <em>all common cluster environments</em>, perform computations at <em>in-memory speed</em> and at <em>any scale</em>.</p>
</blockquote>
<h1 id="1-概念">1 概念</h1>
<h2 id="11-bounded-streams-vs-unbounded-streams--批处理-vs-流处理">1.1 Bounded Streams VS Unbounded Streams & 批处理 VS 流处理</h2>
<p>Bounded Streams 可以理解为有开始也有结束的数据流,处理这类数据流可以等所有数据都到了再处理,也就是常说的<strong>批处理</strong>。</p>
<p>Unbounded Streams 可以理解为有开始没有结束的数据流,这类数据流持续产生数据,所以,也要持续的进行处理而不能等数据流结束再处理,也就是常说的<strong>流处理</strong>。</p>
<p>Apache Flink 既能处理Bounded Streams 也擅长处理Unbounded Streams,既能做批处理也能做流处理。</p>
<h2 id="12-部署方式">1.2 部署方式</h2>
<p>Apache Flink 是一个分布式系统,需要资源以运行其它应用,Apache Flink 支持常见的资源管理器,包括Yarn、 Mesos、K8S,也可以以Stand-Alone Cluster的方式运行。</p>
<h2 id="13-runtime">1.3. Runtime</h2>
<p><img src="../../assets/img/2018/flink-jm-tm.png" alt="架构" /></p>
<p>Apache Flink Runtime 是一个典型的master-slave架构, 包括Jobmanagers 和 Taskmanagers 两部分:</p>
<ul>
<li>Jobmanagers (masters):协调分布式执行,包括调度任务、协调checkpoints、从失败恢复等。一般至少需要一个Jobmanager,在HA环境下,需要有多个Jobmanagers,其中有一个作为leader,其他的standby。</li>
<li>taskmanagers (workers):执行具体的任务,buffer和传递数据流。</li>
</ul>
<p>另外,Clients 不是Apache Flink Runtime的一部分,但常用于准备和提交Job到 Jobmanager。</p>
<h2 id="14-编程模型">1.4 编程模型</h2>
<h3 id="141-抽象">1.4.1 抽象</h3>
<p><img src="../../assets/img/2018/flink-abstract.png" alt="Flink Abstract" /></p>
<p>Apache Flink 提供了不同的抽象级别以开发Flink 应用。</p>
<ul>
<li>Stateful Streaming:抽象级别最低,给应用开发者提供了最大的自由度,实际开发中很少使用。</li>
<li>DataStream / DataSet API: 这是Flink 提供的核心APIs,DataStream API 用于Unbounded Stream Data,DataSet API 用于Boundesd Stream Data,用于使用各种方法对数据进行计算处理,如map等。</li>
<li>Table API:以<strong><em>表</em></strong>为核心的的声明式DSL,该表可以是动态变化的表,该层API提供了诸如Select、Join、Group-by、Aggregate之类的操作,更加简洁。另外,用户可以在Table API和Dataset/Datastream API 之间无缝切换甚至混用。</li>
<li>SQL:跟Table API相似,只不过是以SQL的方式进行描述。</li>
</ul>
<h3 id="142-程序与dataflows">1.4.2 程序与Dataflows</h3>
<p><img src="../../assets/img/2018/flink-dataflow.png" alt="flink dataflow" /></p>
<p>Apache Flink 程序一般包括<strong>data streams</strong>和 <strong>transformations</strong> 两部分,其中,data streams 是数据流,transformations 是操作数据流的算子,以一个或多个数据流为输入,输出一个或多个数据流。</p>
<p>当Apache Flink 程序运行时,Flink 程序可以理解为包含<strong>streams</strong> 和 transformation <strong>operators</strong> 的<strong>streaming dataflow</strong>,每个dataflow 以一个或多个sources开始并以一个或多个sinks 结束,这个dataflow 类似于DAGs(directed acyclic graphs)。(有没有一种熟悉的感觉,比如Spark、Flume…)</p>
<ul>
<li><strong><em>Sources</em></strong>:数据源,常见的如kafka。</li>
<li><strong><em>Transformations</em></strong>:数据转换,可以理解为对数据的操作。</li>
<li><strong><em>Sinks</em></strong>:接收器,Flink 转换后将数据发送到的地方。</li>
</ul>
<h3 id="143-其它重要概念">1.4.3 其它重要概念</h3>
<h4 id="1431-time">1.4.3.1 Time</h4>
<p>在流计算中,Time包括三种:</p>
<ul>
<li>Ingestion Time:数据记录进入Flink Data Source的时间。</li>
<li>Processing Time:Flink Operator进行time-based 操作的本地时间。</li>
<li>Event Time:数据的时间,一般有业务意义。</li>
</ul>
<p>Apache 可以支持这三种Time,每种Time都有特定的用途,后序文章会详细进行说明。</p>
<p><img src="../../assets/img/2018/flink-time.png" alt="Flink Time" /></p>
<h4 id="1432-window">1.4.3.2 Window</h4>
<p>不同于批计算,流计算的计算一般是针对一个窗口的数据的计算,比如“统计过去5分钟的记录数”、“过去100个数据的平均值”等。</p>
<p>窗口包括按时间进行划分的和按数据进行划分的,典型的包括 <strong><em>tumbling windows</em></strong>、 <strong><em>sliding windows</em></strong>, <strong><em>session windows</em></strong>,每种窗口应用于不同的场景。</p>
<h4 id="1433-state">1.4.3.3 State</h4>
<p>有些操作只需要知道当前数据记录即可,还有些操作需要其它数据记录,我们称这种操作是<strong><em>stateful operations</em></strong>,比如要计算Sum、Avg等,这些值是需要存储的,因为会不断变化,这些值就可以理解为 <strong><em>state</em></strong>。</p>
<p>Apache Flink 提供了内置的状态管理,这也是Flink 区别于其它流计算引擎的最主要的区别。</p>
<h4 id="1434-checkpoint">1.4.3.4 Checkpoint</h4>
<p>Apache Flink的checkpoints 可以理解为输入数据流在某一点以及所有operators对应的state,Apache Flink 基于checkpoints 和 stream replay 实现容错,并基于此实现数据一致性(exactly-once)。</p>
<h1 id="2-why-flink">2 Why Flink</h1>
<p>上面主要介绍Flink 中的核心概念,总结下为什么选择Flink:</p>
<ol>
<li>Apache Flink 是一个低延迟、高吞吐、统一的大数据计算引擎。</li>
<li>支持状态管理,提供有状态的计算。</li>
<li>提供准确的结果,即使出现数据乱序或数据延迟。Flink 程序符合自然规律,如多种窗口、event time等能满足正确性的要求,而且,基于checkpoint 和replay 提供故障恢复,支持 Exactly-Once 语义,保障正确性。</li>
<li>支持Yarn / Mesos / K8S等多种资源管理器,可以方便扩展。</li>
<li>是一个批流统一的平台,在未来,批流融合是一大趋势。</li>
<li>Flink SQL 降低了使用门槛,便于推广。</li>
</ol>
<p>下篇文章,开始编写第一个Flink 程序。</p>
<p>扫描下方二维码关注我。</p>
<p><img src="../../wxqr.jpg" alt="wx" /></p>
Flink(1)——基于flink sql的流计算平台设计
2018-11-13T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(1)——基于Flink SQL的流计算平台设计
<p>本文转自个人微信公众号,<a href="https://mp.weixin.qq.com/s/8ICLIEzuGvDuzgOddXwTGg">原文链接</a>。</p>
<p>接<a href="http://lxwei.github.io/posts/Flink(0)-%E5%9F%BA%E4%BA%8EFlink%E7%9A%84%E6%B5%81%E8%AE%A1%E7%AE%97.html">上篇</a>。</p>
<p><strong>使用场景</strong></p>
<p>先说流计算平台应用场景。在我们的业务中,实时平台核心包括几个部分:一是大促看板,比如刚过去的双11,供领导层和运营查看决策使用;二是实时风控的技术支持;三是实时数据接入、清洗、入库功能,为下游提供实时、准确的数据。</p>
<p>为了支持这些业务需求,并最小化技术人员的介入,设计并实现了实时计算平台。</p>
<p><strong>设计</strong></p>
<p>首先,是数据源部分。数据接入包括埋点日志、数据库数据、API上报数据等,埋点数据、API上报的数据等都接入Kafka,平台支持的数据源包括Kafka、MySQL、Redis、Elasticsearch,根据使用经验,Kafka和MySQL 已经基本覆盖我们的业务需求。我们将数据源统一在平台进行管理,使用者不需要关注数据源的具体来源信息。</p>
<p>其次,是Job。Job由数据源和具体的task组成。数据接入后,需要进行运算,要定义算子和工作流。算子就是我们要对数据流进行的操作,同时,对数据可能需要经过中间很多层处理,所以,还需要定义工作流。算子我们采用Flink SQL,且目前仅支持Flink SQL。Flink 使用 <a href="https://calcite.apache.org/docs/reference.html">Apache calcite</a> 解析SQL,它支持 ANSI SQL,这对于BI和分析师,都是比较<strong>容易使用</strong>的。在当前情况下,Flink SQL 对有些语法还不支持,对我们来说,这不算大问题,一是先有语法已经覆盖我们的绝大多数需求,如果我们要等它完美支持后再来使用,反而是得不偿失,正所谓<strong><em>Done is better than perfect.</em></strong>;其次是对于刚需语法,我们可以根据Flink 提供的<a href="https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/table/udfs.html">UDF</a> 自行开发,比如函数 LAST_VALUE()。</p>
<p><strong>部署</strong></p>
<p>Flink 集群支持Standalone、Yarn、Mesos、K8S等多种模式,我们目前的版本采用Standalone cluster模式,现在流行的在生产环境使用较多的是Yarn模式,下表是Standalone 模式和 Yarn 模式的优缺点对比。我们之前采用Standalone 模式的两个原因,一是为了快速实现;二是尽量减少外部依赖特别是对 Yarn 集群的依赖(Yarn 集群主要是离线计算和BI、分析师日常取数使用,尽量减少对他们的影响。如果要采用Yarn 集群模式,我也推荐单独搭建Yarn 集群)。但我还是更推荐Yarn 模式,Job 级别的资源隔离以及失败自动重启会更加重要点。</p>
<table>
<thead>
<tr>
<th> </th>
<th>Standalone cluster</th>
<th>Yarn cluster</th>
</tr>
</thead>
<tbody>
<tr>
<td>优点</td>
<td>1. 外部依赖少 <br />2. 添加、减少task manager方便,可以快速实现<br />3. 方便调试和查看日志</td>
<td>1. Job 级别的资源隔离<br />2. Node 失败自动恢复<br /></td>
</tr>
<tr>
<td>缺点</td>
<td>1. 需要额外部署ZK<br />2. 不支持Job 级别的资源隔离</td>
<td>需要依赖外部系统 Yarn</td>
</tr>
</tbody>
</table>
<p><strong>资源</strong></p>
<p>不同的任务数据量不同,计算量不同,需要的资源也不同,我们支持对不同的Job 配置不同的 parallelism,从而满足不同的资源需求,该值还只是一个经验值,暂时无法做到自适应配置。</p>
<p><strong>使用</strong></p>
<p>Flink 将 savepoint 保存到HDFS,在使用过程中,我们发现HDFS上的savepoint 数量巨大,但一段时间前的savepoint是没有用处的,所以,我们对savepoint 进行了生命周期管理,自动删除过期的savepoint。</p>
<p>另外,在业务方使用过程中,也要做Job的生命周期管理,比如大促看板,否则,实时计算平台的资源就是一个黑洞。</p>
<p><strong>其它</strong></p>
<p>系统还涉及用户管理、权限管理、监控告警等部分,暂不做详细介绍。</p>
<p>扫描下方二维码关注我。</p>
<p><img src="../../wxqr.jpg" alt="wx" /></p>
Flink(0)——基于flink的流计算
2018-10-30T00:00:00+00:00
http://www.readingnotes.site/posts/Flink(0)——基于Flink的流计算
<p>本文转自个人微信公众号,<a href="https://mp.weixin.qq.com/s/yQer9fQAyZXTaRdRhEyCDQ">原文链接</a>。</p>
<p>最近一段时间负责公司的流计算平台,将此中感悟和一些学习总结汇聚成文,陆续发布。</p>
<h1 id="1-流计算架构">1. 流计算架构</h1>
<p>先说说流计算架构。</p>
<p>对于后端数据而言,典型的传统架构是采用一个中心化的数据库系统,该系统用于存储事务性数据,应用程序依靠数据库系统实现。姑且称为传统架构。</p>
<p>随着分布式系统的发展,开始出现了以数据流为基础的架构。以流为基础的架构设计让数据记录持续的从数据源流向应用程序,并在各个应用程序之间持续流动,没有数据库来集中存储全局状态数据,取而代之的是共享且永不停止的流数据。</p>
<p><img src="../../assets/img/2018/flow-arch.jpg" alt="流计算架构" /></p>
<p>如上图所示,我们将系统分为两个部分——消息传输层和流处理层:</p>
<ol>
<li>消息传输层(如上图中的Kafka)从各数据源采集连续事件产生的数据,并传输给订阅了这些数据的应用程序和服务。</li>
<li>流处理层(如上图中的Flink)有三个用途:持续的在应用程序和系统间移动数据;聚合并处理时间;在本地维持应用程序的状态。</li>
</ol>
<p>在流处理架构中,消息传输层很关键。首先,要求消息传输层兼具<strong><em>高效性</em></strong>和<strong><em>持久性</em></strong>,高效才能支持大数据量,持久性让消息可重播,从而才可能让流处理层进行<strong><em>再计算</em></strong>;其次,将生产者和消费者<strong><em>解耦</em></strong>,这样,消费者可以立即使用消息,也可以不立即对消息进行处理,甚至当消息到达时,消费者可以不处于运行状态,这有利于支持<strong><em>微服务</em></strong>。可以看出,Kafka 是很适合用于消息传输层的。</p>
<p>从上可以看出,流计算架构的核心是使各应用程序互联的消息队列。流处理层从消息队列订阅数据并加以处理,处理完成后数据可以流向另一消息队列。</p>
<h1 id="2-why-flink">2. Why Flink</h1>
<blockquote>
<p>Apache Flink is an open source platform for distributed stream and batch data processing. Flink’s core is a streaming dataflow engine that provides data distribution, communication, and fault tolerance for distributed computations over data streams. Flink builds batch processing on top of the streaming engine, overlaying native iteration support, managed memory, and program optimization.</p>
</blockquote>
<p>关于Flink的特点等文章太多,推荐参考<a href="https://mp.weixin.qq.com/s/AoSDPDKbTbjH9rviioK-5Q">阿里巴巴为什么选择Apache Flink</a>。我简单总结下。</p>
<p>Flink 是一个开源的分布式流计算和批处理平台,基于统一的流式执行模型,将批处理当作特殊的流计算,从而将流计算和批处理统一起来。避免了离线计算和实时计算同一套逻辑实现两次的负担(如labmda架构)。</p>
<p>Flink 能够保障流计算的<strong><em>正确性</em></strong>。Flink 的一大优势是使应用程序的构建符合自然规律,比如,Flink 中的窗口函数的定义符合数据产生的自然规律,比如支持的会话窗口,保障<strong><em>正确性</em></strong>;其次,Flink 完美支持事物时间,且能区分不同类型的时间,从而保障应用的<strong><em>正确性</em></strong>;再次,Flink 通过检查点技术,实现了即使在发生故障后也能保障准确,支持Exactly-once语义,保障<strong><em>正确性</em></strong>。</p>
<p>其次,Flink 具有高吞吐、低延迟、高性能的特点。</p>
<p>支持大规模的集群模式,支持Yarn、Mesos,可运行于成千上万节点。</p>
<p>最后,Flink SQL 便于使用。</p>
<h1 id="3-总结">3. 总结</h1>
<p>本文主要是流计算架构的介绍和为什么选Flink的一个总结,下一篇将开始讲具体的Flink 技术。</p>
<p>扫描下方二维码关注我。</p>
<p><img src="../../wxqr.jpg" alt="wx" /></p>
Cassandra教程(四):cql要点整理
2018-02-26T00:00:00+00:00
http://www.readingnotes.site/posts/Cassandra教程(四):CQL要点整理
<p>本文不是详细的<a href="https://cassandra.apache.org/doc/latest/cql/index.html" title="CQL">CQL</a>教程,仅记录下CQL的一些要点。</p>
<h1 id="keyspace">Keyspace</h1>
<p>keyspace类似关系型数据库中的database概念,Cassandra 的 keyspace 是一个命名空间,定义了数据备份的方式。举例如下,keyspace cycling 中所有的table 在数据中心 datacenter1中存在3个replicas。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">KEYSPACE</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">cycling</span> <span class="k">WITH</span> <span class="n">REPLICATION</span> <span class="o">=</span> <span class="err">{</span> <span class="s1">'class'</span> <span class="p">:</span> <span class="s1">'NetworkTopologyStrategy'</span><span class="p">,</span> <span class="s1">'datacenter1'</span> <span class="p">:</span> <span class="mi">3</span> <span class="err">}</span><span class="p">;</span>
</code></pre></div></div>
<h1 id="table">Table</h1>
<p>创建table的语句类似下面的例子,跟SQL很类似。</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">cycling</span><span class="p">.</span><span class="n">cyclist_alt_stats</span> <span class="p">(</span> <span class="n">id</span> <span class="n">UUID</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span> <span class="n">lastname</span> <span class="nb">text</span><span class="p">,</span> <span class="n">birthday</span> <span class="nb">timestamp</span><span class="p">,</span> <span class="n">nationality</span> <span class="nb">text</span><span class="p">,</span> <span class="n">weight</span> <span class="nb">text</span><span class="p">,</span> <span class="n">height</span> <span class="nb">text</span> <span class="p">);</span>
</code></pre></div></div>
<p>几个核心概念:</p>
<h2 id="primary-key">Primary Key</h2>
<p>primary key 用来唯一标记table中的一行数据,定义了数据在table中的位置和顺序,且primary key一旦定义,后续无法修改。primary key 包含两部分,可以表示为(part1, part2),其中,part1是partition key,用于确定数据的partition,part2为可选部分,为clustering key,用于确定数据在partition内部的顺序,其中,clustering key是可选的,而partition key是必须部分。</p>
<p><strong>Simple Primary Key</strong></p>
<p>指primary key只包含一个column,该column为partition key,无clustering key。这种情况下,数据的读写都很快,推荐使用。</p>
<p><strong>Composite Partition Key</strong></p>
<p>partition key包含多columns时,称为Composite Partition Key。这种Partition Key 可以将数据划分为多份,可以解决Cassandra中的热点问题或者是大量数据写入单节点的问题。不过,这种情况下,如果要读取数据,需要指定所有的partition key的columns。</p>
<p><strong>Compound Primary Key</strong></p>
<p>同时包含partition key 和 cluster key。cluster key用于数据在节点内的排序,指定cluster key后,可以快速的读取数据。</p>
<h2 id="counter-table">Counter Table</h2>
<p>counter 是一个特殊的column,存储了一个只会增加或减少的整数。需要注意的是,counter table里只能包含primary key 和 counter column两部分。举例如下:</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">popular_count</span> <span class="p">(</span> <span class="n">id</span> <span class="n">UUID</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span> <span class="n">popularity</span> <span class="n">counter</span> <span class="p">);</span>
</code></pre></div></div>
<p>另外,counter table里的counter column值的写入跟普通column不一样,counter column需要用update方法,而不是insert方法。举例来说,下面的语句第一次之后,popularity的值将等于1。</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">UPDATE</span> <span class="n">popular_count</span>
<span class="k">SET</span> <span class="n">popularity</span> <span class="o">=</span> <span class="n">popularity</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">6</span><span class="n">ab09bec</span><span class="o">-</span><span class="n">e68e</span><span class="o">-</span><span class="mi">48</span><span class="n">d9</span><span class="o">-</span><span class="n">a5f8</span><span class="o">-</span><span class="mi">97</span><span class="n">e6fb4c9b47</span><span class="p">;</span>
</code></pre></div></div>
<h1 id="物化视图">物化视图</h1>
<p>Cassandra 也提供了 materialized view。Cassandra 的 materialized view 其实是基于另外的表的数据创建的一张新表, 新表与原来的表相比具有不同的primay key和属性。在 Cassandra, 数据模型是由query驱动的(相对的,关系型数据库由实体关系驱动),标准的做法是为query创建表,如果有不同的query,则需要建立不同的表,但是,这种情况会给数据维护增加困难,需要应用去维护多张表的更新。materialized view 解决了这个问题,源表更新后,会自动更新materialized view中的数据。</p>
<p>物化视图有些要求:</p>
<ul>
<li>源表的primary key必须是物化视图的primary key的一部分。</li>
<li>物化视图的primary key只能添加一列新列,且不能为static column。</li>
</ul>
<p>同时,物化视图也会有些其他影响:</p>
<ul>
<li>写性能不如普通的表。</li>
<li>源表的写操作性能变差。</li>
<li>物化视图的数据有延迟。原因是用户只能向源表写数据,然后异步的写到物化视图。</li>
</ul>
<h2 id="二级索引">二级索引</h2>
<p>primary key是一级索引,所以,索引也叫二级索引,用于辅助找到一级索引。二级索引提供了一种利用普通的columns访问数据而不使用partition key,可以快速高效的查询数据。二级索引将索引列存储到一个单独的、隐藏的表中,索引列作为primary key,源表的primary key作为新表的值。不过,二级索引有适用场景,也有些场景不适合使用:</p>
<p><strong>适用场景</strong>:表中很多行都具有相同的indexed value的情况比较适合,索引列的值越多,维护和查询的成本就越多。</p>
<p><strong>不适用场景</strong>:</p>
<ul>
<li>
<p>high-cardinality 列,这一列有很多不同的值, 此时,查询了很多值,却只会取其中很小一部分作为结果。<strong><em>此时,可以用物化视图</em></strong>。另一个极端,如布尔值,区分度太小,也不适合。</p>
</li>
<li>counter column。</li>
<li>频繁更新/删除的列。</li>
<li>从多个partition 取数据时,此时,涉及到多个partition,随着集群越来越大,可能越来越慢。</li>
</ul>
<h2 id="insertupdate">Insert/Update</h2>
<p><a href="https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cqlInsert.html">INSERT</a> 和 <a href="https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cqlUpdate.html">UPDATE</a> 操作在使用 <code class="language-plaintext highlighter-rouge">IF</code> 从句时,支持lightweight transactions(Compare and Set (CAS))。 <a href="https://docs.datastax.com/en/cassandra/3.0/cassandra/dml/dmlLtwtTransactions.html">Lightweight transactions</a> 应该谨慎使用,因为会带来更大的延迟。</p>
<h2 id="time-to-livettl">Time-To-Live(TTL)</h2>
<p>columns / table 支持TTL。数据过期后就会被标记为 <a href="https://docs.datastax.com/en/glossary/doc/glossary/gloss_tombstone.html">tombstone</a>.</p>
<p>##Batch Insert/Update</p>
<p>批量insert/update是一个原子操作。单 partition的批量操作自动保证原子性,涉及到多 partition的批量操作,需要用到 batchlog来保证原子性。同时,批量操作可以减少网络传输,可以提高性能。</p>
<p>使用批量操作的一个重要考虑就是一组操作需要保证原子性。单分区的批量操作在服务器端一次性完成。涉及到多分区的批量操作往往会带来性能问题,需慎用。在批量操作时,coordinator 节点负责管理所有的写操作,coordinator 节点很容易成为瓶颈,</p>
<p><strong><em>注意,不能为了性能而盲目的使用批量操作</em></strong>。</p>
<h2 id="query">Query</h2>
<p>Where 条件中 partition key 和 cluster key 产生的结果必须是一个连续的结果集,比如,表的primary为(colA, colB, colC),则where 条件 colA = XXX and colC=XXX 是会报错的。</p>
<p>可以使用ORDER BY子句指定结果集的顺序,partition key 必须在where子句指明, order by子句必须指明cluster key以便排序。</p>
<h1 id="总结">总结</h1>
<p>Cassandra相对于Hbase等更容易上手的一个重要原因是提供了类似SQL的CQL,但是,我们在使用CQL时应该明白,CQL并不是SQL,Cassandra的原理与关系型数据库不一样,CQL与SQL也不一样,如果仅仅是将SQL的经验带过来,会带来很多意想不到的问题。本文只是一些CQL的要点纪录,并不是完整的CQL教程,可以参考<a href="https://cassandra.apache.org/doc/latest/cql/index.html" title="CQL">官网</a>系统学习。</p>
<p>下一篇准备讲如何在Spark中使用Cassandra。</p>
Cassandra教程(三):cassandra架构(下)
2018-02-24T00:00:00+00:00
http://www.readingnotes.site/posts/Cassandra教程(三):Cassandra架构(下)
<p>上篇介绍了Cassandra的架构、数据distribution 与 replication,本文主要介绍Cassandra的内部工作机制,包括存储引擎、Cassandra读写、数据一致性等。</p>
<h1 id="1-存储引擎">1. 存储引擎</h1>
<p>在分布式系统中,有些系统写数据采用<strong>read-and-write</strong> 的方式(如Elasticsearch),Cassandra为了避免read-and-write 带来的性能问题,没有采用read-and-write的方式,存储引擎将写操作保存于内存,每过一段时间,将内存中的数据以追加的方式,写入磁盘,磁盘中的数据都是不可更改、不可重写的。当读数据时,需要将读取的数据组合起来以得到正确的数据。</p>
<p>在内部实现上,Cassandra 采用了类似 <a href="https://en.wikipedia.org/wiki/Log-structured_merge-tree" title="Log-structured merge tree">Log-Structured merge tree</a> 的存储结构存储数据,采用顺序IO,这样的话,即使采用HDD也能有不错的性能。</p>
<h1 id="2-数据读写">2. 数据读写</h1>
<p><strong>write</strong></p>
<p>如下图所示,node接收write请求,将数据写入<strong>memtable</strong>,同时记录到<strong>commit log</strong>。commit log 记录node接收到的每一次write请求,这样,即使发生断电等故障,也不会丢失数据。</p>
<p>memtable是一个cache,按顺序存储write的数据,当memtable 的内容大小达到配置的阈值或者commit log的存储空间大于阈值,memtable里的数据被flush到磁盘,保存为<strong>SSTables</strong>。当memtable中的数据flush到磁盘后,commit log被删除。</p>
<p>在内部实现上,memtable 和 SSTable按table进行划分,不同的table可以共享一个commit log。SSTable本质上是磁盘文件,不可更改,因此,一个partition 包含了多个SSTables。</p>
<p><em>best practice</em>: 重启node前先使用nodetool flush memtable,这样可以减少commit log重放。</p>
<p><img src="http://blog2018.qiniudn.com/cassandra-write-process.png" alt="cassandra写入流程" /></p>
<p><strong>compaction</strong></p>
<p>Cassandra不会采用类似insert/update的方式更新已有数据,而是创建带有时间戳版本信息的新的数据,同时,Cassandra也不删除数据,而是将数据标记为tombstones。这样,随着时间过去,每行数据可能包括不同时间戳版本的多个列集合,读取数据时,可能需要读取越来越多的列才能组成完整的一行数据。为了避免这种情况,Cassandra周期性的合并SSTables并删除旧数据,这个过程称作<strong>compaction</strong>。compaction 读取每行数据所有版本的数据然后用最新的数据组成完整的一行,新数据写入新的SSTable,旧版本数据随后被删除。compaction 提高了Cassandra的read 性能。</p>
<p>另外,在compaction过程中,新旧数据可能同时存在,所以,磁盘使用率上会存在突增;同时,由于数据按照partition key 按序存储,所以,compaction过程中,不使用随机IO。</p>
<p><strong>update</strong></p>
<p>Cassandra 将每个新行视为upsert,如果已经存在该primary key,则视作是对原有数据的update,</p>
<p><strong>delete</strong></p>
<p>Cassandra 删除数据时使用tombstone,tombstone是一个标记,标记column被删除了,在compaction阶段,标记删除的columns被物理删除。在读取阶段,标记为tombstone的数据被忽略。</p>
<p><strong>read</strong></p>
<p>读取数据时,Cassandra可能需要联合memtable和多个SSTables才能拼装出完整的数据。</p>
<h1 id="3-数据一致性">3. 数据一致性</h1>
<p>根据 <a href="https://en.wikipedia.org/wiki/CAP_theorem" title="CAP">CAP</a> 理论,Cassandra 是一个AP系统,提供最终一致性。同时,Cassandra可以灵活配置,使系统更趋向一个CP系统。</p>
<h2 id="31-two-consistency-features">3.1 Two consistency features</h2>
<h3 id="311-tunable-consistency">3.1.1 Tunable consistency</h3>
<p>高一致性意味着高延迟,低一致性意味着低延迟,需要根据自己的需求,自己调节。而且,Cassandra 不仅支持集群级别的一致性设置,还支持请求级别的一致性设置,用户可以针对请求设置一致性。</p>
<p>一致性等级决定了处理读/写请求返回成功的数据副本数,Cassandra赋予用户充分的自主选择权,通常情况下,设置读/写的的一致性等级为”<strong>QUORUM</strong>“,其中,quorum = (sum_of_replication_factors / 2) + 1,sum_of_replication_factors表示所有datacenter中replication factor求和。</p>
<h3 id="312-linearizable-consistency">3.1.2 Linearizable consistency</h3>
<p>存在一些场景,一些操作需要顺序执行且不能被中断,Cassandra通过<strong>lightweight transactions</strong> 来支持这种场景。</p>
<h2 id="32-一致性计算">3.2 一致性计算</h2>
<p><strong>强一致性</strong>: R + W > N</p>
<p><strong>最终一致性</strong>:R + W <= N</p>
<p>其中,R代表read操作的一致性,W表示write操作的一致性,N表示副本数。</p>
<h1 id="总结">总结</h1>
<p>本文介绍了Cassandra的内部实现,下一篇开始介绍CQL。</p>
Cassandra教程(二):cassandra架构(上)
2018-02-23T00:00:00+00:00
http://www.readingnotes.site/posts/Cassandra教程(二):Cassandra架构(上)
<p>Cassandra 设计用来处理多节点大型数据工作负载,系统中没有单点,Cassandra 采用peer-to-peer架构,数据在所有节点之间分发。</p>
<ul>
<li>cluster中所有node具有相同的角色。每个node互相独立,同时在内部又互相沟通。</li>
<li>cluster中所有node都可以处理读写请求,而不用管数据具体在哪儿。</li>
<li>如果一个node挂了,其它node可以处理读写请求。</li>
</ul>
<h1 id="1-node之间的沟通">1. node之间的沟通</h1>
<p>Cassandra 各node之间采用 <a href="https://en.wikipedia.org/wiki/Gossip_protocol" title="gossip">gossip</a> 协议进行沟通,gossip 进程每秒与集群中最多三个node交换信息,信息包括node自身的信息以及与该node交换过信息的node的信息,这样,所有node都可以很快获取集群中所有的node信息。</p>
<h1 id="2-data-distribution-and-replication">2. Data distribution and replication</h1>
<p>Cassandra是一个<strong>分区的按行存储的数据库</strong>(partitioned row store database)。在Cassandra中,数据以table的形式组织起来,primary key唯一标记一行,同时,primary key也决定了数据行存储的node,replication是数据行的备份,Cassandra将数据复制到多个node上,从而实现高可用和容错。</p>
<p>下面分data distribution 和 replication两个方面进行阐述,其中distribution说明将数据分发到哪个node,replication说明如何备份。</p>
<h2 id="21-data-distribution">2.1 Data distribution</h2>
<p>partitioner 决定了数据是怎样在集群中分布的。简单的讲,partitioner是一个函数,根据partition key产生一个唯一标记一行的token,然后,这行数据根据token分发到集群中的节点,通常,partitioner是Hash函数。Cassandra提供了三种partitioner,包括Murmur3Partitioner(default)、RandomPartitioner、ByteOrderedPartitioner。(为更好理解这部分,可以参考<a href="http://blog.codinglabs.org/articles/consistent-hashing.html" title=" 一致性Hash">一致性哈希</a>)。</p>
<h2 id="22-data-replication">2.2 Data replication</h2>
<p>Cassandra将数据存储到多个node以实现高可用和容错,replication策略决定了将数据备份到哪些节点。</p>
<p><strong>replication factor</strong> 指数据在整个cluster中的份数,如果replication factor等于1,则数据在整个集群中仅存在一份,此时,如果存储数据的node出现故障,那么数据就丢失了。在实践中,replication factor 应该不超过cluster中node的数量。</p>
<p>Cassandra提供了两种replication 策略:</p>
<ul>
<li>SimpleStrategy: 仅适用于单datacenter 单 rack。根据partitioner存储第一份replica,然后在顺时针方向的下一个node上存放下一份replica(不考虑网络拓扑信息)。</li>
<li>NetworkTopologyStrategy: 可以方便的扩展到多datacenter,推荐使用,同时,NetworkTopologyStrategy尽量避免将数据存储到相同的rack上。</li>
</ul>
<h1 id="3-snitches">3. Snitches</h1>
<p>snitch 决定了node属于哪个datacenter的哪个rack。可以用于告知Cassandra集群网络拓扑信息,以实现高效的请求路由与分发、备份数据。</p>
<p>主要的snitch包括:</p>
<ul>
<li>dynamic snitching</li>
<li>SimpleSnitch</li>
<li>RackInferringSnitch</li>
<li>PropertyFileSnitch</li>
<li>GossipingPropertyFileSnitch</li>
<li>…</li>
</ul>
<h1 id="4-总结">4. 总结</h1>
<p>本文主要介绍Cassandra的架构、数据distribution 与 replication,下一章介绍Cassandra的内部信息,包括存储引擎、Cassandra读写、数据一致性等。</p>
Cassandra教程(一):cassandra简介
2018-02-22T00:00:00+00:00
http://www.readingnotes.site/posts/Cassandra教程(一):Cassandra简介
<h1 id="overview">Overview</h1>
<p>Apache Cassandra 是一个大规模可扩展的分布式开源NoSQL数据库,完美适用于跨数据中心/云端的结构化数据、半结构化数据和非结构化数据,同时,Cassandra 高可用、线性可扩展、高性能、无单点。</p>
<h2 id="特点">特点</h2>
<ul>
<li>scalable,线性可扩展</li>
<li>fault-tolerant,且没有单点(peer-to-peer)</li>
<li>column-oriented database & partitioned row store database</li>
<li>distribution design 基于 Amazon 的 Dynamo</li>
<li>data model 基于 Google 的 Bigtable</li>
<li>灵活的数据存储,支持结构化、半结构化、非结构化数据</li>
<li>支持事务</li>
<li>写性能好</li>
<li>由 Facebook开源</li>
</ul>
<h1 id="数据模型">数据模型</h1>
<h2 id="内部数据结构">内部数据结构</h2>
<p>Cassandra是一个column-oriented database,也就是说,不用像关系型数据库一样事先定义好列,在Cassandra中,不同行的列可以不一样。</p>
<p>在Cassandra中,数据模型由keyspaces、column families、primary key 和 columns组成,对比关系型数据库,如下表:</p>
<table>
<thead>
<tr>
<th>关系型数据库</th>
<th>Cassandra</th>
</tr>
</thead>
<tbody>
<tr>
<td>Database</td>
<td>Keyspace</td>
</tr>
<tr>
<td>Table</td>
<td>CF(column family)</td>
</tr>
<tr>
<td>Primary Key</td>
<td>Primary Key</td>
</tr>
<tr>
<td>Column Name</td>
<td>Key / Column Name</td>
</tr>
<tr>
<td>Column Value</td>
<td>Column Value</td>
</tr>
</tbody>
</table>
<p>在Cassandra中,Primary Key包括partition key 和 cluster key两部分,其中cluster key可选,partition key确定数据行分发到哪个node,cluster key用于node内部数据排序。</p>
<p>对于每一个column family,不要想象成关系型数据库的表,而要想像成一个多层嵌套的排序散列表(Nested sorted map)。这样能更好地理解和设计Cassandra的数据模型。</p>
<p>散列表可用提供高效的键值查找,排序的散列表可提供高效的范围查找,在Cassandra里,我们可以使用primary key和column key做高效的键值查询和范围查询,而且,在Cassandra中,列的名称可以直接包含数据,也就是说,有的列可以只有列名没有列值。</p>
<blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Map<RowKey, SortedMap<ColumnKey, ColumnValue>>
</code></pre></div> </div>
</blockquote>
<h2 id="cql">CQL</h2>
<p>CQL (Cassandra Query Language)是用于 Cassandra 的查询语言,可类比用于关系型数据库的SQL,注意,虽然CQL 和 SQL 看起来比较相似,但二者内部原理完全不同。</p>
<h1 id="举个例子">举个例子</h1>
<h2 id="搭建cassandra">搭建Cassandra</h2>
<p>学习Cassandra时搭建环境最简单的方式是使用docker,可以参考<a href="https://hub.docker.com/_/cassandra/" title="cassandra docker image">镜像</a> 。</p>
<h2 id="例子">例子</h2>
<p>如下图所示,首先创建keyspaces,然后创建table,往table中插入数据,再查询该table。</p>
<p><img src="http://blog2018.qiniudn.com/introduce-cassandra.png" alt="例子" /></p>
<h1 id="总结">总结</h1>
<p>本文简单介绍了Cassandra,并举例说明了基本的使用。下一篇将介绍Cassandra的数据模型。</p>
Airflow介绍
2018-01-17T00:00:00+00:00
http://www.readingnotes.site/posts/airflow介绍
<p>最近工作需要,使用airflow搭建了公司的ETL系统,顺带在公司分享了一次airflow,整理成文,Enjoy!</p>
<h1 id="1-airflow-介绍">1. airflow 介绍</h1>
<h2 id="11-airflow-是什么">1.1 airflow 是什么</h2>
<blockquote>
<p>Airflow is a platform to programmatically author, schedule and monitor workflows.</p>
</blockquote>
<p>airflow 是一个编排、调度和监控workflow的平台,由Airbnb开源,现在在Apache Software Foundation 孵化。airflow 将workflow编排为tasks组成的DAGs,调度器在一组workers上按照指定的依赖关系执行tasks。同时,airflow 提供了丰富的命令行工具和简单易用的用户界面以便用户查看和操作,并且airflow提供了监控和报警系统。</p>
<h2 id="12-airflow-核心概念">1.2 airflow 核心概念</h2>
<ol>
<li>DAGs:即有向无环图(Directed Acyclic Graph),将所有需要运行的tasks按照依赖关系组织起来,描述的是所有tasks执行的顺序。</li>
<li>Operators:可以简单理解为一个class,描述了DAG中一个具体的task具体要做的事。其中,airflow内置了很多operators,如<code class="language-plaintext highlighter-rouge">BashOperator</code> 执行一个bash 命令,<code class="language-plaintext highlighter-rouge">PythonOperator</code> 调用任意的Python 函数,<code class="language-plaintext highlighter-rouge">EmailOperator</code> 用于发送邮件,<code class="language-plaintext highlighter-rouge">HTTPOperator</code> 用于发送HTTP请求, <code class="language-plaintext highlighter-rouge">SqlOperator</code> 用于执行SQL命令…同时,用户可以自定义Operator,这给用户提供了极大的便利性。</li>
<li>Tasks:Task 是 Operator的一个实例,也就是DAGs中的一个node。</li>
<li>Task Instance:task的一次运行。task instance 有自己的状态,包括<strong>“running”, “success”, “failed”, “skipped”, “up for retry”</strong>等。</li>
<li>Task Relationships:DAGs中的不同Tasks之间可以有依赖关系,如 <code class="language-plaintext highlighter-rouge">TaskA >> TaskB</code>,表明TaskB依赖于TaskA。</li>
</ol>
<p>通过将DAGs和Operators结合起来,用户就可以创建各种复杂的 workflow了。</p>
<h2 id="13-其它概念">1.3 其它概念</h2>
<ol>
<li>Connections: 管理外部系统的连接信息,如外部MySQL、HTTP服务等,连接信息包括<code class="language-plaintext highlighter-rouge">conn_id</code>/<code class="language-plaintext highlighter-rouge">hostname</code> / <code class="language-plaintext highlighter-rouge">login</code> / <code class="language-plaintext highlighter-rouge">password</code>/<code class="language-plaintext highlighter-rouge">schema</code> 等,可以通过界面查看和管理,编排workflow时,使用<code class="language-plaintext highlighter-rouge">conn_id</code> 进行使用。</li>
<li>Pools: 用来控制tasks执行的并行数。将一个task赋给一个指定的<code class="language-plaintext highlighter-rouge">pool</code>,并且指明<code class="language-plaintext highlighter-rouge">priority_weight</code>,可以干涉tasks的执行顺序。</li>
<li>XComs:在airflow中,operator一般(not always)是原子的,也就是说,他们一般独立执行,同时也不需要和其他operator共享信息,如果两个operators需要共享信息,如filename之类的, 推荐将这两个operators组合成一个operator。如果实在不能避免,则可以使用XComs (cross-communication)来实现。XComs用来在不同tasks之间交换信息。</li>
<li>Trigger Rules:指task的触发条件。默认情况下是task的直接上游执行成功后开始执行,airflow允许更复杂的依赖设置,包括<code class="language-plaintext highlighter-rouge">all_success</code>(所有的父节点执行成功),<code class="language-plaintext highlighter-rouge">all_failed</code>(所有父节点处于failed或upstream_failed状态),<code class="language-plaintext highlighter-rouge">all_done</code>(所有父节点执行完成),<code class="language-plaintext highlighter-rouge">one_failed</code>(一旦有一个父节点执行失败就触发,不必等所有父节点执行完成),<code class="language-plaintext highlighter-rouge">one_success</code>(一旦有一个父节点执行成功就触发,不必等所有父节点执行完成),<code class="language-plaintext highlighter-rouge">dummy</code>(依赖关系只是用来查看的,可以任意触发)。另外,airflow提供了<code class="language-plaintext highlighter-rouge">depends_on_past</code>,设置为True时,只有上一次调度成功了,才可以触发。</li>
</ol>
<h1 id="2-示例">2. 示例</h1>
<p>先来看一个简单的DAG。图中每个节点表示一个task,所有tasks组成一个DAG,各个tasks之间的依赖关系可以根据节点之间的线看出来。</p>
<p><img src="http://blog2018.qiniudn.com/airflow-dag1.jpeg" alt="DAGs" /></p>
<h3 id="21-实例化dag">2.1 实例化DAG</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># -*- coding: UTF-8 -*-
</span>
<span class="c1">## 导入airflow需要的modules
</span><span class="kn">from</span> <span class="nn">airflow</span> <span class="kn">import</span> <span class="n">DAG</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span>
<span class="n">default_args</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">'owner'</span><span class="p">:</span> <span class="s">'lxwei'</span><span class="p">,</span>
<span class="s">'depends_on_past'</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span> <span class="c1"># 如上文依赖关系所示
</span> <span class="s">'start_date'</span><span class="p">:</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">2018</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">17</span><span class="p">),</span> <span class="c1"># DAGs都有个参数start_date,表示调度器调度的起始时间
</span> <span class="s">'email'</span><span class="p">:</span> <span class="p">[</span><span class="s">'lxwei@github.com'</span><span class="p">],</span> <span class="c1"># 用于alert
</span> <span class="s">'email_on_failure'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
<span class="s">'email_on_retry'</span><span class="p">:</span> <span class="bp">False</span><span class="p">,</span>
<span class="s">'retries'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="c1"># 重试策略
</span> <span class="s">'retry_delay'</span><span class="p">:</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dag</span> <span class="o">=</span> <span class="n">DAG</span><span class="p">(</span><span class="s">'example-dag'</span><span class="p">,</span> <span class="n">default_args</span><span class="o">=</span><span class="n">default_args</span><span class="p">,</span> <span class="n">schedule_interval</span><span class="o">=</span><span class="s">'0 0 * * *'</span><span class="p">)</span>
</code></pre></div></div>
<p>在创建DAGs时,我们可以显示的给每个Task传递参数,但通过default_args,我们可以定义一个默认参数用于创建tasks。</p>
<p><strong>注意,schedule_interval 跟官方文档不一致,官方文档的方式已经被deprecated</strong>。</p>
<h2 id="22-定义依赖关系">2.2 定义依赖关系</h2>
<p>这个依赖关系是我自己定义的,key表示某个taskId,value里的每个元素也表示一个taskId,其中,key依赖value里的所有task。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"dependencies"</span><span class="p">:</span> <span class="p">{</span>
<span class="s">"goods_sale_2"</span><span class="p">:</span> <span class="p">[</span><span class="s">"goods_sale_1"</span><span class="p">],</span> <span class="c1"># goods_sale_2 依赖 goods_sale1
</span> <span class="s">"shop_sale_1_2"</span><span class="p">:</span> <span class="p">[</span><span class="s">"shop_sale_1_1"</span><span class="p">],</span>
<span class="s">"shop_sale_2_2"</span><span class="p">:</span> <span class="p">[</span><span class="s">"shop_sale_2_1"</span><span class="p">],</span>
<span class="s">"shop_sale_2_3"</span><span class="p">:</span> <span class="p">[</span><span class="s">"shop_sale_2_2"</span><span class="p">],</span>
<span class="s">"etl_task"</span><span class="p">:</span> <span class="p">[</span><span class="s">"shop_info"</span><span class="p">,</span> <span class="s">"shop_sale_2_3"</span><span class="p">,</span> <span class="s">"shop_sale_realtime_1"</span><span class="p">,</span> <span class="s">"goods_sale_2"</span><span class="p">,</span> <span class="s">"shop_sale_1_2"</span><span class="p">],</span>
<span class="s">"goods_sale_1"</span><span class="p">:</span> <span class="p">[</span><span class="s">"timelySalesCheck"</span><span class="p">,</span> <span class="s">"productDaySalesCheck"</span><span class="p">],</span>
<span class="s">"shop_sale_1_1"</span><span class="p">:</span> <span class="p">[</span><span class="s">"timelySalesCheck"</span><span class="p">,</span> <span class="s">"productDaySalesCheck"</span><span class="p">],</span>
<span class="s">"shop_sale_realtime_1"</span><span class="p">:</span> <span class="p">[</span><span class="s">"timelySalesCheck"</span><span class="p">,</span> <span class="s">"productDaySalesCheck"</span><span class="p">],</span>
<span class="s">"shop_sale_2_1"</span><span class="p">:</span> <span class="p">[</span><span class="s">"timelySalesCheck"</span><span class="p">,</span> <span class="s">"productDaySalesCheck"</span><span class="p">],</span>
<span class="s">"shop_info"</span><span class="p">:</span> <span class="p">[</span><span class="s">"timelySalesCheck"</span><span class="p">,</span> <span class="s">"productDaySalesCheck"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="23-定义tasks和依赖关系">2.3 定义tasks和依赖关系</h2>
<p>首先,实例化operators,构造tasks。如代码所示,其中,<code class="language-plaintext highlighter-rouge">EtlTask</code>、<code class="language-plaintext highlighter-rouge">MySQLToWebDataTransfer</code>、<code class="language-plaintext highlighter-rouge">MySQLSelector</code> 是自定义的三种Operator,根据taskType实例化operator,并存放到taskDict中,便于后期建立tasks之间的依赖关系。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">taskConf</span> <span class="ow">in</span> <span class="n">tasksConfs</span><span class="p">:</span>
<span class="n">taskType</span> <span class="o">=</span> <span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskType"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">taskType</span> <span class="o">==</span> <span class="s">"etlTask"</span><span class="p">:</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">EtlTask</span><span class="p">(</span>
<span class="n">task_id</span><span class="o">=</span><span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskId"</span><span class="p">),</span>
<span class="n">httpConnId</span><span class="o">=</span><span class="n">httpConn</span><span class="p">,</span>
<span class="n">etlId</span><span class="o">=</span><span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"etlId"</span><span class="p">),</span>
<span class="n">dag</span><span class="o">=</span><span class="n">dag</span><span class="p">)</span>
<span class="n">taskDict</span><span class="p">[</span><span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskId"</span><span class="p">)]</span> <span class="o">=</span> <span class="n">task</span>
<span class="k">elif</span> <span class="n">taskType</span> <span class="o">==</span> <span class="s">"MySQLToWebDataTransfer"</span><span class="p">:</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">MySqlToWebdataTransfer</span><span class="p">(</span>
<span class="n">task_id</span> <span class="o">=</span> <span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskId"</span><span class="p">),</span>
<span class="n">sql</span><span class="o">=</span> <span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"sql"</span><span class="p">),</span>
<span class="n">tableName</span><span class="o">=</span><span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"tableName"</span><span class="p">),</span>
<span class="n">mysqlConnId</span> <span class="o">=</span><span class="n">mysqlConn</span><span class="p">,</span>
<span class="n">httpConnId</span><span class="o">=</span><span class="n">httpConn</span><span class="p">,</span>
<span class="n">dag</span><span class="o">=</span><span class="n">dag</span>
<span class="p">)</span>
<span class="n">taskDict</span><span class="p">[</span><span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskId"</span><span class="p">)]</span> <span class="o">=</span> <span class="n">task</span>
<span class="k">elif</span> <span class="n">taskType</span> <span class="o">==</span> <span class="s">"MySQLSelect"</span><span class="p">:</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">StatusChecker</span><span class="p">(</span>
<span class="n">task_id</span> <span class="o">=</span> <span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskId"</span><span class="p">),</span>
<span class="n">mysqlConnId</span> <span class="o">=</span> <span class="n">mysqlConn</span><span class="p">,</span>
<span class="n">sql</span> <span class="o">=</span> <span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"sql"</span><span class="p">),</span>
<span class="n">dag</span> <span class="o">=</span> <span class="n">dag</span>
<span class="p">)</span>
<span class="n">taskDict</span><span class="p">[</span><span class="n">taskConf</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"taskId"</span><span class="p">)]</span> <span class="o">=</span> <span class="n">task</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">logging</span><span class="p">.</span><span class="n">error</span><span class="p">(</span><span class="s">"error. TaskType is illegal."</span><span class="p">)</span>
</code></pre></div></div>
<p>构建tasks之间的依赖关系,其中,dependencies中定义了上面的依赖关系,<code class="language-plaintext highlighter-rouge">A >> B</code> 表示A是B的父节点,相应的,<code class="language-plaintext highlighter-rouge">A << B</code> 表示A是B的子节点。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">sourceKey</span> <span class="ow">in</span> <span class="n">dependencies</span><span class="p">:</span>
<span class="n">destTask</span> <span class="o">=</span> <span class="n">taskDict</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">sourceKey</span><span class="p">)</span>
<span class="n">sourceTaskKeys</span> <span class="o">=</span> <span class="n">dependencies</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">sourceKey</span><span class="p">)</span>
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">sourceTaskKeys</span><span class="p">:</span>
<span class="n">sourceTask</span> <span class="o">=</span> <span class="n">taskDict</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">sourceTask</span> <span class="o">!=</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">destTask</span> <span class="o">!=</span> <span class="bp">None</span><span class="p">):</span>
<span class="n">sourceTask</span> <span class="o">>></span> <span class="n">destTask</span>
</code></pre></div></div>
<h1 id="3-常用命令">3. 常用命令</h1>
<p>命令行输入<code class="language-plaintext highlighter-rouge">airflow -h</code>,得到帮助文档</p>
<blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>backfill Run subsections of a DAG for a specified date range
list_tasks List the tasks within a DAG
clear Clear a set of task instance, as if they never ran
pause Pause a DAG
unpause Resume a paused DAG
trigger_dag Trigger a DAG run
pool CRUD operations on pools
variables CRUD operations on variables
kerberos Start a kerberos ticket renewer
render Render a task instance's template(s)
run Run a single task instance
initdb Initialize the metadata database
list_dags List all the DAGs
dag_state Get the status of a dag run
task_failed_deps Returns the unmet dependencies for a task instance
from the perspective of the scheduler. In other words,
why a task instance doesn't get scheduled and then
queued by the scheduler, and then run by an executor).
task_state Get the status of a task instance
serve_logs Serve logs generate by worker
test Test a task instance. This will run a task without
checking for dependencies or recording it's state in
the database.
webserver Start a Airflow webserver instance
resetdb Burn down and rebuild the metadata database
upgradedb Upgrade the metadata database to latest version
scheduler Start a scheduler instance
worker Start a Celery worker node
flower Start a Celery Flower
version Show the version
connections List/Add/Delete connections
</code></pre></div> </div>
</blockquote>
<p>其中,使用较多的是<strong><em>backfill</em></strong>、<strong><em>run</em></strong>、<strong><em>test</em></strong>、<strong><em>webserver</em></strong>、<strong><em>scheduler</em></strong>。其他操作在web界面操作更方便。另外,<strong><em>initdb</em></strong> 用于初始化metadata,使用一次即可;<strong><em>resetdb</em></strong>会重置metadata,清除掉数据(如connection数据), 需要慎用。</p>
<h1 id="4-问题">4. 问题</h1>
<p>在使用airflow过程中,曾把DAGs里的task拆分得很细,这样的话,如果某个task失败,重跑的代价会比较低。但是,在实践中发现,tasks太多时,airflow在调度tasks会很低效,airflow一直处于选择待执行的task的过程中,会长时间没有具体task在执行,从而整体执行效率大幅降低。</p>
<h1 id="5-总结">5. 总结</h1>
<p>airflow 很好很强大。如果只是简单的ETL之类的工作,可以很容易的编排。调度灵活,而且监控和报警系统完备,可以很方便的投入生产环节。</p>
<h1 id="6-参阅">6. 参阅</h1>
<p><a href="https://airflow.apache.org">airflow 官网</a></p>
<p><a href="https://github.com/apache/incubator-airflow">github</a></p>
Spark 介绍
2017-10-12T00:00:00+00:00
http://www.readingnotes.site/posts/Spark 介绍
<p>最近工作开始接触Spark,本系列博客可以作为学习思考的纪录。</p>
<p>如果无特殊说明,均针对Spark 2.2 。</p>
<h1 id="1-spark-介绍">1. Spark 介绍</h1>
<h2 id="11-spark-是什么">1.1 Spark 是什么</h2>
<blockquote>
<p>Apache Spark is a fast and general engine for large-scale data processing.</p>
</blockquote>
<p>Spark 官网将Spark 定义为一个大型可扩展数据的<strong>快速</strong>和<strong>通用</strong>处理引擎。</p>
<p>首先,Spark 采用了先进的DAG执行引擎,支持循环数据流和内存计算,使得 Spark 速度更快,在内存中的速度是Hadoop MR的百倍,在磁盘上的速度是Hadoop MR的十倍(官网数据) 。</p>
<p>其次,Spark 是一个通用的处理引擎。Spark 被设计用来做批处理、迭代运算、交互式查询、流处理、机器学习等。</p>
<p>另外,Spark 易用,可以用Scala、Java、Python、R等快速开发分布式应用,Spark 提供了大量的高级API,方便开发(对比MapReduce…)。</p>
<p>最后,Spark 集成了多种数据源,并且可以通过Yarn、Mesos、Standalone(Spark 提供的部署方式)等各种模式运行。</p>
<h2 id="12-为什么需要spark">1.2 为什么需要Spark</h2>
<p>在Spark 之前,我们已经有了Hadoop,Hadoop 作为大数据时代企业首选技术,方兴未艾,我们为什么还需要Spark 呢?</p>
<p>我的理解是,Hadoop 对某些工作并不是最优的选择:</p>
<ol>
<li>中间输出到磁盘,会产生较高的延迟。</li>
<li>缺少对迭代运算的支持。</li>
</ol>
<p>总的来说,Hadoop 设计得比较适合处理离线数据,在实时查询、迭代计算方面存在不足,而业界对实时查询和迭代计算有着越来越多的需求。Spark 的出现正好能解决这些问题,快速、易用、通用,而且对有效支持Hadoop。</p>
<h2 id="13-spark-核心生态圈与重要扩展">1.3 Spark 核心生态圈与重要扩展</h2>
<p><img src="http://7sbmb1.com1.z0.glb.clouddn.com/spark-eco.png" alt="Spark处理框架" /></p>
<p>上图是一个比较常见的以 Spark 为核心的大数据处理框架。</p>
<p>其中,Spark Core 提供了 Spark 中的任务调度、内存管理、错误恢复、与存储系统交互等基本功能,而且,Spark Core 定义了RDDs(resilient distributed datasets,弹性分布式数据集,是Spark 的核心抽象)和操作RDDs的各种APIs。</p>
<p>基于Spark Core,提供六大核心扩展。Spark SQL 提供交互式SQL查询功能;Spark 2.0 引入了 Structured Streaming,Structured Streaming 是建立在Spark SQL 之上的可扩展、高容错的流处理引擎;MLlib 提供机器学习;GraphX提供图计算服务;Spark Streaming 基于 Spark 核心 API 提供可扩展、高吞吐量、高容错的实时流处理;SparkR 是Spark的一个R开发包。这些核心扩展,除了Structured Streaming,都基于Spark 核心API处理问题,方法几乎是通用的,处理的数据可共享,大大提高了数据集成的灵活性。</p>
<p>Spark 可扩展至大量节点,为实现这个目的并最大程度的保证灵活性,Spark 支持多种资源管理器(cluster manageers),包括 Yarn、Mesos 以及 Spark 提供的Standalone,另外,local模式主要用于开发测试。</p>
<p>最后,Spark 可支持多种数据集,包括本地文件系统、HDFS、Hbase、Cassandra等。</p>
<p>可见,Spark 提供了一站式数据处理能力,这是大数据时代相对很多专用引擎来说所不具备的。</p>
<h1 id="2-spark核心概念">2. Spark核心概念</h1>
<h2 id="21-基本抽象">2.1 基本抽象</h2>
<p>Spark 基于两个抽象,分别是RDDs和Shared Variables。</p>
<h3 id="211-rdds">2.1.1 RDDs</h3>
<p>Spark 提出了一种分布式的数据抽象,称为 RDDs(resilient distributed datasets,弹性分布式数据集),是一个可并行处理且支持容错的数据集,同时,也是一个受限的数据集,RDDs是一个只读的、记录分区的数据集,仅支持transformation和action两种操作,这些受限,使得RDDs可以以较小的成本实现高容错性、可靠性。</p>
<p>RDDs有两种创建方式,一种是从外部数据源创建,另一种是从其它RDDs transform而来。<code class="language-plaintext highlighter-rouge">transformation</code> 是对RDDs进行确定性的操作,输入是RDDs,输出RDDs。<code class="language-plaintext highlighter-rouge">action</code> 是向应用程序返回值或者将结果写到外部存储。</p>
<p>最后,transformation具有 <strong>LAZY</strong> 的特点,当在RDDs上进行一次transformation时,并不会立即执行,只会在进行action时,前面的transformation才会真正执行。这个特点,被 Spark 用来优化整个工作链路,可以有效减少网络沟通、传输时间(大数据处理过程中,网络传输可以说是最大的性能杀手),从而大幅提高运行速度。</p>
<p>举个例子,我们具有如下代码:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">lines</span> <span class="k">=</span> <span class="nv">spark</span><span class="o">.</span><span class="py">textFile</span><span class="o">(</span><span class="s">"hdfs://..."</span><span class="o">)</span>
<span class="n">errors</span> <span class="k">=</span> <span class="nv">lines</span><span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">startsWith</span><span class="o">(</span><span class="s">"ERROR"</span><span class="o">))</span>
<span class="nv">errors</span><span class="o">.</span><span class="py">cache</span><span class="o">()</span>
<span class="nv">errors</span><span class="o">.</span><span class="py">count</span><span class="o">()</span>
</code></pre></div></div>
<p>第一行,读取外部数据源,生成一个RDDs;第二行,在RDDs lines上做了一次transformation运算 filter,取出以”ERROR” 开头的所有行,得到一个新的RDDs errors;第三行,缓存RDDs;第四行,在errors 上执行action,得到errors的行数。在整个过程中,只有在执行count()时,才会真正开始读取数据、过滤、缓存、计算行数。</p>
<p><img src="http://blog2017.qiniudn.com/spark-lineage.png" alt="lineage" /></p>
<p>如上图所示,展示了整个过程,称为<code class="language-plaintext highlighter-rouge">lineage</code>,根据lineage,可以从具体的物理数据,计算出相应的结果。在Spark中,实现容错就是根据 lineage,当某个分区失败后,重新进行一次计算即可,而不是采用检查点、回滚等代价高昂的方式。同时,lineage 是Spark用来优化计算流程的依据。</p>
<p>最后,Spark 支持RDD persist/cache。当第一次执行action时,会将调用 <code class="language-plaintext highlighter-rouge">persist()</code> 或<code class="language-plaintext highlighter-rouge">cache()</code>的RDD缓存下来,在下次进行action操作时,直接使用缓存数据,这使得后边的action操作速度更快,在迭代运算或交互运算中,缓存使用较多。</p>
<h3 id="212-shared-variables">2.1.2 Shared variables</h3>
<p>在Spark中,具体的运算都在集群的节点上进行,这些运算操作的是从driver program 拷贝的变量的副本,且不会更新driver program上的变量,而要实现多任务共享的可读写变量会非常低效,Spark在这方面仅支持受限的共享变量。</p>
<h4 id="broadcast-variables">Broadcast variables</h4>
<p>广播变量是支持每台机器持有而不是每个task持有的只读变量,比如,给每台机器分发大型的输入数据集就会变得更加高效,同时,Spark 采用了高效的分发算法来实现广播变量的分发。</p>
<h4 id="accumulators">Accumulators</h4>
<p>累加器是只被相关变量累加的变量,可以用于计数(sum)。在Spark中,原生支持数值类型的累加器,并且可以自己实现对其他类型的累加器。</p>
<h1 id="3-总结">3. 总结</h1>
<p>本文主要简单介绍Spark的基础,包括Spark的基本介绍与Spark的核心概念。在下一篇,介绍如何搭建Spark项目。</p>
<h1 id="4-参阅">4. 参阅</h1>
<p><a href="http://www.eecs.berkeley.edu/Pubs/TechRpts/2011/EECS-2011-82.pdf">Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing</a></p>
<p><a href="https://spark.apache.org/docs/">官方文档</a></p>
<p><a href="https://book.douban.com/subject/22139960/">Learning Spark</a></p>
<p><a href="https://book.douban.com/subject/26810099/">Spark核心技术与高级应用</a></p>
Elasticsearch shard 生命周期
2017-01-06T00:00:00+00:00
http://www.readingnotes.site/posts/Elasticsearch Shard 生命周期
<p>Elasticsearch 说自己是一个准实时的搜索引擎,为什么能做到准实时呢?</p>
<p>往索引里写了一条数据,为什么有时需要等一段时间才能搜到?</p>
<p>往索引里写了一条数据,搜不出来,进行一下<code class="language-plaintext highlighter-rouge">refresh</code>或者<code class="language-plaintext highlighter-rouge">flush</code>操作就能搜出来了,是为什么呢,<code class="language-plaintext highlighter-rouge">flush</code> 和 <code class="language-plaintext highlighter-rouge">refresh</code>有什么区别?</p>
<p>Elasticsearch 是如何保证索引持久化且不丢数据的?</p>
<p>这篇文章将解释这些问题。</p>
<h1 id="动态索引">动态索引</h1>
<p>众所周知,搜索引擎的基础是倒排索引,一般情况下,索引中的数据都是实时变化的,那么,索引系统如何实时反映这种变化呢?在搜索引擎中,普遍采用动态索引来做,如下图所示。</p>
<p><img src="http://blog2017.qiniudn.com/dynamic_index.jpg" alt="老系统架构" title="dynamic index" /></p>
<p>在这个动态索引中,有三个关键的索引结构:倒排列表、临时索引、已删除列表。倒排索引是已经建好的索引结果,倒排列表存在磁盘文件中,单词词典在内存中。临时索引是在内存中实时建立的倒排索引,结果与倒排列表一样,只是存在于内存中,当有新文档时,实时解析文档并加到这个临时索引中。已删除列表存储已被删除的文档的文档ID。另外,当一个文档被更改,搜索引擎中一个普遍的做法是删除旧文档,然后新建一个新文档,间接实现更新操作,这么做的原因主要是索引文件存储在磁盘文件,写磁盘不方便。</p>
<p>当用户搜索时,搜索引擎同时到倒排列表和临时索引进行查询,找到包含用户查询的文档集合,并对结果进行合并,之后利用删除文档进行过滤,形成最终结果,返回给用户。这样就实现了动态环境下的准实时搜索功能。</p>
<h1 id="elasticsearch-实现">Elasticsearch 实现</h1>
<p>上面简单介绍了搜索引擎实现准实时搜的原理和普遍做法,下面看看Elasticsearch的具体实现。</p>
<h2 id="elasticsearch-动态更新">Elasticsearch 动态更新</h2>
<p>Elasticsearch 基于Lucene开发,Lucene 提供了 <code class="language-plaintext highlighter-rouge">segment</code> 的概念,segment 代表 Lucene 的一个完成的索引段,通常一个索引包含多个 segment,每个segment 包含一个 <code class="language-plaintext highlighter-rouge">commit point</code>,这些segment对外提供搜索服务。</p>
<p>当往索引里新写数据时,新文档先写到内存中的一个buffer中,当buffer被commited时,就写到磁盘中,生成一个新的segment,并对外提供服务,同时,buffer被清空。</p>
<p>每个 commit point 维护了一个<code class="language-plaintext highlighter-rouge">.del</code>文件,存储已被删除的文档,即上一节介绍的已删除文档列表。</p>
<h2 id="elasticsearch-准实时搜索">Elasticsearch 准实时搜索</h2>
<p>要把数据写到磁盘,需要调用 <a href="https://en.wikipedia.org/wiki/Fsync" title="fsync">fsync</a>,但是fsync十分耗资源,无法频繁的调用,在这种情况下,Elasticsearch 利用了<code class="language-plaintext highlighter-rouge">filesystem cache</code>,新文档先写到in-memory buffer,然后写入到 filesystem cache,过一段时间后,再将segment写到磁盘。在这个过程中,只要文档写到filesystem cache,就可以被搜索到了。</p>
<h2 id="elasticsearch-持久化">Elasticsearch 持久化</h2>
<p>必须调用fsync将segment刷到磁盘上,才能保证数据不丢失。</p>
<p>同时,Elasticsearch 使用<code class="language-plaintext highlighter-rouge">translog</code> 来记录Elasticsearch中的操作。</p>
<ul>
<li>当新文档被添加到索引中时,新文档被加入到 in-memory buffer中,并且在translog中记录下来。</li>
<li>当进行refresh操作时,in-memory buffer里的数据被清空,translog保持不变。</li>
<li>经过一段时间,或者translog大到一定程度,整个index会被<code class="language-plaintext highlighter-rouge">flushed</code>,这时,进行一次commit,in-memory buffer的文档被写到新的segment,buffer被清空,一个commit point 被写到磁盘,filesystem cache 也被flushed到磁盘,老的translog被删除。</li>
</ul>
<p>translog持久化存储了所有没有flush到磁盘的操作。当启动Elasticsearch时,Elasticsearch 首先根据最后的commit point 从磁盘恢复已知的segment,然后重放translog恢复没有commit的文档。这样,既实现了持久化,也能保证不丢数据。</p>
<h2 id="refresh-vs-flush">refresh VS flush</h2>
<p>在Elasticsearch中,<strong>refresh是轻量级的写和打开一个新segment的操作,默认情况下,每个分片每秒refresh一次,这就是我们说Elasticsearch是一个准实时搜索引擎的原因</strong>,因为每个文档的修改,最多经过一秒钟就可以知道了。虽然refresh是一个轻量级的操作,但是,还是会带来一定的消耗,所以,还是要注意不要太频繁的操作,而且,我们很多应用并不需要这么实时,比如在<code class="language-plaintext highlighter-rouge">ELK</code>中,我们可以将这个时间设置到30s甚至更大。</p>
<p>在Elasticsearch中,执行commit操作并删除translog的操作叫<code class="language-plaintext highlighter-rouge">flush</code>,每个shard每30分钟或translog太大时自动flush一次,使用者很少需要手动进行flush操作。</p>
<h2 id="段合并">段合并</h2>
<p>如果不停的产生新的segment,Elasticsearch中很快就会段爆炸,每个段都要消耗文件描述符、内存、CPU 周期,且每个search请求都需要遍历所有的segment,会造成搜索操作很慢。</p>
<p>所以,Elasticsearch会在后台对segment进行合并,在段合并的过程中,被删除的文档被丢弃。</p>
<p>Elasticsearch 提供了 <code class="language-plaintext highlighter-rouge">optimize</code> 接口,可以看做是一个强制进行段合并的API,使shard进行段合并到指定段数目,从而可以提高查询性能。</p>
<p>需要注意的是,merge操作会消耗大量的CPU和I/O,默认情况下,Elasticsearch 会控制merge操作的资源使用,从而不至于影响正常的search操作。但是,如果是手动进行optimize操作,这时,Elasticsearch 不会对merge使用的资源进行控制,从而消耗大量I/O,影响正常的搜索和集群的稳定。</p>
<h1 id="总结">总结</h1>
<p>本文首先介绍搜索引擎中普遍的做法,然后,介绍了Elasticsearch的具体做法,一是明白了Elasticsearch的原理和中这几个操作的不同,同时,也可以看出,具体形式多变,仍然摆脱不了发明多年的搜索引擎的基础。</p>
<p><strong><em>参考</em></strong></p>
<ol>
<li><a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/index.html" title="es definitive guide">Elasticsearch: The definitive guide</a></li>
<li><a href="https://book.douban.com/subject/7006719/" title="this is search engine">这就是搜索引擎</a></li>
<li><a href="https://book.douban.com/subject/7154449/" title="information retrieval">信息检索</a></li>
</ol>
Elasticsearch 集群的监控与报警
2016-10-15T00:00:00+00:00
http://www.readingnotes.site/posts/Elasticsearch 集群的监控与报警
<p>前段时间和运维小伙伴一起完善了公司 Elasticsearch 集群的监控和报警,整理成此文,涉及到公司架构和系统信息的不便透露的,都隐去了。</p>
<h1 id="系统架构">系统架构</h1>
<p>谈监控之前,先看看我们的系统架构,下图是个简化的搜索架构:</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/es-architecture.png" alt="搜索系统架构" title="es architecture" /></p>
<p>系统分两部分:</p>
<ol>
<li>蓝色部分,表示用户查询部分,客户端请求到达Tengine,Tengine将请求转发到AS(Advanced Search),AS将请求转发到Elasticsearch,Elasticsearch查询,然后将结果原路返回。其中,AS是我们自主研发的,用于Query验证、改写、记日志、相关性计算等等。在这里,AS和ES都是一个集群。</li>
<li>绿色部分,表示实时同步部分,系统日志、DB数据等通过消息队列到到我们的Sync程序,Sync将数据实时写入到ES集群。</li>
</ol>
<p>上图覆盖了最重要的两个部分——实时索引与查询,全量索引仅需周期性进行一次,并未体现在此图中。对搜索集群添加监控,主要是针对图中各组件添加监控。</p>
<h1 id="具体监控">具体监控</h1>
<p><strong>基础监控</strong></p>
<p>基础监控主要是一些常用的运维监控,主要包括:</p>
<ul>
<li>CPU 使用率</li>
<li>IO</li>
<li>内存</li>
<li>load</li>
<li>磁盘空间、INode</li>
<li>重传率</li>
<li>fd</li>
<li>ntp</li>
<li>coredump</li>
<li>JVM</li>
<li>…</li>
</ul>
<p>这部分,运维都有很丰富的经验,不用操心。</p>
<p><strong>网关监控</strong></p>
<p>网关监控,主要关注两个指标,RT和状态码,当RT > 1s 或者出现5XX的时候,进行报警。</p>
<p><strong>AS监控</strong></p>
<ol>
<li>进程监控,当进程数发生变化时,报警;</li>
<li>日志监控,当日志出现ERROR时,报警。</li>
</ol>
<p><strong>Elasticsearch 进程监控</strong></p>
<ol>
<li>进程监控,进程数发生变化时,报警。</li>
<li>ES 产生ERROR日志时,报警。</li>
</ol>
<p><strong>Elasticsearch指标监控</strong></p>
<p>Elasticsearch 提供了丰富的API接口以便于采集集群、node、index等状态指标,可以根据这些指标做监控和报警。我们主要在集群级别、node 级别、index 级别三个层次进行采集和监控报警。</p>
<p>下面是一些主要关注的指标。</p>
<p>一、集群级别监控,采用<code class="language-plaintext highlighter-rouge">GET _cluster/health</code>,返回数据如下:</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">"cluster_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cluster_name_1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"green"</span><span class="p">,</span><span class="w">
</span><span class="nl">"timed_out"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"number_of_nodes"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
</span><span class="nl">"number_of_data_nodes"</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">"active_primary_shards"</span><span class="p">:</span><span class="w"> </span><span class="mi">38</span><span class="p">,</span><span class="w">
</span><span class="nl">"active_shards"</span><span class="p">:</span><span class="w"> </span><span class="mi">68</span><span class="p">,</span><span class="w">
</span><span class="nl">"relocating_shards"</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">"initializing_shards"</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">"unassigned_shards"</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></code></pre></div></div>
<p>对于我们来说,一般重点关注<strong>集群状态 <em>status</em></strong>以及其中的node数量。当集群状态变为yellow或red时报警。</p>
<p>另外,Elasticsearch 提供API对集群进行修改,而且,Elasticsearch 没有提供有效的权限控制,从而,也给我们带来了风险,所以,我们还加了一层监控,当集群配置发生变化时,报警。</p>
<p>二、node级别的监控,采用<code class="language-plaintext highlighter-rouge">GET _nodes/stats</code>接口取回node相关监控数据。</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/es_node.jpg" alt="node_stats" title="node stats" /></p>
<p>大部分数据在基础监控里已经做了,所以,这里主要关注红框部分——indices、thread_pool和fielddata_breaker三部分。</p>
<p>indices部分如下:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nl">"indices"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"docs"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"count"</span><span class="p">:</span><span class="w"> </span><span class="mi">3344</span><span class="p">,</span><span class="w">
</span><span class="nl">"deleted"</span><span class="p">:</span><span class="w"> </span><span class="mi">983</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"store"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">6529920</span><span class="p">,</span><span class="w">
</span><span class="nl">"throttle_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">2263</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"indexing"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"index_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">114516</span><span class="p">,</span><span class="w">
</span><span class="nl">"index_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">66226</span><span class="p">,</span><span class="w">
</span><span class="nl">"index_current"</span><span class="p">:</span><span class="w"> </span><span class="mi">969</span><span class="p">,</span><span class="w">
</span><span class="nl">"delete_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">802</span><span class="p">,</span><span class="w">
</span><span class="nl">"delete_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">93</span><span class="p">,</span><span class="w">
</span><span class="nl">"delete_current"</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">"get"</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">449</span><span class="p">,</span><span class="w">
</span><span class="nl">"time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w">
</span><span class="nl">"exists_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">571</span><span class="p">,</span><span class="w">
</span><span class="nl">"exists_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">1847</span><span class="p">,</span><span class="w">
</span><span class="nl">"missing_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">878</span><span class="p">,</span><span class="w">
</span><span class="nl">"missing_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">56</span><span class="p">,</span><span class="w">
</span><span class="nl">"current"</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">"search"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"open_contexts"</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">"query_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">3419</span><span class="p">,</span><span class="w">
</span><span class="nl">"query_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">262</span><span class="p">,</span><span class="w">
</span><span class="nl">"query_current"</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">"fetch_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">319</span><span class="p">,</span><span class="w">
</span><span class="nl">"fetch_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">189</span><span class="p">,</span><span class="w">
</span><span class="nl">"fetch_current"</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">"merges"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"current"</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">"current_docs"</span><span class="p">:</span><span class="w"> </span><span class="mi">175</span><span class="p">,</span><span class="w">
</span><span class="nl">"current_size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">374</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">557</span><span class="p">,</span><span class="w">
</span><span class="nl">"total_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">77614</span><span class="p">,</span><span class="w">
</span><span class="nl">"total_docs"</span><span class="p">:</span><span class="w"> </span><span class="mi">3878</span><span class="p">,</span><span class="w">
</span><span class="nl">"total_size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">9545</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"refresh"</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">4036</span><span class="p">,</span><span class="w">
</span><span class="nl">"total_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">3788</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"flush"</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">07</span><span class="p">,</span><span class="w">
</span><span class="nl">"total_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">03</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"warmer"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"current"</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">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">45</span><span class="p">,</span><span class="w">
</span><span class="nl">"total_time_in_millis"</span><span class="p">:</span><span class="w"> </span><span class="mi">233</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"filter_cache"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"memory_size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">124</span><span class="p">,</span><span class="w">
</span><span class="nl">"evictions"</span><span class="p">:</span><span class="w"> </span><span class="mi">778</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"id_cache"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"memory_size_in_bytes"</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">"fielddata"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"memory_size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">1348</span><span class="p">,</span><span class="w">
</span><span class="nl">"evictions"</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">"percolate"</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">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"time_in_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">"current"</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">"memory_size_in_bytes"</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">"memory_size"</span><span class="p">:</span><span class="w"> </span><span class="s2">"-1b"</span><span class="p">,</span><span class="w">
</span><span class="nl">"queries"</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">"completion"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"size_in_bytes"</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">"segments"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"count"</span><span class="p">:</span><span class="w"> </span><span class="mi">350</span><span class="p">,</span><span class="w">
</span><span class="nl">"memory_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">392</span><span class="p">,</span><span class="w">
</span><span class="nl">"index_writer_memory_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">961</span><span class="p">,</span><span class="w">
</span><span class="nl">"version_map_memory_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"translog"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"operations"</span><span class="p">:</span><span class="w"> </span><span class="mi">12025</span><span class="p">,</span><span class="w">
</span><span class="nl">"size_in_bytes"</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">"suggest"</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">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"time_in_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">"current"</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></code></pre></div></div>
<ul>
<li>
<p>doc 部分显示node中的所有documents数量,以及已经被标记删除的。当每分钟被标记删除的数量突然增大时,需要关注下。</p>
</li>
<li>
<p>store显示物理存储信息。</p>
</li>
<li>indexing 显示了被索引的docs数量,是一个累计递增值,只要内部进行index操作就会增加,所以,index、create、update都会增加。可以利用这个累计值,监控每分钟的变化,从而做出预警。</li>
<li>
<table>
<tbody>
<tr>
<td>get 显示的是调用 GET</td>
<td>HEAD 方法的次数,也是累计值。</td>
</tr>
</tbody>
</table>
</li>
<li>search 描述search 相关监控数据,<code class="language-plaintext highlighter-rouge">query_time_in_millis/query_total</code>可以用来粗略估计搜索引擎的查询效率。fetch 相关统计描述搜索过程(query-then-fetch)中fetch 操作的统计数据。</li>
<li>merge 包含Lucene 段合并的一些信息,对于实时写数据量大的ES集群,merge相关指标十分重要,因为merge操作会消耗大量的CPU和IO。</li>
<li>fielddata 描述fielddata占的内存以及被淘汰的情况,需要关注<code class="language-plaintext highlighter-rouge">fielddata.eviction</code>,这个值应该很小。另外,fielddata很耗内存,如果fielddata统计占内存很多,也要考虑下这样使用的业务场景是否合理。</li>
<li>segment 统计Lucene 的segments相关信息,这个值应该不会太大。</li>
</ul>
<p>Elasticsearch 自身维护它的线程池,一般来讲,我们不需要配置,默认的即可。在API返回的结果中,有很多种线程池,格式都是统一的,如下:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"threads"</span><span class="p">:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w">
</span><span class="nl">"queue"</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">"active"</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">"rejected"</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">"largest"</span><span class="p">:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w">
</span><span class="nl">"completed"</span><span class="p">:</span><span class="w"> </span><span class="mi">676231817</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>上面的信息展示了线程池里现场的数量,有多少现成正在work,有多少在队列里。如果队列满了,新的请求将被rejected掉,显示在<strong>rejected</strong>,这时候,通常说明我们的系统到了瓶颈了,因为,我们的系统以最大的速度处理都处理不过来了,所以,这个指标要注意。</p>
<p>除了上面显示的index 线程池,还有很多线程池,不过很多都可以忽略,但有些重要的还是需要关注:</p>
<ul>
<li>index: 处理一般index请求的线程池。</li>
<li>bulk:处理bulk indexing请求的线程池。</li>
<li>get:处理get-by-ID请求的线程池。</li>
<li>search:处理所有search和query请求的线程池。</li>
<li>merge:管理 Lucene merge的线程池。</li>
</ul>
<p>最后,需要关注<a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/_limiting_memory_usage.html#circuit-breaker" title="circuit breaker">Circuit Breaker</a>的相关指标:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="nl">"fielddata_breaker"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"maximum_size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">11699</span><span class="p">,</span><span class="w">
</span><span class="nl">"maximum_size"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.7gb"</span><span class="p">,</span><span class="w">
</span><span class="nl">"estimated_size_in_bytes"</span><span class="p">:</span><span class="w"> </span><span class="mi">568</span><span class="p">,</span><span class="w">
</span><span class="nl">"estimated_size"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.2gb"</span><span class="p">,</span><span class="w">
</span><span class="nl">"overhead"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.03</span><span class="p">,</span><span class="w">
</span><span class="nl">"tripped"</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></code></pre></div></div>
<p>简单来说,fielddata的大小只有在加载完了之后才会知道,如果fielddata的大小比分配内存还大,那就会导致OOM,于是,Elasticsearch 引入了断路器,用于预先估算内存够不够,如果不够,断路器就会被触发(tripped)并返回异常,而不至于导致OOM。</p>
<p>所以,这个指标里,最重要的就是 <code class="language-plaintext highlighter-rouge">tripped</code>了,如果这个值很大或者说一直在增长,那么,就说明你的查询需要优化或者说需要更多内存了。</p>
<p>三、index 级别监控</p>
<p>index 级别的监控可以以index为单位进行统计,如某个index有多少search请求、search时耗费了多少时间等,可以用于发现热点索引或比较慢的索引。</p>
<p>然而,在实际应用中,index级别的监控并不是很有用,一般来说,瓶颈都出现在node级别,不会到index级别;而且,index级别的数据采集自不同机器,不同环境也会受影响。</p>
<p>查看index 级别的监控命令是 <code class="language-plaintext highlighter-rouge">GET index_name/_stats</code>,如果index_name改成 <code class="language-plaintext highlighter-rouge">_all</code>,则统计所有index的数据。</p>
<p><strong>同步程序监控</strong></p>
<p>同步程序监控主要包括三个方面:</p>
<ol>
<li>基础监控。同步数据量大,同步程序单独部署了一个集群,也需要相应的基础监控。</li>
<li>同步程序进程数监控。</li>
<li>同步程序日志监控,如版本冲突等ERROR都能采集到。</li>
</ol>
<p><strong>业务监控</strong></p>
<p>我们还做了业务层面的监控,针对不同index进行基础埋点,同时,新上业务时,也会添加使用的Query语句进行埋点,系统定时跑这些埋点,当出现异常时报警。</p>
<h1 id="总结">总结</h1>
<p>Elasticsearch 集群的监控是在运维同事的帮助下搭建的,到目前为止,基本能满足需求,但因为自身以前没有这方面经验,所以,这方面也许做得还不够,还望批评指正。</p>
Elasticsearch 简介
2016-10-14T00:00:00+00:00
http://www.readingnotes.site/posts/Elasticsearch 简介
<p>这是一篇科普文。</p>
<h1 id="1-背景">1. 背景</h1>
<p>Elasticsearch 在公司的使用越来越广,很多同事之前并没有接触过 Elasticsearch,所以,最近在公司准备了一次关于 Elasticsearch 的分享,整理成此文。此文面向 Elasticsearch 新手,老司机们可以撤了。</p>
<h1 id="2-倒排索引">2. 倒排索引</h1>
<p>先简单介绍下搜索引擎的基础数据结构倒排索引。</p>
<p>我们在平时,会经常使用各种各样的索引,如我们根据链接,可以找到链接里的具体文本,这就是索引。反过来,如果,如果我们能根据具体文本,找到文本存在的具体链接,这就是倒排索引,可简单理解为从文本到链接的映射。我们平时在使用Google、百度时,就是根据具体文本去找链接,这就是以倒排索引为基础的。</p>
<p>可参看<a href="https://zh.wikipedia.org/wiki/%E5%80%92%E6%8E%92%E7%B4%A2%E5%BC%95" title="inverted index">维基百科</a> 。</p>
<h1 id="3-elasticsearch-简介与基本概念">3. Elasticsearch 简介与基本概念</h1>
<blockquote>
<p>Elasticsearch is a real-time <strong>distributed search and analytics engine</strong>. It allows you to explore your data at a speed and at a scale never before possible. It is used for <strong>full-text search, structured search, analytics, and all three in combination</strong>.</p>
</blockquote>
<p>在 《Elasticsearch : The Definitive Guide》里,这样介绍Elasticsearch,总的来说,Elasticsearch 是一个分布式的搜索和分析引擎,可以用于全文检索、结构化检索和分析,并能将这三者结合起来。Elasticsearch 基于 Lucene 开发,现在是使用最广的开源搜索引擎之一,Wikipedia、Stack Overflow、GitHub 等都基于 Elasticsearch 来构建他们的搜索引擎。</p>
<p>先介绍下 Elasticsearch 里的基本概念,下图是 Elasticsearch 插件 head 的一个截图。</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/es-head.jpeg" alt="Elasticsearch 插件head截图" title="head" /></p>
<ul>
<li>node:即一个 Elasticsearch 的运行实例,使用多播或单播方式发现 cluster 并加入。</li>
<li>cluster:包含一个或多个拥有相同集群名称的 node,其中包含一个master node。</li>
<li>index:类比关系型数据库里的DB,是一个逻辑命名空间。</li>
<li>alias:可以给 index 添加零个或多个alias,通过 alias 使用index 和根据index name 访问index一样,但是,alias给我们提供了一种切换index的能力,比如重建了index,取名customer_online_v2,这时,有了alias,我要访问新 index,只需要把 alias 添加到新 index 即可,并把alias从旧的 index 删除。不用修改代码。</li>
<li>type:类比关系数据库里的Table。其中,一个index可以定义多个type,但一般使用习惯仅配一个type。</li>
<li>mapping:类比关系型数据库中的 schema 概念,mapping 定义了 index 中的 type。mapping 可以显示的定义,也可以在 document 被索引时自动生成,如果有新的 field,Elasticsearch 会自动推测出 field 的type并加到mapping中。</li>
<li>document:类比关系数据库里的一行记录(record),document 是 Elasticsearch 里的一个 JSON 对象,包括零个或多个field。</li>
<li>field:类比关系数据库里的field,每个field 都有自己的字段类型。</li>
<li>shard:是一个Lucene 实例。Elasticsearch 基于 Lucene,shard 是一个 Lucene 实例,被 Elasticsearch 自动管理。之前提到,index 是一个逻辑命名空间,shard 是具体的物理概念,建索引、查询等都是具体的shard在工作。shard 包括primary shard 和 replica shard,写数据时,先写到primary shard,然后,同步到replica shard,查询时,primary 和 replica 充当相同的作用。replica shard 可以有多份,也可以没有,replica shard的存在有两个作用,一是容灾,如果primary shard 挂了,数据也不会丢失,集群仍然能正常工作;二是提高性能,因为replica 和 primary shard 都能处理查询。另外,如上图右侧红框所示,shard数和replica数都可以设置,但是,shard 数只能在建立index 时设置,后期不能更改,但是,replica 数可以随时更改。但是,由于 Elasticsearch 很友好的封装了这部分,在使用Elasticsearch 的过程中,我们一般仅需要关注 index 即可,不需关注shard。</li>
</ul>
<p>综上所述,shard、node、cluster 在物理上构成了 Elasticsearch 集群,field、type、index 在逻辑上构成一个index的基本概念,在使用 Elasticsearch 过程中,我们一般关注到逻辑概念就好,就像我们在使用MySQL 时,我们一般就关注DB Name、Table和schema即可,而不会关注DBA维护了几个MySQL实例、master 和 slave 等怎么部署的一样。</p>
<p>下表用Elasticsearch 和 关系数据库做了类比:</p>
<ul>
<li>index => databases</li>
<li>type => table</li>
<li>field => field</li>
<li>document => record</li>
<li>mapping => schema</li>
</ul>
<p>最后,来从 Elasticsearch 中取出一条数据(document)看看:</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/es-result.png" alt="ES result" title="es result" /></p>
<p>由index、type和id三者唯一确定一个document,_source 字段中是具体的document 值,是一个JSON 对象,有5个field组成。</p>
<h1 id="4-elasticsearch-基本使用">4. Elasticsearch 基本使用</h1>
<p>下面介绍下 Elasticsearch 的基本使用,这里仅介绍 Elasticsearch 能做什么,而不详细介绍语法。</p>
<h2 id="41-基础操作">4.1 基础操作</h2>
<ul>
<li>index:写 document 到 Elasticsearch 中,如果不存在,就创建,如果存在,就用新的取代旧的。</li>
<li>create:写 document 到 Elasticsearch 中,与 index 不同的是,如果存在,就抛出异常<code class="language-plaintext highlighter-rouge">DocumentAlreadyExistException</code>。</li>
<li>get:根据ID取出document。</li>
<li>update:如果是更新整个 document,可用index 操作。如果是部分更新,用update操作。在Elasticsearch中,更新document时,是把旧数据取出来,然后改写要更新的部分,删除旧document,创建新document,而不是在原document上做修改。</li>
<li>delete:删除document。Elasticsearch 会标记删除document,然后,在Lucene 底层进行merge时,会删除标记删除的document。</li>
</ul>
<h2 id="42-filter-与-query">4.2 Filter 与 Query</h2>
<p>Elasticsearch 使用 domain-specific language(DSL)进行查询,DSL 使用 JSON 进行表示。</p>
<p>DSL 由一些子查询组成,这些子查询可应用于两类查询,分别是filter 和 query。</p>
<p>filter 正如其字面意思“过滤”所说的,是起过滤的作用,任何一个document 对 filter 来说,就是<strong>match 与否</strong>的问题,是个二值问题,0和1,<strong>没有scoring</strong>的过程。</p>
<p>使用query的时候,是表示<strong>match 程度问题</strong>,<strong>有scroing</strong> 过程。</p>
<p>另外,Filter 和 Query 还有性能上的差异,Elasticsearch 底层对Filter做了很多优化,会对过滤结果进行缓存;同时,Filter 没有相关性计算过程,所以,Filter 比 Query 快。</p>
<p>所以,官网推荐,作为一条比较通用的规则,<strong><em>仅在全文检索时使用Query,其它时候都用Filter</em></strong>。但是,根据我们的使用情况来看,在过滤条件不是很强的情况下,缓存可能会占用较多内存,如果这些数据不是频繁使用,用空间换时间不一定划算。</p>
<h2 id="43-一些重要的查询">4.3 一些重要的查询</h2>
<p>在Elasticsearch 中,有几类最重要的查询子句,掌握了就可以覆盖日常90%以上的需求。</p>
<h3 id="431-match_all">4.3.1 match_all</h3>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"match_all"</span><span class="p">:{}}</span><span class="w">
</span></code></pre></div></div>
<p>表示取出所有documents,在与filter结合使用时,会经常使用match_all。</p>
<h3 id="432-match">4.3.2 match</h3>
<p>一般在全文检索时使用,首先利用analyzer 对具体查询字符串进行分析,然后进行查询;如果是在数值型字段、日期类型字段、布尔字段或not_analyzed 的字符串上进行查询时,不对查询字符串进行分析,表示精确匹配,两个简单的例子如:</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">"match"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"tweet"</span><span class="p">:</span><span class="w"> </span><span class="s2">"About Search"</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">"match"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="mi">26</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></code></pre></div></div>
<h3 id="433-term">4.3.3 term</h3>
<p>term 用于精确查找,可用于数值、date、boolean值或not_analyzed string,当使用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">"term"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2014-09-01"</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></code></pre></div></div>
<h3 id="434-terms">4.3.4 terms</h3>
<p>terms 和 term 类似,但是,terms 里可以指定多个值,只要doc满足terms 里的任意值,就是满足查询条件的。与term 相同,terms 也是用于精确查找。</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">"terms"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"tag"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"search"</span><span class="p">,</span><span class="w"> </span><span class="s2">"full_text"</span><span class="p">,</span><span class="w"> </span><span class="s2">"nosql"</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>注意,terms 表示的是contains 关系,而不是 equals关系。</p>
<h3 id="435-range">4.3.5 range</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">"range"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"gte"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w">
</span><span class="nl">"lt"</span><span class="p">:</span><span class="w"> </span><span class="mi">30</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>
<ul>
<li>gt:大于</li>
<li>gte:大于等于</li>
<li>lt:小于</li>
<li>lte:小于等于</li>
</ul>
<h3 id="436-exists-和-missing">4.3.6 exists 和 missing</h3>
<p>exists 用于查找字段含有一个或多个值的document,而missing用于查找某字段不存在值的document,可类比关系数据库里的 <em>is not null</em> (exists) 和 <em>is null</em> (missing).</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">"exists"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"field"</span><span class="p">:</span><span class="w"> </span><span class="s2">"title"</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="437-bool">4.3.7 bool</h3>
<p>前面讲的都是些最原子的查询子句,那么,怎么实现复合查询呢?Elasticsearch 使用bool 子句来将各种子查询关联起来,组成布尔表达式,bool 子句可以随意组合、嵌套。</p>
<p>bool子句主要包括:</p>
<ol>
<li>must:表示必须匹配。</li>
<li>must_not:表示一定不能匹配。</li>
<li>should:表示可以匹配,类似于布尔运算里的”或”。如果bool 子句里,没有must子句,那么,should子句里至少匹配一个,如果有must子句,那么,should子句至少匹配零个。可以使用<code class="language-plaintext highlighter-rouge">minimum_should_match</code> 来对最小匹配数进行设置。</li>
</ol>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"bool"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"must"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"term"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"user"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"kimchy"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"must_not"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"range"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"age"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"from"</span><span class="w"> </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">"to"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">20</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">"should"</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">"term"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"tag"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"wow"</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">"term"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"tag"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"elasticsearch"</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">"minimum_should_match"</span><span class="w"> </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">"boost"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mf">1.0</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>
<h2 id="44-聚合功能">4.4 聚合功能</h2>
<p>前面说的都是 Elasticsearch 当做搜索引擎使用,Elasticsearch 还可以作为分析引擎使用。</p>
<p>和 MySQL 等关系数据库类似,Elasticsearch 有聚合操作,而且,可作用于大量数据,提供实时的分析结果,速度快;同时,聚合操作可以与搜索结合使用,例如将聚合作用于搜索结果等。总之,Elasticsearch的聚合功能十分强大,有很多公司利用 Elasticsearch 来做分析,其中,广泛使用的 ELK(Elasticsearch + Logstash + Kibana),Kibana的数据显示和分析功能就是基于 Elasticsearch 的聚合功能做的。</p>
<p>具体可参看 <a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/aggregations.html" title="agg">Elasticsearch: The Definitive Guide</a></p>
<h2 id="45-geolocation">4.5 Geolocation</h2>
<p>Elasticsearch 还提供了基于地理位置的搜索,而且能将地理位置与全文检索、结构化搜索、分析等结合起来使用,比如查找距离某点一定范围内的符合搜索条件的地点、计算两点的距离、判断两个形状是否相交或包含等。</p>
<p>具体参考 <a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/geoloc.html" title="geo">Elasticsearch: The Definitive Guide</a></p>
<h1 id="5-elasticsearch-使用时注意的几个问题">5. Elasticsearch 使用时注意的几个问题</h1>
<p><strong>深度分页问题</strong>:Elasticsearch 作为一个分布式搜索与分析引擎,深度分页问题会带来严重的问题,给CPU、内存、IO、网络带来巨大压力,所以,在Elasticsearch 不建议使用深度分页,如果要遍历数据,可以采用 SCROLL的方式,<a href="http://lxwei.github.io/posts/%E4%BD%BF%E7%94%A8scroll%E5%AE%9E%E7%8E%B0Elasticsearch%E6%95%B0%E6%8D%AE%E9%81%8D%E5%8E%86%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%88%86%E9%A1%B5.html" title="deep pagenation">可参考我另一篇博客</a>。</p>
<p><strong>排序问题</strong>:根据某field排序时,Elasticsearch 会将这个 field 的所有值给加载到内存,然后,这部分数据会常驻内存,如果数据量大或排序字段多,就会给系统带来巨大压力,所以,在使用 field 进行排序时,要慎重。不过,在Elasticsearch 2.X版本,开始使用 doc value 来优化这部分。</p>
<p><strong>terms 问题</strong>: terms 里可以传多个值,但是,量不能太多,搜索引擎的基本数据结构是倒排索引,terms 里传多个值,原理上来说是查很多的倒排索引,量大了也会给系统带来很大压力。</p>
<h1 id="6-总结">6 总结</h1>
<p>本文是一篇 Elasticsearch 的入门文章,涵盖的是一些基本概念,篇幅有限,并不深入,如DSL的具体语法、聚合功能等都点到为止,希望大家知道的是Elasticsearch能干什么,具体要做的时候,再去详查就好了。</p>
使用scroll实现Elasticsearch数据遍历和深度分页
2016-05-14T00:00:00+00:00
http://www.readingnotes.site/posts/使用scroll实现Elasticsearch数据遍历和深度分页
<h1 id="背景">背景</h1>
<p>Elasticsearch 是一个实时的分布式搜索与分析引擎,被广泛用来做全文搜索、结构化搜索、分析。在使用过程中,有一些典型的使用场景,比如分页、遍历等。在使用关系型数据库中,我们被告知要注意甚至被明确禁止使用深度分页,同理,在 Elasticsearch 中,也应该尽量避免使用深度分页。这篇文章主要介绍 Elasticsearch 中使用分页的方式、Elasticsearch 搜索执行过程以及为什么深度分页应该被禁止,最后再介绍使用 scroll 的方式遍历数据。</p>
<h1 id="elasticsearch-搜索内部执行原理">Elasticsearch 搜索内部执行原理</h1>
<p>一个最基本的 Elasticsearch 查询语句是这样的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /my_index/my_type/_search
{
"query": { "match_all": {}},
"from": 100,
"size": 10
}
</code></pre></div></div>
<p>上面的查询表示从搜索结果中取第100条开始的10条数据。下面讲解搜索过程时也以这个请求为例。</p>
<p>那么,这个查询语句在 Elasticsearch 集群内部是怎么执行的呢?为了方便描述,我们假设该 index 只有primary shards,没有 replica shards。</p>
<p>在 Elasticsearch 中,搜索一般包括两个阶段,query 和 fetch 阶段,可以简单的理解,query 阶段确定要取哪些doc,fetch 阶段取出具体的 doc。</p>
<h2 id="query-阶段">Query 阶段</h2>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/2016_es_query.jpg" alt="query" title="query" /></p>
<p>如上图所示,描述了一次搜索请求的 query 阶段。</p>
<ol>
<li>Client 发送一次搜索请求,node1 接收到请求,然后,node1 创建一个大小为 from + size 的优先级队列用来存结果,我们管 node1 叫 coordinating node。</li>
<li>coordinating node将请求广播到涉及到的 shards,每个 shard 在内部执行搜索请求,然后,将结果存到内部的大小同样为 from + size 的优先级队列里,可以把优先级队列理解为一个包含 top N 结果的列表。</li>
<li>每个 shard 把暂存在自身优先级队列里的数据返回给 coordinating node,coordinating node 拿到各个 shards 返回的结果后对结果进行一次合并,产生一个全局的优先级队列,存到自身的优先级队列里。</li>
</ol>
<p>在上面的例子中,coordinating node 拿到 (from + size) * 6 条数据,然后合并并排序后选择前面的 from + size 条数据存到优先级队列,以便 fetch 阶段使用。另外,各个分片返回给 coordinating node 的数据用于选出前 from + size 条数据,所以,只需要返回唯一标记 doc 的 _id 以及用于排序的 _score 即可,这样也可以保证返回的数据量足够小。</p>
<p>coordinating node 计算好自己的优先级队列后,query 阶段结束,进入 fetch 阶段。</p>
<h2 id="fetch-阶段">Fetch 阶段</h2>
<p>query 阶段知道了要取哪些数据,但是并没有取具体的数据,这就是 fetch 阶段要做的。</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/2016_es_fetch.jpg" alt="fetch" title="fetch" /></p>
<p>上图展示了 fetch 过程:</p>
<ol>
<li>coordinating node 发送 GET 请求到相关shards。</li>
<li>shard 根据 doc 的 _id 取到数据详情,然后返回给 coordinating node。</li>
<li>coordinating node 返回数据给 Client。</li>
</ol>
<p>coordinating node 的优先级队列里有 from + size 个 _doc _id,但是,在 fetch 阶段,并不需要取回所有数据,在上面的例子中,前100条数据是不需要取的,只需要取优先级队列里的第101到110条数据即可。</p>
<p>需要取的数据可能在不同分片,也可能在同一分片,coordinating node 使用 <code class="language-plaintext highlighter-rouge">multi-get</code> 来避免多次去同一分片取数据,从而提高性能。</p>
<h1 id="深度分页的问题">深度分页的问题</h1>
<p>Elasticsearch 的这种方式提供了分页的功能,同时,也有相应的限制。举个例子,一个索引,有10亿数据,分10个 shards,然后,一个搜索请求,from=1,000,000,size=100,这时候,会带来严重的性能问题:</p>
<ul>
<li>CPU</li>
<li>内存</li>
<li>IO</li>
<li>网络带宽</li>
</ul>
<p>CPU、内存和IO消耗容易理解,网络带宽问题稍难理解一点。在 query 阶段,每个shards需要返回 1,000,100 条数据给 coordinating node,而 coordinating node 需要接收 10 * 1,000,100 条数据,即使每条数据只有 _doc _id 和 _score,这数据量也很大了,而且,这才一个查询请求,那如果再乘以100呢?</p>
<p>在另一方面,我们意识到,这种深度分页的请求并不合理,因为我们是很少人为的看很后面的请求的,在很多的业务场景中,都直接限制分页,比如只能看前100页。</p>
<p>不过,这种深度分页确实存在,比如,被爬虫了,这个时候,直接干掉深度分页就好;又或者,业务上有遍历数据的需要,比如,有1千万粉丝的微信大V,要给所有粉丝群发消息,或者给某省粉丝群发,这时候就需要取得所有符合条件的粉丝,而最容易想到的就是利用 from + size 来实现,不过,这个是不现实的,这时,可以采用 Elasticsearch 提供的 scroll 方式来实现遍历。</p>
<h1 id="利用-scroll-遍历数据">利用 scroll 遍历数据</h1>
<p>可以把 scroll 理解为关系型数据库里的 cursor,因此,scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发。</p>
<p>可以把 scroll 分为初始化和遍历两步,初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照,在遍历时,从这个快照里取数据,也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果。</p>
<h2 id="使用介绍">使用介绍</h2>
<p>下面介绍下scroll的使用,可以通过 Elasticsearch 的 HTTP 接口做试验下,包括初始化和遍历两个部分。</p>
<h3 id="初始化">初始化</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST ip:port/my_index/my_type/_search?scroll=1m
{
"query": { "match_all": {}}
}
</code></pre></div></div>
<p>初始化时需要像普通 search 一样,指明 index 和 type (当然,search 是可以不指明 index 和 type 的),然后,加上参数 scroll,表示暂存搜索结果的时间,其它就像一个普通的search请求一样。</p>
<p>初始化返回一个 _scroll_id,_scroll_id 用来下次取数据用。</p>
<h3 id="遍历">遍历</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /_search?scroll=1m
{
"scroll_id":"XXXXXXXXXXXXXXXXXXXXXXX I am scroll id XXXXXXXXXXXXXXX"
}
</code></pre></div></div>
<p>这里的 scroll_id 即 上一次遍历取回的 _scroll_id 或者是初始化返回的 _scroll_id,同样的,需要带 scroll 参数。 重复这一步骤,直到返回的数据为空,即遍历完成。<strong><em>注意,每次都要传参数 scroll,刷新搜索结果的缓存时间</em></strong>。另外,<strong><em>不需要指定 index 和 type</em></strong>。</p>
<p>设置scroll的时候,需要使搜索结果缓存到下一次遍历完成,同时,也不能太长,毕竟空间有限。</p>
<h2 id="scroll-scan">Scroll-Scan</h2>
<p>Elasticsearch 提供了 Scroll-Scan 方式进一步提高遍历性能。还是上面的例子,微信大V要给粉丝群发这种后台任务,是不需要关注顺序的,只要能遍历所有数据即可,这时候,就可以用Scroll-Scan。</p>
<p>Scroll-Scan 的遍历与普通 Scroll 一样,初始化存在一点差别。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST ip:port/my_index/my_type/_search?search_type=scan&scroll=1m&size=50
{
"query": { "match_all": {}}
}
</code></pre></div></div>
<p>需要指明参数:</p>
<ul>
<li>search_type。赋值为scan,表示采用 Scroll-Scan 的方式遍历,同时告诉 Elasticsearch 搜索结果不需要排序。</li>
<li>scroll。同上,传时间。</li>
<li>size。与普通的 size 不同,这个 size 表示的是每个 shard 返回的 size 数,最终结果最大为 number_of_shards * size。</li>
</ul>
<p>Scroll-Scan 方式与普通 scroll 有几点不同:</p>
<ol>
<li>Scroll-Scan 结果没有排序,按 index 顺序返回,没有排序,可以提高取数据性能。</li>
<li>初始化时只返回 _scroll_id,没有具体的 hits 结果。</li>
<li>size 控制的是每个分片的返回的数据量而不是整个请求返回的数据量。</li>
</ol>
<h2 id="java-实现">Java 实现</h2>
<p>用 Java 举个例子。</p>
<h3 id="初始化-1">初始化</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try {
response = esClient.prepareSearch(index)
.setTypes(type)
.setSearchType(SearchType.SCAN)
.setQuery(query)
.setScroll(new TimeValue(timeout))
.setSize(size)
.execute()
.actionGet();
} catch (ElasticsearchException e) {
// handle Exception
}
</code></pre></div></div>
<p>初始化返回 _scroll_id,然后,用 _scroll_id 去遍历,注意,上面的query是一个JSONObject,不过这里很多种实现方式,我这儿只是个例子。</p>
<h3 id="遍历-1">遍历</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try {
response = esClient.prepareSearchScroll(scrollId)
.setScroll(new TimeValue(timeout))
.execute()
.actionGet();
} catch (ElasticsearchException e) {
// handle Exception
}
</code></pre></div></div>
<h1 id="总结">总结</h1>
<ol>
<li>深度分页不管是关系型数据库还是Elasticsearch还是其他搜索引擎,都会带来巨大性能开销,特别是在分布式情况下。</li>
<li>有些问题可以考业务解决而不是靠技术解决,比如很多业务都对页码有限制,google 搜索,往后翻到一定页码就不行了。</li>
<li>Elasticsearch 提供的 Scroll 接口专门用来获取大量数据甚至全部数据,在顺序无关情况下,首推Scroll-Scan。</li>
<li>描述搜索过程时,为了简化描述,假设 index 没有备份,实际上,index 肯定会有备份,这时候,就涉及到选择 shard。</li>
</ol>
<p><strong><em>PS</em></strong>:Elasticsearch 各个版本可能有区别,但原理基本相同,本文包括文末的代码都基于Elasticsearch 1.3。</p>
<p><code class="language-plaintext highlighter-rouge">***One more thing***</code>: <a href="http://job.youzan.com/?user=konggu#64" title="hire">We are hiring</a></p>
Saiku+Kylin多维分析平台探索
2016-03-19T00:00:00+00:00
http://www.readingnotes.site/posts/Saiku+Kylin多维分析平台探索
<h1 id="背景">背景</h1>
<p>为了应对各种数据需求,通常,我们的做法是这样的:</p>
<ol>
<li>对于临时性的数据需求:写HQL到Hive里去查一遍,然后将结果转为excel发送给需求人员。</li>
<li>对于周期性的、长期性的数据需求:编写脚本,结合Hive跑出结果,将结果写入对应DB库,然后开发前端页面对结果进行展现。</li>
</ol>
<p>这样做简洁明了,但是,有很明显的问题:</p>
<ol>
<li>开发成本太高。每来一个需求,不管是临时需求还是长期需求,都需要进行定制开发,这种情况下,我们的人力深陷其中。</li>
<li>使用不灵活。一个报表,只能进行展示,没有分析功能,如果要进行分析,需要将数据复制到excel里,利用excel进行处理分析,而我们的数据使用人员不一定具备这种能力。</li>
<li>维护成本高。太多人开发报表,没有统一口径,没有详细文档,没有人能接手。</li>
<li>资源浪费。不同人员开发的报表,很多情况下存在很多重复计算。</li>
</ol>
<p>在这种情况下,开始考虑构建一个多维分析平台,提供平台和基础数据,数据需求方通过托拉拽的形式获取需要的数据。本文主要记录这方面的一些探索。</p>
<h1 id="saiku">Saiku</h1>
<p>首先,选择使用 <a href="http://www.meteorite.bi/products/saiku" title="saiku">Saiku</a> 作为我们的数据分析平台,下面是 Saiku 官网对 Saiku 的介绍:</p>
<blockquote>
<p>Saiku allows business users to explore complex data sources, using a familiar drag and drop interface and easy to understand business terminology, all within a browser. Select the data you are interested in, look at it from different perspectives, drill into the detail. Once you have your answer, save your results, share them, export them to Excel or PDF, all straight from the browser.</p>
</blockquote>
<p>Saiku 是基于 <a href="mondrian.pentaho.com" title="Mondrian">Mondrian</a> 开发的,Mondrian 是一款开源的 OLAP 引擎,能提供对大量数据的处理分析,但是,Mondrian 并没有提供友好的界面,而Saiku作为另一个开源产品,很好的充当了这个角色。</p>
<p>Saiku 提供JDBC 和 ODBC 接口,可以连接很多的数据源,既可以连接 MySQL、SQL Server、Oracle DB 等传统关系型数据库,也可以连接 Hive、Spark、Impala等 Big Data 平台。</p>
<h2 id="saiku--mysql">Saiku + MySQL</h2>
<p>首先,我们用 Saiku+MySQL很快搭建了测试平台,可以实现基本的多维分析,但是,仅适用于小数据量的分析,否则会有很严重的性能问题。</p>
<h2 id="saiku--spark">Saiku + Spark</h2>
<p>由于 Saiku+MySQL 的性能问题,又尝试了 Saiku+Spark 的方案,数据存储于 Hive,分析引擎使用 Spark,在这种情况下,可以处理大数据量的查询分析,大部分查询都会在几十秒到几分钟内返回结果,如果数据量太大,也会存在执行失败的情况。所以,开始探索其他方案。</p>
<h1 id="kylin">Kylin</h1>
<blockquote>
<p>Apache Kylin™ is an open source Distributed Analytics Engine designed to provide SQL interface and multi-dimensional analysis (OLAP) on Hadoop supporting extremely large datasets, original contributed from eBay Inc.</p>
</blockquote>
<p><a href="http://kylin.apache.org/" title="Kylin">Kylin</a> 相对于其他OLAP分析引擎,一个重要特点是采用空间换时间,根据定义的cube进行预计算,并将计算结果存储到Hbase中,在进行查询时,直接查询Hbase,所以,Kylin 的查询可以到毫秒级,性能完全不是问题。</p>
<p>Kylin 提供 ANSI SQL 接口来对数据进行查询,没有可视化操作,所以,有一定的使用门槛。同时,由于Kylin 提供的 ANSI SQL接口,Kylin 也就可以和BI平台进行对接,比如,Kylin 已经支持 Tableau 等BI工具,奈何 Tableau 是商业软件,so…</p>
<h1 id="saiku--kylin-实现多维分析">Saiku + Kylin 实现多维分析</h1>
<p>Saiku 根据用户在页面的操作,生成 MDX,然后,Mondrian根据MDX生成查询语句SQL,而 Kylin 可以根据SQL 查询 cube,快速得到结果,所以,如果 Saiku 和 Kylin 中定义了相同的 cube,那么,就可以通过Saiku 来查询 Kylin了,从而将 Saiku 的操作页面和 Kylin 的高性能查询能力结合起来。</p>
<p>Google 一下,<a href="https://github.com/mustangore/kylin-mondrian-interaction" title="Saiku+Kylin">Github 已经有人这么做了</a>,按照该项目说明就可以轻松搭建 Saiku+Kylin 多维分析平台。</p>
<p>Saiku+Kylin 的分析平台能实现的分析能力,很大程度取决于 Kylin 支持的 SQL 和 Mondrian 生成的 SQL的共通点,经过测试,Kylin 能对绝大多数 SQL 提供友好支持,可以和 Saiku 进行完美结合。</p>
<h1 id="总结">总结</h1>
<p>Saiku 作为分析平台,提供可视化的操作,能方便的对数据进行查询、分析,并提供图形化显示,但是,Saiku 有一定的使用门槛,特别是对国内的使用者来说,所以,可能需要一些定制开发。</p>
<p>Kylin 作为分析引擎,根据空间换时间的思想,对数据进行预计算,从而提供极高的查询性能,并且提供 ANSI SQL 接口,可以极大程度满足日常查询需求。但是,Kylin 对 Hadoop 生态版本有较高的要求,所以,尽量按照官方推荐版本安装配置。</p>
<p>PS:本文采用 Saiku 3.7.4 和 Kylin 1.2。</p>
不停服务 ElasticSearch 集群物理拆分
2016-02-18T00:00:00+00:00
http://www.readingnotes.site/posts/Elasticsearch集群物理拆分
<h1 id="背景">背景</h1>
<p>公司使用 Elasticsearch 来做全文检索和大数据量的结构化查询,由于历史包袱,只有一个 Elasticsearch 集群,各个业务线都随便使用该集群,没有有效的管理和制约,比较混乱。而且,随着数据量的暴增,开始出现性能瓶颈,比如一个慢查询拖慢整个集群状态,甚至搞挂整个集群,所以开始着手对集群进行系统改造。</p>
<h1 id="老系统">老系统</h1>
<p>下图是老系统:</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/2016-old_ES_cluster.png" alt="老系统架构" title="老系统架构图" /></p>
<p>可以看到,系统就一个 Elasticsearch 集群,所有 index 公用一个集群,当集群出现问题时,会影响所有 index,甚至导致全站挂掉。其次,一个集群涉及到的 index 太多,涉及的业务线太多,当我们需要对集群进行改造、升级时,会带来极大挑战。</p>
<p>基于此,需要将 Elasticsearch 物理拆分成多个集群。</p>
<h1 id="新系统">新系统</h1>
<p>新系统如下图所示,重构时顺便做了其他修改,但跟本文无关,所以先隐掉。</p>
<p><img src="http://7sbmb0.com1.z0.glb.clouddn.com/2016-new_es_cluster.jpg" alt="新系统架构" title="新系统架构图" /></p>
<p>客户端请求,经过中间的处理,转发到不同的 Elasticsearch 集群,各个集群物理隔离,这样,即使集群挂了,也只影响一小部分。</p>
<h1 id="行动">行动</h1>
<h2 id="拆分策略">拆分策略</h2>
<p>确定要物理拆分集群后,首先要确定的是按照什么标准拆分,最重要的是哪些 index 应该放在一个集群。</p>
<p>首先想的是根据访问量来划分,保证每个集群的访问量基本相等。后来考虑到不同 index 的访问量是变化的,不可能每个集群的访问量都一直一样,而且,对于出现了访问量激增的情况下,我们可以通过增加机器来改善,所以,该方案没有意义。</p>
<p>然后,考虑根据优先级划分,将 index 分为高、中、低三个优先级,使每个优先级都分散在不同集群,这样,即使某个集群挂掉,也不至于所有高优先级的 index 涉及的业务挂掉。该方案也没有实施,一是优先级并不好确定,其次,优先级会动态变化,同时,会有index被删除,也会有index加入。</p>
<p>最后,我们按照业务线划分,同一业务线的 index 部署在同一集群,这样,便于维护。</p>
<h2 id="index-迁移">index 迁移</h2>
<p>确定了拆分策略,到落实阶段了,问题便成了怎么迁移,我们分两种方案迁移。</p>
<h3 id="写入少且量小的-index">写入少且量小的 index</h3>
<p>对于这种index,比如有的 index 就百万、千万级数据量,同时,每天就写入一次,这种 index,我们采用的策略是直接从老集群把数据迁移到新集群,现在有不少工具可以实现这种功能,比如 <a href="https://www.npmjs.com/package/elasticdump">elasticdump</a>,就可以一行命令把 index 给迁移到新集群。</p>
<h3 id="写入多或量大的-index">写入多或量大的 index</h3>
<p>对于这种 index,我们采用重建 index 的方式,因为我们不可能停止服务,所以,我们采用的是<strong><em>全量 index + 增量 index</em></strong> 的方式,当新集群上 index 完成后,将业务切到新集群。</p>
<ol>
<li>全量 index:使用 <code class="language-plaintext highlighter-rouge">es-hadoop</code> 来做。</li>
<li>增量 index:在开始全量 index 前,监听 <code class="language-plaintext highlighter-rouge">binlog</code>,将消息丢进 <code class="language-plaintext highlighter-rouge">NSQ</code>,停止消费对应<code class="language-plaintext highlighter-rouge">channel</code>;当全量 index 完成后,开始消费该<code class="language-plaintext highlighter-rouge">channel</code>,当消费完成后,索引重建完毕。</li>
</ol>
<p>全量 index 和增量 index 都需要对业务相当了解,在写HQL和消费 <code class="language-plaintext highlighter-rouge">NSQ</code> 消息脚本时,可以寻求业务部门相助。</p>
<h1 id="问题">问题</h1>
<ol>
<li>全量 index 时,如果数据量太大,是可能造成写入失败的。在这种情况下,一是优化新建 index 性能,二是分批次新建 index。</li>
<li>全量 index 需要编写 HQL,增量 index 需要将 DB 数据变动写入 NSQ,还要写消费脚本,针对每个 index 都需要这两个脚本,如果 index 变动或者涉及到的数据来源发生变动就需要重新对应脚本,会比较繁杂,所以,还缺少<strong>高效的全量 index 和增量 index</strong> 方案,如果有好的方案,记得告诉我。</li>
<li>缺乏有效的测试方案,在整个 index 迁移过程中,主要采用抽样对比新老集群数据来测试,一定要仔细!</li>
<li>沟通,仔细、耐心。</li>
</ol>
<h1 id="失败的拆分方案">失败的拆分方案</h1>
<p>最后,提供一种失败的拆分方案,大家就不要尝试了,血的教训。</p>
<p>Elasticsearch 提供了 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/1.4/index-modules-allocation.html">index shard allocation</a> 功能,我们可以利用该功能对 Elasticsearch 集群进行逻辑拆分。比如,我们可以给每个 Elasticsearch 节点打上标签,然后将不同业务涉及的 index 指定到不同节点,这样,虽然还是一个物理集群,但是,从逻辑上来说,可以理解为几个集群。按理来说,如果某个 index 特别大,或者有慢查询,是不会影响其他业务线的 index 的。但是,该方案有个很严重的问题,当我们人为让 index 在一个集群内不同节点之间迁移时,集群为了均衡数据,其他 index 也会迁移,这样,集群内部网络带宽占用就会很高,而且,Elasticsearch 没有提供控制集群内部迁移速递的 API,此时,集群处于极不稳定状态,特别是整个集群的数据量大的时候。我们遇到过集群内部网络带宽用尽,集群节点间的心跳无法正常接收,从而造成脑裂,一度不停选举master,整个集群挂掉的情况。即使采用数据迁移完成,由于没有物理隔离,还是有整个集群挂掉的可能性的,所以,还是物理拆分吧。</p>
MySQL 数据类型
2015-01-09T00:00:00+00:00
http://www.readingnotes.site/posts/MySQL 数据类型
<p>上一篇主要讲了 MySQL 的简介,这篇主要总结 MySQL 中的数据类型。由于 MySQL 很多特性都是由存储引擎决定的,而自己使用得基本都是 InnoDB,所以,下面都已 InnoDB 为准。</p>
<h1 id="整数类型">整数类型</h1>
<p>MySQL 中整数类型包括五种,分别是 <code class="language-plaintext highlighter-rouge">TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT</code>,分别占用8、16、24、32、64位存储空间,同时,所有整数类型都可以是 <code class="language-plaintext highlighter-rouge">UNSIGNED</code>或<code class="language-plaintext highlighter-rouge">SIGNED</code>型,表示是否可以有负数。</p>
<p>在创建数据库的时候,经常指定整数类型的长度,如定义 <strong>INT(11)</strong>来表示手机号,但多数情况下,长度是没有意义的,不会限制数值的合法范围,数值的合法范围仍由占的存储空间决定,长度只是规定了 MySQL 的一些交互工具显示字符的个数。</p>
<h1 id="实数类型">实数类型</h1>
<p>实数是带有小数部分的数字,但也可以用来存储比BIGINT大的整数。实数包括FLOAT, DOUBLE, DECIMAL。</p>
<p><code class="language-plaintext highlighter-rouge">FLOAT, DOUBLE</code>支持使用标准浮点数进行<strong>近似计算</strong>,<code class="language-plaintext highlighter-rouge">DECIMAL</code> 用于<strong>存储精确小数</strong>,在新版本的 MySQL 中,DECIMAL 也支持<strong>精确计算</strong>。</p>
<p>MySQL 中实数可以指定精度,如 DECIMAL(20,10),指定一共20位,小数点前后各10位;对于浮点数FLOAT和DOUBLE,有多种方法可以指定所需要的精度,但可能让 MySQL 选择不同的数据类型或在存储时进行取舍,同时,这些精度定义是非标准的,所以,尽量<strong>只定义数据类型,不指定精度</strong>。</p>
<p>使用 DECIMAL 时,会带来额外的空间开销,计算 DECIMAL 时,由 MySQL 自己实现的高精度计算不如 CPU 支持的浮点数计算快,也会带来计算开销,所以,尽量只在对小数进行精确计算时才使用 DECIMAL。 另外,也可以考虑使用整数类型来代替,如在存储金钱时,数据库中以分为单位进行存储、计算,显示的时候再换算成元,既节省空间开销,也加快计算速度。</p>
<h1 id="字符串类型">字符串类型</h1>
<h2 id="char-与-varchar">CHAR 与 VARCHAR</h2>
<p>CHAR 和 VARCHAR 是用得最广的两种字符串数据类型。</p>
<p><strong>CHAR</strong></p>
<p>用来存储定长字符串,MySQL 根据定义的字符串长度来分配空间,CHAR 适用于以下几种情况:</p>
<ol>
<li>存储很短的字符串</li>
<li>所有值长度都较为接近,如存储MD5值。</li>
<li>存储经常更新变动的值,不会产生碎片。</li>
</ol>
<p><strong>VARCHAR</strong></p>
<p>用来存储可变长字符串,因为值占用必要的存储空间,所以,一般情况下比定长字符串节省空间,但是,VARCHAR 需要一个或两个额外字节用来存储字符串长度,当长度小于256时是1个字节,否则是2个字节,所以,在特殊情况下,VARCHAR 也会比 CHAR 占用更多存储空间,如存储性别时,”M” 和 “F” 分别表示男女。</p>
<p>由于VARCHAR 是变长的,那么在更新时,可能需要做一些额外的工作,比如分裂页等。</p>
<p>在下面的情况下,适合使用VARCHAR:</p>
<ol>
<li>字符串最大长度比平均长度大得多。</li>
<li>列更新少。</li>
<li>使用了类似 UTF-8 编码,每个字符使用不同字节数进行存储。</li>
</ol>
<h2 id="blob-与-text-类型">BLOB 与 TEXT 类型</h2>
<h1 id="日期与时间类型">日期与时间类型</h1>
<h1 id="位数据类型">位数据类型</h1>
<h1 id="使用原则">使用原则</h1>
高性能Web缓存浅析
2014-12-09T23:30:41+00:00
http://www.readingnotes.site/posts/高性能Web缓存浅析
<h1 id="目录">目录</h1>
<ul>
<li><a href="#dynamic_content_cache">1. 动态内容缓存</a></li>
<li><a href="#browser_content_cache">2. 浏览器缓存</a></li>
<li><a href="#server_cache">3. 服务器缓存</a></li>
<li><a href="#reserve_proxy_cache">4. 反向代理缓存</a></li>
<li><a href="#distribute_cache">5. 分布式缓存</a></li>
<li><a href="#summary">6. 总结</a></li>
</ul>
<p>本文标题叫“高性能Web缓存浅析”,首先,必须声明,“浅析”并非自己谦虚,而是真的是“浅”析,作为一枚刚毕业应届生,在提笔写这篇文章前一周刚上线了自己作为程序员的第一个正式系统,文中所有内容均来源于阅读和自己的一些思考,与实际生产环境可能会有出入,还请不吝赐教。</p>
<p>首先,简单说下缓存,缓存的思想由来已久,将需要花费大量时间开销的计算结果保存起来,在以后需要的时候直接使用,避免重复计算。在计算机系统中,缓存的应用不胜枚举,比如计算机的三级存储结构、Web 服务中的缓存,本文主要讨论缓存在 Web 环境中的使用,包括浏览器缓存、服务器缓存、反向代理缓存以及分布式缓存等方面。</p>
<h1 id="--1-动态内容缓存-"><a id="dynamic_content_cache"> 1. 动态内容缓存 </a></h1>
<p>现代 Web 站点更多的提供动态内容,如动态网页、动态图片、Web服务等,它们通常在 Web 服务器端进行计算,生成 HTML 并返回。在生成 HTML 页面的过程中,涉及到大量的 CPU 计算和 I/O 操作,比如数据库服务器的 CPU 计算和磁盘 I/O,以及与数据库服务器通信的网络I/O。这些操作会花费大量时间,然而,大多数情况下,动态网页在多次请求时的生成结果几乎一样,那么,就可以考虑通过缓存去掉这部分时间开销。</p>
<h2 id="11-页面缓存">1.1 页面缓存</h2>
<p>对于动态网页来说,我们将生成的 HTML 缓存起来,称为页面缓存(Page Cache),对于其它动态内容如动态图片、动态 XML 数据,我们也可以相同策略将它们的结果整体进行缓存。</p>
<p>对于页面缓存具体方法,有很多实现方法,如类似 Smarty 的模板引擎或者类似 Zend、Diango 的 MVC 框架,控制器和视图分离,控制器很方便的拥有自己的缓存控制权。</p>
<h3 id="111-存储方式">1.1.1 存储方式</h3>
<p>通常,我们将动态内容的缓存<strong>存储在磁盘</strong>上,磁盘提供了廉价的、存储大量文件的方式,不用担心由于空间问题而淘汰缓存,这是一种简单且容易部署的方法。但是,还是可能造成 cache 目录下存在大量缓存文件的可能,从而使 CPU 在遍历目录时花费大量时间,针对这个问题,可以使用缓存目录分级来解决这一问题,从而将每个目录下的子目录或文件数量控制在少量范围内。这样,在存储大量缓存文件的情况下,可以减少 CPU 遍历目录的时间消耗。</p>
<p>当将缓存数据存储在磁盘文件中时,每次缓存加载和过期检查都存在磁盘 I/O 开销,同时也受磁盘负载影响,如果磁盘 I/O 负载大,则缓存文件的 I/O 操作会存在一定的延迟。</p>
<p>另外,可以<strong>将缓存放在本机内存中</strong>,借助 PHP 的 APC 模块或 PHP 缓存扩展 XCache 可以很方便的实现,这样,加载缓存时就没有任何磁盘 I/O 操作。</p>
<p>最后,还可以将缓存存储在<strong>独立的缓存服务器中</strong>,利用 memcached 可以很方便的通过 TCP 将缓存存储在其他服务器。使用 memcached,速度会比使用本机内存稍慢,但相比将缓存存放到本机内存,使用 memcached 来实现缓存有两个优势:</p>
<ol>
<li>Web 服务器内存宝贵,无法提供大量空间做 HTML 缓存。</li>
<li>使用独立的缓存服务器可以提供良好的可扩展性。</li>
</ol>
<h3 id="112-过期检查">1.1.2 过期检查</h3>
<p>既然谈到缓存,就不得不谈过期检查,缓存过期检查主要根据<strong>缓存有效期</strong>机制来检查,主要两种机制:</p>
<ol>
<li>根据缓存创建时间、缓存有效期设置的时间长度以及当前时间,来判断是否过期,也就是说如果当前时间距缓存创建的时间长度超过有效期长度,则认为缓存过期。</li>
<li>根据缓存的过期时间和当前时间来判断。</li>
</ol>
<p>对于缓存有效期的设置并不是件容易的事,如果太长,虽然缓存命中率高了,但动态内容更新不及时;如果太短,虽然动态内容更新及时,但缓存命中率降低了。所以,设置合理的有效期十分重要,但更重要的是,我们需要具备能意识到有效期何时需要变换的能力,然后在任何时候找到合适的取值。</p>
<p>除了缓存有效期,缓存还提供了<strong>随时强行清除缓存</strong>的控制方法。</p>
<h3 id="113-局部无缓存">1.1.3 局部无缓存</h3>
<p>在有些情况下,需要页面中某块区域的内容及时更新,如果因为一块区域需要及时更新而重建整个页面缓存的话,会显得不值得。在流行的模板框架中,都提供了局部无缓存的支持,如 Smary。</p>
<h2 id="12-静态化内容">1.2 静态化内容</h2>
<p>前面的方法需动态地控制是否使用缓存,静态化方法将动态内容生成静态内容,然后让用户直接请求静态内容,大幅度提高吞吐率。</p>
<p>同样的,对于静态化内容,同样需要更新,一般有两种方法:</p>
<ol>
<li>数据更新时重新生成静态化内容。</li>
<li>定时重新生成静态化内容。</li>
</ol>
<p>与前面提到的动态缓存一样,静态页面也可以不更新整个页面,可以通过服务器包含(SSI)技术实现各个局部页面的独立更新,从而大大减少重建整个页面的计算开销和磁盘 I/O 开销,以及分发时的网络 I/O 开销。现在主流的 Web 服务器都支持 SSI 技术,比如 Apache、lighttpd等。</p>
<h1 id="--2-浏览器缓存"><a id="browser_content_cache"> 2. 浏览器缓存</a></h1>
<p>串通角度来看,人们习惯将浏览器仅仅看着 PC 上的一个软件,但实际上,<strong>浏览器是 Web 站点的重要组成部分</strong>。如果将内容缓存在浏览器上,不仅可以减少服务器计算开销,还能避免不必要的传输和带宽浪费。为了在浏览器端存储缓存内容,一般是在用户的文件系统中创建一个目录用来存储缓存文件,并给每个缓存文件打上一些必要标签,如过期时间等。另外,不同浏览器在存储缓存文件时会有细微差别。</p>
<h2 id="21-实现">2.1 实现</h2>
<p>浏览器缓存内容存储在浏览器端,而内容由 Web 服务器生成,要利用浏览器缓存,浏览器和 Web 服务器之间必须沟通,这就是 HTPP 中的“缓存协商”。</p>
<h3 id="211-缓存协商">2.1.1 缓存协商</h3>
<p>当 Web 服务器接收到浏览器请求后,Web 服务器需要告知浏览器哪些内容可以缓存,一旦浏览器知道哪些内容可以缓存后,下次当浏览器需要请求这个内容时,浏览器便不会直接向服务器请求完整内容,二是询问服务器是否可以使用本地缓存,服务器在收到浏览的询问后回应是使用浏览器本地缓存还是将最新内容传回给浏览器。</p>
<p><strong>Last-Modified</strong></p>
<p>Last-Modified 是一种协商方式。通过动态程序为 HTTP 相应添加最后修改时间的标记</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
</code></pre></div></div>
<p>此时,Web 服务器的响应头部会多出一条:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Last-Modified: Fri, 9 Dec 2014 23:23:23 GMT
</code></pre></div></div>
<p>这代表 Web 服务器对浏览器的暗示,告诉浏览器当前请求内容的最后修改时间。收到 Web 服务器响应后,再次刷新页面,注意到发出的 HTTP 请求头部多了一段标记:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>If-Modified-Since: Fri, 9 Dec 2014 23:23:23 GMT
</code></pre></div></div>
<p>这表示浏览器询问 Web 服务器在该时间后是否有更新过请求的内容,此时,Web 服务器需要检查请求的内容在该时间后是否有过更新并反馈给浏览器,这其实就是缓存过期检查。</p>
<p>如果这段时间里请求的内容没有发生变化,服务器做出回应,此时,Web 服务器响应头部:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 304 Not Modified
</code></pre></div></div>
<p>注意到此时的状态码是<strong>304</strong>,意味着 Web 服务器告诉浏览器这个内容没有更新,浏览器使用本地缓存。如下图所示:
<img src="http://blog2014.qiniudn.com/12-Last-Modified.jpg" alt="Last-Modified" /></p>
<p><strong>ETag</strong></p>
<p>HTTP/1.1 还支持ETag缓存协商方法,与最后过期时间不同的是,ETag不再采用内容的最后修改时间,而是采用一串编码来标记内容,称为ETag,<em>如果一个内容的 ETag 没有变化,那么这个内容就一定没有更新</em>。</p>
<p>ETag 由 Web 服务器生成,浏览器在获得内容的 ETag 后,会在下次请求该内容时,在 HTTP 请求头中附加上相应标记来询问服务器该内容是否发生了变化:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>If-None-Match: "87665-c-090f0adfadf"
</code></pre></div></div>
<p>这时,服务器需要重新计算这个内容的 ETag,并与 HTTP 请求中的 ETag 进行对比,如果相同,便返回 304,若不同,则返回最新内容。如下图所示,服务器发现请求的 ETag 与重新计算的 ETag 不同,返回最新内容,状态码为200。
<img src="http://blog2014.qiniudn.com/12-ETag.jpg" alt="ETag" /></p>
<p><strong>Last-Modified VS ETag</strong></p>
<p>基于最后修改时间的缓存协商存在一些缺点,如有时候文件需频繁更新,但内容并没有发生变化,这种情况下,每次文件修改时间变化后,无论内容是否发生变化,都会重新获取全部内容。另外,在采用多台 Web 服务器时,用户请求可能在多台服务器间变化,而不同服务器上同一文件最后修改时间很难保证完全一样,便会导致重新获取所有内容。采用 ETag 方法就可以避免这些问题。</p>
<h3 id="212-性能">2.1.2 性能</h3>
<p>首先,原本使用浏览器缓存的动态内容,在使用浏览器缓存后,能否获得大的吞吐率提升,关键在于是否能避免一些额外的计算开销,同事,还取决于 HTTP 响应正文的长度,若 HTTP 响应较长,如较长的视频,则能带来大的吞吐率提到。</p>
<p>但使用浏览器缓存的最大价值并不在此,而在于减少带宽消耗。使用浏览器缓存后,如果 Web 服务器计算后发现可以使用浏览器端缓存,则返回的响应长度将大大减少,从而,大大减少带宽消耗。</p>
<h2 id="22-彻底消灭请求">2.2 彻底消灭请求</h2>
<blockquote>
<p>The goal of caching in HTTP/1.1 is to eliminate the need to send requests in many cases.</p>
</blockquote>
<h3 id="221-expires-标记">2.2.1 Expires 标记</h3>
<p>在上面两图中,有个<code class="language-plaintext highlighter-rouge">Expires</code>标记,告诉浏览器该内容何时过期,在内容过期前<strong>不需要再询问服务器,直接使用本地缓存即可</strong>。</p>
<h3 id="222-请求页面方式">2.2.2 请求页面方式</h3>
<p>对于主流浏览器,有三种请求页面方式:</p>
<ol>
<li>Ctrl + F5:强制刷新,使网页以及所有组件都直接向 Web 浏览器发送请求,并且不适用缓存协商,从而获取所有内容的最新版本。等价于按住 Ctrl 键后点击浏览器刷新按钮。</li>
<li>F5:允许浏览器在请求中附加必要的缓存协商,但不允许直接使用本地缓存,即让Last-Modified生效、Expires无效。等价于单击浏览器刷新按钮。</li>
<li>单击浏览器地址栏“转到”按钮或通过超链接跳转:浏览器对于所有没过期的内容直接使用本地缓存,Expires只对这种方式生效。等价于在地址栏输入 URL 后回车。</li>
</ol>
<table>
<tr>
<th></th><th>Last-Modified</th><th>Expires</th>
</tr>
<tr>
<td>Ctrl + F5</td> <td>无效</td><td>无效</td>
</tr>
<tr>
<td>F5</td> <td>有效</td><td>无效</td>
</tr>
<tr>
<td>转到</td> <td>有效</td><td>有效</td>
</tr>
</table>
<h3 id="223-适应过期时间">2.2.3 适应过期时间</h3>
<p>Expires指定的过期时间来源于 Web 服务器的系统时间,如果与用户本地时间不一致,就会影响到本地缓存的有效期检查。</p>
<p>一般情况下,操作系统都使用基于 <strong>GMT</strong> 的标准时间,然后通过时区来进行偏移计算,HTTP 中也使用 GMT 时间,所以,一般不会因为时区导致本地与服务器相差数个小时,但没人能保证本地时间与服务器一直,甚至有时服务器时间也是错误的。</p>
<p>针对这个问题,HTTP/1.1 添加了标记 Cache-Control,如上图1所示,<code class="language-plaintext highlighter-rouge">max-age</code> 指定缓存过期的相对时间,单位是秒,相对时间指相对浏览器本地时间。目前,当 HTTP 响应中同时含有 Expires 和 Cache-Control 时,浏览器会优先使用 Cache-Control。</p>
<h2 id="23-总结">2.3 总结</h2>
<p>HTTP 是浏览器与 Web 服务器沟通的语言,且是它们唯一的沟通方式,好好学学 HTTP 吧!</p>
<h1 id="--3-web-服务器缓存"><a id="server_cache"> 3. Web 服务器缓存</a></h1>
<p>前面提到的动态内容缓存和静态化基本都是通过动态程序来实现的,下面讨论 Web 服务器自己实现缓存机制。</p>
<p>Web 服务器接收到 HTTP 请求后,需要解析 URL,然后将 URL 映射到实际内容或资源,这里的“映射”指服务器处理请求并生成响应内容的过程。很多时候,在一段时间内,一个 URL 对应一个唯一的响应内容,比如静态内容或更新不频繁的动态内容,如果将最终内容缓存起来,下次 Web 服务器接收到请求后可以直接将响应内容返回给浏览器,从而节省大量开销。现在,主流 Web 服务器都提供了对这种类型缓存的支持。</p>
<h2 id="31-简介">3.1 简介</h2>
<p>当使用 Web 服务器缓存时,如果直接命中,那么将省略后面的一系列操作,如 CPU 计算、数据库查询等,所以,Web 服务器缓存能带来较大性能提升,但对于普通 HTML 也,带来的性能提升较有限。</p>
<p>那么,缓存内容存储在什么位置呢?一般来说,本机内存和磁盘是主要选择,也可以采用分布式设计,存储到其它服务器的内存或磁盘中,这点跟前面提到的动态内容缓存类似,Apache、lighttpd 和 Nginx 都提供了支持,但配置上略有差别。</p>
<p>提到缓存,就不得不提有效期控制。与浏览器缓存相似,Web 服务器缓存过期检查仍然建立在 HTTP/1.1 协议上,要指定缓存有效期,仍然是在 HTTP 响应头中加入 <code class="language-plaintext highlighter-rouge">Expires</code> 标记,如果希望不缓存某个动态内容,那么最简单的办法就是使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>header("Expires: 0");
</code></pre></div></div>
<p>这样一来,Web服务器就不会将这个动态内容缓存起来,当然,也有其它方法实现这个功能。</p>
<p>如果动态内容没有输出 Expires 标记,也可以采用 <code class="language-plaintext highlighter-rouge">Last-Modified</code>来实现,具体方法不再叙述。</p>
<h2 id="32-取代动态内容缓存">3.2 取代动态内容缓存</h2>
<p>那么,是否可以使用 Web 服务器缓存取代动态程序自身的缓存机制呢?当然可以,但有些注意:</p>
<ol>
<li>让动态程序依赖特定 Web 服务器,降低应用的可移植性。</li>
<li>Web 服务器缓存机制实质上是以 URL 为键的 key-value 结构缓存,所以,必须保证所有希望缓存的动态内容有唯一的 URL。</li>
<li>编写面向 HTTP 缓存友好的动态程序是唯一需要考虑的事。</li>
</ol>
<h2 id="33-缓存文件描述符">3.3 缓存文件描述符</h2>
<p>对静态内容,特别是大量小文件站点, Web 服务器很大一部分开销花在了打开文件上,所以,可以考虑将打开后的文件描述符直接缓存到 Web 服务器的内存中,从而减少开销。但是,缓存文件描述符仅仅适用于静态内容,而且仅适用于小文件,对大文件,处理它们的开销主要在传送数据上,打开文件开销小,缓存文件描述符带来的收益小。</p>
<h1 id="--4-反向代理缓存"><a id="reserve_proxy_cache"> 4. 反向代理缓存</a></h1>
<h2 id="41-反向代理简介">4.1 反向代理简介</h2>
<blockquote>
<p>代理(Proxy),也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。提供代理服务的电脑系统或其它类型的网络终端称为代理服务器(Proxy Server)。</p>
</blockquote>
<p>上面是维基百科对代理的定义,在这种情况下,用户隐藏在代理服务器后,那么,反向代理服务器便刚好与此相反,Web 服务器隐藏在代理服务器后,这种机制即反向代理(Reverse Proxy),同样的,实现这种机制的服务器,便称为反向代理服务器(Reverse Proxy Server)。我们通常称反向代理服务器后的 Web 服务器为后端服务器(Back-end Server),相应的,反向代理服务器便称为前端服务器(Front-end Server),通常,反向代理服务器暴露在互联网中,后端的 Web 服务器通过内部网络与它相连,用户将通过反向代理服务器来间接访问 Web 服务器,这既带来一定的安全性,也可以实现基于缓存的加速。</p>
<h2 id="42-反向代理缓存">4.2 反向代理缓存</h2>
<p>有很多方式可以实现反向代理,如最常见的 Nginx 服务器就可以作为反向代理服务器。</p>
<h3 id="421-修改缓存规则">4.2.1 修改缓存规则</h3>
<p>用户浏览器、Web 服务器要想正常工作,都需要经过反向代理服务器,所以,反向代理服务器拥有很大的控制权,可以通过任何手段重写经过它的 HTTP 头信息,也可以通过其他自定义机制来直接干预缓存策略。从前面的内容知道,HTTP 头信息决定内容是否可以被缓存,所以,反向代理服务器本着提高性能的原则可以修改经过它的数据的 HTTP 头信息,决定哪些内容可以被缓存,哪些不能被缓存。</p>
<h3 id="422-清除缓存">4.2.2 清除缓存</h3>
<p>反向代理服务器也提供了清除缓存的功能,但是,与动态内容缓存不同的是,在动态内容缓存中,我们可以通过主动删除缓存的方法实现缓存到期之前的更新,而基于 HTTP 的反向代理缓存机制则不容易做到,后端的动态程序无法做到主动删除某个缓存内容,除非清空反向代理服务器上的缓存区。</p>
<h1 id="--5-分布式缓存"><a id="distribute_cache"> 5. 分布式缓存</a></h1>
<h2 id="51-memcached">5.1 memcached</h2>
<p>现在已经有很多成熟的分布式缓存系统,如 memcached。为了实现高速缓存,我们不会将缓存内容放在磁盘上,基于这个原则,memcached 使用物理内存作为缓存区,使用 key-value 的方式存储数据,这是一种单索引的结构和数据组织形式,我们将每个 key 以及对应 value 合起来称为数据项,所有数据项之间彼此独立,每个数据项以 key 作为唯一索引,用户可以通过 key 来读取或更新这个数据项,memcached 使用基于 key 的hash 算法来设计存储数据结构,使用精心设计的内存分配器,使得数据项查询时间复杂度达到O(1)。</p>
<p>memcached 使用基于 LRU(Lease Recently Used) 算法的淘汰机制淘汰数据,同时,也可以为数据项设置过期时间,同样的,过期时间的设置在前面已经讨论过了。</p>
<p>作为分布式缓存系统,memcached 可以运行在独立服务器上,动态内容通过 TCP Socket 来访问,这种情况下,memcached 本身的网络并发处理模型就显得十分重要。memcached 使用 libevent 函数库来实现网络并发模型,可以在较大并发用户数环境下使用 memcached。</p>
<h2 id="52-读写缓存">5.2 读写缓存</h2>
<p>在使用缓存系统实现读操作时,相当于使用了数据库的“前置读缓存”,可以较大的提高吞吐率。</p>
<p>对于写操作,缓存系统也能带来巨大好处。通常的数据写操作包括插入、更新、删除,这些写操作往往同时伴随着查找和索引更新,往往带来巨大开销。在使用分布式缓存时,我们可以暂时将数据存储在缓存中,然后进行批量写操作。</p>
<h2 id="53-缓存监控">5.3 缓存监控</h2>
<p>memcached 作为一个分布式缓存系统,可以出色的完成任务,同时,memcached 也提供了协议,让我们可以获取它的实时状态,其中有几个重要信息:</p>
<ol>
<li>空间使用率:关注缓存空间使用率,可以让我们知道何时需要为缓存系统扩容,以避免由于缓存空间已满造成的数据被动淘汰。</li>
<li>缓存命中率</li>
<li>I/O 流量:反映了缓存系统的工作量,可以从中得知 memcached 是空闲还是繁忙。</li>
</ol>
<h2 id="54-缓存扩展">5.4 缓存扩展</h2>
<p>并发处理能力、缓存空间容量等都可能到达极限,扩展在所难免。</p>
<p>当存在多台缓存服务器后,我们面临的问题是,如何将缓存数据<strong>均衡</strong>的分布在多台缓存服务器上?在这种情况下,可以选择一种基于 key 的划分方法,将所有数据项的 key 均衡分布在不同服务器上,比如采取取余的方法。这种情况下,当我们扩展系统后,由于分区算法的改变,会需要将缓存数据从一台缓存服务器迁移到另一台缓存服务器,实际上,<strong>根本不需要考虑迁移</strong>,因为是缓存数据,重建缓存即可。</p>
<h1 id="--6总结"><a id="summary"> 6.总结</a></h1>
<p>缓存如何使用还得具体问题具体分析。举例来讲,当年在百度实习时,仅仅是一次搜索的结果,使用动态内容缓存都分了好多层,总之是能用缓存就用缓存,能尽早用缓存就尽早用缓存;而在我现在工作的创业公司<a href="http://job.youzan.com" title="youzan">有赞</a>,现在也就使用了 redis 做动态内容缓存,随着业务量的增大,正在一步步完善,哦,差点忘了,还有浏览器缓存。</p>
Mac新手兼菜鸟程序员升级Yosemite,挖坑填坑
2014-11-29T23:04:41+00:00
http://www.readingnotes.site/posts/Mac新手升级Yosemite
<h1 id="目录">目录</h1>
<ul>
<li><a href="#update">1. 升级</a></li>
<li><a href="#java">2. Java挂了</a></li>
<li><a href="#homebrew">3. Homebrew跪了,泪奔</a></li>
<li><a href="#summary">4. 总结</a></li>
</ul>
<p>周围小伙伴一个个都升级到Yosemite了,搞得我也蠢蠢欲动,恰巧手机跟电脑用的同一个appid,要在手机上使用1password需要把电脑升级下,才能使用icloud同步密钥数据,想想以后应该会有不少这种情况,所以,趁早升级。</p>
<p>作为Mac新手和菜鸟程序员,所有问题解决均借助Google和小伙伴,有问题还请轻拍。之所以在此强调程序员,是因为有些问题只有程序员才会遇到。</p>
<h1 id="--1升级-"><a id="update"> 1.升级 </a></h1>
<p>用惯了Windows和Linux,升级Mac还有点不习惯,有点虚,虽然看到周围小伙伴依然好好的正常的用着Yosemite,但毕竟头一次,而且网上也说了Homebrew会带来些问题,连Mac都还没用熟就升级系统,还是有点怕。</p>
<p>首先,我没有对系统进行备份,这都是被周围人忽悠的,虽然Mac升级一般不会有问题,但还是要养成备份的好习惯,行走江湖,还是得防着点,万一跪了还能回来。而且Mac提供了Time Machine这种神器,方便快捷,不会用Time Machine?<a href="http://support.apple.com/zh-cn/ht1427">点这里</a>。</p>
<p>可以采用在线升级的方法,<a href="https://www.apple.com/hk/osx/how-to-upgrade/">官网</a>有教程。为了节省下载时间,我找某大师拷贝了个安装镜像,然后一路继续,经过多次重启电脑之后,终于进去了,界面不错。</p>
<p><img src="http://blog2014.qiniudn.com/12-Yosemite_screen.png" alt="桌面截图" /></p>
<p>高兴劲儿还没过,就发现问题来了。可是没有备份,回不去了,只能硬着头皮往下走。备份多重要啊!</p>
<h1 id="--2java挂了"><a id="java"> 2.Java挂了</a></h1>
<p>Java挂了,所有依赖Java的开发工具都不能用了,看了池大大的<a href="http://weibo.com/p/1001603766056865935197">Blog</a>好像有写,赶紧去<a href="http://support.apple.com/kb/DL1572?viewlocale=en_US&locale=en_US">下载</a>、安装,哈哈,终于又能用了。</p>
<h1 id="--3homebrew跪了泪奔"><a id="homebrew"> 3.Homebrew跪了,泪奔</a></h1>
<p>Homebrew在我初始配置开发环境时提供了巨大的便利,但是,升级完却不能用了,看了下原因应该是Ruby版本变成2.0了,但既然有问题就慢慢解决了,还好是周末,慢慢折腾吧……</p>
<h2 id="31-nginx">3.1 nginx</h2>
<p>由于现在用nginx做服务器,所以,想先看下环境是否能跑起来,运行命令</p>
<blockquote>
<p>sudo nginx</p>
</blockquote>
<p>报错:</p>
<blockquote>
<p>nginx: [emerg] mkdir() “/usr/local/var/run/nginx/client_body_temp” failed (2: No such file or directory)</p>
</blockquote>
<p>不希望看到的还是出现了。上网搜索了下,很高兴的发现有人遇到了相同的问题(<a href="http://stackoverflow.com/questions/26450085/nginx-broken-after-upgrade-to-osx-yosemite">点击查看</a>),但是,考虑到nginx在Mavericks上运行正常,现在不对了,那么应该不只是因为没有这个文件夹,所以,我没有直接<code class="language-plaintext highlighter-rouge">mkdir</code>这个文件夹,而是采用了<code class="language-plaintext highlighter-rouge">brew update ; brew upgrade nginx</code>,结果发现并没有解决问题,而且,我也没打算再mkdir那个文件夹来试试,现在想想应该是脑子被驴踢了,毕竟建立没有的文件夹才是收获赞最多的答案。</p>
<p>然后,我搜到了池大大的<a href="http://weibo.com/p/1001603766056865935197">blog</a>,谁让池大大在我心中是大神一般的存在呢,所以,不管三七二十一,直接执行了池大大给的解决方案。执行到<code class="language-plaintext highlighter-rouge">brew upgrade</code>时,报错了,nginx、mysql、redis、vim报错,其中mysql第一个报错:</p>
<blockquote>
<p>C compiler not found, but GCC is installed</p>
</blockquote>
<p>上网一搜,升级xcode时,把 <strong>Command Line Tools</strong> 干掉了,所以,运行命令:</p>
<blockquote>
<p>xcode-select –install</p>
</blockquote>
<p>然后,要做的就是等待了,这个有点慢。</p>
<p>当安装完成后,重启电脑,然后运行<code class="language-plaintext highlighter-rouge">brew upgrade</code>,nginx、mysql、redis、vim全部升级成功,长舒一口气,爽!</p>
<p>然后,运行<code class="language-plaintext highlighter-rouge">sudo nginx</code>,采用ps命令查看进程,发现nginx正常运行,哈哈~</p>
<h2 id="32-php没了">3.2 PHP没了</h2>
<p>Mac自带了PHP,但是,为了后面统一采用Homebrew对开发环境进行管理,我自己安装了PHP,其中系统自带的PHP时5.4.24,自己安装的是5.4.32,通过修改<strong>.profile</strong>文件里的PATH环境变量,使系统默认使用5.4.32,然而,我现在运行命令:</p>
<blockquote>
<p>php -v</p>
</blockquote>
<p>查看到php版本是5.5.19,然后运行命令:</p>
<blockquote>
<p>which php</p>
</blockquote>
<p>发现是/usr/bin/php,然后使用locate命令查看,发现只有系统自带的php了,<strong>通过Homebrew自行安装的php没了,没了</strong>!</p>
<p>没有了再装就是了,所以,我很快使用<code class="language-plaintext highlighter-rouge">brew install php54</code>重装了php5.4,然后,居然还是找不到,但是,我在<code class="language-plaintext highlighter-rouge">/usr/local/Cellar/php54/</code>文件夹下找到了5.4.32和5.4.35两个版本,原来5.4.32一直都在,只是我没去找而已!具体文件如下图所示:</p>
<p><img src="http://blog2014.qiniudn.com/12-php-fpm.jpg" alt="php-fpm" /></p>
<p>然后,我运行</p>
<blockquote>
<p>php -v</p>
</blockquote>
<p>显示版本是<em>5.4.35</em>,可是我运行<code class="language-plaintext highlighter-rouge">sudo php-fpm</code>,然后写了个页面输出 phpinfo() 却看到的是<em>5.4.32</em>,晕了。现在还没明白,这里为什么是这样,有高手看到,还请告知,不胜感激。</p>
<p>然后,我到5.4.32目录下直接运行php-fpm:</p>
<p><img src="http://blog2014.qiniudn.com/12-libicui18n.53.dylib.jpg" alt=""/usr/local/opt/icu4c/lib/libicui18n.53.dylib" doesn't exist" /></p>
<p>居然报错,放Google,搜到<a href="https://github.com/Homebrew/homebrew-php/issues/1421">答案</a>,按照提示操作,如上图所示,再次,启动,Done。哈哈,一切正常了,至少看起来是这样。</p>
<p>然后,我运行</p>
<blockquote>
<p>php -v</p>
</blockquote>
<p>居然是5.5.19,然后运行
php info.php</p>
<p>报错:</p>
<blockquote>
<p>dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.53.dylib</p>
</blockquote>
<p>上网搜了下,估计是两个php 5.4引起的,所以,先删除一个再说,反正多一个也没用,再试下。运行</p>
<blockquote>
<p>brew remove php54</p>
</blockquote>
<blockquote>
<p>php info.php</p>
</blockquote>
<p>搞定,至少不再报错。可是查看php版本,依然是5.5.19,既然Mac下会把Homebrew安装的程序链接到<code class="language-plaintext highlighter-rouge">/usr/local/bin</code>下,所以,我去<code class="language-plaintext highlighter-rouge">/usr/local/bin</code>下查看,但没有发现php,所以,自己在/usr/local/bin 目录下搞了个软链接</p>
<blockquote>
<p>ln -s ../Cellar/php54/5.4.32/bin/php php</p>
</blockquote>
<p>然后,执行</p>
<blockquote>
<p>cd ~</p>
</blockquote>
<blockquote>
<p>source .profile</p>
</blockquote>
<blockquote>
<p>php -v</p>
</blockquote>
<p>5.4.32回来了!</p>
<h1 id="--4总结"><a id="summary"> 4.总结</a></h1>
<p>折腾了大半天,系统暂时好了,那么,问题来了:</p>
<ol>
<li>升级前是否该备份?虽然周围小伙伴都不备份,但我觉得还是备份吧,不然,各种硬盘里装那么多片也不能当干粮,何不给备份让点空间?</li>
<li>在没有完全搞清楚原因的情况下,为什么要照抄池大大的解决方案?实际上,据其他小伙伴说,升级完系统,直接<code class="language-plaintext highlighter-rouge">brew update, brew upgrade</code>就搞定了,压根儿不用折腾。哎,可我为什么会倒腾出这事儿呢?还不是因为池大大是Mac界的偶像级人物,自己认为照抄就可以了,但实际上,池大大升级时,Yosemite都还没正式放出来,那会儿出的问题都不是我们这些屁民会遇到的,一个半月时间,苹果早修复了,不然,人家还能是苹果?还得变化的看待问题,Too young, too simple.</li>
<li>菜鸟,活该。自己以前都用集成环境搞定了,甚至重来没用过nginx,刚开始折腾,对nginx + php + mysql 的架构还不熟悉,自己配置环境的能力还太弱,这方面得多研究研究,即使这次把环境搞定了,但还是有很多问题没搞清楚,努力吧,少年!</li>
</ol>
读薄《代码整洁之道》—— Clean Code
2013-12-18T22:22:41+00:00
http://www.readingnotes.site/posts/cleanCode
<p>每个想写出优美代码的程序员都应该好好读读《Clean Code》。</p>
<p>注:因为读的是英文版Clean Code,本文有些翻译可能与中文版不一致,我会在文中适当注明英文。</p>
<h1 id="目录">目录</h1>
<ul>
<li><a href="#brief">综述</a></li>
<li><a href="#meaningfulName">有意义的命名</a></li>
<li><a href="#function">函数</a></li>
</ul>
<h1 id="--1-综述-"><a id="brief"> 1. 综述 </a></h1>
<p>糟糕的代码总让人深恶痛绝,使团队生产力下降,延缓工作进度,在竞争激烈的互联网时代,甚至能毁掉公司。</p>
<p>需求变化背离初期设计,紧张的进度,愚蠢的经理,苛求的用户……我们找了太多理由,究竟为什么好代码会这么快变质成糟糕的代码?其实,不怨别人,都是我们自己,自作自受,我们自己太不专业!</p>
<p>什么是整洁的代码?不同的程序员有不同的理解,但可以归纳出一些共同点。比如优雅、高效、容错、简单、直接、没有重复……那么,怎么才能写出整洁的代码呢?写整洁的代码,需要遵循大量的小技巧,还需要贯彻刻苦实践,习得“整洁感”。下面,对作者提到的技巧做一些总结。</p>
<h1 id="-2-有意义的命名"><a id="meaningfulName"> 2. 有意义的命名</a></h1>
<h2 id="21-使用名副其实的名字">2.1 使用名副其实的名字</h2>
<p>标识符名字必须能够反映作者意图,如果一个标识符需要注释,那么,这个标识符命名就没能反映作者意图,需要进行修改。</p>
<h2 id="22-避免误导信息">2.2 避免误导信息</h2>
<p>需要避免多意词,比如,缩略词常常会带来误导;单词拼写差异很小的情况也应该避免,如ControllerForEfficientHandlingOfString与ControllerForEfficientStorageOfString,这给阅读代码带来多大干扰;最后,大写字母O、数字0,小写字母l和数字1应该尽量避免,它们实在太像了。</p>
<h2 id="23-使用有意义的区分">2.3 使用有意义的区分</h2>
<p>添加数字编号或干扰词来区分标识符可不算明智的做法。如果标识符不同,那么,他们就得有不同的含义,举例来说:</p>
<blockquote>
<p>char * strcpy(char a1[], char a2[])</p>
</blockquote>
<blockquote>
<p>char * strcpy(char destination[], char source[])</p>
</blockquote>
<p>二者相比,第二种写法显然更clean。</p>
<p>再说干扰词(noise words),干扰词是另一个无意义的区分,是多余的。举例来说,有个class叫product,然而,可能还有另外两个类,分别叫productInfo和productData,这里,Info和Date即是干扰词,这种命名会在后期带来不必要的麻烦。</p>
<h2 id="24-使用便于读的名字">2.4 使用便于读的名字</h2>
<p>编程是一项社会化的活动,编程要便于交流。举例来说,有个变量名叫“BCR3cnt”,这会给沟通带来多大不便啊</p>
<h2 id="25-使用便于搜索的名字">2.5 使用便于搜索的名字</h2>
<p>单个字母、数字常量之类的都有一个问题:它们不便于搜索。想象一下,如果你在当前目录下使用命令进行搜索,简直就是灾难</p>
<blockquote>
<p>grep -rn “e” .</p>
</blockquote>
<blockquote>
<p>grep -rn “7” .</p>
</blockquote>
<p>所以,单个字母仅可以用来在小作用域做局部变量,名字长度应该和作用域的大小相关。</p>
<h2 id="26-不要编码avoid-encoding">2.6 不要编码(Avoid Encoding)</h2>
<p>匈牙利标记法、成员前缀并不会带来便利,接口前不要加前缀“I”表示是接口,接口的实现前也不用加前缀“Imp”。最后,对于单个字母变量,需要避免心理映射(Mental Mapping),单个字母作为循环计数器并没有不妥,但用作它用时,程序员很容易将其映射到某具体的概念,如果因为变量”a”和变量”b”已经用了,就给新定义变量命名为”c”,这简直太难以理解了。</p>
<h2 id="27-类名与方法名">2.7 类名与方法名</h2>
<p>类名和对象名应该用名词或者名词短语,而方法名应用动词或动词短语</p>
<h2 id="28-其它">2.8 其它</h2>
<ul>
<li>不要卖萌</li>
</ul>
<p>如果标识符名字很诙谐,那么,只有明白程序员的幽默的人在记得这个玩笑的时间内能看懂;另外,不要使用方言、俚语等。总之,Choose clarity over entertainment value. And Say what you mean. Mean what you say.</p>
<ul>
<li>每个意思只选一个词</li>
</ul>
<p>为每个概念选一个词并一直使用,名字应该唯一,并在使用中保持一直。举例来说,如果有fetch、get和retrieve三个方法,那么,调用方法时,自会增加许多不必要的麻烦。</p>
<ul>
<li>避免一语双关</li>
</ul>
<p>要避免一词多意,代码要易读,而且很多时候是快速略读(quick skim),如果一次多意,那么在读代码过程中就需要去核实究竟用的是哪个意思。</p>
<ul>
<li>使用解决方案领域的名字</li>
</ul>
<p>阅读程序的基本都是程序员,他们不一定会问题领域很了解,所以,尽可能的使用CS的专业词汇。只有当没有程序员的说法时,才使用问题域的名字。最后,上下文语境也会有所帮助。</p>
<h2 id="29-总结">2.9 总结</h2>
<p>好的命名需要好的描述技巧和一些文化背景,这不是技巧、商业和管理问题,所以,大部分程序员并不愿学习如何做好命名工作。但应该果断的做这件事,虽然短期内会付出一些精力,但收获会很大。</p>
<h1 id="3-函数-"><a id="function">3. 函数 </a></h1>
<p>这章主要是函数编写方面的技巧,它能让函数更短小、命名合理、更友好的组织。</p>
<h2 id="31-小">3.1 小</h2>
<p>使用小函数。80年代,程序员常说函数不应该超过一屏,而且,那时候的屏幕仅能显示24行,每行80字符,虽然现在的屏幕更大,编辑器也更智能,但函数也应该尽可能的小。如果需要上下移动眼珠才能扫视完一个函数,函数就可能太长;如果需要移动头部才能扫视完一个函数,那么,这个函数一定太长;如果需要滚动屏幕才能才能完整显示函数,那么,赶紧重构。</p>
<h2 id="32-空格与缩进">3.2 空格与缩进</h2>
<p>正确的使用空格和良好的缩进可以使代码更易读。其次,if、else、while等语句应该只占一行,如果太长,可以在这些语句里使用函数调用,一是使函数变短,二是函数调用能很好的说明作者意图。最后,这条规则也说明,函数里层次不应太多,也就是说缩进最多两层。</p>
<h2 id="33-只做一件事">3.3 只做一件事</h2>
<blockquote>
<p>Functions should do one thing. They should do it well. They should do it only.</p>
</blockquote>
<p>函数只做一件事,上升一个层级,那么,类也应该封装一种事物,若函数做了不只一件事,或类封装了不只一种事物,就是权责不清。不过,对于“一件事”的理解,需要大量实践经验。</p>
<p>那么,如何定义”One thing”?可以这样约定:如果一个函数下所有步骤都是相同等级的抽象,那么,就认为该函数只做一件事。如果一个函数里混合了各种层次的抽象,那么,很容易造成混乱,使人迷惑。就像破窗户理论,一旦细节和基本概念混淆,函数中的细节就会变得越来越多。</p>
<p>其次,需要自顶向下阅读代码。说的是整个文件的布局。每个函数下,应该是函数的下一层抽象函数,这样,阅读代码时可以自顶向下,而不用跳来跳去。</p>
<p>最后,是switch语句。switch语句通常都较长,而且,switch语句总是做多件事情。这可以采用抽象工厂的方式进行解决。</p>
<p>不过,对于“一件事”的理解,需要大量实践经验。</p>
<h2 id="34-使用描述性强的名字">3.4 使用描述性强的名字</h2>
<p>不要害怕使用长名字,也不要怕花太多时间在选择名字上,选择描述性强的名字可以极大简化脑海中的模块设计并有助于改进。</p>
<h2 id="35-函数参数">3.5 函数参数</h2>
<p>函数参数应该尽可能的少,理想情况下是没有参数,参数越多,越不易读;参数越多,越不容易测试;参数不应该用来做输出,输出应该使用函数返回值。</p>
<p>其次,标记参数,比如,给函数传递一个boolean参数要尽量不用(ugly)。首先,它使问题复杂化了;其次,这明显是做了两件事,违背了一个函数只做一件事的原则。可以在上一抽象级使用if语句来进行判断,或者根据一个全局变量来进行判断。</p>
<p>当函数参数较多时,可以把概念相关的变量打包为类或者结构。不仅减少参数数量,也使得参数的描述性增强。</p>
<p>为了使函数描述性更强,可以使函数名与参数形成“动词\/名词对”的形式,如write(name)。</p>
<h2 id="36-执行与查询分离command-query-separation">3.6 执行与查询分离(command query separation)</h2>
<p>一个函数只应该做一件事,要么执行一个操作,要么回答一个问题,不要两个都做。比如下面的函数:</p>
<blockquote>
<p>public boolean set(String attribute, String value)</p>
</blockquote>
<p>函数表示,如果没有attribute属性,返回false;如果有,将其值设置为value,并返回true。那么,在函数调用时,如:</p>
<blockquote>
<p>if (set(“username”,”lx”))…</p>
</blockquote>
<p>读者可能感到迷惑,”username”是以前就被设置为”lx”了还是表示设置是否成功呢?如果将查询和执行分开,就不会有这种疑惑,把上述代码改成:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">if</span> <span class="p">(</span><span class="n">attributeExists</span><span class="p">(</span><span class="s">"username"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">setAttribute</span><span class="p">(</span><span class="s">"username"</span><span class="p">,</span><span class="s">"lx"</span><span class="p">);</span></code></pre></figure>
<h2 id="37-使用异常不要用错误码">3.7 使用异常,不要用错误码</h2>
<p>当使用错误码时,调用函数必须马上处理;当使用异常时,异常处理与正常代码分离。但是,try/catch模块打乱了代码结构,将异常处理代码与正常代码混合,所以,最好将try/catch模块抽取出来,抽取成他们自己的函数调用。最好,错误/异常处理函数也应该只做一件事,所以,函数里应该只有try/catch块。一个常见异常处理如下所示:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"> <span class="k">public</span> <span class="kt">void</span> <span class="nf">delete</span> <span class="p">(</span><span class="n">Page</span> <span class="n">page</span><span class="p">)</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="n">deletePageAndAllReferences</span><span class="p">(</span><span class="n">page</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">){</span>
<span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="38-拒绝重复">3.8 拒绝重复</h2>
<blockquote>
<p>Duplication may be the root of all evil in software.</p>
</blockquote>
<p>许多原则和方法都是为了消除重复。引用的话已经说明了一切。</p>
<h2 id="39-总结">3.9 总结</h2>
<p>编程就像写作,一开始写得很糟糕,结构混乱,然后,需要修改、重构,直到满意。编程也一样,程序开始也很长很复杂,有太多缩进与嵌套,很长的参数列表,乱七八糟的命名,重复冗余的代码。然后,需要修改和重构代码,拆分函数,改变命名,消除冗余代码,重新编排文件,当然,还得通过所有单元测试。最后,按照前面的规则进行规范。没有人能从一开始就严格遵守规范,但一定能循序渐进地修改。</p>
<h1 id="4-注释-"><a id="comments">4. 注释 </a></h1>
<p>最近看CPABE的开源代码,痛苦……</p>
<p>但是,Robert大叔并没有强调注释的重要性,而是说“comments are, at best, a necessary evil”,如果代码表述得足够清楚,那么,就不需要注释,注释是对表达性不足的代码的补充。 为什么Robert大叔这么说?因为注释会<strong>撒谎</strong>,人们经常会修改代码,但经常不会一并修改相应的注释,而错误的注释比没有注释坏处更大,所以,尽管注释有时是必须的,但本书更多的会讨论减少注释。</p>
从源代码到可执行文件——编译全过程解析
2013-12-06T07:52:39+00:00
http://www.readingnotes.site/posts/262
<p>程序的生命周期从一个高级C语言程序开始,这种形式能够被人读懂,却不能被机器读懂,为了在系统上运行这个程序,该源程序需要被其他程序转化为一系列低级机器语言指令,然后将这些指令按照可执行目标程序的格式打包并以二进制磁盘文件形式存储起来。</p>
<p>在Linux系统下,可用以下指令完成源程序到目标程序的转化:</p>
<blockquote>
<p>gcc -o hello hello.c main.c</p>
</blockquote>
<p>gcc 编译器驱动程序读取源文件hello.c和main.c,经过预处理、编译、汇编、链接(分别使用预处理器、编译器、汇编器、链接器,这四个程序构成了编译系统)四个步骤,将其翻译成可执行目标程序hello。如下图所示:</p>
<p><img src="../../assets/img/2013/12/cc-process.jpeg" alt="编译流程" /></p>
<p>运行以下命令:</p>
<blockquote>
<blockquote>
<p>gcc –help</p>
</blockquote>
</blockquote>
<p>如下图所示,分别对应上图四个阶段:</p>
<p><img src="../../assets/img/2013/12/gcc-option.png" alt="gcc选项" /></p>
<h1 id="1示例程序">1.示例程序</h1>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"> <span class="c1">//main.c</span>
<span class="cp">#include<stdio.h>
</span> <span class="kt">void</span> <span class="nf">hello</span><span class="p">();</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">hello</span><span class="p">();</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//hello.c</span>
<span class="cp">#include<stdio.h>
</span> <span class="kt">void</span> <span class="nf">hello</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Hello world</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<h1 id="2预处理">2.预处理</h1>
<p>预处理器(CPP)根据源程序中以字符”#”开头的命令,修改源程序,得到另一个源程序,常以.i作为文件扩展名。修改主要包括#include、#define和条件编译三个方面。</p>
<p>可执行以下命令查看程序变化:</p>
<blockquote>
<p>gcc -o main.i -E main.c
gcc -o hello.i -E hello.c</p>
</blockquote>
<p>查看hello.i,如下图所示(main.i类似):</p>
<p><img src="../../assets/img/2013/12/hello.i.png" alt="hello.i" /></p>
<p>从上图可以看出,预处理只是对源文件进行了扩展,得到的仍然是C语言源程序。</p>
<h1 id="3-编译">3. 编译</h1>
<p>编译器(CCL)将经过预处理器处理得到的文本文件hello.i和main.i翻译成hello.s与main.s,其中包含了汇编语言程序,汇编语言程序以一种标准的文本格式确切描述一条低级机器语言指令。</p>
<p>运行以下命令进行编译:</p>
<blockquote>
<p>gcc -S main.i hello.i</p>
</blockquote>
<p>查看main.s和hello.s:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"> <span class="c1">//main.s</span>
<span class="p">.</span><span class="n">file</span> <span class="s">"main.c"</span>
<span class="p">.</span><span class="n">text</span>
<span class="p">.</span><span class="n">globl</span> <span class="n">main</span>
<span class="p">.</span><span class="n">type</span> <span class="n">main</span><span class="p">,</span> <span class="err">@</span><span class="n">function</span>
<span class="n">main</span><span class="o">:</span>
<span class="p">.</span><span class="n">LFB0</span><span class="o">:</span>
<span class="p">.</span><span class="n">cfi_startproc</span>
<span class="n">pushl</span> <span class="o">%</span><span class="n">ebp</span>
<span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">8</span>
<span class="p">.</span><span class="n">cfi_offset</span> <span class="mi">5</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span>
<span class="n">movl</span> <span class="o">%</span><span class="n">esp</span><span class="p">,</span> <span class="o">%</span><span class="n">ebp</span>
<span class="p">.</span><span class="n">cfi_def_cfa_register</span> <span class="mi">5</span>
<span class="n">andl</span> <span class="err">$</span><span class="o">-</span><span class="mi">16</span><span class="p">,</span> <span class="o">%</span><span class="n">esp</span>
<span class="n">call</span> <span class="n">hello</span>
<span class="n">movl</span> <span class="err">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="n">eax</span>
<span class="n">leave</span>
<span class="p">.</span><span class="n">cfi_restore</span> <span class="mi">5</span>
<span class="p">.</span><span class="n">cfi_def_cfa</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span>
<span class="n">ret</span>
<span class="p">.</span><span class="n">cfi_endproc</span>
<span class="p">.</span><span class="n">LFE0</span><span class="o">:</span>
<span class="p">.</span><span class="n">size</span> <span class="n">main</span><span class="p">,</span> <span class="p">.</span><span class="o">-</span><span class="n">main</span>
<span class="p">.</span><span class="n">ident</span> <span class="s">"GCC: (Ubuntu/Linaro 4.8.1-10ubuntu8) 4.8.1"</span>
<span class="p">.</span><span class="n">section</span> <span class="p">.</span><span class="n">note</span><span class="p">.</span><span class="n">GNU</span><span class="o">-</span><span class="n">stack</span><span class="p">,</span><span class="s">""</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span>
<span class="c1">//hello.s</span>
<span class="p">.</span><span class="n">file</span> <span class="s">"hello.c"</span>
<span class="p">.</span><span class="n">section</span> <span class="p">.</span><span class="n">rodata</span>
<span class="p">.</span><span class="n">LC0</span><span class="o">:</span>
<span class="p">.</span><span class="n">string</span> <span class="s">"Hello world"</span>
<span class="p">.</span><span class="n">text</span>
<span class="p">.</span><span class="n">globl</span> <span class="n">hello</span>
<span class="p">.</span><span class="n">type</span> <span class="n">hello</span><span class="p">,</span> <span class="err">@</span><span class="n">function</span>
<span class="n">hello</span><span class="o">:</span>
<span class="p">.</span><span class="n">LFB0</span><span class="o">:</span>
<span class="p">.</span><span class="n">cfi_startproc</span>
<span class="n">pushl</span> <span class="o">%</span><span class="n">ebp</span>
<span class="p">.</span><span class="n">cfi_def_cfa_offset</span> <span class="mi">8</span>
<span class="p">.</span><span class="n">cfi_offset</span> <span class="mi">5</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span>
<span class="n">movl</span> <span class="o">%</span><span class="n">esp</span><span class="p">,</span> <span class="o">%</span><span class="n">ebp</span>
<span class="p">.</span><span class="n">cfi_def_cfa_register</span> <span class="mi">5</span>
<span class="n">subl</span> <span class="err">$</span><span class="mi">24</span><span class="p">,</span> <span class="o">%</span><span class="n">esp</span>
<span class="n">movl</span> <span class="err">$</span><span class="p">.</span><span class="n">LC0</span><span class="p">,</span> <span class="p">(</span><span class="o">%</span><span class="n">esp</span><span class="p">)</span>
<span class="n">call</span> <span class="n">puts</span>
<span class="n">leave</span>
<span class="p">.</span><span class="n">cfi_restore</span> <span class="mi">5</span>
<span class="p">.</span><span class="n">cfi_def_cfa</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span>
<span class="n">ret</span>
<span class="p">.</span><span class="n">cfi_endproc</span>
<span class="p">.</span><span class="n">LFE0</span><span class="o">:</span>
<span class="p">.</span><span class="n">size</span> <span class="n">hello</span><span class="p">,</span> <span class="p">.</span><span class="o">-</span><span class="n">hello</span>
<span class="p">.</span><span class="n">ident</span> <span class="s">"GCC: (Ubuntu/Linaro 4.8.1-10ubuntu8) 4.8.1"</span>
<span class="p">.</span><span class="n">section</span> <span class="p">.</span><span class="n">note</span><span class="p">.</span><span class="n">GNU</span><span class="o">-</span><span class="n">stack</span><span class="p">,</span><span class="s">""</span><span class="p">,</span><span class="err">@</span><span class="n">progbits</span></code></pre></figure>
<h1 id="4汇编">4.汇编</h1>
<p>汇编器(AS)将hello.s和main.s翻译成机器语言指令,并打包成可重定位目标程序,一般以.o为文件扩展名。可重定位目标程序是二进制文件,它的字节编码是机器语言指令而不是字符。</p>
<p>运行以下指令可得到重定位目标程序main.o和hello.o:</p>
<blockquote>
<p>gcc -c main.s hello.s</p>
</blockquote>
<p>用文本编辑器打开main.o和hello.o发现文件是乱码,因为此时已经是二进制文件。</p>
<h1 id="5链接">5.链接</h1>
<p>链接程序(LD)将main.o和hello.o以及一些其他必要的目标文件组合起来,创建可执行目标文件。</p>
<blockquote>
<p>gcc -o hello main.o hello.o</p>
</blockquote>
<p>得到可执行程序hello.</p>
<p>在终端运行./hello,程序加载并运行,</p>
My first blog with jekyll
2013-12-06T00:00:00+00:00
http://www.readingnotes.site/posts/firstblog
<h1 id="博客由来">博客由来</h1>
<p>年中在<a href="http://www.homezz.com/">homezz</a>购买了vps,在<a href="http://www.iisp.com/">耐思尼克</a>购买域名,搭建了Wordpress博客,日常管理博客需要花费很多精力,而且,一直想尝试用git+github+markdown+jekyll的方式来写作,故搭建此博客。</p>
<h1 id="搭建过程">搭建过程</h1>
<ol>
<li>apt-get安装rubygems之后,gem install jekyll安装jekyll,并用同样的gem命令安装directory_watcher、liquid、open4、maruku、classifier,rdiscount这几个包。</li>
<li>github上建立项目“lxWei.github.com”,注意将“lx.Wei”换成自己的用户名。</li>
<li>git clone项目到本地。</li>
<li>为简单起见,下载别人的jekyll模板框架,我用的是<a href="https://github.com/Hawstein/hawstein.github.com">Hawstein</a>的,另外,<a href="https://github.com/mojombo/jekyll/wiki/sites">https://github.com/mojombo/jekyll/wiki/sites</a>上有很多优秀的模板框架。</li>
<li>用下载的模板框架覆盖掉自己的项目,切记,一定要删除下载的.git文件夹。</li>
<li>改动框架中的内容,主要是修改个人信息和文章,如果个人懂前端技术,可以自己修改页面。</li>
<li>在_post文件夹下添加yy-mm–dd-title.md格式命名的文件。写完之后push到github上即可。</li>
<li>Congratulations!</li>
</ol>
<h1 id="迁移wordpress的blog">迁移wordpress的blog</h1>
<ul>
<li>sudo apt-get install python-yaml python-beautifulsoup python-html2text。</li>
<li>git clone git://github.com/thomasf/exitwp.git。</li>
<li>将wordpress导出的xml文件放到exitwp下的wordpress-xml文件夹下。</li>
<li>python exitwp。此时,可能报错,需要安装BeautifulSoup。</li>
<li>生成的markdown文件在build文件夹下,拷贝到博客的_post文件夹下,提交即可。</li>
</ul>
<h1 id="参考资料">参考资料</h1>
<ul>
<li><a href="http://ellochen.github.io/2013/03/写作环境搭建(git+github+markdown+jekyll)/">写作环境搭建(git+github+markdown+jekyll)</a></li>
<li><a href="http://www.yangzhiping.com/tech/git.html">Git与Github入门资料</a></li>
<li><a href="http://wowubuntu.com/markdown/basic.html">Markdown: Basics (快速入门)</a></li>
<li><a href="https://github.com/Hawstein/hawstein.github.com">Hawstein的blog</a></li>
<li><a href="http://yishanhe.net/exitwp-convert-wordpress--markdown/">exitwp</a>。</li>
</ul>
读《大规模Web服务开发技术》
2013-07-31T14:38:41+00:00
http://www.readingnotes.site/posts/%e8%af%bb%e3%80%8a%e5%a4%a7%e8%a7%84%e6%a8%a1web%e6%9c%8d%e5%8a%a1%e5%bc%80%e5%8f%91%e6%8a%80%e6%9c%af%e3%80%8b
<blockquote>
<p>问:大规模web服务环境中,由于I/O负载问题,服务器变得很慢,怎么办?
答:加服务器!!!</p>
</blockquote>
<p>如果你的答案跟上边一样,那么,极度推荐你读读这本书。</p>
<h1 id="1-适读人群">1. 适读人群</h1>
<p>如书的前言所言,这本书的内容主要来自Hatena株式会社为学生们举行的暑期实习课程,因此,形式以讲义为主,风格与一般技术读物不同。全书内容甚广,涵盖性能优化、分布式、算法、系统架构、硬件以及经济成本等,同时,书中使用了大量Hatena的实例,实战性超强。</p>
<p>同时,本书来源于为学生举办的暑期实习课程,也就限定了本书并不会讲太多高深的知识,所以,本书更多的是面向几乎没有大规模web服务开发经验的学生,内容较为浅显,不适合经验丰富的大规模web服务开发人员,但对缺乏经验的学生来说确实难得的好书,使学生在几天时间内学到系统的大规模服务开发所需知识。</p>
<p>一句话:这本书极度适合本人这种没有大规模web开发经验而又有志向这方面发展的菜鸟学生。本书扉页所书“Web开发人员必读”并没有言过其实。</p>
<h1 id="2-大规模数据处理难点">2. 大规模数据处理难点</h1>
<p>本书在介绍大规模web服务开发定位后,首先介绍了大规模数据处理难点内存和磁盘,可扩展性的要点。指出了大规模Web服务中的核心问题——内存和磁盘的速度差异,同时,这也是考虑可扩展性的重点。</p>
<p>既然要考虑可扩展性,就要考虑是scale out还是scale up,现在普遍采用的是scale out,即横向扩展。扩展需要考虑两个要点——I/O负载和CPU负载,在大规模Web服务中,应用程序主要进行计算,包括接受HTTP请求、查询数据库,将查询结果加工成HTML并发送给客户端等,基本只消耗CPU;相反,数据库服务器需要相对较多的I/O操作。对于应用服务器,采用负载均衡器(load balancer)可以比较容易的实现扩展;但是,对于数据库服务器来说,数据库的可扩展性就相当困难了,大规模环境下产生I/O负载的服务器本来就很难分散,再加上频繁的磁盘I/O,很容易导致服务器变慢,这就是问题的本质。</p>
<h1 id="3-大规模数据处理基础知识">3. 大规模数据处理基础知识</h1>
<p>本书主要讨论了三点</p>
<ol>
<li>
<p>操作系统的缓存。</p>
</li>
<li>
<p>以分布式为前提的RDBMS的使用。</p>
</li>
<li>
<p>大规模环境中算法和数据结构的使用。</p>
</li>
</ol>
<h2 id="31-操作系统的缓存">3.1 操作系统的缓存</h2>
<p>究其原因,还是磁盘太慢。内存的速度是磁盘的105-106倍,所以,可以考虑使用内存以减少磁盘磁盘访问。这时候,操作系统很配合的提供了缓存机制,比如Linux的页面缓存机制等,所以,一个有效方法产生了——增加内存以减少访问磁盘,从而降低I/O负载,再说,现在的内存已经白菜价了,加内存已经不是难事(本人实习时所用测试机都是128G内存),但对于经济状况不好的公司,还是要注意下经济成本,呵呵…</p>
<p>但是,即使增加内存也不一定能全部缓存,这时候就要扩展到多台服务器了,但是,单纯增加服务器数量并不能解决问题,这时,就要利用局部性原理了,利用局部性进行分区(Partitioning),实现分布式。</p>
<h2 id="32-以分布式为前提的rdbms的使用数据库的横向扩展">3.2 以分布式为前提的RDBMS的使用——数据库的横向扩展</h2>
<p>首先,结合上面所讲,要灵活应用操作系统的缓存,尽量保持数据量小于物理内存,同时,还要主要表结构的设计,当数据量大了以后,每个字段多占一个字节最后产生的数据量也可能上G,这时候可以考虑一些表的分割等,而这部分在数据库课程上可没有学过。</p>
<p>其次,一定要注意索引。在MySQL中,索引不仅改善复杂度,而且,由于索引针对磁盘结构进行优化,能改善磁盘的寻道次数。</p>
<p>最后,是数据库的分布式。MySQL提供了replication功能,使用master/slave架构,slave接受查询请求,master结构更新请求,各slave进行polling,将master写入的内容更新到自身。这种情况下,slave机器可以扩展,但是,master却无法扩展,考虑到web应用程序90%以上都是查询操作,写操作较少的特点,master很少成为瓶颈。但是,偶然也会有应用进行较多的写操作,这时候,就要对表进行分割了,或者不再使用RDBMS。表的分割也会带来一些不良后果,如运维困难,故障率上升等。</p>
<h2 id="33-大规模环境中算法和数据结构的使用">3.3 大规模环境中算法和数据结构的使用</h2>
<p>前两点主要是运维方面,这点主要是应用程序开发。开发过程中要注意数据结构的选择和算法,如特殊用途的索引、压缩编程等。本书在这里结合Hatena介绍了大量例子,包括Hatena关键字链接技术、文章分类技术和全文搜索技术等,理论结合实际,可以深究。</p>
<h1 id="4-大规模数据处理的服务器基础设施">4. 大规模数据处理的服务器/基础设施</h1>
<p>大规模数据处理的服务器/基础设施要注意三个重点</p>
<ol>
<li>
<p>低成本、高效率;</p>
</li>
<li>
<p>重视可扩展性、响应性方面的设计;</p>
</li>
<li>
<p>基础设置要重视开发速度,应为服务提供灵活的资源。</p>
</li>
</ol>
<h2 id="41-可扩展性">4.1 可扩展性</h2>
<p>大规模的Web服务无法在一台服务器上运行,所以,各层都需要能灵活扩展。应用服务器可以很容易地扩展,读操作多的分布式可以较容易扩展,写操作多的分布式扩展较难。</p>
<p>在进行扩展时,需要用到一些工具,掌握系统的负载,如平均负载(load average)、内存和CPU相关信息,再根据用途进行调优。</p>
<h2 id="42-冗余性">4.2 冗余性</h2>
<p>应用程序服务器的冗余性跟可扩展性思路相同,基本就是增加服务器,服务器增加后,即使一两台停机,也能保证充分的处理能力。这里需要负载均衡器实现失败转移(Failover)让故障服务器自动下线、失败恢复(Failback)让恢复正常的服务器再次上线。</p>
<p>对于数据库服务器,同样需要增加树立,master和slave都需要冗余化,master的冗余化比较困难,可以考虑使用Multi-master等方式。</p>
<p>对于存储服务器,主要保存图像等媒体文件等,可以采用一些分布式的文件系统。</p>
<h2 id="43-系统稳定性">4.3 系统稳定性</h2>
<p>为了保证系统稳定性,有多个方面需要权衡,如资源利用率、速度等,同时,保持系统的冗余性也有助于系统稳定性。</p>
<p>系统的不稳定性因素有很多,如功能增加、内存泄露、用户访问方式、数据量增加、硬件故障等。为了维护系统稳定,需要维持适当余量,如CPU和内存的使用上限设置为70%;还要消灭不稳定性因素,如降低SQL负载,减少内存泄露和发生异常行为时的自律控制等。</p>
<h2 id="44-提高效率">4.4 提高效率</h2>
<p>使用虚拟化技术能提高主机的及程度,提高整体资源利用率。引入虚拟化技术可带来良好的扩展性、性价比和高可用性,现在主要的虚拟化技术包括VMWare、Xen、Virtual PC和Parallels等。对于CPU空闲的机器,可添加Web服务器;对I/O空闲的,可添加数据库服务器;对内存空闲的,可添加缓存服务器;同时,要避免资源消耗倾向相同且负载较高的同类服务器在一起组合,有效提高硬件资源利用率。</p>
<p>虚拟化也会带来一些性能损耗,如CPU、内存、I/O,特别是网络性能会下降一半左右。所以,虚拟化技术并不是万能的。</p>
<h2 id="45-硬件">4.5 硬件</h2>
<p>由于摩尔定律,处理器性能提升迅速,同时,内存和硬盘成本下降。可以以虚拟化为前提,有效利用廉价硬件。现在,也可以考虑SSD。</p>
<h2 id="46-web服务增长限制">4.6 Web服务增长限制</h2>
<p>对于web服务的增长并不是无限的,如超过1Gbps(路由器性能来看约30万pps)是PC路由器的极限,超过500台主机会带来子网极限,全球化时代,一个数据中心是不够的。</p>
<h1 id="5-其它技术">5. 其它技术</h1>
<p>最后,本书简单介绍了构建Web服务所需实践技术,如作业队列系统、存储方式选择、缓存系统以及计算集群等。这部分介绍较简单,只做了解。</p>
<h1 id="6-写在最后">6. 写在最后</h1>
<p>本书对云计算、大数据处理如Hadoop等介绍较少,而云计算、大数据等在当今大规模Web服务以及其它很多方面应用越来越广,这应该是很重要的一部分,但全书介绍较少,可能是由于本书起源的原因,但还是感觉很遗憾。</p>
常用排序算法总结
2013-07-25T14:43:24+00:00
http://www.readingnotes.site/posts/%e5%b8%b8%e7%94%a8%e6%8e%92%e5%ba%8f%e7%ae%97%e6%b3%95%e6%80%bb%e7%bb%93
<h1 id="1-选择排序">1. 选择排序</h1>
<hr />
<p>原理比较简单,每次循环选择最小的未排序元素将其放到未排序的最前或每次循环选择最大的未排序元素将其放到未排序的最后,进行N次循环即可。代码如下:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="kt">void</span> <span class="nf">selectionSort</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">r</span><span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">min</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">j</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="n">j</span> <span class="o"><=</span> <span class="n">r</span><span class="p">;</span> <span class="n">j</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="n">min</span><span class="p">])</span> <span class="p">{</span>
<span class="n">min</span> <span class="o">=</span> <span class="n">j</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">min</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="11-适用场景">1.1 适用场景</h2>
<p>对于元素较大,关键字较小的文件,选择排序通常比其它算法移动数据较少,应该选择选择排序。</p>
<h2 id="12-效率">1.2 效率</h2>
<p>比较次数:N2/2,交换次数:N。时间复杂度:O(n2),空间复杂度:O(1)。</p>
<h2 id="13-缺点">1.3 缺点</h2>
<p>运行时间对文件中已有序的部分依赖较少,对已排好序的文件或各元素都相同的文件排序所花时间与对随机排列的文件排序所花时间基本一样。</p>
<h1 id="2-插入排序">2. 插入排序</h1>
<hr />
<p>原理比较简单,代码如下:<!-- more --></p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="kt">void</span> <span class="nf">insertionSort</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">//设置观察哨</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span> <span class="n">i</span> <span class="o">></span> <span class="n">l</span><span class="p">;</span> <span class="o">--</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="p">{</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">l</span><span class="o">+</span><span class="mi">2</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">r</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="n">item</span> <span class="n">v</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="c1">//后移</span>
<span class="k">while</span> <span class="p">(</span><span class="n">v</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="p">{</span>
<span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="o">--</span> <span class="n">j</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//插入</span>
<span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>注意:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 从后往前,减少比较次数,而且不用交换,直接用a[i-1]覆盖掉a[i]即可。
2. 添加哨兵,数组a[0]位置存放最小值作为哨兵,这样可以不用if语句判断是否到达最前位置,能有效提高效率。
</code></pre></div></div>
<h2 id="21-特点">2.1 特点</h2>
<p><strong>运行时间与输入文件数据的原始排列顺序密切相关</strong>。关键字已经排好序或基本排好序,插入排序比选择要快。</p>
<h2 id="22-效率">2.2 效率</h2>
<p>平均情况,比较次数是N2/4,交换次数为N2/4。最坏情况是平均情况的2倍</p>
<h1 id="3-冒泡排序">3. 冒泡排序</h1>
<hr />
<p>直接看代码:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="kt">void</span> <span class="nf">bubbleSort</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">r</span><span class="p">;</span> <span class="o">++</span> <span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">j</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span> <span class="n">j</span> <span class="o">></span> <span class="n">i</span><span class="p">;</span> <span class="o">--</span> <span class="n">j</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="p">{</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="31-优化策略">3.1 优化策略:</h2>
<ol>
<li>当其中一步已经没有进行任何交换操作,即文件已经排好序,可以提前终止程序,可以提高冒泡排序的运行效率,但仍比不上插入排序。</li>
</ol>
<h2 id="32-效率">3.2 效率</h2>
<p>平均情况下比较次数:N2/2,交换次数:N2/2。最坏情况与平均情况一样。</p>
<h1 id="4-希尔排序">4. 希尔排序</h1>
<hr />
<p>插入排序效率低的原因是所执行的交换操作涉及到临近的元素,使元素每次只能移动一位。希尔排序通过允许非相邻元素进行交换来提高执行效率,是插入排序的扩展。即使对于大文件,希尔排序也有<strong>较高的运行效率</strong>,且代码简单容易执行,<strong>应用较广</strong>。</p>
<p>看代码:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="kt">void</span> <span class="nf">shellSort</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">h</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">//设置步长序列</span>
<span class="c1">//该步长序列由Knuth提出</span>
<span class="k">for</span> <span class="p">(</span><span class="n">h</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">h</span> <span class="o"><=</span> <span class="p">(</span><span class="n">r</span><span class="o">-</span><span class="n">l</span><span class="p">)</span><span class="o">/</span><span class="mi">9</span><span class="p">;</span> <span class="n">h</span><span class="o">=</span> <span class="mi">3</span><span class="o">*</span><span class="n">h</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="p">;</span><span class="c1">//空语句</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span> <span class="p">;</span> <span class="n">h</span> <span class="o">></span> <span class="mi">0</span><span class="p">;</span> <span class="n">h</span> <span class="o">/=</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">l</span> <span class="o">+</span> <span class="n">h</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">r</span><span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="n">item</span> <span class="n">v</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="k">while</span> <span class="p">(</span><span class="n">j</span> <span class="o">>=</span> <span class="n">l</span><span class="o">+</span><span class="n">h</span> <span class="o">&&</span> <span class="n">v</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="n">h</span><span class="p">])</span> <span class="p">{</span>
<span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="n">h</span><span class="p">];</span>
<span class="n">j</span> <span class="o">-=</span> <span class="n">h</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>步长序列能影响代码执行效率,本例采用Knuth在1969年提出的1、4、13、40……</p>
<h1 id="5-快排">5. 快排</h1>
<hr />
<p>如果一个应用程序不能确定使用快排是否正确,就可以使用希尔排序,对大型文件,快排的性能大概是希尔排序的5-10倍。</p>
<p>代码:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="kt">int</span> <span class="nf">partition</span><span class="p">(</span><span class="n">item</span> <span class="o">*</span><span class="n">arr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">arr</span> <span class="o">||</span> <span class="n">r</span> <span class="o"><</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"invalid input</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//选择最后一个元素作为划分元素</span>
<span class="n">item</span> <span class="n">tag</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="n">r</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">l</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">while</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="o">++</span><span class="n">i</span><span class="p">]</span> <span class="o"><</span> <span class="n">tag</span><span class="p">)</span> <span class="p">{</span>
<span class="p">;</span>
<span class="p">}</span>
<span class="k">while</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="o">--</span><span class="n">j</span><span class="p">]</span> <span class="o">></span> <span class="n">tag</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">j</span> <span class="o">==</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">>=</span> <span class="n">j</span><span class="p">)</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
<span class="p">}</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">r</span><span class="p">]);</span>
<span class="k">return</span> <span class="n">i</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">quickSort</span><span class="p">(</span><span class="n">item</span> <span class="o">*</span><span class="n">arr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">r</span> <span class="o"><=</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">partition</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="n">quickSort</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="n">quickSort</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span> <span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>这是最基本最简单的快排。</p>
<h2 id="51-效率">5.1 效率</h2>
<p>最坏情况下大约使用N2/2次比较,这对于大文件是不可接受的。平均情况下2NlgN次比较。</p>
<p>同时,基于递归的空间复杂度O(N)</p>
<h2 id="52-优化">5.2 优化</h2>
<h3 id="521-空间效率非递归实现">5.2.1 空间效率——非递归实现</h3>
<p>非递归实现减少了函数调用与返回,同时,能减少部分栈空间使用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//非递归程序,节省空间,不能改进时间开销
</code></pre></div></div>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">node</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">l</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">r</span><span class="p">;</span>
<span class="p">}</span><span class="n">NODE</span><span class="p">;</span>
<span class="n">NODE</span> <span class="n">stack</span><span class="p">[</span><span class="n">STACKSIZE</span><span class="p">];</span>
<span class="kt">void</span> <span class="nf">quickSortNoRecursion</span><span class="p">(</span><span class="n">item</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">data</span> <span class="o">||</span> <span class="n">r</span> <span class="o"><</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">pointer</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">l</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">r</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
<span class="o">++</span> <span class="n">pointer</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">pointer</span><span class="p">)</span> <span class="p">{</span>
<span class="n">l</span> <span class="o">=</span> <span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">l</span><span class="p">;</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">r</span><span class="p">;</span>
<span class="o">--</span> <span class="n">pointer</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">r</span> <span class="o"><=</span> <span class="n">l</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">partition</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="c1">//先让元素多的子数组进栈,先解决栈小的,有利于节省空间</span>
<span class="k">if</span><span class="p">(</span> <span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="n">l</span><span class="p">)</span> <span class="o">></span> <span class="p">(</span><span class="n">r</span><span class="o">-</span><span class="n">i</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">l</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">r</span> <span class="o">=</span> <span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="o">++</span> <span class="n">pointer</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">l</span> <span class="o">=</span> <span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">r</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
<span class="o">++</span> <span class="n">pointer</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">l</span> <span class="o">=</span> <span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">r</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
<span class="o">++</span> <span class="n">pointer</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">l</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span>
<span class="n">stack</span><span class="p">[</span><span class="n">pointer</span><span class="p">].</span><span class="n">r</span> <span class="o">=</span> <span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="o">++</span> <span class="n">pointer</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="522时间效率">5.2.2 时间效率</h3>
<p>1、 划分元素的选择</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 原理:快排的基本实现选择data[r]作为划分元素,这种情况下,如果数组已经有序,那么,排序时间将达到N2,可针对此进行优化。
2. 优化方式
* 采用随即元素作为划分元素。
* 从数组中取三个元素,如data[l]、data[(l+r)/2]、data[r],取中间元素作为划分元素。这使得快排的最坏情况基本不可能出现。
</code></pre></div></div>
<p>2、小数组处理</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 原理:递归过程会产生很多小的数组,即r-l比较小,会占用大量空间和时间,可以针对此对其进行改进。
2. 优化方案:
* quicksort函数的if(r<=l)改为 > if(r - l <= M) { > insertionSort(data,l,r); > }
</code></pre></div></div>
<p>其中, M表示一个小正整数,如5-25。
* quicksort函数的if(r<=l)改为</p>
<blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> if(r - l <= M) {
return ;
}
</code></pre></div> </div>
</blockquote>
<blockquote>
<p>然后在调用quicksort的函数之后调用一次insertionSort。在数组基本有序的情况下,调用插入排序,可在线性时间内完成排序。</p>
</blockquote>
<p>3、重复元素处理</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 原理:基本的快排每次递归只能确定一个元素(划分元素)的最终位置,待排序数组中等于划分元素的其它元素的应该与划分元素相邻,可以利用这点进行优化,这样可以大幅度提高效率。代码如下:
</code></pre></div></div>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="kt">void</span> <span class="nf">quickSortRepeat</span><span class="p">(</span><span class="n">item</span> <span class="n">arr</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">arr</span> <span class="o">||</span> <span class="n">r</span> <span class="o"><=</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">l</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">p</span> <span class="o">=</span> <span class="n">l</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">q</span> <span class="o">=</span> <span class="n">r</span><span class="p">;</span>
<span class="n">item</span> <span class="n">v</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="n">r</span><span class="p">];</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">while</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="o">++</span><span class="n">i</span><span class="p">]</span> <span class="o"><</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
<span class="p">;</span><span class="c1">//空语句</span>
<span class="p">}</span>
<span class="k">while</span><span class="p">(</span><span class="n">v</span> <span class="o"><</span> <span class="n">arr</span><span class="p">[</span><span class="o">--</span><span class="n">j</span><span class="p">])</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">j</span> <span class="o">==</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">>=</span> <span class="n">j</span><span class="p">)</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
<span class="k">if</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
<span class="n">p</span> <span class="o">++</span><span class="p">;</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">p</span><span class="p">],</span> <span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">==</span> <span class="n">v</span><span class="p">)</span> <span class="p">{</span>
<span class="n">q</span> <span class="o">--</span><span class="p">;</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">q</span><span class="p">],</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">//设置划分元素</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">r</span><span class="p">]);</span>
<span class="n">j</span> <span class="o">=</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">i</span> <span class="o">++</span><span class="p">;</span>
<span class="c1">//将与划分元素相等的元素放到划分元素两边</span>
<span class="k">for</span><span class="p">(</span><span class="n">k</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span> <span class="n">k</span> <span class="o"><</span> <span class="n">p</span><span class="p">;</span> <span class="n">k</span> <span class="o">++</span><span class="p">,</span> <span class="n">j</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">k</span><span class="p">],</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">for</span><span class="p">(</span><span class="n">k</span> <span class="o">=</span> <span class="n">r</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="n">k</span> <span class="o">></span> <span class="n">q</span><span class="p">;</span> <span class="n">k</span> <span class="o">--</span><span class="p">,</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">k</span><span class="p">],</span><span class="o">&</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="n">quickSortRepeat</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span><span class="n">l</span><span class="p">,</span><span class="n">j</span><span class="p">);</span>
<span class="n">quickSortRepeat</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span><span class="n">i</span><span class="p">,</span><span class="n">r</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<h1 id="6-归并排序">6. 归并排序</h1>
<hr />
<blockquote>
<p>合并两个长度分别为M和N的有序数组,使得结果仍然的时间复杂度是…</p>
</blockquote>
<p>如果一个问题太大不好解决,那么,是否应该考虑分解成一个个小问题,对小问题分别求解,解决万小问题,合并起来,大问题是不是就解决了呢?</p>
<p>归并排序正是基于这两点!</p>
<h2 id="61-效率">6.1 效率</h2>
<p>归并排序的运行时间主要取决于输入关键字的个数,对关键字的顺序不太敏感(对比快排),无论对什么输入,对N个元素的文件的排序所需时间与NlgN成正比,没有最坏情况。</p>
<h2 id="62-缺点">6.2 缺点</h2>
<p>所需空间与N成正比。要克服这个缺点,会很复杂,而且开销较大,实际应用中并不处理。</p>
<h2 id="63-代码实现">6.3 代码实现</h2>
<h3 id="631-两路归并">6.3.1 两路归并</h3>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="cm">/**
* 合并两个有序数组,使结果仍然有序
*/</span>
<span class="kt">void</span> <span class="nf">merge</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">m</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">m</span> <span class="o"><</span> <span class="n">l</span> <span class="o">||</span> <span class="n">r</span> <span class="o"><</span> <span class="n">m</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">item</span> <span class="n">aux</span><span class="p">[</span><span class="n">NUM</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">m</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">></span> <span class="n">l</span><span class="p">;</span> <span class="n">i</span> <span class="o">--</span><span class="p">)</span> <span class="p">{</span>
<span class="n">aux</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">j</span> <span class="o">=</span> <span class="n">m</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="n">r</span><span class="p">;</span> <span class="n">j</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">aux</span><span class="p">[</span><span class="n">r</span><span class="o">+</span><span class="n">m</span><span class="o">-</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">k</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span> <span class="n">k</span> <span class="o"><=</span> <span class="n">r</span><span class="p">;</span> <span class="n">k</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">aux</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o"><</span> <span class="n">aux</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">{</span>
<span class="n">data</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">aux</span><span class="p">[</span><span class="n">j</span><span class="o">--</span><span class="p">];</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">data</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">aux</span><span class="p">[</span><span class="n">i</span><span class="o">++</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>这里,前两个循环先建立bitonic序列(关键字序列先递增、再递减或先递减、再递增的序列),从而将最大元素作为观察哨,这样,就不用判断两个序列是否处理完,去除if判断,能有效提高merge效率。</p>
<h3 id="632-递归自顶向下">6.3.2 递归——自顶向下</h3>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="cm">/**
* 采用递归,自顶向下归并排序
*/</span>
<span class="kt">void</span> <span class="nf">mergeSortRecursion</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span> <span class="o">||</span> <span class="n">r</span> <span class="o"><=</span> <span class="n">l</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">m</span> <span class="o">=</span> <span class="n">l</span> <span class="o">+</span> <span class="p">(</span><span class="n">r</span><span class="o">-</span><span class="n">l</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">mergeSortRecursion</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">m</span><span class="p">);</span>
<span class="n">mergeSortRecursion</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">m</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="n">merge</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<h3 id="633-非递归自底向上">6.3.3 非递归——自底向上</h3>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="cm">/**
* 不用递归,自底向上进行归并排序
*/</span>
<span class="kt">void</span> <span class="nf">mergeSortNoRecursion</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">m</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">m</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">m</span> <span class="o"><=</span> <span class="n">r</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="n">m</span> <span class="o">+=</span> <span class="n">m</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">l</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">r</span><span class="o">-</span><span class="n">m</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">m</span><span class="o">+</span><span class="n">m</span><span class="p">)</span> <span class="p">{</span>
<span class="n">merge</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">i</span><span class="o">+</span><span class="n">m</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">((</span><span class="n">i</span><span class="o">+</span><span class="n">m</span><span class="o">+</span><span class="n">m</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o"><</span> <span class="n">r</span><span class="p">)</span> <span class="o">?</span> <span class="n">i</span><span class="o">+</span><span class="n">m</span><span class="o">+</span><span class="n">m</span><span class="o">-</span><span class="mi">1</span> <span class="o">:</span> <span class="n">r</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>可以说,归并排序是最理想的直接排序方法</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 运行时间与NlgN成正比
2. 如果归并算法稳定,那么,归并排序是稳定的(本文所用归并算法并不稳定)
</code></pre></div></div>
<p>虽然说快排比归并排序要快一些,但是,快排是不稳定的。</p>
<h1 id="7-堆排序">7. 堆排序</h1>
<hr />
<p>最后,以大根堆为例,实现堆排序。</p>
<h2 id="71-基本概念">7.1 基本概念</h2>
<p>如果一棵树的每个节点的关键字都大于或等于其所有子节点的关键字,就称树是堆有序的。</p>
<p>定义堆是一个节点的集合,表示为数组,关键字按照堆有序的完全二叉树的形式排列。</p>
<p>规定,数组0的位置存放节点个数,数组1的位置开始存放堆中元素,所以,对于节点i,如果其左右孩子存在,则存在与2i和2i+1;如果其父节点存在,则存在与i/2。</p>
<h2 id="72-堆排序">7.2 堆排序</h2>
<p>大根堆中,树根节点保存当前堆中的最大值,如果每次取出该值,与堆中最后一个节点交换,然后,堆进行调整。取出堆中全部元素后,即排序完成,代码如下:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="cm">/**
* 如果节点的值小于其子节点,则向下调整
*/</span>
<span class="kt">void</span> <span class="nf">fixDown</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">k</span><span class="p">,</span> <span class="kt">int</span> <span class="n">count</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">k</span> <span class="o"><=</span> <span class="n">count</span><span class="p">)</span> <span class="p">{</span>
<span class="n">j</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">k</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="o"><</span> <span class="n">count</span> <span class="o">&&</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span> <span class="p">{</span>
<span class="n">j</span> <span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">>=</span> <span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">])</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">k</span><span class="p">],</span> <span class="o">&</span><span class="n">data</span><span class="p">[</span><span class="n">j</span><span class="p">]);</span>
<span class="n">k</span> <span class="o">=</span> <span class="n">j</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp">
<span class="cm">/**
* 根节点保存当前堆中的最大值,每次取出根节点的值与最后一个节点交换,调整堆
*/</span>
<span class="kt">void</span> <span class="nf">heapSort</span><span class="p">(</span><span class="n">item</span> <span class="n">data</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">l</span><span class="p">,</span> <span class="kt">int</span> <span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">r</span> <span class="o">-</span> <span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">item</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">data</span> <span class="o">+</span> <span class="n">l</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">k</span> <span class="o">=</span> <span class="n">count</span><span class="p">;</span> <span class="n">k</span> <span class="o">>=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fixDown</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">k</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span><span class="n">count</span> <span class="o">></span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="n">exchange</span><span class="p">(</span><span class="o">&</span><span class="n">p</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="o">&</span><span class="n">p</span><span class="p">[</span><span class="n">count</span><span class="p">]);</span>
<span class="n">fixDown</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">--</span><span class="n">count</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="73-效率">7.3 效率</h2>
<p>效率分两部分,构建堆和排序。如上程序所示,构建堆所需时间为线性时间;排序时间为NlgN。</p>
<h2 id="74-堆排序快排归并排序的选择">7.4 堆排序、快排、归并排序的选择</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. **堆排序与归并排序:排序的(不)稳定性和是否需要额外空间。**
2. **堆排序与快速排序:平均情况和最坏情况的选择。**
</code></pre></div></div>
C语言的贪心
2013-07-13T14:08:04+00:00
http://www.readingnotes.site/posts/c%e8%af%ad%e8%a8%80%e7%9a%84%e8%b4%aa%e5%bf%83
<p>例:当C编译器读入一个‘/’,后面又跟着一个‘*’,这时候,编译器需要做出判断:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* 将'/'和'*'当作两个符号分别对待;
* 将'/'和'*'合起来当一个符号对待。
</code></pre></div></div>
<p>在C语言中,处理这个问题可以用“每一个符号应该包含尽可能多的字符”这条规则来判断,称为“贪心法”,K&R在曾对这个方法做了如下描述:</p>
<blockquote>
<p>如果(编译器的)输入流截止至某个字符之前都已经被分解为一个个符号,那么下一个符号将包括从该符号之后可能组成一个符号的最长字符串。</p>
</blockquote>
<p>贪心法很好理解,但由于不良编写习惯,很可能给程序带来一些隐藏的bug,举例如下:<!-- more --></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x = y / *p;
x = y/*p;
</code></pre></div></div>
<p>是完全不同的两个表达式,第一个语句表示<em>p是除数,而第二个语句“/</em>”开始表示注释,如上式编辑器还能发现错误,但如果改成这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x = y/*p /* p是指向除数的指针 */ ;
</code></pre></div></div>
<p>编译器就发现不了了错误了。</p>
<p>再举个例子,表达式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a---b
</code></pre></div></div>
<p>表示什么呢?</p>
<p>根据贪心法,表达的意思应该是a– -b;但作者本来的意思很可能是a- –b,see?这就出问题了。</p>
<p>可以看出,良好的编码风格,合理运用空格、制表符和换行符,不仅使程序易读,也能防止很多意外bug的出现。</p>
位图排序
2013-06-14T08:09:22+00:00
http://www.readingnotes.site/posts/bitmap_sort
<h1 id="1-题目">1. 题目:</h1>
<p><em>输入:一个最多包含N个正整数的文件,每个数都小于N,N=10000000,这些数没有重复。</em></p>
<p><em>输出:按升序排列的数据。</em></p>
<p><em>要求:内存空间约1M,磁盘空间无限。</em></p>
<h1 id="2-数据来源">2. 数据来源:</h1>
<p>自己编写脚本产生随机数,由于最近在写PHP,就PHP吧,代码如下,没什么说的:</p>
<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp"><?php</span>
<span class="nv">$num</span> <span class="o">=</span> <span class="mi">800000</span><span class="p">;</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
<span class="k">for</span><span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$num</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$data</span><span class="p">[]</span> <span class="o">=</span> <span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">10000000</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nb">array_unique</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="nv">$path</span> <span class="o">=</span> <span class="s2">"data.txt"</span><span class="p">;</span>
<span class="nv">$file</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="nv">$path</span><span class="p">,</span><span class="s2">"a"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="kc">NULL</span> <span class="o">==</span> <span class="nv">$file</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"open file failed"</span><span class="p">;</span>
<span class="k">exit</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">foreach</span><span class="p">(</span><span class="nv">$data</span> <span class="k">as</span> <span class="nv">$v</span><span class="p">)</span>
<span class="p">{</span>
<span class="nb">fputs</span><span class="p">(</span><span class="nv">$file</span><span class="p">,</span><span class="nv">$v</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span>
<span class="cp">?></span></code></pre></figure>
<p>产生的数据保存在data.txt中,改变$num可以改变产生的随机数数量。需要的可以自己运行脚本产生数据进行测试,没有PHP环境的可以到<a href="http://pan.baidu.com/share/link?shareid=1848260083&uk=657450545">这里</a>下载测试数据。</p>
<h1 id="3-解答">3. 解答</h1>
<h2 id="31-理想状态">3.1 理想状态</h2>
<p>首先,如果不缺内存,可以怎么弄?</p>
<p>最简单的,从data.txt中读取数据,将数据存到全局变量data数组中,然后,利用快排进行排序即可。</p>
<p>下面的程序从文件中将数据读到数组中。</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#define NUM 10000000
</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">data</span><span class="p">[</span><span class="n">NUM</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="kt">long</span> <span class="kt">int</span> <span class="nf">getData</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">file</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">char</span> <span class="n">ch</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="n">file</span><span class="p">,</span><span class="s">"r"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span> <span class="nb">NULL</span> <span class="o">==</span> <span class="n">f</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"open file failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span> <span class="n">fgets</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="n">f</span><span class="p">)</span> <span class="o">&&</span> <span class="n">i</span> <span class="o"><</span> <span class="n">NUM</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">*</span><span class="n">ch</span> <span class="o">==</span> <span class="n">EOF</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"fgets failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">atol</span><span class="p">(</span><span class="n">ch</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">return</span> <span class="n">i</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>在C语言中,快排用qsort即可,于是,很容易写下如下程序</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#include <time.h>
</span><span class="c1">//asc</span>
<span class="kt">int</span> <span class="nf">comp</span><span class="p">(</span><span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">p</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">q</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span><span class="n">p</span> <span class="o">-</span> <span class="o">*</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span><span class="n">q</span> <span class="p">);</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">file</span> <span class="o">=</span> <span class="s">"data.txt"</span><span class="p">;</span>
<span class="kt">clock_t</span> <span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="p">;</span><span class="c1">//为了统计运行时间</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
<span class="kt">long</span> <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">getData</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
<span class="n">qsort</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span><span class="p">),</span> <span class="n">comp</span><span class="p">);</span>
<span class="n">finish</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
<span class="kt">double</span> <span class="n">duration</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span><span class="p">)(</span><span class="n">finish</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">/</span> <span class="n">CLOCKS_PER_SEC</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%lfs</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">duration</span><span class="p">);</span><span class="c1">//显示运行时间</span>
<span class="c1">//保存结果,主要是为了测试</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="n">fopen</span> <span class="p">(</span><span class="s">"ans.txt"</span><span class="p">,</span><span class="s">"w"</span><span class="p">);</span>
<span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">len</span><span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">f</span><span class="p">,</span><span class="s">"%ld</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>上面是理想状态,但是</p>
<h2 id="32-残酷现实">3.2 残酷现实</h2>
<p>我们的内存并不总是足够的,特别是当排序是某个大系统的很小的一部分的时候,更是受到各方面的限制,不仅仅是内存,很可能对时间要求也会更高,这里采用快排,时间复杂度达到N*logN,那么,有没有更好的方法呢?</p>
<p>注意,这里的数没有重复,那么,很自然的想到了位向量/位图。</p>
<p>定义一个保存结果的int型数组result[N],每位对应一个整数,如果该整数存在,那么,将result数组中该位设为1,否则,设为0,最后,顺序扫描一遍结果数组result,顺序取出为1的位对应的整数,即得到排序后的结果。现在,剩下的问题就变成了待排序的整数怎么跟结果result数组中的位一一对应起来的问题了。</p>
<p>带排序数组data[NUM],位向量result[N]。result数组为int型,在此,假设每个int型变量占4个字节,即32位,那么,对于data[NUM]中任意一个数data[i],其对应位一定存在与result[i/32]这个数据中,对应于是result[i]中第 i%32 位。</p>
<p>于是,很容易写下操作位向量的相关代码:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#define WORD 32
</span>
<span class="c1">//set the bit</span>
<span class="kt">void</span> <span class="nf">set</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="o">/</span><span class="n">WORD</span><span class="p">]</span> <span class="o">|=</span> <span class="p">(</span> <span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="n">i</span><span class="o">%</span><span class="n">WORD</span><span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">//clear the bit</span>
<span class="kt">void</span> <span class="nf">clear</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="o">>/</span><span class="n">WORD</span><span class="p">]</span> <span class="o">&=</span> <span class="o">~</span><span class="p">(</span> <span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="n">i</span><span class="o">%</span><span class="n">WORD</span><span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">//test the bit</span>
<span class="kt">int</span> <span class="nf">test</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="o">/</span><span class="n">WORD</span><span class="p">]</span> <span class="o">&</span> <span class="p">(</span> <span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="n">i</span><span class="o">%</span><span class="n">WORD</span><span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></figure>
<p>可是,大多数资料上的代码是这样的:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#define SHIFT 5
</span>
<span class="cp">#define MASK 0x1F
</span>
<span class="c1">//set the bit</span>
<span class="kt">void</span> <span class="nf">set</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="o">>></span><span class="n">SHIFT</span><span class="p">]</span> <span class="o">|=</span> <span class="p">(</span> <span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="n">i</span><span class="o">&</span><span class="n">MASK</span><span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">//clear the bit</span>
<span class="kt">void</span> <span class="nf">clear</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="o">>></span><span class="n">SHIFT</span><span class="p">]</span> <span class="o">&=</span> <span class="o">~</span><span class="p">(</span> <span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="n">i</span><span class="o">&</span><span class="n">MASK</span><span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">//test the bit</span>
<span class="kt">int</span> <span class="nf">test</span><span class="p">(</span><span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="o">>></span><span class="n">SHIFT</span><span class="p">]</span> <span class="o">&</span> <span class="p">(</span> <span class="mi">1</span> <span class="o"><<</span> <span class="p">(</span><span class="n">i</span><span class="o">&</span><span class="n">MASK</span><span class="p">)</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></figure>
<p>其实,上述两段代码功能是一样的,i»SHIFT,表示位运算i右移5位,25 = 32,表示i/32,举个例子,i=111,二进制表示为1101111,右移5位后,变成11,对应十进制3,刚好等于111/32。</p>
<p>同理,i & MASK,与i%32的功能一样。MASK的二进制表示位011111(前面的0省略了),i & MASK相当于取i的最后5位,前面的都置0,也就表示i % 32.</p>
<p>那么,既然功能一样,为什么用第二种方法,即位运算,而不是第一种方法,直接用除法和取模呢?</p>
<p>从查到的资料来看,这涉及到效率问题了,<strong>在现代架构中,位运算的速度比乘除法快很多,跟加减法差不多</strong>。但是,我在这里对两种方法进行了测试,发现二者所需时间基本一致,测试方法如下面的程序所示。即使将</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">finish</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span></code></pre></figure>
<p>放到最后也一样。</p>
<p>这是为什么呢?<strong>原因是现代的编译器对乘除法进行了优化</strong>!</p>
<p>最后,利用位运算进行排序,并测试结果,代码如下:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="cp">#include <time.h>
</span><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">file</span> <span class="o">=</span> <span class="s">"data.txt"</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">ch</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="err"> </span> <span class="err"> </span> <span class="kt">clock_t</span> <span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="p">;</span>
<span class="err"> </span> <span class="err"> </span> <span class="n">start</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="n">file</span><span class="p">,</span><span class="s">"r"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span> <span class="nb">NULL</span> <span class="o">==</span> <span class="n">f</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"open file failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">long</span> <span class="kt">int</span> <span class="n">tempdata</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">long</span> <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">//读取数据,设置相应位的值</span>
<span class="k">while</span><span class="p">(</span> <span class="n">fgets</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="n">f</span><span class="p">)</span> <span class="o">&&</span> <span class="n">len</span> <span class="o"><</span> <span class="n">NUM</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">*</span><span class="n">ch</span> <span class="o">==</span> <span class="n">EOF</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"fgets failed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">tempdata</span> <span class="o">=</span> <span class="n">atol</span><span class="p">(</span><span class="n">ch</span><span class="p">);</span>
<span class="n">len</span> <span class="o">++</span><span class="p">;</span>
<span class="n">set</span><span class="p">(</span><span class="n">tempdata</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">f</span><span class="p">);</span>
<span class="c1">//统计运行时间</span>
<span class="n">finish</span> <span class="o">=</span> <span class="n">clock</span><span class="p">();</span>
<span class="err"> </span> <span class="err"> </span> <span class="kt">double</span> <span class="n">duration</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span><span class="p">)(</span><span class="n">finish</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">/</span> <span class="n">CLOCKS_PER_SEC</span><span class="p">;</span>
<span class="err"> </span> <span class="err"> </span> <span class="n">printf</span><span class="p">(</span><span class="s">"%lfs</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">duration</span><span class="p">);</span>
<span class="c1">//保存结果以便测试</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">fout</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"ans_new.txt"</span><span class="p">,</span><span class="s">"w"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span> <span class="nb">NULL</span> <span class="o">==</span> <span class="n">fout</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"fopen fail when save the result</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">long</span> <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">NUM</span><span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span> <span class="n">test</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">fout</span><span class="p">,</span><span class="s">"%ld</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">fout</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></figure>
<p>总结一下发现,采用位向量进行排序将时间复杂度降到了O(N),同时,占用的空间也大大的减少了。</p>
<p>利用qsort和位向量两种方法时,分别进行了时间统计,如程序中所示。其中,测试数据中有9996663条数据。</p>
<ul>
<li>
<p>qsort:3.78s</p>
</li>
<li>
<p>位向量:1.25s</p>
</li>
</ul>
<h1 id="4-扩展">4. 扩展</h1>
<h2 id="41-适用条件">4.1 适用条件</h2>
<p><em>要用位向量进行排序有一定的条件:</em></p>
<ol>
<li><em>待排序数据需要限制在一个较小的范围内。</em></li>
<li><em>数据没有重复。 </em></li>
</ol>
<p><em>那么,条件能不能放宽呢?</em></p>
<p>对于第一条,应该是不行的(如果有,还望不吝赐教啊)。</p>
<p>对第二条,如果输入的数据是有重复的,每个数据至多出现K次,是否仍然能用位向量进行排序呢?</p>
<p>答案是肯定的,例如K=13,23 < K < 24, 所以,那么,可以用result中的4位对应一个整数,也就是说,修改3个位向量函数即可。</p>
<h2 id="42-内存限制">4.2 内存限制</h2>
<p><em>文中的算法需要占用约1.25M内存,那么,如果严格要求1M内存,该怎么办?</em></p>
<p>这样,可以借助外排思想,分两次处理。顺序读取输入数据,如果是[0,5000000),则处理,设置result相应位,否则,读取下一个数据。保存[0,5000000)内的结果。读取输入数据,如果是[5000000,10000000),则处理,设置result相应位,否则,读取下一个数据。保存[5000000,10000000)内的结果追加到[0,5000000)内的结果后面(因为结果也是有序的,所以,直接追加即可)。</p>