﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-jinfeng_wang-随笔分类-2016-thinking</title><link>http://www.blogjava.net/jinfeng_wang/category/55141.html</link><description>G-G-S,D-D-U!</description><language>zh-cn</language><lastBuildDate>Fri, 03 Feb 2017 14:39:44 GMT</lastBuildDate><pubDate>Fri, 03 Feb 2017 14:39:44 GMT</pubDate><ttl>60</ttl><item><title>分布式系统理论基础 - 时间、时钟和事件顺序</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 07:44:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432285.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432285.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432285.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/bangerlee/p/5448766.html<br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><blockquote style="padding: 0px 0px 0px 45px; margin: 0px auto; color: #666666; font-size: 13px; width: 827.094px; background-image: url(&quot;images/bq.gif&quot;); background-color: #ffffff; background-position: 0% 0%; background-repeat: no-repeat;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">十六号&#8230;&#8230; 四月十六号。一九六零年四月十六号下午三点之前的一分钟你和我在一起，因为你我会记住这一分钟。从现在开始我们就是一分钟的朋友，这是事实，你改变不了，因为已经过去了。我明天会再来。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp; &nbsp; &#8212;&#8212; 《阿飞正传》</p></blockquote><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">现实生活中时间是很重要的概念，时间可以记录事情发生的时刻、比较事情发生的先后顺序。分布式系统的一些场景也需要记录和比较不同节点间事件发生的顺序，但不同于日常生活使用物理时钟记录时间，分布式系统使用逻辑时钟记录事件顺序关系，下面我们来看分布式系统中几种常见的逻辑时钟。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>物理时钟 vs 逻辑时钟</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">可能有人会问，为什么分布式系统不使用物理时钟(physical clock)记录事件？每个事件对应打上一个时间戳，当需要比较顺序的时候比较相应时间戳就好了。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">这是因为现实生活中物理时间有统一的标准，而分布式系统中每个节点记录的时间并不一样，即使设置了&nbsp;<a href="http://www.zhihu.com/question/24960940" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">NTP</a>&nbsp;时间同步节点间也存在毫秒级别的偏差<sup>[1][2]</sup>。因而分布式系统需要有另外的方法记录事件顺序关系，这就是逻辑时钟(logical clock)。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160501132311347-349996615.jpg" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>Lamport timestamps</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><a href="https://en.wikipedia.org/wiki/Leslie_Cheung" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Leslie</a>&nbsp;<a href="https://en.wikipedia.org/wiki/Leslie_Lamport" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Lamport</a>&nbsp;在1978年提出逻辑时钟的概念，并描述了一种逻辑时钟的表示方法，这个方法被称为Lamport时间戳(Lamport timestamps)<sup>[3]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">分布式系统中按是否存在节点交互可分为三类事件，一类发生于节点内部，二是发送事件，三是接收事件。Lamport时间戳原理如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160501174922566-1686627384.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><em>图1: Lamport timestamps space time (图片来源: wikipedia)</em></p><ol style="padding-left: 40px;"><li style="padding: 0px; list-style-type: decimal;">每个事件对应一个Lamport时间戳，初始值为0</li><li style="padding: 0px; list-style-type: decimal;">如果事件在节点内发生，时间戳加1</li><li style="padding: 0px; list-style-type: decimal;">如果事件属于发送事件，时间戳加1并在消息中带上该时间戳</li><li style="padding: 0px; list-style-type: decimal;">如果事件属于接收事件，时间戳 = Max(本地时间戳，消息中的时间戳) + 1</li></ol><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">假设有事件a、b，C(a)、C(b)分别表示事件a、b对应的Lamport时间戳，如果C(a) &lt; C(b)，则有a发生在b之前(happened before)，记作 a -&gt; b，例如图1中有 C1 -&gt; B1。通过该定义，事件集中Lamport时间戳不等的事件可进行比较，我们获得事件的<a href="https://en.wikipedia.org/wiki/Partially_ordered_set#Formal_definition" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">偏序关系</a>(partial order)。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">如果C(a) = C(b)，那a、b事件的顺序又是怎样的？假设a、b分别在节点P、Q上发生，P<sub>i、</sub>Q<sub>j</sub>分别表示我们给P、Q的编号，如果&nbsp;C(a) = C(b) 并且&nbsp;P<sub>i</sub>&lt;<sub>&nbsp;</sub>Q<sub>j</sub>，同样定义为a发生在b之前，记作 a =&gt; b。假如我们对图1的A、B、C分别编号A<sub>i</sub>&nbsp;= 1、B<sub>j</sub>&nbsp;= 2、C<sub>k</sub>&nbsp;= 3，因 C(B4) = C(C3) 并且 B<sub>j</sub>&nbsp;&lt; C<sub>k</sub>，则 B4 =&gt; C3。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">通过以上定义，我们可以对所有事件排序、获得事件的<a href="https://en.wikipedia.org/wiki/Total_order" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">全序关系</a>(total order)。上图例子，我们可以从C1到A4进行排序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>Vector clock</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">Lamport时间戳帮助我们得到事件顺序关系，但还有一种顺序关系不能用Lamport时间戳很好地表示出来，那就是同时发生关系(concurrent)<sup>[4]</sup>。例如图1中事件B4和事件C3没有因果关系，属于同时发生事件，但Lamport时间戳定义两者有先后顺序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">Vector clock是在Lamport时间戳基础上演进的另一种逻辑时钟方法，它通过vector结构不但记录本节点的Lamport时间戳，同时也记录了其他节点的Lamport时间戳<sup>[5][6]</sup>。Vector clock的原理与Lamport时间戳类似，使用图例如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160502134654404-1109556515.png" alt="" width="656" height="371" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><em>图2: Vector clock space time (<em>图片来源: wikipedia)</em></em></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">假设有事件a、b分别在节点P、Q上发生，Vector clock分别为T<sub>a</sub>、T<sub>b</sub>，如果 T<sub>b</sub>[Q] &gt; T<sub>a</sub>[Q] 并且 T<sub>b</sub>[P] &gt;= T<sub>a</sub>[P]，则a发生于b之前，记作 a -&gt; b。到目前为止还和Lamport时间戳差别不大，那Vector clock怎么判别同时发生关系呢？</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">如果&nbsp;T<sub>b</sub>[Q] &gt; T<sub>a</sub>[Q] 并且 T<sub>b</sub>[P] &lt; T<sub>a</sub>[P]，则认为a、b同时发生，记作 a &lt;-&gt; b。例如图2中节点B上的第4个事件 (A:2，B:4，C:1) 与节点C上的第2个事件 (B:3，C:2) 没有因果关系、属于同时发生事件。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>Version vector</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">基于Vector clock我们可以获得任意两个事件的顺序关系，结果或为先后顺序或为同时发生，识别事件顺序在工程实践中有很重要的引申应用，最常见的应用是发现数据冲突(detect conflict)。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">分布式系统中数据一般存在多个副本(replication)，多个副本可能被同时更新，这会引起副本间数据不一致<sup>[7]</sup>，Version vector的实现与Vector clock非常类似<sup>[8]</sup>，目的用于发现数据冲突<sup>[9]</sup>。下面通过一个例子说明Version vector的用法<sup>[10]</sup>：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160502183034013-800335383.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><em>图3: Version vector</em></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><ul style="list-style: none; margin: 10px 10px 10px 30px; padding: 0px;"><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">client端写入数据，该请求被S<sub>x</sub>处理并创建相应的vector ([S<sub>x</sub>, 1])，记为数据D1</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">第2次请求也被S<sub>x</sub>处理，数据修改为D2，vector修改为([S<sub>x</sub>, 2])</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">第3、第4次请求分别被S<sub>y</sub>、S<sub>z</sub>处理，client端先读取到D2，然后D3、D4被写入S<sub>y</sub>、S<sub>z</sub></li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">第5次更新时client端读取到D2、D3和D4 3个数据版本，通过类似Vector clock判断同时发生关系的方法可判断D3、D4存在数据冲突，最终通过一定方法解决数据冲突并写入D5</li></ul><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">Vector clock只用于发现数据冲突，不能解决数据冲突。如何解决数据冲突因场景而异，具体方法有以最后更新为准(last write win)，或将冲突的数据交给client由client端决定如何处理，或通过quorum决议事先避免数据冲突的情况发生<sup>[11]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">由于记录了所有数据在所有节点上的逻辑时钟信息，Vector clock和Version&nbsp;vector在实际应用中可能面临的一个问题是vector过大，用于数据管理的元数据(meta data)甚至大于数据本身<sup>[12]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">解决该问题的方法是使用server id取代client id创建vector (因为server的数量相对client稳定)，或设定最大的size、如果超过该size值则淘汰最旧的vector信息<sup>[10][13]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>小结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">以上介绍了分布式系统里逻辑时钟的表示方法，通过Lamport timestamps可以建立事件的全序关系，通过Vector clock可以比较任意两个事件的顺序关系并且能表示无因果关系的事件，将Vector clock的方法用于发现数据版本冲突，于是有了Version vector。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[1]&nbsp;<a href="https://queue.acm.org/detail.cfm?id=2878574" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Time is an illusion</a>, George Neville-Neil, 2016</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[2]&nbsp;<a href="https://queue.acm.org/detail.cfm?id=2745385&amp;__hstc=53389751.f1483a2189ec5c779270b00cdb849993.1461983406379.1461983406379.1461997241982.2&amp;__hssc=53389751.1.1461997241982&amp;__hsfp=1028666893" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">There is No Now</a>,&nbsp;Justin Sheehy, 2015</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[3]&nbsp;<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Time, Clocks, and the Ordering of Events in a Distributed System</a>, Leslie Lamport, 1978</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[4]&nbsp;<a href="http://zoo.cs.yale.edu/classes/cs426/2012/lab/bib/fidge88timestamps.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Timestamps in Message-Passing Systems That Preserve the Partial Ordering</a>, Colin J. Fidge, 1988</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[5]&nbsp;<a href="http://www.vs.inf.ethz.ch/publ/papers/VirtTimeGlobStates.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Virtual Time and Global States of Distributed Systems</a>, Friedemann Mattern, 1988</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[6]&nbsp;<a href="http://basho.com/posts/technical/why-vector-clocks-are-easy/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Why Vector Clocks are Easy</a>, Bryan Fink, 2010</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[7]&nbsp;<a href="http://guide.couchdb.org/draft/conflicts.html" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Conflict Management</a>, CouchDB</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[8]&nbsp;<a href="https://haslab.wordpress.com/2011/07/08/version-vectors-are-not-vector-clocks/?__hstc=53389751.f1483a2189ec5c779270b00cdb849993.1461983406379.1461983406379.1461997241982.2&amp;__hssc=53389751.1.1461997241982&amp;__hsfp=1028666893" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Version Vectors are not Vector Clocks</a>, Carlos Baquero,&nbsp;2011</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[9]&nbsp;<a href="http://zoo.cs.yale.edu/classes/cs422/2013/bib/parker83detection.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Detection of Mutual Inconsistency in Distributed Systems</a>, IEEE Transactions on Software Engineering , 1983</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[10]&nbsp;<a href="http://s3.amazonaws.com/AllThingsDistributed/sosp/amazon-dynamo-sosp2007.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Dynamo: Amazon&#8217;s Highly Available Key-value Store</a>, Amazon, 2007</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[11]&nbsp;<a href="http://pl.atyp.us/wordpress/?p=2601" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Conflict Resolution</a>, Jeff Darcy , 2010</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[12]&nbsp;<a href="http://basho.com/posts/technical/why-vector-clocks-are-hard/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Why Vector Clocks Are Hard</a>, Justin Sheehy, 2010</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[13]&nbsp;<a href="http://www.bailis.org/blog/causality-is-expensive-and-what-to-do-about-it/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Causality Is Expensive (and What To Do About It)</a>, Peter Bailis ,2014</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432285.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 15:44 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式系统理论基础 - 选举、多数派和租约</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 07:25:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432284.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432284.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432284.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/bangerlee/p/5767845.html<br /><br /><br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">选举(election)是分布式系统实践中常见的问题，通过打破节点间的对等关系，选得的leader(或叫master、coordinator)有助于实现事务原子性、提升决议效率。 多数派(quorum)的思路帮助我们在网络分化的情况下达成决议一致性，在leader选举的场景下帮助我们选出唯一leader。租约(lease)在一定期限内给予节点特定权利，也可以用于实现leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">下面我们就来学习分布式系统理论中的选举、多数派和租约。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>选举(electioin)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">一致性问题(consistency)是独立的节点间如何达成决议的问题，选出大家都认可的leader本质上也是一致性问题，因而如何应对宕机恢复、网络分化等在leader选举中也需要考量。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">Bully算法<sup>[1]</sup>是最常见的选举算法，其要求每个节点对应一个序号，序号最高的节点为leader。leader宕机后次高序号的节点被重选为leader，过程如下：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201608/116770-20160814110211906-1201598126.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(a). 节点4发现leader不可达，向序号比自己高的节点发起重新选举，重新选举消息中带上自己的序号</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(b)(c). 节点5、6接收到重选信息后进行序号比较，发现自身的序号更大，向节点4返回OK消息并各自向更高序号节点发起重新选举</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(d). 节点5收到节点6的OK消息，而节点6经过超时时间后收不到更高序号节点的OK消息，则认为自己是leader</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(e). 节点6把自己成为leader的信息广播到所有节点</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">回顾<a href="http://www.cnblogs.com/bangerlee/p/5268485.html" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">《分布式系统理论基础 - 一致性、2PC和3PC》</a>就可以看到，Bully算法中有2PC的身影，都具有提议(propose)和收集反馈(vote)的过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在一致性算法<a href="http://www.cnblogs.com/bangerlee/p/5655754.html" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Paxos</a>、ZAB<sup>[2]</sup>、Raft<sup>[3]</sup>中，为提升决议效率均有节点充当leader的角色。ZAB、Raft中描述了具体的leader选举实现，与Bully算法类似ZAB中使用zxid标识节点，具有最大zxid的节点表示其所具备的事务(transaction)最新、被选为leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>多数派(quorum)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在网络分化的场景下以上Bully算法会遇到一个问题，被分隔的节点都认为自己具有最大的序号、将产生多个leader，这时候就需要引入多数派(quorum)<sup>[4]</sup>。多数派的思路在分布式系统中很常见，其确保网络分化情况下决议唯一。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">多数派的原理说起来很简单，假如节点总数为2f+1，则一项决议得到多于 f 节点赞成则获得通过。leader选举中，网络分化场景下只有具备多数派节点的部分才可能选出leader，这避免了多leader的产生。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201608/116770-20160814195846250-9979865.png" alt="" width="601" height="318" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">多数派的思路还被应用于副本(replica)管理，根据业务实际读写比例调整写副本数V<sub>w</sub>、读副本数V<sub>r</sub>，用以在可靠性和性能方面取得平衡<sup>[5]</sup>。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>租约(lease)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">选举中很重要的一个问题，以上尚未提到：怎么判断leader不可用、什么时候应该发起重新选举？最先可能想到会通过心跳(heart beat)判别leader状态是否正常，但在网络拥塞或瞬断的情况下，这容易导致出现双主。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">租约(lease)是解决该问题的常用方法，其最初提出时用于解决分布式缓存一致性问题<sup>[6]</sup>，后面在分布式锁<sup>[7]</sup>等很多方面都有应用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201608/116770-20160821195833933-818514275.png" alt="" width="449" height="236" style="border: 0px; max-width: 900px;" />&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">租约的原理同样不复杂，中心思想是每次租约时长内只有一个节点获得租约、到期后必须重新颁发租约。假设我们有租约颁发节点Z，节点0、1和2竞选leader，租约过程如下：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(a). 节点0、1、2在Z上注册自己，Z根据一定的规则(例如先到先得)颁发租约给节点，该租约同时对应一个有效时长；这里假设节点0获得租约、成为leader</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(b). leader宕机时，只有租约到期(timeout)后才重新发起选举，这里节点1获得租约、成为leader</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">租约机制确保了一个时刻最多只有一个leader，避免只使用心跳机制产生双主的问题。在实践应用中，zookeeper、ectd可用于租约颁发。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>小结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在分布式系统理论和实践中，常见leader、quorum和lease的身影。分布式系统内不一定事事协商、事事民主，leader的存在有助于提升决议效率。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">本文以leader选举作为例子引入和讲述quorum、lease，当然quorum和lease是两种思想，并不限于leader选举应用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">最后提一个有趣的问题与大家思考，leader选举的本质是一致性问题，Paxos、Raft和ZAB等解决一致性问题的协议和算法本身又需要或依赖于leader，怎么理解这个看似&#8220;蛋生鸡、鸡生蛋&#8221;的问题？<sup>[8]</sup></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[1]&nbsp;<a href="http://homepage.divms.uiowa.edu/~ghosh/Bully.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Elections in a Distributed Computing System</a>, Hector Garcia-Molina, 1982</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[2]&nbsp;<a href="http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">ZooKeeper&#8217;s atomic broadcast protocol: Theory and practice</a>, Andre Medeiros, 2012</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[3]&nbsp;<a href="https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">In Search of an Understandable Consensus Algorithm</a>, Diego Ongaro and John Ousterhout, 2013</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[4]&nbsp;<a href="https://ecommons.cornell.edu/bitstream/handle/1813/6323/82-483.pdf?sequence=1" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">A quorum-based commit protocol</a>, Dale Skeen, 1982</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[5]&nbsp;<a href="http://lass.cs.umass.edu/~shenoy/courses/spring04/677/readings/gifford.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Weighted Voting for Replicated Data</a>, David K. Gifford, 1979</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[6]&nbsp;<a href="http://web.stanford.edu/class/cs240/readings/89-leases.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency</a>, Cary G. Gray and David R. Cheriton, 1989</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[7]&nbsp;<a href="http://research.google.com/archive/chubby-osdi06.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">The Chubby lock service for loosely-coupled distributed systems</a>, Mike Burrows, 2006</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[8]&nbsp;<a href="http://stackoverflow.com/questions/23798724/why-is-paxos-leader-election-not-done-using-paxos" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Why is Paxos leader election not done using Paxos?</a></p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432284.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 15:25 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式系统理论基础 - 一致性、2PC和3PC</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 07:02:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432283.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432283.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432283.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/bangerlee/p/5268485.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>引言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">狭义的分布式系统指由网络连接的计算机系统，每个节点独立地承担计算或存储任务，节点间通过网络协同工作。广义的分布式系统是一个相对的概念，正如<a href="https://en.wikipedia.org/wiki/Leslie_Lamport" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Leslie Lamport</a>所说<sup>[1]</sup>：</p><blockquote style="padding: 0px 0px 0px 45px; margin: 0px auto; color: #666666; font-size: 13px; width: 827.094px; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-image: url(&quot;images/bq.gif&quot;); background-color: #ffffff; background-position: 0% 0%; background-repeat: no-repeat;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">What is a distributed systeme.&nbsp;<strong><span style="line-height: 1.8; color: #000000;">Distribution is in the eye of the beholder</span>.</strong><br />To the user sitting at the keyboard, his IBM personal computer is a nondistributed system.&nbsp;<br />To a flea crawling around on the circuit board, or to the engineer who designed it, it's very much a distributed system.</p></blockquote><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;一致性是分布式理论中的根本性问题，近半个世纪以来，科学家们围绕着一致性问题提出了很多理论模型，依据这些理论模型，业界也出现了很多工程实践投影。下面我们从一致性问题、特定条件下解决一致性问题的两种方法(2PC、3PC)入门，了解最基础的分布式系统理论。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一致性(consensus)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">何为一致性问题？简单而言，一致性问题就是相互独立的节点之间如何达成一项决议的问题。分布式系统中，进行数据库事务提交(commit transaction)、Leader选举、序列号生成等都会遇到一致性问题。这个问题在我们的日常生活中也很常见，比如牌友怎么商定几点在哪打几圈麻将：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313132041413-375351900.jpg" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>《赌圣》，1990</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">假设一个具有N个节点的分布式系统，当其满足以下条件时，我们说这个系统满足一致性：</p><ol style="padding-left: 40px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px; list-style-type: decimal;"><strong>全认同(agreement)</strong>: 所有N个节点都认同一个结果</li><li style="padding: 0px; list-style-type: decimal;"><strong>值合法(validity)</strong>: 该结果必须由N个节点中的节点提出</li><li style="padding: 0px; list-style-type: decimal;"><strong>可结束(termination)</strong>: 决议过程在一定时间内结束，不会无休止地进行下去</li></ol><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">有人可能会说，决定什么时候在哪搓搓麻将，4个人商量一下就ok，这不很简单吗？</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">但就这样看似简单的事情，分布式系统实现起来并不轻松，因为它面临着这些问题：</p><ul style="list-style: none; margin: 10px 10px 10px 30px; padding: 0px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>消息传递异步无序(asynchronous)</strong>: 现实网络不是一个可靠的信道，存在消息延时、丢失，节点间消息传递做不到同步有序(synchronous)</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>节点宕机(fail-stop)</strong>: 节点持续宕机，不会恢复</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>节点宕机恢复(fail-recover)</strong>: 节点宕机一段时间后恢复，在分布式系统中最常见</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>网络分化(network partition)</strong>: 网络链路出现问题，将N个节点隔离成多个部分</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>拜占庭将军问题(byzantine failure)</strong><sup>[2]</sup>: 节点或宕机或逻辑失败，甚至不按套路出牌抛出干扰决议的信息</li></ul><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">假设现实场景中也存在这样的问题，我们看看结果会怎样：</p><div style="margin: 5px 0px; font-size: 12px !important;"><div style="margin-top: 5px;"><span style="padding-right: 5px; line-height: 1.5 !important;"><a title="复制代码" style="outline: none; color: #3d81ee; border: none !important;"><img src="http://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border: none #dddddd !important; max-width: 900px; background-color: #ffffff;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; max-width: 600px; font-family: 'Courier New' !important;"><span style="line-height: 1.5 !important;">我: 老王，今晚7点老地方，搓够48圈不见不散！ &#8230;&#8230; （第二天凌晨3点） 隔壁老王: 没问题！       </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 消息延迟</span> <span style="line-height: 1.5 !important;">我: &#8230;&#8230; ---------------------------------------------- 我: 小张，今晚7点老地方，搓够48圈不见不散！ 小张: No &#8230;&#8230;                           </span> <span style="line-height: 1.5 !important;">（两小时后&#8230;&#8230;） 小张: No problem！                     </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 宕机节点恢复</span> <span style="line-height: 1.5 !important;">我: &#8230;&#8230; ----------------------------------------------- 我: 老李头，今晚7点老地方，搓够48圈不见不散！ 老李: 必须的，大保健走起！               </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 拜占庭将军<br /></span>（这是要打麻将呢？还是要大保健？还是一边打麻将一边大保健&#8230;&#8230;）</pre><div style="margin-top: 5px;"><span style="padding-right: 5px; line-height: 1.5 !important;"><a title="复制代码" style="outline: none; color: #3d81ee; border: none !important;"><img src="http://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border: none #dddddd !important; max-width: 900px; background-color: #ffffff;" /></a></span></div></div><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">还能不能一起愉快地玩耍...<img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313010025194-2394933.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">我们把以上所列的问题称为系统模型(system model)，讨论分布式系统理论和工程实践的时候，必先划定模型。例如有以下两种模型：</p><ol style="padding-left: 40px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px; list-style-type: decimal;">异步环境(asynchronous)下，节点宕机(fail-stop)</li><li style="padding: 0px; list-style-type: decimal;">异步环境(asynchronous)下，节点宕机恢复(fail-recover)、网络分化(network partition)</li></ol><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">2比1多了节点恢复、网络分化的考量，因而对这两种模型的理论研究和工程解决方案必定是不同的，在还没有明晰所要解决的问题前谈解决方案都是一本正经地耍流氓。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">一致性还具备两个属性，一个是强一致(safety)，它要求所有节点状态一致、共进退；一个是可用(liveness)，它要求分布式系统24*7无间断对外服务。FLP定理(FLP impossibility)<sup>[3][4]&nbsp;</sup>已经证明在一个收窄的模型中(异步环境并只存在节点宕机)，不能同时满足 safety 和 liveness。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">FLP定理是分布式系统理论中的基础理论，正如物理学中的能量守恒定律彻底否定了永动机的存在，FLP定理否定了同时满足safety 和 liveness 的一致性协议的存在。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160314181639599-564845788.jpg" alt="" width="599" height="337" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>《怦然心动 (Flipped)》，2010<br /></em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">工程实践上根据具体的业务场景，或保证强一致(safety)，或在节点宕机、网络分化的时候保证可用(liveness)。2PC、3PC是相对简单的解决一致性问题的协议，下面我们就来了解2PC和3PC。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>2PC</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">2PC(tow phase commit)两阶段提交<sup>[5]</sup>顾名思义它分成两个阶段，先由一方进行提议(propose)并收集其他节点的反馈(vote)，再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator)，其他参与决议节点称为参与者(participants, 或cohorts)：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313202532507-1396598167.png" alt="" width="646" height="310" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>2PC, phase one</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在阶段1中，coordinator发起一个提议，分别问询各participant是否接受。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313203429600-179395429.png" alt="" width="306" height="308" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>2PC, phase two</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在阶段2中，coordinator根据participant的反馈，提交或中止事务，如果participant全部同意则提交，只要有一个participant不同意就中止。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在异步环境(asynchronous)并且没有节点宕机(fail-stop)的模型下，2PC可以满足全认同、值合法、可结束，是解决一致性问题的一种协议。但如果再加上节点宕机(fail-recover)的考虑，2PC是否还能解决一致性问题呢？</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">coordinator如果在发起提议后宕机，那么participant将进入阻塞(block)状态、一直等待coordinator回应以完成该次决议。这时需要另一角色把系统从不可结束的状态中带出来，我们把新增的这一角色叫协调者备份(coordinator watchdog)。coordinator宕机一定时间后，watchdog接替原coordinator工作，通过问询(query) 各participant的状态，决定阶段2是提交还是中止。这也要求&nbsp;coordinator/participant 记录(logging)历史状态，以备coordinator宕机后watchdog对participant查询、coordinator宕机恢复后重新找回状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">从coordinator接收到一次事务请求、发起提议到事务完成，经过2PC协议后增加了2次RTT(propose+commit)，带来的时延(latency)增加相对较少。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>3PC</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">3PC(three phase commit)即三阶段提交<sup>[6][7]</sup>，既然2PC可以在异步网络+节点宕机恢复的模型下实现一致性，那还需要3PC做什么，3PC是什么鬼？</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在2PC中一个participant的状态只有它自己和coordinator知晓，假如coordinator提议后自身宕机，在watchdog启用前一个participant又宕机，其他participant就会进入既不能回滚、又不能强制commit的阻塞状态，直到<span style="line-height: 1.5;">participant宕机恢复。这引出两个疑问：</span></p><ol style="padding-left: 40px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px; list-style-type: decimal;">能不能去掉阻塞，使系统可以在commit/abort前回滚(rollback)到决议发起前的初始状态</li><li style="padding: 0px; list-style-type: decimal;">当次决议中，participant间能不能相互知道对方的状态，又或者participant间根本不依赖对方的状态</li></ol><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">相比2PC，3PC增加了一个准备提交(prepare to commit)阶段来解决以上问题：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160314002734304-489496391.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>图片截取自wikipedia</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">coordinator接收完participant的反馈(vote)之后，进入阶段2，给各个participant发送准备提交(prepare to commit)指令。participant接到准备提交指令后可以锁资源，但要求相关操作必须可回滚。coordinator接收完确认(ACK)后进入阶段3、进行commit/abort，3PC的阶段3与2PC的阶段2无异。协调者备份(coordinator watchdog)、状态记录(logging)同样应用在3PC。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">participant如果在不同阶段宕机，我们来看看3PC如何应对：</p><ul style="list-style: none; margin: 10px 10px 10px 30px; padding: 0px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>阶段1</strong>:&nbsp;coordinator或watchdog未收到宕机participant的vote，直接中止事务；宕机的participant恢复后，读取logging发现未发出赞成vote，自行中止该次事务</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>阶段2</strong>:&nbsp;coordinator未收到宕机participant的precommit ACK，但因为之前已经收到了宕机participant的赞成反馈(不然也不会进入到阶段2)，coordinator进行commit；watchdog可以通过问询其他participant获得这些信息，过程同理；宕机的participant恢复后发现收到precommit或已经发出赞成vote，则自行commit该次事务</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>阶段3</strong>: 即便coordinator或watchdog未收到宕机participant的commit ACK，也结束该次事务；宕机的participant恢复后发现收到commit或者precommit，也将自行commit该次事务</li></ul><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">因为有了准备提交(prepare to commit)阶段，3PC的事务处理延时也增加了1个RTT，变为3个RTT(propose+precommit+commit)，但是它防止participant宕机后整个系统进入阻塞态，增强了系统的可用性，对一些现实业务场景是非常值得的。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>小结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">以上介绍了分布式系统理论中的部分基础知识，阐述了一致性(consensus)的定义和实现一致性所要面临的问题，最后讨论在异步网络(asynchronous)、节点宕机恢复(fail-recover)模型下2PC、3PC怎么解决一致性问题。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">阅读前人对分布式系统的各项理论研究，其中有严谨地推理、证明，有一种数学的美；观现实中的分布式系统实现，是综合各种因素下妥协的结果。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[1]&nbsp;<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/solved-and-unsolved.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Solved Problems, Unsolved Problems and Problems in Concurrency</a>,&nbsp;Leslie Lamport, 1983</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[2]&nbsp;<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/byz.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">The Byzantine Generals Problem</a>,&nbsp;Leslie Lamport,Robert Shostak and Marshall Pease, 1982</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[3]&nbsp;<a href="http://cs-www.cs.yale.edu/homes/arvind/cs425/doc/fischer.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Impossibility of Distributed Consensus with One Faulty Process</a>,&nbsp;Fischer, Lynch and Patterson, 1985</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[4]&nbsp;<a href="http://danielw.cn/FLP-proof/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">FLP Impossibility的证明</a>,&nbsp;Daniel Wu, 2015</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[5]&nbsp;<a href="http://the-paper-trail.org/blog/consensus-protocols-two-phase-commit/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Consensus Protocols: Two-Phase Commit</a>,&nbsp;Henry Robinson, 2008</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[6]&nbsp;<a href="http://the-paper-trail.org/blog/consensus-protocols-three-phase-commit/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Consensus Protocols: Three-phase Commit</a>,&nbsp;Henry Robinson, 2008</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[7]&nbsp;<a href="https://en.wikipedia.org/wiki/Three-phase_commit_protocol" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Three-phase commit protocol</a>,&nbsp;Wikipedia</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432283.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 15:02 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CoreOS 实战：剖析 etcd</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 06:12:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432282.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432282.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432282.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://www.infoq.com/cn/articles/coreos-analyse-etcd/【编者按】CoreOS是一个基于Docker的轻量级容器化Linux发行版，专为大型数据中心而设计，旨在通过轻量的系统架构和灵活的应用程序部署能力简化数据中心的维护成本和复杂度。CoreOS作为Docker生态圈中的重要一员，日益得到各大云服务商的重视，目前已经完成了A轮融资，发展风头正劲。I...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432282.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 14:12 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>水平分库分表的关键步骤以及可能遇到的问题</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 17 Jan 2017 06:29:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432269.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432269.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432269.html</trackback:ping><description><![CDATA[<div><div>http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-split-table</div><div>http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table</div><br /><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在谈论数据库架构和数据库优化的时候，我们经常会听到&#8220;分库分表&#8221;、&#8220;分片&#8221;、&#8220;Sharding&#8221;&#8230;这样的关键词。让人感到高兴的是，这些朋友所服务的公司业务量正在（或者即将面临）高速增长，技术方面也面临着一些挑战。让人感到担忧的是，他们系统真的就需要&#8220;分库分表&#8221;了吗？&#8220;分库分表&#8221;有那么容易实践吗？为此，笔者整理了分库分表中可能遇到的一些问题，并结合以往经验介绍了对应的解决思路和建议。</p><h2>垂直分表</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">垂直分表在日常开发和设计中比较常见，通俗的说法叫做&#8220;大表拆小表&#8221;，拆分是基于关系型数据库中的&#8220;列&#8221;（字段）进行的。通常情况，某个表中的字段比较多，可以新建立一张&#8220;扩展表&#8221;，将不经常使用或者长度较大的字段拆分出去放到&#8220;扩展表&#8221;中，如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/20.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>小结</h3><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在字段很多的情况下，拆分开确实更便于开发和维护（笔者曾见过某个遗留系统中，一个大表中包含100多列的）。某种意义上也能避免&#8220;跨页&#8221;的问题（MySQL、MSSQL底层都是通过&#8220;数据页&#8221;来存储的，&#8220;跨页&#8221;问题可能会造成额外的性能开销，这里不展开，感兴趣的朋友可以自行查阅相关资料进行研究）。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分，则需要改写以前的查询语句，会额外带来一定的成本和风险，建议谨慎。</p><h2>垂直分库</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">垂直分库在&#8220;微服务&#8221;盛行的今天已经非常普及了。基本的思路就是按照业务模块来划分出不同的数据库，而不是像早期一样将所有的数据表都放到同一个数据库中。如下图：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/21.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>小结</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">系统层面的&#8220;服务化&#8221;拆分操作，能够解决业务系统层面的耦合和性能瓶颈，有利于系统的扩展维护。而数据库层面的拆分，道理也是相通的。与服务的&#8220;治理&#8221;和&#8220;降级&#8221;机制类似，我们也能对不同业务类型的数据进行&#8220;分级&#8221;管理、维护、监控、扩展等。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">众所周知，数据库往往最容易成为应用系统的瓶颈，而数据库本身属于&#8220;有状态&#8221;的，相对于Web和应用服务器来讲，是比较难实现&#8220;横向扩展&#8221;的。数据库的连接资源比较宝贵且单机处理能力也有限，在高并发场景下，垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈，是大型分布式系统中优化数据库架构的重要手段。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">然后，很多人并没有从根本上搞清楚为什么要拆分，也没有掌握拆分的原则和技巧，只是一味的模仿大厂的做法。导致拆分后遇到很多问题（例如：跨库join，分布式事务等）。</p><h2>水平分表</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">水平分表也称为横向分表，比较容易理解，就是将表中不同的数据行按照一定规律分布到不同的数据库表中（这些表保存在同一个数据库中），这样来降低单表数据量，优化查询性能。最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/22.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>小结</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">水平分表，能够降低单表的数据量，一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中，所以库级别还是会有IO瓶颈。所以，一般不建议采用这种做法。</p><h2>水平分库分表</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">水平分库分表与上面讲到的水平分表的思想相同，唯一不同的就是将这些拆分出来的表保存在不同的数据中。这也是很多大型互联网公司所选择的做法。如下图：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/23.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">某种意义上来讲，有些系统中使用的&#8220;冷热数据分离&#8221;（将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上，通常默认只提供热点数据的查询），也是类似的实践。在高并发和海量数据的场景下，分库分表能够有效缓解单机和单库的性能瓶颈和压力，突破IO、连接数、硬件资源的瓶颈。当然，投入的硬件成本也会更高。同时，这也会带来一些复杂的技术问题和挑战（例如：跨分片的复杂查询，跨分片事务等）</p><h3>分库分表的难点</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">垂直分库带来的问题和解决思路：</p><h3>跨库join的问题</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在拆分之前，系统中很多列表和详情页所需的数据是可以通过sql join来完成的。而拆分后，数据库可能是分布式在不同实例和不同的主机上，join将变得非常麻烦。而且基于架构规范，性能，安全性等方面考虑，一般是禁止跨库join的。那该怎么办呢？首先要考虑下垂直分库的设计问题，如果可以调整，那就优先调整。如果无法调整的情况，下面笔者将结合以往的实际经验，总结几种常见的解决思路，并分析其适用场景。</p><h3>跨库Join的几种解决思路</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">全局表</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">所谓全局表，就是有可能系统中所有模块都可能会依赖到的一些表。比较类似我们理解的&#8220;数据字典&#8221;。为了避免跨库join查询，我们可以将这类表在其他每个数据库中均保存一份。同时，这类数据通常也很少发生修改（甚至几乎不会），所以也不用太担心&#8220;一致性&#8221;问题。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">字段冗余</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">这是一种典型的反范式设计，在互联网行业中比较常见，通常是为了性能来避免join查询。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">举个电商业务中很简单的场景：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">&#8220;订单表&#8221;中保存&#8220;卖家Id&#8221;的同时，将卖家的&#8220;Name&#8221;字段也冗余，这样查询订单详情的时候就不需要再去查询&#8220;卖家用户表&#8221;。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">字段冗余能带来便利，是一种&#8220;空间换时间&#8221;的体现。但其适用场景也比较有限，比较适合依赖字段较少的情况。最复杂的还是数据一致性问题，这点很难保证，可以借助数据库中的触发器或者在业务代码层面去保证。当然，也需要结合实际业务场景来看一致性的要求。就像上面例子，如果卖家修改了Name之后，是否需要在订单信息中同步更新呢？</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">数据同步</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">定时A库中的tab_a表和B库中tbl_b有关联，可以定时将指定的表做同步。当然，同步本来会对数据库带来一定的影响，需要性能影响和数据时效性中取得一个平衡。这样来避免复杂的跨库查询。笔者曾经在项目中是通过ETL工具来实施的。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">系统层组装</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在系统层面，通过调用不同模块的组件或者服务，获取到数据并进行字段拼装。说起来很容易，但实践起来可真没有这么简单，尤其是数据库设计上存在问题但又无法轻易调整的时候。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">具体情况通常会比较复杂。下面笔者结合以往实际经验，并通过伪代码方式来描述。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">简单的列表查询的情况</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/24.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">伪代码很容易理解，先获取&#8220;我的提问列表&#8221;数据，然后再根据列表中的UserId去循环调用依赖的用户服务获取到用户的RealName，拼装结果并返回。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">有经验的读者一眼就能看出上诉伪代码存在效率问题。循环调用服务，可能会有循环RPC，循环查询数据库&#8230;不推荐使用。再看看改进后的：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/25.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">这种实现方式，看起来要优雅一点，其实就是把循环调用改成一次调用。当然，用户服务的数据库查询中很可能是In查询，效率方面比上一种方式更高。（坊间流传In查询会全表扫描，存在性能问题，传闻不可全信。其实查询优化器都是基本成本估算的，经过测试，在In语句中条件字段有索引的时候，条件较少的情况是会走索引的。这里不细展开说明，感兴趣的朋友请自行测试）。</p><h3>小结</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">简单字段组装的情况下，我们只需要先获取&#8220;主表&#8221;数据，然后再根据关联关系，调用其他模块的组件或服务来获取依赖的其他字段（如例中依赖的用户信息），最后将数据进行组装。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">通常，我们都会通过缓存来避免频繁RPC通信和数据库查询的开销。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">列表查询带条件过滤的情况</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在上述例子中，都是简单的字段组装，而不存在条件过滤。看拆分前的SQL：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/26.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">这种连接查询并且还带条件过滤的情况，想在代码层面组装数据其实是非常复杂的（尤其是左表和右表都带条件过滤的情况会更复杂），不能像之前例子中那样简单的进行组装了。试想一下，如果像上面那样简单的进行组装，造成的结果就是返回的数据不完整，不准确。&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">有如下几种解决思路：</p><ol style="margin: 10px 0px 10px 10px; padding: 0px 0px 0px 20px; border: 0px; width: 549px; clear: left; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">查出所有的问答数据，然后调用用户服务进行拼装数据，再根据过滤字段state字段进行过滤，最后进行排序和分页并返回。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">这种方式能够保证数据的准确性和完整性，但是性能影响非常大，不建议使用。</p></li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">查询出state字段符合/不符合的UserId，在查询问答数据的时候使用in/not in进行过滤，排序，分页等。过滤出有效的问答数据后，再调用用户服务获取数据进行组装。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">这种方式明显更优雅点。笔者之前在某个项目的特殊场景中就是采用过这种方式实现。</p></li></ol><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">跨库事务（分布式事务）的问题</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">按业务拆分数据库之后，不可避免的就是&#8220;分布式事务&#8221;的问题。以往在代码中通过spring注解简单配置就能实现事务的，现在则需要花很大的成本去保证一致性。这里不展开介绍，&nbsp;<br style="margin: 0px; border: 0px; padding: 0px;" />感兴趣的读者可以自行参考《分布式事务一致性解决方案》，链接地址：&nbsp;<br style="margin: 0px; border: 0px; padding: 0px;" />http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency</p><h2>垂直分库总结和实践建议</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">本篇中主要描述了几种常见的拆分方式，并着重介绍了垂直分库带来的一些问题和解决思路。读者朋友可能还有些问题和疑惑。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">1. 我们目前的数据库是否需要进行垂直分库？</span></p><blockquote style="margin-top: 0px; margin-right: 0px; margin-left: 0px; padding-top: 10px; padding-bottom: 10px; padding-left: 45px; border-width: 1px; border-color: #e8e8e8; clear: both; width: 553px; color: #000000; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; float: none !important; background-image: url(&quot;i/gg.jpg&quot;); background-color: #f4f4f4; background-position: 0% 0%; background-repeat: no-repeat;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">根据系统架构和公司实际情况来，如果你们的系统还是个简单的单体应用，并且没有什么访问量和数据量，那就别着急折腾&#8220;垂直分库&#8221;了，否则没有任何收益，也很难有好结果。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">切记，&#8220;过度设计&#8221;和&#8220;过早优化&#8221;是很多架构师和技术人员常犯的毛病。</p></blockquote><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">2. 垂直拆分有没有原则或者技巧？</span></p><blockquote style="margin-top: 0px; margin-right: 0px; margin-left: 0px; padding-top: 10px; padding-bottom: 10px; padding-left: 45px; border-width: 1px; border-color: #e8e8e8; clear: both; width: 553px; color: #000000; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; float: none !important; background-image: url(&quot;i/gg.jpg&quot;); background-color: #f4f4f4; background-position: 0% 0%; background-repeat: no-repeat;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">没有什么黄金法则和标准答案。一般是参考系统的业务模块拆分来进行数据库的拆分。比如&#8220;用户服务&#8221;，对应的可能就是&#8220;用户数据库&#8221;。但是也不一定严格一一对应。有些情况下，数据库拆分的粒度可能会比系统拆分的粒度更粗。笔者也确实见过有些系统中的某些表原本应该放A库中的，却放在了B库中。有些库和表原本是可以合并的，却单独保存着。还有些表，看起来放在A库中也OK，放在B库中也合理。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">如何设计和权衡，这个就看实际情况和架构师/开发人员的水平了。</p></blockquote><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">3. 上面举例的都太简单了，我们的后台报表系统中join的表都有n个了，&nbsp;<br style="margin: 0px; border: 0px; padding: 0px;" /></span><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">分库后该怎么查？</span></p><blockquote style="margin-top: 0px; margin-right: 0px; margin-left: 0px; padding-top: 10px; padding-bottom: 10px; padding-left: 45px; border-width: 1px; border-color: #e8e8e8; clear: both; width: 553px; color: #000000; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; float: none !important; background-image: url(&quot;i/gg.jpg&quot;); background-color: #f4f4f4; background-position: 0% 0%; background-repeat: no-repeat;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">有很多朋友跟我提过类似的问题。其实互联网的业务系统中，本来就应该尽量避免join的，如果有多个join的，要么是设计不合理，要么是技术选型有误。请自行科普下OLAP和OLTP，报表类的系统在传统BI时代都是通过OLAP数据仓库去实现的（现在则更多是借助离线分析、流式计算等手段实现），而不该向上面描述的那样直接在业务库中执行大量join和统计。</p></blockquote><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">由于篇幅关系，下篇中我们再继续细聊&#8220;水平分库分表&#8221;相关的话题。&nbsp;</p><div><ul style="margin: 0px 0px 20px; padding: 5px 0px; list-style: none; border-width: 1px 0px; border-top-style: solid; border-bottom-style: solid; border-top-color: #ebf0f3; border-bottom-color: #ebf0f3; float: left; clear: both; width: 969px; position: relative; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><div bdshare-button-style0-16"="" data-bd-bind="1484634439652" style="margin: 0px; border: 0px; zoom: 1;"><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="more" style="text-decoration: none; color: #333333; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px 0px; background-repeat: no-repeat;">分享到：</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="tsina" title="分享到微博" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -104px; background-repeat: no-repeat;">微博</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="weixin" title="分享到微信" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1612px; background-repeat: no-repeat;">微信</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="fbook" title="分享到Facebook" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1352px; background-repeat: no-repeat;">Facebook</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="twi" title="分享到Twitter" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1404px; background-repeat: no-repeat;">Twitter</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="youdao" title="分享到有道云笔记" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2080px; background-repeat: no-repeat;">有道云笔记</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="mail" title="分享到邮件分享" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2340px; background-repeat: no-repeat;">邮件分享</a></div></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a id="bookmarkBtn" href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" title="" style="text-decoration: none; color: #ffffff; margin: -2px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/read_later_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-right-color: #0c3b6f; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/read_later_right.jpg&quot;) 0% 50% repeat-x;">稍后阅读</q></a></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a href="http://www.infoq.com/cn/showbookmarks.action" style="text-decoration: none; color: #133660; margin: -2px 104px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/reading_list_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-width: 0px 1px 0px 0px; border-right-style: solid; border-right-color: #afc0c7; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/reading_list_right.jpg&quot;) 0% 50% repeat-x;">我的阅读清单</q></a></li></ul><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"></div><div text_content_container"="" style="margin: 0px; border: 0px; float: left; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><div text_info_article"="" style="margin: 10px 0px; border: 0px; float: left; clear: both; width: 610px; line-height: 1.8;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><a href="http://mp.weixin.qq.com/s?__biz=MzA5Nzc4OTA1Mw==&amp;mid=2659598135&amp;idx=1&amp;sn=2f1daf51d92b9c5ed06d9422fdd19d49&amp;scene=21#wechat_redirect" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">在之前的文章中</a>，我介绍了分库分表的几种表现形式和玩法，也重点介绍了垂直分库所带来的问题和解决方法。本篇中，我们将继续聊聊水平分库分表的一些技巧。</p><h2>分片技术的由来</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">关系型数据库本身比较容易成为系统性能瓶颈，单机存储容量、连接数、处理能力等都很有限，数据库本身的&#8220;有状态性&#8221;导致了它并不像Web和应用服务器那么容易扩展。在互联网行业海量数据和高并发访问的考验下，聪明的技术人员提出了分库分表技术（有些地方也称为Sharding、分片）。同时，流行的分布式系统中间件（例如MongoDB、ElasticSearch等）均自身友好支持Sharding，其原理和思想都是大同小异的。</p><h2>分布式全局唯一ID</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在很多中小项目中，我们往往直接使用数据库自增特性来生成主键ID，这样确实比较简单。而在分库分表的环境中，数据分布在不同的分片上，不能再借助数据库自增长特性直接生成，否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种ID生成算法。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">&nbsp;</p><ol style="margin: 10px 0px 10px 10px; padding: 0px 0px 0px 20px; border: 0px; width: 549px; clear: left;"><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">Twitter的Snowflake（又名&#8220;雪花算法&#8221;）</li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">UUID/GUID（一般应用程序和数据库均支持）</li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">MongoDB ObjectID（类似UUID的方式）</li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">Ticket Server（数据库生存方式，Flickr采用的就是这种方式）</li></ol><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">其中，Twitter 的Snowflake算法是笔者近几年在分布式系统项目中使用最多的，未发现重复或并发的问题。该算法生成的是64位唯一Id（由41位的timestamp+ 10位自定义的机器码+ 13位累加计数器组成）。这里不做过多介绍，感兴趣的读者可自行查阅相关资料。</p><h2>常见分片规则和策略</h2><h3>分片字段该如何选择</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在开始分片之前，我们首先要确定分片字段（也可称为&#8220;片键&#8221;）。很多常见的例子和场景中是采用ID或者时间字段进行拆分。这也并不绝对的，我的建议是结合实际业务，通过对系统中执行的sql语句进行统计分析，选择出需要分片的那个表中最频繁被使用，或者最重要的字段来作为分片字段。</p><h3>常见分片规则</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">常见的分片策略有随机分片和连续分片这两种，如下图所示：</p><div id="lowerFullwidthVCR" style="margin: 0px; border: 0px;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/10.png" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">当需要使用分片字段进行范围查找时，连续分片可以快速定位分片进行高效查询，大多数情况下可以有效避免跨分片查询的问题。后期如果想对整个分片集群扩容时，只需要添加节点即可，无需对其他分片的数据进行迁移。但是，连续分片也有可能存在数据热点的问题，就像图中按时间字段分片的例子，有些节点可能会被频繁查询压力较大，热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据，很少需要被查询到。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">随机分片其实并不是随机的，也遵循一定规则。通常，我们会采用Hash取模的方式进行分片拆分，所以有些时候也被称为离散分片。随机分片的数据相对比较均匀，不容易出现热点和并发访问的瓶颈。但是，后期分片集群扩容起来需要迁移旧的数据。使用一致性Hash算法能够很大程度的避免这个问题，所以很多中间件的分片集群都会采用一致性Hash算法。离散分片也很容易面临跨分片查询的复杂问题。</p><h3>数据迁移，容量规划，扩容等问题</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">很少有项目会在初期就开始考虑分片设计的，一般都是在业务高速发展面临性能和存储的瓶颈时才会提前准备。因此，不可避免的就需要考虑历史数据迁移的问题。一般做法就是通过程序先读出历史数据，然后按照指定的分片规则再将数据写入到各个分片节点中。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">此外，我们需要根据当前的数据量和QPS等进行容量规划，综合成本因素，推算出大概需要多少分片（一般建议单个分片上的单表数据量不要超过1000W）。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">如果是采用随机分片，则需要考虑后期的扩容问题，相对会比较麻烦。如果是采用的范围分片，只需要添加节点就可以自动扩容。</p><h2>跨分片技术问题</h2><h3>跨分片的排序分页</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">一般来讲，分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候，我们通过分片规则可以比较容易定位到指定的分片，而当排序字段非分片字段的时候，情况就会变得比较复杂了。为了最终结果的准确性，我们需要在不同的分片节点中将数据进行排序并返回，并将不同分片返回的结果集进行汇总和再次排序，最后再返回给用户。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/11.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">上面图中所描述的只是最简单的一种情况（取第一页数据），看起来对性能的影响并不大。但是，如果想取出第10页数据，情况又将变得复杂很多，如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/12.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">有些读者可能并不太理解，为什么不能像获取第一页数据那样简单处理（排序取出前10条再合并、排序）。其实并不难理解，因为各分片节点中的数据可能是随机的，为了排序的准确性，必须把所有分片节点的前N页数据都排序好后做合并，最后再进行整体的排序。很显然，这样的操作是比较消耗资源的，用户越往后翻页，系统性能将会越差。</p><h3>跨分片的函数处理</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候，需要先在每个分片数据源上执行相应的函数处理，然后再将各个结果集进行二次处理，最终再将处理结果返回。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/13.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>跨分片join</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">Join是关系型数据库中最常用的特性，但是在分片集群中，join也变得非常复杂。应该尽量避免跨分片的join查询（这种场景，比上面的跨分片分页更加复杂，而且对性能的影响很大）。通常有以下几种方式来避免：</p><h3>全局表</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">全局表的概念之前在&#8220;垂直分库&#8221;时提过。基本思想一致，就是把一些类似数据字典又可能会产生join查询的表信息放到各分片中，从而避免跨分片的join。</p><h3>ER分片</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在关系型数据库中，表之间往往存在一些关联的关系。如果我们可以先确定好关联关系，并将那些存在关联关系的表记录存放在同一个分片上，那么就能很好的避免跨分片join问题。在一对多关系的情况下，我们通常会选择按照数据较多的那一方进行拆分。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/14.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这样一来，Data Node1上面的订单表与订单详细表就可以直接关联，进行局部的join查询了，Data Node2上也一样。基于ER分片的这种方式，能够有效避免大多数业务场景中的跨分片join问题。</p><h3>内存计算</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">随着spark内存计算的兴起，理论上来讲，很多跨数据源的操作问题看起来似乎都能够得到解决。可以将数据丢给spark集群进行内存计算，最后将计算结果返回。</p><h2>跨分片事务问题</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">跨分片事务也分布式事务，想要了解分布式事务，就需要了解&#8220;XA接口&#8221;和&#8220;两阶段提交&#8221;。值得提到的是，MySQL5.5x和5.6x中的xa支持是存在问题的，会导致主从数据不一致。直到5.7x版本中才得到修复。Java应用程序可以采用Atomikos框架来实现XA事务（J2EE中JTA）。感兴趣的读者可以自行参考《分布式事务一致性解决方案》，链接地址：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency</p><h2>我们的系统真的需要分库分表吗</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">读完上面内容，不禁引起有些读者的思考，我们的系统是否需要分库分表吗？</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">其实这点没有明确的判断标准，比较依赖实际业务情况和经验判断。依照笔者个人的经验，一般MySQL单表1000W左右的数据是没有问题的（前提是应用系统和数据库等层面设计和优化的比较好）。当然，除了考虑当前的数据量和性能情况时，作为架构师，我们需要提前考虑系统半年到一年左右的业务增长情况，对数据库服务器的QPS、连接数、容量等做合理评估和规划，并提前做好相应的准备工作。如果单机无法满足，且很难再从其他方面优化，那么说明是需要考虑分片的。这种情况可以先去掉数据库中自增ID，为分片和后面的数据迁移工作提前做准备。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">很多人觉得&#8220;分库分表&#8221;是宜早不宜迟，应该尽早进行，因为担心越往后公司业务发展越快、系统越来越复杂、系统重构和扩展越困难&#8230;这种话听起来是有那么一点道理，但我的观点恰好相反，对于关系型数据库来讲，我认为&#8220;能不分片就别分片&#8221;，除非是系统真正需要，因为数据库分片并非低成本或者免费的。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这里笔者推荐一个比较靠谱的过渡技术&#8211;&#8220;表分区&#8221;。主流的关系型数据库中基本都支持。不同的分区在逻辑上仍是一张表，但是物理上却是分开的，能在一定程度上提高查询性能，而且对应用程序透明，无需修改任何代码。笔者曾经负责优化过一个系统，主业务表有大约8000W左右的数据，考虑到成本问题，当时就是采用&#8220;表分区&#8221;来做的，效果比较明显，且系统运行的很稳定。</p><h2>小结</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">最后，有很多读者都想了解当前社区中有没有开源免费的分库分表解决方案，毕竟站在巨人的肩膀上能省力很多。当前主要有两类解决方案：</p><ol style="margin: 10px 0px 10px 10px; padding: 0px 0px 0px 20px; border: 0px; width: 549px; clear: left;"><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">基于应用程序层面的DDAL（分布式数据库访问层）&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">比较典型的就是淘宝半开源的TDDL，当当网开源的Sharding-JDBC等。分布式数据访问层无需硬件投入，技术能力较强的大公司通常会选择自研或参照开源框架进行二次开发和定制。对应用程序的侵入性一般较大，会增加技术成本和复杂度。通常仅支持特定编程语言平台（Java平台的居多），或者仅支持特定的数据库和特定数据访问框架技术（一般支持MySQL数据库，JDBC、MyBatis、Hibernate等框架技术）。</p></li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">数据库中间件，比较典型的像mycat（在阿里开源的cobar基础上做了很多优化和改进，属于后起之秀，也支持很多新特性），基于Go语言实现kingSharding，比较老牌的Atlas（由360开源）等。这些中间件在互联网企业中大量被使用。另外，MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术，不过国内使用的企业较少。&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">中间件也可以称为&#8220;透明网关&#8221;，大名鼎鼎的mysql_proxy大概是该领域的鼻祖（由MySQL官方提供，仅限于实现&#8220;读写分离&#8221;）。中间件一般实现了特定数据库的网络通信协议，模拟一个真实的数据库服务，屏蔽了后端真实的Server，应用程序通常直接连接中间件即可。而在执行SQL操作时，中间件会按照预先定义分片规则，对SQL语句进行解析、路由，并对结果集做二次计算再最终返回。引入数据库中间件的技术成本更低，对应用程序来讲侵入性几乎没有，可以满足大部分的业务。增加了额外的硬件投入和运维成本，同时，中间件自身也存在性能瓶颈和单点故障问题，需要能够保证中间件自身的高可用、可扩展。</p></li></ol><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">总之，不管是使用分布式数据访问层还是数据库中间件，都会带来一定的成本和复杂度，也会有一定的性能影响。所以，还需读者根据实际情况和业务发展需要慎重考虑和选择。</p></div></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432269.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-17 14:29 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>微信开源：生产级paxos类库PhxPaxos实现原理介绍</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 17 Jan 2017 03:35:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432267.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432267.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432267.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://www.infoq.com/cn/articles/weinxin-open-source-paxos-phxpaxos微信重磅开源生产级Paxos类库PhxPaxos！本文将用科普的口吻向大家介绍PhxPaxos背后的实现原理以及一些有趣的细节。本文由微信后台团队授权转载，ID：gh_93b1115dc96f开源地址：https://github.com/tencent-wecha...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432267.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-17 11:35 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>蘑菇街每秒订单数25倍提升历程</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 17 Jan 2017 03:34:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432266.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432266.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432266.html</trackback:ping><description><![CDATA[<div>http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance<br /><br /><div style="margin: 0px; border: 0px; display: flex; flex-flow: row nowrap; justify-content: space-between; align-items: center; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"></div><span style="margin: 0px 0px 10px; border: 0px; padding: 0px; color: #666666; font-size: 12px; float: left; display: block; clear: both; width: 969px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;">作者&nbsp;<ul style="margin: 0px; padding: 0px; list-style: none; border: 0px; display: inline;"><li style="margin: 0px; padding: 0px; border: 0px; float: none; clear: none; display: inline;"><a f_taxonomyeditor"="" href="http://www.infoq.com/cn/author/%E4%B8%83%E5%85%AC" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px; font-weight: 600;">七公</a></li>&nbsp;</ul>发布于 2016年12月27日&nbsp;<span style="margin: 0px; border: 0px; padding: 0px;">|</span>&nbsp;<span style="margin: 1px 0px 0px; border: 0px; padding: 5px; float: right; background-color: #fff8cc;">欲知区块链、VR、TensorFlow等潮流技术和框架，请锁定<a href="http://2017.qconbeijing.com/?utm_source=infoq&amp;utm_medium=notices&amp;utm_campaign=seven" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px; font-weight: 600;">QCon北京站！</a></span><a id="noOfComments" title="" href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#theCommentsSection" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px; font-weight: 600;"><span style="margin: -3px 0px 0px 7px; border: 0px; padding: 0px 1px 0px 5px; display: inline-block; font-family: Arial, Helvetica, sans-serif; font-weight: normal; position: relative; bottom: -5px; background: url(&quot;i/bg_comment_left.png&quot;) 0% 0% no-repeat, url(&quot;i/bg_comment_right.png&quot;) 100% 0% no-repeat;"><span style="margin: 0px; border-width: 1px 0px; border-top-style: solid; border-bottom-style: solid; border-top-color: #ccdceb; border-bottom-color: #ccdceb; padding: 1px 5px 0px; float: left; color: #000000; text-align: center; line-height: 15px; display: block; position: relative;">1</span></span>&nbsp;讨论</a></span><ul style="margin: 0px 0px 20px; padding: 5px 0px; list-style: none; border-width: 1px 0px; border-top-style: solid; border-bottom-style: solid; border-top-color: #ebf0f3; border-bottom-color: #ebf0f3; float: left; clear: both; width: 969px; position: relative; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><div bdshare-button-style0-16"="" data-bd-bind="1484557566087" style="margin: 0px; border: 0px; zoom: 1;"><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="more" style="text-decoration: none; color: #333333; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px 0px; background-repeat: no-repeat;">分享到：</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="tsina" title="分享到微博" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -104px; background-repeat: no-repeat;">微博</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="weixin" title="分享到微信" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1612px; background-repeat: no-repeat;">微信</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="fbook" title="分享到Facebook" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1352px; background-repeat: no-repeat;">Facebook</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="twi" title="分享到Twitter" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1404px; background-repeat: no-repeat;">Twitter</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="youdao" title="分享到有道云笔记" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2080px; background-repeat: no-repeat;">有道云笔记</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="mail" title="分享到邮件分享" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2340px; background-repeat: no-repeat;">邮件分享</a></div></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a id="bookmarkBtn" href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" title="" style="text-decoration: none; color: #ffffff; margin: -2px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/read_later_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-right-color: #0c3b6f; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/read_later_right.jpg&quot;) 0% 50% repeat-x;">稍后阅读</q></a></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a href="http://www.infoq.com/cn/showbookmarks.action" style="text-decoration: none; color: #133660; margin: -2px 104px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/reading_list_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-width: 0px 1px 0px 0px; border-right-style: solid; border-right-color: #afc0c7; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/reading_list_right.jpg&quot;) 0% 50% repeat-x;">我的阅读清单</q></a></li></ul><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"></div><div text_content_container"="" style="margin: 0px; border: 0px; float: left; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><div text_info_article"="" style="margin: 10px 0px; border: 0px; float: left; clear: both; width: 610px; line-height: 1.8;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">本文根据白辉在2016ArchSummit全球架构师（深圳）峰会上的演讲整理而成。ArchSummit北京站即将在12月2日开幕，更多专题讲师信息请到<a href="http://bj2016.archsummit.com/" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">北京站官网</a>查询。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">非常荣幸在这里跟大家一起来探讨&#8220;海量服务架构探索&#8221;相关专题的内容。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我叫白辉，花名是七公。2014年之前主要在阿里B2B负责资金中心、评价、任务中心等系统。2015年加入蘑菇街，随着蘑菇街的飞速成长，经历了网站技术架构的大</p><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">变革。今天分享的内容来自于去年我们做的事情，题目用了一个关键词是&#8220;篱笆&#8221;，篱笆的英文是Barrier，是指2015年蘑菇街面临的问题和艰巨的困难。我们越过了这些篱笆，取得了很好的成果。</p><h2>引言</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/40.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">今天分享的内容主要分为五部分。第一部分，概述电商系统发展中期面临的一般性问题。第二部分，如何解决面临的问题，主要的策略是做拆分、做服务化。第三、四部分，服务化之后业务的大增长、网站流量飞速的增加、&#8220;双11&#8221;大促等的挑战很大，我们做了服务的专项系统优化以及稳定性治理。第五部分，进行了总结和展望。</p><h2>电商系统发展中期面临的一般性问题</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们先看第一部分的内容。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/41.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我总结了一下，一般电商系统发展到中期都会面临三个方面的问题（如图）。第一方面是业务问题。比如，一开始做业务的时候可能很随意，一是并不考虑业务模型、系统架构，二是业务之间的耦合比较严重，比如交易和资金业务，有可能资金和外部第三方支付公司的交互状态耦合在交易系统里，这些非常不利于业务发展。第二方面是系统问题。2014年我们面临单体应用，400人开发一个大应用，扩展性很差，业务比较难做。第三方面是支撑问题，比如关于环境、开发框架和质量工具等。这些是电商系统发展到中期都会面临的问题，中期的概念是用户过了千万，PV过了1亿。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/42.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们来看一下蘑菇街2015年初面临的问题。蘑菇街2015年用户过亿，PV过10亿，业务在超高速发展，每年保持3倍以上的增长。电商促销、交易、支付等业务形态都在快速膨胀，我们需要快速支持业务发展，而不是成为业务的瓶颈。那么就是要去做系统的拆分和服务化。</p><h2>系统拆分与服务化过程</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">第二部分的内容，是关于蘑菇街系统拆分与服务化的历程。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">按照如下几条思路（见图），我们进行系统拆分以及服务化。最开始，大家在同一个应用里开发一些业务功能，都是选择速度最快的方式，所有的DB和业务代码都是在一起的。首先我们将DB做垂直拆分。第二步是做业务系统垂直拆分，包括交易、资金等。第三步是在系统拆完了之后要考虑提供什么样的API来满足业务的需求？这里我们要做数据建模+业务建模，数据建模方面包括数据表的设计和扩展支持，数据模型应该非常稳定；业务建模方面，使用标准和灵活的API，而且尽量不用修改代码或者改少量代码就能支持业务需求。第四步是需要将业务逻辑下沉到服务，Web层专注于展示逻辑和编排，不要涉及过多业务的事情。然后用SOA中间件建设服务化系统。最后会做一些服务的治理。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/43.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">来看一个API服务化的例子，在做服务化之前和做服务化之后，交易创建下单业务有什么不一样。服务化之前我们面临的问题有：入口分散，如果要在底层做任何一个微小的改动，十几个入口需要几十个人配合修改，这是非常不合理的一种方式；多端维护多套接口，成本非常高；还有稳定性的问题，依赖非常复杂，维护很难。我刚到蘑菇街的时候，一次大促活动就导致数据库崩溃，暴露了系统架构很大的问题和总量上的瓶颈。按照上面提到几条思路去做服务化，看看有了哪些改善？首先是API统一，多个端、多个业务都用统一的API提供；其次是依赖有效管理起来，大事务拆分成多个本地小事务；最后降低了链路风险，逻辑更加清晰，稳定性更好。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/44.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">2015年3月我来到蘑菇街之后，先制订了服务化的规范，探讨了到底什么是标准的服务化。在做服务化的过程中，发现大家代码风格完全不一样，所以制定编码规范非常重要。2015年8月，我们完成了各个模块的改造，包括用户、商品、交易、订单、促销、退款等，然后有了服务化架构1.0的体系。在此基础之上，我们进一步做了提升流量和稳定性等更深度的建设。2015年9月，我们实施了分库分表和链路性能提升优化，2015年10月做了服务治理和服务保障。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/45.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">接下来，以服务架构和服务体系建设为主线，讲一下去年整个网站架构升级的过程。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/46.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在服务化1.0体系完成之后，我们得到了一个简单的体系，包含下单服务、营销服务、店铺服务、商品服务和用户服务，还有简单的RPC框架Tesla。当时，我们并没有做很多性能优化的事情，但是通过业务流程化简和逻辑优化，每秒最大订单数从400提升到1K，基础服务也都搭建了起来。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/47.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">有了1.0初步的服务化体系之后，更进一步，我们一是要继续深入网站如资金等的服务化，二是要做服务内部的建设，比如容量、性能，这也是接下来要讲的内容。</p><h2>购买链路的性能提升</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/48.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这个链路（见图）是比较典型的电商链路，有商品页、下单、支付、营销和库存等内容。一开始每个点都有瓶颈，每个瓶颈都是一个篱笆，我们要正视它，然后翻越它。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/49.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们先来看第一个篱笆墙：下单的瓶颈。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">2015年&#8220;3.21&#8221;大促的时候，DB崩溃了，这个瓶颈很难突破。下一个订单要插入很多条数据记录到单DB的DB表。我们已经用了最好的硬件，但是瓶颈依然存在，最主要的问题就是DB单点，需要去掉单点，做成可水平扩展的。流量上来了，到DB的行写入数是2万/秒，对DB的压力很大。写应该控制在一个合理的量，DB负载维持在较低水平，主从延时也才会在可控范围内。所以DB单点的问题非常凸显，这座大山必须迈过去，我们做了一个分库分表组件TSharding来实施分库分表。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/50.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">将我们写的分库分表工具与业界方案对比，业界有淘宝TDDL Smart Client的方式，还有Google的Vitess等的Proxy方式，这两种成熟方案研发和运维的成本都太高，短期内我们接受不了，所以借鉴了Mybatis Plugin的方式，但Mybatis Plugin不支持数据源管理，也不支持事务。我大概花了一周时间写了一个组件&#8212;&#8212;自研分库分表组件TSharding（<a href="https://github.com/baihui212/tsharding" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">https://github.com/baihui212/tsharding</a>），然后快速做出方案，把这个组件应用到交易的数据库，在服务层和DAO层，订单容量扩展到千亿量级，并且可以继续水平扩展。TSharding上线一年之后，我们将其开放出来。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/51.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">第二个篱笆墙就是营销服务RT的问题。促销方式非常多，包括各种红包、满减、打折、优惠券等。实际上促销的接口逻辑非常复杂，在&#8220;双11&#8221;备战的时候，面对这个复杂的接口，每轮链路压测促销服务都会发现问题，之后优化再压测，又发现新的问题。我们来一起看看遇到的各种问题以及是如何解决的。首先是压测出现接口严重不可用，这里可以看到DB查询频次高，响应很慢，流量一上来，这个接口就崩溃了。那怎么去排查原因和解决呢？</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">首先是SQL优化，用工具识别慢SQL，即全链路跟踪系统Lurker。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/52.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这张图我简单介绍一下。遇到SQL执行效率问题的时候，就看是不是执行到最高效的索引，扫表行数是不是很大，是不是有filesort。有ORDER BY的时候，如果要排序的数据量不大或者已经有索引可以走到，在数据库的内存排序缓存区一次就可以排序完。如果一次不能排序完，那就先拿到1000个做排序，然后输出到文件，然后再对下1000个做排序，最后再归并起来，这就是filesort的大致过程，效率比较低。所以尽量要走上索引，一般类的查询降低到2毫秒左右可以返回。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">其次是要读取很多优惠规则和很多优惠券，数据量大的时候DB是很难扛的，这时候我们要做缓存和一些预处理。特别是查询DB的效率不是很高的时候，尽量缓存可以缓存的数据、尽量缓存多一些数据。但如果做缓存，DB和缓存数据的一致性是一个问题。在做数据查询时，首先要看本地缓存有没有开启，如果本地缓存没有打开，就去查分布式缓存，如果分布式缓存中没有就去查DB，然后从DB获取数据过来。需要尽量保持DB、缓存数据的一致性，如果DB有变化，可以异步地做缓存数据失效处理，数据百毫秒内就失效掉，减少不一致的问题。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">另外，如果读到本地缓存，这个内存访问比走网络请求性能直接提升了一个量级，但是带来的弊端也很大，因为本地缓存没有办法及时更新，平时也不能打开，因为会带来不一致问题。但大促高峰期间我们会关闭关键业务数据变更入口，开启本地缓存，把本地缓存设置成一分钟失效，一分钟之内是可以缓存的，也能容忍短暂的数据不一致，所以这也是一个很好的做法。同样的思路，我们也会把可能会用到的数据提前放到缓存里面，做预处理。在客户端进行数据预处理，要么直接取本地数据，或者在本地直接做计算，这样更高效，避免了远程的RPC。大促期间我们就把活动价格信息预先放到商品表中，这样部分场景可以做本地计价，有效解决了计价接口性能的问题。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">再就是读容量问题，虽然缓存可以缓解压力，但是DB还是会有几十K的读压力，单点去扛也是不现实的，所以要把读写分离，如果从库过多也有延时的风险，我们会把数据库的并行复制打开。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/53.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们来看一下数据。这是去年&#8220;双11&#8221;的情况（如图）。促销服务的RT得到了有效控制，所以去年&#8220;双11&#8221;平稳度过。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/54.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">接下来讲一个更基础、更全局的优化，就是异步化。比如说下单的流程，有很多业务是非实时性要求的，比如下单送优惠券，如果在下单的时候同步做，时间非常长，风险也更大，其实业务上是非实时性或者准实时性的要求，可以做异步化处理，这样可以减少下单对机器数量的要求。另外是流量高峰期的一些热点数据。大家可以想象一下，下单的时候，一万个人竞争同一条库存数据，一万个节点锁在这个请求上，这是多么恐怖的事情。所以我们会有异步队列去削峰，先直接修改缓存中的库存数目，改完之后能读到最新的结果，但是不会直接竞争DB，这是异步队列削峰很重要的作用。还有，数据库的竞争非常厉害，我们需要把大事务做拆分，尽量让本地事务足够小，同时也要让多个本地事务之间达到一致。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">异步是最终达到一致的关键，异步的处理是非常复杂的。可以看一下这个场景（见图），这是一个1-6步的处理过程，如果拆分成步骤1、2、3、4、end，然后到5，可以异步地做；6也一样，并且5和6可以并行执行。同时，这个步骤走下来链路更短，保障也更容易；步骤5和6也可以单独保障。所以异步化在蘑菇街被广泛使用。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/55.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">异步化之后面临的困难也是很大的，会有分布式和一致性的问题。交易创建过程中，订单、券和库存要把状态做到绝对一致。但下单的时候如果先锁券，锁券成功了再去减库存，如果减库存失败了就是很麻烦的事情，因为优化券服务在另外一个系统里，如果要同步调用做券的回滚，有可能这个回滚也会失败，这个时候处理就会非常复杂。我们的做法是，调用服务超时或者失败的时候，我们就认为失败了，就会异步发消息通知回滚。优惠券服务和库存服务被通知要做回滚时，会根据自身的状态来判断是否要回滚，如果锁券成功了券就回滚，减库存也成功了库存做回滚；如果库存没有减就不用回滚。所以我们是通过异步发消息的方式保持多个系统之间的一致性；如果不做异步就非常复杂，有的场景是前面所有的服务都调用成功，第N个服务调用失败。另外的一致性保障策略包括Corgi MQ生产端发送失败会自动重试保证发成功，消费端接收ACK机制保证最终的一致。另外，与分布式事务框架比起来，异步化方案消除了二阶段提交等分布式事务框架的侵入性影响，降低了开发的成本和门槛。&nbsp;&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/56.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">另一个场景是，服务调用上会有一些异步的处理。以购物车业务为例，购物车列表要调用10个Web服务，每一个服务返回的时间都不一样，比如第1个服务20毫秒返回，第10个服务40毫秒返回，串行执行的效率很低。而电商类的大多数业务都是IO密集型的，而且数据量大时还要分批查询。所以我们要做服务的异步调用。比如下图中这个场景，步骤3处理完了之后callback马上会处理，步骤4处理完了callback也会马上处理，步骤3和4并不相互依赖，且处理可以同时进行了，提高了业务逻辑执行的并行度。目前我们是通过JDK7的Future和Callback实现的，在逐步往JDK8的Completable Future迁移。这是异步化在网站整体的应用场景，异步化已经深入到我们网站的各个环节。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/57.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">刚才我们讲了链路容量的提升、促销RT的优化，又做了异步化的一些处理。那么优化之后怎么验证来优化的效果呢？到底有没有达到预期？我们有几个压测手段，如线下单机压测识别应用单机性能瓶颈，单链路压测验证集群水位及各层核?系统容量配比，还有全链路压测等。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这是去年&#8220;双11&#8221;之前做的压测（见图），达到了5K容量的要求。今年对每个点进一步深入优化，2016年最大订单提升到了10K，比之前提升了25倍。实际上这些优化可以不断深入，不仅可以不断提高单机的性能和单机的QPS，还可以通过对服务整体上的优化达到性能的极致，并且可以引入一些廉价的机器（如云主机）来支撑更大的量。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/58.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们为什么要做这些优化？业务的发展会对业务系统、服务框架提出很多很高的要求。因此，我们对Tesla做了这些改善（见图），服务的配置推送要更快、更可靠地到达客户端，所以有了新的配置中心Metabase，也有了Lurker全链路监控，服务和服务框架的不断发展推动了网站其他基础中间件产品的诞生和发展。2015年的下半年我们进行了一系列中间件的自研和全站落地。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/59.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们得到了服务架构1.5的体系（见图），首先是用户服务在最底层，用户服务1200K的QPS，库存250K，商品服务400K，营销200K，等等。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">接下来我们看一下这一阶段，Tesla开始做服务管控，真正成为了一个服务框架。我们最开始做发布的时候，客户端、服务端由于做的只是初级的RPC调用，如果服务端有变更，客户端可能是几秒甚至数十秒才能拉到新配置，导致经常有客户投诉。有了对服务变更推送更高的要求后，我们就有了Matabase配置中心，服务端如果有发布或者某一刻崩溃了，客户端马上可以感知到，这样就完成了整个服务框架连接优化的改进，真正变成服务管控、服务治理框架的开端。</p><h2>购买链路的稳定性提升&nbsp;</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/60.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">有了上面讲到的服务化改进和性能提升之后，是不是大促的时候看一看监控就行了？其实不是。大流量来的时候，万一导致整个网站崩溃了，一分钟、两分钟的损失是非常大的，所以还要保证服务是稳的和高可用的。只有系统和服务是稳定的，才能更好地完成业务指标和整体的经营目标。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">下面会讲一下服务SLA保证的内容。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/61.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">首先SLA体现在对容量、性能、程度的约束，包括程度是多少的比例。那么要保证这个SLA约束和目标达成，首先要把关键指标监控起来；第二是依赖治理、逻辑优化；第三是负载均衡、服务分组和限流；第四是降级预案、容灾、压测、在线演练等。这是我们服务的关键指标的监控图（见上图）。支付回调服务要满足8K QPS，99%的RT在30ms内，但是图中监控说明SLA未达到，RT程度指标方面要优化。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">服务的SLA保证上，服务端超时和限流非常重要。如果没有超时，很容易引起雪崩。我们来讲一个案例，有次商品服务响应变慢，就导致上层的其他服务都慢，而且商品服务积压了很多请求在线程池中，很多请求响应过慢导致客户端等待超时，客户端早就放弃调用结果结束掉了，但是在商品服务线程池线程做处理时拿到这个请求还会处理，客户都跑了，再去处理，客户也拿不到这个结果，最后还会造成上层服务请求的堵塞，堵塞原因缓解时产生洪流。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/62.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">限流是服务稳定的最后一道保障。一个是HTTP服务的限流，一个是RPC服务的限流。我们服务的处理线程是Tesla框架分配的，所以服务限流可以做到非常精确，可以控制在服务级别和服务方法级别，也可以针对来源做限流。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们做了这样一系列改造之后，服务框架变成了有完善的监控、有负载均衡、有服务分组和限流等完整管控能力的服务治理框架。服务分组之后，如果通用的服务崩溃了，购买链路的服务可以不受影响，这就做到了隔离。这样的一整套服务体系（如图）就构成了我们的服务架构2.0，最终网站的可用性做到了99.979%，这是今年6月份的统计数据。我们还会逐步把服务的稳定性和服务质量做到更好。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/63.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h2>总结及下一步展望</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/64.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">最后总结一下，服务框架的体系完善是一个漫长的发展过程，不需要一开始就很强、什么都有的服务框架，最早可能就是一个RPC框架。服务治理慢慢随着业务量增长也会发展起来，服务治理是服务框架的重要组成部分。另外，Tesla是为蘑菇街业务体系量身打造的服务框架。可以说服务框架是互联网网站架构的核心和持续发展的动力。选择开源还是自建，要看团队能力、看时机。我们要深度定制服务框架，所以选择了自研，以后可能会开源出来。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/65.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">服务框架是随着业务发展不断演变的，我们有1.0、1.5和2.0架构的迭代。要前瞻性地谋划和实施，要考虑未来三年、五年的容量。有一些系统瓶颈可能是要提前解决的，每一个场景不一样，根据特定的场景选择最合适的方案。容量和性能关键字是一切可扩展、Cache、IO、异步化。目前我们正在做的是服务治理和SLA保障系统化，未来会做同城异地的双活。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">谢谢大家！</p><hr style="margin: 0px; border: 0px; padding: 0px;" /><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">感谢<a href="http://www.infoq.com/cn/author/%E9%99%88%E5%85%B4%E7%92%90" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">陈兴璐</a>对本文的审校。</p></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432266.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-17 11:34 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>commons.pool2 对象池的使用</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432146.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 07:36:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432146.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432146.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432146.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432146.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432146.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: https://my.oschina.net/xinxingegeya/blog/391560commons.pool2 对象池的使用&lt;dependency&gt; &nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.apache.commons&lt;/groupId&gt; &nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&g...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432146.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432146.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 15:36 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432146.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>服务注册发现与调度</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/15/432125.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Thu, 15 Dec 2016 07:46:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/15/432125.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432125.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/15/432125.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432125.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432125.html</trackback:ping><description><![CDATA[<div>http://www.tuicool.com/articles/VfQbauB<br /><br /><h2>远程服务依赖</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">依赖分为两种，本地的lib依赖，远程的服务依赖。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img1.tuicool.com/N3MFryb.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">本地的依赖其实是很复杂的问题。从操作系统的apt-get，到各种语言的pip, npm。包管理是无穷无尽的问题。但是所有的本地依赖已经被docker终结了。无论是依赖了什么，全部给你打包起来，从操作系统开始。除了你依赖的cpu指令集没法给你打包成镜像了，其他都给打包了。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">docker之后，依赖问题就只剩远程服务依赖的问题。这个问题就是服务注册发现与调度需要解决的问题。从软件工程的角度来说，所有的解耦问题都可以通过抽取lib的方式解决。lib也可以实现独立的发布周期，良好定义的IDL接口。所以如果非必要，请不要把lib依赖升级成网络服务依赖的角度。除非是从非功能性需求的角度，比如独立的扩缩容，支持scale out这些。很多时候微服务是因为基于lib的工具链支持不全，使得大家义无反顾地走上了拆分网络服务的不归路。</p><h2>名字服务</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">服务名又称之为Service Qualifier，是一个人类可理解的英文标识。所谓的服务注册和发现就是在一个Service Qualifier下注册一堆Endpoint。一个Endpoint就是一个ip+端口的网络服务。就是一个非常类似DNS的名字服务，其实DNS本身就可以做服务的注册和发现，用SRV类型记录。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">名字服务的存在意义是简化服务的使用方，也就是主调方。过去在使用方的代码里需要填入一堆ip加端口的配置，现在有了名字服务就可以只填一个服务名，实际在运行时用服务名找到那一堆endpoint。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img1.tuicool.com/fY32iyy.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">从名字服务的角度来讲并不比DNS要强多少。可能也就是通过&#8220;服务发现的lib&#8221;帮你把ip和端口都获得了。而DNS默认lib（也就是libc的getHostByName）只支持host获取，并不能获得port。当然既然你都外挂了一个服务发现的lib了，和libc做对比也就优势公平了。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">lib提供的接口类似</p><pre markdown"="" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; color: #444444; border-radius: 4px; margin-top: 0px; margin-bottom: 0.75em; line-height: 1.5em; word-break: break-all; word-wrap: break-word; border: none; overflow-x: auto; background-color: #f6f6f6;">$endpoints = listServiceEnpoints('redis'); echo($endpoints[<span style="color: #880000;">0</span>][<span style="color: #bc6060;">'ip</span>]);</pre><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">甚至可以直接提供拼接url的接口</p><pre ruby"="" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; color: #444444; border-radius: 4px; margin-top: 0px; margin-bottom: 0.75em; line-height: 1.5em; word-break: break-all; word-wrap: break-word; border: none; overflow-x: auto; background-color: #f6f6f6;">$url = getServiceUrl(<span style="color: #880000;">'order'</span>, <span style="color: #880000;">'/newOrder'</span>); <span style="color: #888888;"># http://xxx:yyy/newOrder</span></pre><h2>比DNS更快的广播速度</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">传统DNS的服务发现机制是缓存加上TTL过期时间，新的endpoint要传播到使用方需要各级缓存的刷新。而且即便endpoint没有更新，因为TTL到期了也要去上游刷新。为了减少网络间定时刷新endpoint的流量，一般TTL都设得比较长。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img0.tuicool.com/iYrMzqI.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">而另外一个极端是gossip协议。所有人连接到所有人。一个服务的endpoint注册了，可以通过gossip协议很快广播到全部的节点上去。但是gossip的缺点是不基于订阅的。无论我是不是使用这个服务，我都会被动地被gossip这个服务的endpoint。这样就造成了无谓的网络间带宽的开销。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img2.tuicool.com/RzMremN.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">比较理想的更新方式是基于订阅的。如果业务对某个服务进行了发现，那么缓存服务器就保持一个订阅关系获得最新的endpoint。这样可以比定时刷新更及时，也消耗更小。这个方面要黑一下etcd 2.0，它的基于http连接的watch方案要求每个watch独占一个tcp连接，严重限制了watch的数量。而etcd 3.0基于gRPC的实现就修复了这个问题。而consul的msgpack rpc从一开始就是复用tcp连接的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img2.tuicool.com/367BviR.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">图中的observer是类似的zookeeper的observer角色，是为了帮权威服务器分担watch压力的存在。也就是说服务发现的核心其实是一个基于订阅的层级消息网络。服务注册和发现并不承诺任何的一致性，它只是尽力地进行分发，并不保证所有的节点对一个服务的endpoint是哪些有一致的view，因为这并没有价值。因为一个qualifier下的多个endpoint by design 就是等价的，只要有足够的endpint能够承担负载，对于abc三个endpoint具体是让ab可见，还是bc可见，并无任何影响。</p><h2>服务发现agent的高可用</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">DNS的方案是在每台机器上装一个dnsmasq做为缓存服务器。服务发现也是类似的，在每台机器上有一个agent进程。如果dnsmasq挂了，dns域名就会解析失败，这样的可用性是不够的。服务发现的agent会把服务的配置和endpoint dump一份成本机的文件，服务发现的lib在无法访问agent的时候会降级去读取本机的文件，从而保证足够的可用性。当然你要愿意搞什么共享内存，也没人阻拦。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img0.tuicool.com/miqqimZ.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">无法实现对dns服务器的降级。因为哪怕是降级到 /etc/hosts 的实现，其一个巨大的缺陷是 /etc/hosts 对于一个域名只能填一个ip，无法满足扩展性。而如果这一个ip填的是代理服务器的话，则失去了做服务发现的意义，都有代理了那就让代理去发现服务好了。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img1.tuicool.com/UBfUbiV.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">更进一步，很多基于zk的方案是把服务发现的agent和业务进程做到一个进程里去了。所以就不需要担心外挂的进程是否还存活的问题了。</p><h2>软负载均衡</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">这点上和DNS是类似的。理论来说ttl设置为0的DNS服务器也可以起到负载均衡的作用。通过把权重分发到服务发现的agent上，可以让业务&#8220;每次发现&#8221;的endpoint都不一样，从而达到均衡负载的作用。权重的实现通过简单的随机算法就可以实现。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">通过软负载均衡理论上可以实现小流量，灰度地让一个新的endpoint加入集群。也可以实现某一些endpoint承担更大的调用量，以达到在线压测的目的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">不要小瞧了这么一点调权的功能。能够中央调度，智能调度流量，是非常有用的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img1.tuicool.com/qIvUnmM.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><h2>故障检测（减endpoint）</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">故障检测其实是好做的。无非就是一个qualifier下挂了很多个endpoint，根据某种探活机制摘掉其中已经无法提供正常服务的endpoint。摘除最好是软摘除，这样不会出现一个闪失把所有endpoint全摘掉的问题。比如zookeeper的临时节点就是硬摘除，不可取。</p><h3>本地探活</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">在业务拿到endpoint之后，做完了rpc可以知道这个endpoint是否可用。这个时候对endpoint的健康状态本地做一个投票累积。如果endpoint连续不可用则标记为故障，被临时摘除。过一段时间之后再重新放出小黑屋，进行探活。这个过程和nginx对upstream的被动探活是非常类似的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">被动探活的好处是非常敏感而且真实可信（不可用就是我不能调你，就是不可用），本地投票完了立即就可以判定故障。缺陷是每个主调方都需要独立去进行重复的判定。对于故障的endpoint，为了探活其是否存活需要以latency做为代价。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">被动探活不会和具体的rpc机制绑定。无论是http还是thrift，无论是redis还是mysql，只要是网络调用都可以通过rpc后投票的方式实现被动探活。</p><h3>主动探活</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">主动探活比较难做，而且效果也未必好：</p><ul style="padding: 0px; margin: 0px 0px 0.75em 25px; list-style-type: none; font-size: 16px; line-height: 27.2px; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><li style="line-height: 1.7em; list-style-type: disc;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">所有的主动探活的问题都在于需要指定如何去探测。不是tcp连接得上就算是能提供服务的。</p></li><li style="line-height: 1.7em; list-style-type: disc;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">主动探活受到网络路由的影响，a可以访问b，并不带表c也可以访问b</p></li><li style="line-height: 1.7em; list-style-type: disc;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">主动探测带来额外的网络开销，探测不能过于频繁</p></li><li style="line-height: 1.7em; list-style-type: disc;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">主动探测的发起者过少则容易对发起者产生很大的探活压力，需要很高的性能</p></li></ul><h3>本地主动探活</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">consul 的本机主动探活是一个很有意思的组合。避免了主动探活的一些缺点，可以是被动探活的一些补充。</p><h3>心跳探活</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">无论是zookeeper那样一来tcp连接的心跳（tcp连接的保持其实也是定时ttl发ip包保持的）。还是etcd，consul支持的基于ttl的心跳。都是类似的。</p><h3>gossip探活</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">改进版本的心跳。减少整体的网络间通信量。</p><h2>服务注册（加endpoint）</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">服务endpoint注册比endpoint摘除要难得多。</p><h3>无状态服务注册</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">无状态服务的注册没有任何约束。不管是中央管理服务注册表，用web界面注册。还是和部署系统联动，在进程启动时自动注册都可以做。</p><h3>有状态服务的注册</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">有状态服务，比如redis的某个分片的master。其有两个约束：</p><ul style="padding: 0px; margin: 0px 0px 0.75em 25px; list-style-type: none; font-size: 16px; line-height: 27.2px; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><li style="line-height: 1.7em; list-style-type: disc;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">一致性：同一个分片不能有两个master</p></li><li style="line-height: 1.7em; list-style-type: disc;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">可用性：分片不能没有master，当master挂了，要自发选举出新的master</p></li></ul><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">除非是在数据层协议上做ack（paxos，raft）或者协议本身支持冲突解决（crdt），否则基于服务注册来实现的分布式要么牺牲一致性，要么牺牲可用性。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">有状态服务的注册需求，和普通的注册发现需求是本质不同的。有状态服务需要的是一个一致性决策机制，在consistency和availability之间取平衡。这个机制可以是外挂一个zookeeper，也可以是集群的数据节点自身做一个gossip的投票机制。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">而普通的注册和发现就是要给广播渠道，提供visibility。尽可能地让endpoint曝光到其使用方那。不同的问题需要的解决方案是不同的。对于有状态服务的注册表需要非常可靠的故障检测机制，不能随意摘除master。而用于广播的服务注册表则很随意，故障检测机制也可以做到尽可能错杀三千不放过一个。广播的机制需要解决的问题是大集群，怎么让服务可见。而数据节点的选主要解决的是相对小的集群，怎么保持一致地情况下尽量可用。拿zookeeper的临时节点这样的机制放在大集群背景下，去做无状态节点探活就是技术用错了地方。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><img src="http://img0.tuicool.com/Z7Vnm2E.png!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">比如kafka，其有状态服务部分的注册和发现是用zookeeper实现的。而无状态服务的注册与发现是用data node自身提供集群的metadata来实现的。也就是消费者和生产者是不需要从zookeeper里去集群分片信息的（也就是服务注册表），而是从data node拿。这个时候data node其是充当了一个服务发现的agent的作用。如果不用data node干这个活，我们把data node的内容放到DNS里去，其实也是可以work的。只是这些存储的给业务使用的客户端lib已经把这些逻辑写好了，没有人会去修改这个默认行为了。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">但是广播用途的服务注册和发现，比如DNS不是只提供visibility而不能保证任何consistency吗？那我读到分片信息是旧的，把slave当master用了怎么办呢？所有做得好的存储分片选主方案，在data node上自己是知道自己的角色的。如果你使用错了，像redis cluster会回一个move指令，相当于http 302让你去别的地方做这个操作。kafka也是类似的。</p><h2>接入方式</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">libc只支持getHostByName，任何更高级的服务发现都需要挖空心思想怎么简化接入。反正操作系统和语言自身的工具链上是没有标准的支持的。每个公司都有一套自己的玩法。行业严重缺乏标准。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">无论哪种方式都是要修改业务代码的。即便是用proxy方式接入，业务代码里也得写死固定的proxy ip才行。从可读性的角度来说，固定proxy ip的可读性是最差的，而用服务名或者域名是可读性最好的。</p><h3>给每种语言写lib</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">最笨拙的方法，也是最保险的。业务代码直接写服务名，获得endpoint。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">探活也就是硬改各种rpc的lib，在调用后面加上投票的代码。</p><h3>复用libc的getHostByName</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">因为所有的语言基本上都支持DNS域名解析。利用这一层的接口，用钩子换掉lib的实际实现。业务代码里写域名，端口固定。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">socket的钩子要难做得多，而且仅仅tcp4层探活也是不够的（http 500了往往也要认为对方是挂了的）。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">实际上考虑golang这种没有libc的，java这种自己缓存域名结果的，钩子的方案其实没有想得那么美好。</p><h3>本地 proxy</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">proxy其实是一种简化服务发现接入方式的手段。业务可以不用知道服务名，而是使用固定的ip和端口访问。由proxy去做服务发现，把请求转给对方。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">http的proxy也很成熟，在proxy里对rpc结果进行跳票也有现成的工具（比如nginx）。很多公司都是这种本地proxy的架构，比如airbnb，yelp，eleme，uber。当用lib方式接业务接不动的时候，大家都会往这条路上转的。</p><h3>远程 proxy</h3><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">远程proxy的缺陷是固定ip导致了路由是固定的。这条路由上的所有路由器和交换机都是故障点。无法做到多条网络路由冗余容错。而且需要用lvs做虚ip，也引入了运维成本。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">而且远程proxy无法支持分区部署多套环境。除非引入bgp anycast这样妖孽的实现。让同一个ip在不同的idc里路由到不同的服务器。</p><h2>分区部署</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">国内大部分的网游都是分区分服的。这种架构就是一种简化的存储层数据分片。存储层的数据分片一般都做得非常完善，可以做到key级别的搬迁（当你访问key的时候告诉你我可以响应，还是告诉你搬迁到哪里去了），可以做到访问错了shard告诉你正确的shard在哪里。而分区部署往往是没有这么完善的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">所以为了支持分区部署。往往是给不同分区的服务区不同的服务名。比如模块叫 chat，那么给hb_set（华北大区）的chat模块就命名为hb_set.chat，给hn_set（华南大区）的chat模块就命名为hn_set.chat。当时如果我们是gamesvr模块，需要访问chat模块，代码都是同一份，我怎么知道应该访问hn_set.chat还是hb_set.chat呢？这个就需要让gamesvr先知道自己所在的set，然后去访问同set下的其他模块。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">again，这种分法也就是因为分区部署做为一个大的组合系统没法像一个孤立地存储做得那么好。像kafka的broker，哪怕你访问的不是它的本地分片，它可以帮你去做proxy连接到正确的分片上。而我们没法要求一个组合出来的业务系统也做到这么完备地程度。所以凑合着用吧。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">但是这种分法也有问题。有一些模块如果不是分区的，是全局的怎么办？这个时候服务发现就得起一个路由表的作用，把不同分区的服务通过路由串起来。</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432125.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-15 15:46 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/15/432125.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式系统互斥性与幂等性问题的分析与解决</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432110.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 14 Dec 2016 12:57:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432110.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432110.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432110.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432110.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432110.html</trackback:ping><description><![CDATA[ http://www.open-open.com/lib/view/open1475219934034.html 
前言
随着互联网信息技术的飞速发展，数据量不断增大，业务逻辑也日趋复杂，对系统的高并发访问、海量数据处理的场景也越来越多。如何用较低成本实现系统的高可用、易伸缩、可扩展等目标就显得越发重要。为了解决这一系列问题，系统架构也在不断演进。传统的集中式系统已经逐渐无法满足要求，分布式系统被使用在更多的场景中。
分布式系统由独立的服务器通过网络松散耦合组成。在这个系统中每个服务器都是一台独立的主机，服务器之间通过内部网络连接。分布式系统有以下几个特点：
可扩展性：可通过横向水平扩展提高系统的性能和吞吐量。
高可靠性：高容错，即使系统中一台或几台故障，系统仍可提供服务。
高并发性：各机器并行独立处理和计算。
廉价高效：多台小型机而非单台高性能机。
然而，在分布式系统中，其环境的复杂度、网络的不确定性会造成诸如时钟不一致、&#8220;拜占庭将军问题&#8221;（Byzantine failure）等。存在于集中式系统中的机器宕机、消息丢失等问题也会在分布式环境中变得更加复杂。
基于分布式系统的这些特征，有两种问题逐渐成为了分布式环境中需要重点关注和解决的典型问题：
互斥性问题。
幂等性问题。
今天我们就针对这两个问题来进行分析。
互斥性问题
先看两个常见的例子：
例1：某服务记录关键数据X，当前值为100。A请求需要将X增加200；同时，B请求需要将X减100。
在理想的情况下，A先读取到X=100，然后X增加200，最后写入X=300。B请求接着从读取X=300，减少100，最后写入X=200。
然而在真实情况下，如果不做任何处理，则可能会出现：A和B同时读取到X=100；A写入之前B读取到X；B比A先写入等等情况。
例2：某服务提供一组任务，A请求随机从任务组中获取一个任务；B请求随机从任务组中获取一个任务。
在理想的情况下，A从任务组中挑选一个任务，任务组删除该任务，B从剩下的的任务中再挑一个，任务组删除该任务。
同样的，在真实情况下，如果不做任何处理，可能会出现A和B挑中了同一个任务的情况。
以上的两个例子，都是常见的操作互斥性问题。互斥性问题用通俗的话来讲，就是对共享资源的抢占问题。如果不同的请求对同一个或者同一组资源读取并修改时，无法保证按序执行，无法保证一个操作的原子性，那么就很有可能会出现预期外的情况。因此操作的互斥性问题，也可以理解为一个需要保证时序性、原子性的问题。
在传统的基于数据库的架构中，对于数据的抢占问题往往是通过数据库事务（ACID）来保证的。在分布式环境中，出于对性能以及一致性敏感度的要求，使得分布式锁成为了一种比较常见而高效的解决方案。
事实上，操作互斥性问题也并非分布式环境所独有，在传统的多线程、多进程情况下已经有了很好的解决方案。因此在研究分布式锁之前，我们先来分析下这两种情况的解决方案，以期能够对分布式锁的解决方案提供一些实现思路。
多线程环境解决方案及原理解决方案
《Thinking in Java》书中写到：
基本上所有的并发模式在解决线程冲突问题的时候，都是采用序列化访问共享资源的方案。
在多线程环境中，线程之间因为公用一些存储空间，冲突问题时有发生。解决冲突问题最普遍的方式就是用互斥锁把该资源或对该资源的操作保护起来。
Java JDK中提供了两种互斥锁Lock和synchronized。不同的线程之间对同一资源进行抢占，该资源通常表现为某个类的普通成员变量。因此，利用ReentrantLock或者synchronized将共享的变量及其操作锁住，即可基本解决资源抢占的问题。
下面来简单聊一聊两者的实现原理。
原理
ReentrantLock
ReentrantLock主要利用CAS+CLH队列来实现。它支持公平锁和非公平锁，两者的实现类似。
CAS：Compare and Swap，比较并交换。CAS有3个操作数：内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时，将内存值V修改为B，否则什么都不做。该操作是一个原子操作，被广泛的应用在Java的底层实现中。在Java中，CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。CLH队列：带头结点的双向非循环链表(如下图所示)：
ReentrantLock的基本实现可以概括为：先通过CAS尝试获取锁。如果此时已经有线程占据了锁，那就加入CLH队列并且被挂起。当锁被释放之后，排在CLH队列队首的线程会被唤醒，然后CAS再次尝试获取锁。在这个时候，如果：
非公平锁：如果同时还有另一个线程进来尝试获取，那么有可能会让这个线程抢先获取；公平锁：如果同时还有另一个线程进来尝试获取，当它发现自己不是在队首的话，就会排到队尾，由队首的线程获取到锁。
下面分析下两个片段：
finalbooleannonfairTryAcquire(int acquires){ final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); returntrue; } } elseif (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflowthrownew Error("Maximum lock count exceeded"); setState(nextc); returntrue; } returnfalse;
}
在尝试获取锁的时候，会先调用上面的方法。如果状态为0，则表明此时无人占有锁。此时尝试进行set，一旦成功，则成功占有锁。如果状态不为0，再判断是否是当前线程获取到锁。如果是的话，将状态+1，因为此时就是当前线程，所以不用CAS。这也就是可重入锁的实现原理。
finalbooleanacquireQueued(final Node node, int arg){ boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }
}
privatefinalbooleanparkAndCheckInterrupt(){ LockSupport.park(this); return Thread.interrupted();
}
该方法是在尝试获取锁失败加入CHL队尾之后，如果发现前序节点是head，则CAS再尝试获取一次。否则，则会根据前序节点的状态判断是否需要阻塞。如果需要阻塞，则调用LockSupport的park方法阻塞该线程。
synchronized
在Java语言中存在两种内建的synchronized语法：synchronized语句、synchronized方法。
synchronized语句：当源代码被编译成字节码的时候，会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令;synchronized方法：在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1。这个在specification中没有明确说明。
在Java虚拟机的specification中，有关于monitorenter和monitorexit字节码指令的详细描述：http://docs.oracle.com/Javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter。
monitorenter
The objectref must be of type reference.
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
每个对象都有一个锁，也就是监视器（monitor）。当monitor被占有时就表示它被锁定。线程执行monitorenter指令时尝试获取对象所对应的monitor的所有权，过程如下：
如果monitor的进入数为0，则该线程进入monitor，然后将进入数设置为1，该线程即为monitor的所有者;如果线程已经拥有了该monitor，只是重新进入，则进入monitor的进入数加1;如果其他线程已经占用了monitor，则该线程进入阻塞状态，直到monitor的进入数为0，再重新尝试获取monitor的所有权。
monitorexit
The objectref must be of type reference.
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
执行monitorexit的线程必须是相应的monitor的所有者。
指令执行时，monitor的进入数减1，如果减1后进入数为0，那线程退出monitor，不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。
在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的，但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行，这种切换的代价是非常昂贵的。然而在现实中的大部分情况下，同步方法是运行在单线程环境（无锁竞争环境）。如果每次都调用Mutex Lock将严重的影响程序的性能。因此在JDK 1.6之后的版本中对锁的实现做了大量的优化，这些优化在很大程度上减少或避免了Mutex Lock的使用。
多进程的解决方案
在多道程序系统中存在许多进程，它们共享各种资源，然而有很多资源一次只能供一个进程使用，这便是临界资源。多进程中的临界资源大致上可以分为两类，一类是物理上的真实资源，如打印机；一类是硬盘或内存中的共享数据，如共享内存等。而进程内互斥访问临界资源的代码被称为临界区。
针对临界资源的互斥访问，JVM层面的锁就已经失去效力了。在多进程的情况下，主要还是利用操作系统层面的进程间通信原理来解决临界资源的抢占问题。比较常见的一种方法便是使用信号量（Semaphores）。
信号量在POSIX标准下有两种，分别为有名信号量和无名信号量。无名信号量通常保存在共享内存中，而有名信号量是与一个特定的文件名称相关联。信号量是一个整数变量，有计数信号量和二值信号量两种。对信号量的操作，主要是P操作（wait）和V操作（signal）。
P操作：先检查信号量的大小，若值大于零，则将信号量减1，同时进程获得共享资源的访问权限，继续执行；若小于或者等于零，则该进程被阻塞后，进入等待队列。V操作：该操作将信号量的值加1，如果有进程阻塞着等待该信号量，那么其中一个进程将被唤醒。
举个例子，设信号量为1，当一个进程A在进入临界区之前，先进行P操作。发现值大于零，那么就将信号量减为0，进入临界区执行。此时，若另一个进程B也要进去临界区，进行P操作，发现信号量等于0，则会被阻塞。当进程A退出临界区时，会进行V操作，将信号量的值加1，并唤醒阻塞的进程B。此时B就可以进入临界区了。
这种方式，其实和多线程环境下的加解锁非常类似。因此用信号量处理临界资源抢占，也可以简单地理解为对临界区进行加锁。
通过上面的一些了解，我们可以概括出解决互斥性问题，即资源抢占的基本方式为：
对共享资源的操作前后（进入退出临界区）加解锁，保证不同线程或进程可以互斥有序的操作资源。
加解锁方式，有显式的加解锁，如ReentrantLock或信号量；也有隐式的加解锁，如synchronized。那么在分布式环境中，为了保证不同JVM不同主机间不会出现资源抢占，那么同样只要对临界区加解锁就可以了。
然而在多线程和多进程中，锁已经有比较完善的实现，直接使用即可。但是在分布式环境下，就需要我们自己来实现分布式锁。
分布式环境下的解决方案&#8212;&#8212;分布式锁
首先，我们来看看分布式锁的基本条件。
分布式锁条件
基本条件
再回顾下多线程和多进程环境下的锁，可以发现锁的实现有很多共通之处，它们都需要满足一些最基本的条件：
需要有存储锁的空间，并且锁的空间是可以访问到的。锁需要被唯一标识。锁要有至少两种状态。
仔细分析这三个条件：
存储空间
锁是一个抽象的概念，锁的实现，需要依存于一个可以存储锁的空间。在多线程中是内存，在多进程中是内存或者磁盘。更重要的是，这个空间是可以被访问到的。多线程中，不同的线程都可以访问到堆中的成员变量；在多进程中，不同的进程可以访问到共享内存中的数据或者存储在磁盘中的文件。但是在分布式环境中，不同的主机很难访问对方的内存或磁盘。这就需要一个都能访问到的外部空间来作为存储空间。
最普遍的外部存储空间就是数据库了，事实上也确实有基于数据库做分布式锁（行锁、version乐观锁），如quartz集群架构中就有所使用。除此以外，还有各式缓存如Redis、Tair、Memcached、Mongodb，当然还有专门的分布式协调服务Zookeeper，甚至是另一台主机。只要可以存储数据、锁在其中可以被多主机访问到，那就可以作为分布式锁的存储空间。
唯一标识
不同的共享资源，必然需要用不同的锁进行保护，因此相应的锁必须有唯一的标识。在多线程环境中，锁可以是一个对象，那么对这个对象的引用便是这个唯一标识。多进程环境中，信号量在共享内存中也是由引用来作为唯一的标识。但是如果不在内存中，失去了对锁的引用，如何唯一标识它呢？上文提到的有名信号量，便是用硬盘中的文件名作为唯一标识。因此，在分布式环境中，只要给这个锁设定一个名称，并且保证这个名称是全局唯一的，那么就可以作为唯一标识。
至少两种状态
为了给临界区加锁和解锁，需要存储两种不同的状态。如ReentrantLock中的status，0表示没有线程竞争，大于0表示有线程竞争；信号量大于0表示可以进入临界区，小于等于0则表示需要被阻塞。因此只要在分布式环境中，锁的状态有两种或以上：如有锁、没锁；存在、不存在等等，均可以实现。
有了这三个条件，基本就可以实现一个简单的分布式锁了。下面以数据库为例，实现一个简单的分布式锁：
数据库表，字段为锁的ID（唯一标识），锁的状态（0表示没有被锁，1表示被锁）。
伪代码为：
lock = mysql.get(id);
while(lock.status == 1) { sleep(100);
}
mysql.update(lock.status = 1);
doSomething();
mysql.update(lock.status = 0);
问题
以上的方式即可以实现一个粗糙的分布式锁，但是这样的实现，有没有什么问题呢？
问题1：锁状态判断原子性无法保证
从读取锁的状态，到判断该状态是否为被锁，需要经历两步操作。如果不能保证这两步的原子性，就可能导致不止一个请求获取到了锁，这显然是不行的。因此，我们需要保证锁状态判断的原子性。
问题2：网络断开或主机宕机，锁状态无法清除
假设在主机已经获取到锁的情况下，突然出现了网络断开或者主机宕机，如果不做任何处理该锁将仍然处于被锁定的状态。那么之后所有的请求都无法再成功抢占到这个锁。因此，我们需要在持有锁的主机宕机或者网络断开的时候，及时的释放掉这把锁。
问题3：无法保证释放的是自己上锁的那把锁
在解决了问题2的情况下再设想一下，假设持有锁的主机A在临界区遇到网络抖动导致网络断开，分布式锁及时的释放掉了这把锁。之后，另一个主机B占有了这把锁，但是此时主机A网络恢复，退出临界区时解锁。由于都是同一把锁，所以A就会将B的锁解开。此时如果有第三个主机尝试抢占这把锁，也将会成功获得。因此，我们需要在解锁时，确定自己解的这个锁正是自己锁上的。
进阶条件
如果分布式锁的实现，还能再解决上面的三个问题，那么就可以算是一个相对完整的分布式锁了。然而，在实际的系统环境中，还会对分布式锁有更高级的要求。
可重入：线程中的可重入，指的是外层函数获得锁之后，内层也可以获得锁，ReentrantLock和synchronized都是可重入锁；衍生到分布式环境中，一般仍然指的是线程的可重入，在绝大多数分布式环境中，都要求分布式锁是可重入的。羊群效应（Herd Effect）：在分布式锁中，羊群效应指的是，在有多个请求等待获取锁的时候，一旦占有锁的线程释放之后，如果所有等待的方都同时被唤醒，尝试抢占锁。但是这样的情况会造成比较大的开销，那么在实现分布式锁的时候，应该尽量避免羊群效应的产生。公平锁和非公平锁：不同的需求，可能需要不同的分布式锁。非公平锁普遍比公平锁开销小。但是业务需求如果必须要锁的竞争者按顺序获得锁，那么就需要实现公平锁。阻塞锁和自旋锁：针对不同的使用场景，阻塞锁和自旋锁的效率也会有所不同。阻塞锁会有上下文切换，如果并发量比较高且临界区的操作耗时比较短，那么造成的性能开销就比较大了。但是如果临界区操作耗时比较长，一直保持自旋，也会对CPU造成更大的负荷。
保留以上所有问题和条件，我们接下来看一些比较典型的实现方案。
典型实现
ZooKeeper的实现
ZooKeeper（以下简称&#8220;ZK&#8221;）中有一种节点叫做顺序节点，假如我们在/lock/目录下创建3个节点，ZK集群会按照发起创建的顺序来创建节点，节点分别为/lock/0000000001、/lock/0000000002、/lock/0000000003。
ZK中还有一种名为临时节点的节点，临时节点由某个客户端创建，当客户端与ZK集群断开连接，则该节点自动被删除。EPHEMERAL_SEQUENTIAL为临时顺序节点。
根据ZK中节点是否存在，可以作为分布式锁的锁状态，以此来实现一个分布式锁，下面是分布式锁的基本逻辑：
客户端调用create()方法创建名为&#8220;dlm-locks/lockname/lock-&#8221;的临时顺序节点。客户端调用getChildren(&#8220;lockname&#8221;)方法来获取所有已经创建的子节点。客户端获取到所有子节点path之后，如果发现自己在步骤1中创建的节点是所有节点中序号最小的，那么就认为这个客户端获得了锁。如果创建的节点不是所有节点中需要最小的，那么则监视比自己创建节点的序列号小的最大的节点，进入等待。直到下次监视的子节点变更的时候，再进行子节点的获取，判断是否获取锁。
释放锁的过程相对比较简单，就是删除自己创建的那个子节点即可。
Menagerie中的lock首先实现了可重入锁，利用ThreadLocal存储进入的次数，每次加锁次数加1，每次解锁次数减1。如果判断出是当前线程持有锁，就不用走获取锁的流程。
通过tryAcquireDistributed方法尝试获取锁，循环判断前序节点是否存在，如果存在则监视该节点并且返回获取失败。如果前序节点不存在，则再判断更前一个节点。如果判断出自己是第一个节点，则返回获取成功。
为了在别的线程占有锁的时候阻塞，代码中使用JUC的condition来完成。如果获取尝试锁失败，则进入等待且放弃localLock，等待前序节点唤醒。而localLock是一个本地的公平锁，使得condition可以公平的进行唤醒，配合循环判断前序节点，实现了一个公平锁。
这种实现方式非常类似于ReentrantLock的CHL队列，而且zk的临时节点可以直接避免网络断开或主机宕机，锁状态无法清除的问题，顺序节点可以避免羊群效应。这些特性都使得ZK成为了最普遍的分布式锁实现之一。
Redis的实现
Redis的分布式缓存特性使其成为了分布式锁的一种基础实现。通过Redis中是否存在某个锁ID，则可以判断是否上锁。为了保证判断锁是否存在的原子性，保证只有一个线程获取同一把锁，Redis有SETNX（即SET if Not
eXists）和GETSET（先写新值，返回旧值，原子性操作，可以用于分辨是不是首次操作）操作。
为了防止主机宕机或网络断开之后的死锁，Redis没有ZK那种天然的实现方式，只能依赖设置超时时间来规避。
以下是一种比较普遍但不太完善的Redis分布式锁的实现步骤：
线程A发送SETNX lock.orderid 尝试获得锁，如果锁不存在，则set并获得锁。如果锁存在，则再判断锁的值（时间戳）是否大于当前时间，如果没有超时，则等待一下再重试。如果已经超时了，在用GETSET lock.{orderid} 来尝试获取锁，如果这时候拿到的时间戳仍旧超时，则说明已经获得锁了。如果在此之前，另一个线程B快一步执行了上面的操作，那么A拿到的时间戳是个未超时的值，这时A没有如期获得锁，需要再次等待或重试。但是尽管A没拿到锁，但它改写了B设置的锁的超时值。但因为解锁并不是依据超时值，所以影响不大。
该实现还有一个需要考虑的问题是全局时钟问题，由于生产环境主机时钟不能保证完全同步，对时间戳的判断也可能会产生误差。
以上是Redis的一种常见的实现方式，除此以外还可以用SETNX+EXPIRE来实现。Redisson是一个官方推荐的Redis客户端并且实现了很多分布式的功能。它的分布式锁就提供了一种更完善的解决方案，源码：https://github.com/mrniko/redisson。
Tair的实现
Tair和Redis的实现类似，Tair客户端封装了一个expireLock的方法：通过锁状态和过期时间戳来共同判断锁是否存在，只有锁已经存在且没有过期的状态才判定为有锁状态。在有锁状态下，不能加锁，能通过大于或等于过期时间的时间戳进行解锁。
采用这样的方式，可以不用在Value中存储时间戳，并且保证了判断是否有锁的原子性。更值得注意的是，由于超时时间是由Tair判断，所以避免了不同主机时钟不一致的情况。
以上的几种分布式锁实现方式，都是比较常见且有些已经在生产环境中应用。随着应用环境越来越复杂，这些实现可能仍然会遇到一些挑战。
强依赖于外部组件：分布式锁的实现都需要依赖于外部数据存储如ZK、Redis等等，因此一旦这些外部组件出现故障，那么分布式锁就不可用了。
无法完全满足需求：不同分布式锁的实现，都有相应的特点，对于一些需求并不能很好的满足，如实现公平锁、给等待锁加超时时间等等。
基于以上问题，结合多种实现方式，我们开发了Cerberus（得名自希腊神话里守卫地狱的猛犬），致力于提供灵活可靠的分布式锁。
Cerberus分布式锁
Cerberus有以下几个特点。
特点一：一套接口多种引擎
Cerberus分布式锁使用了多种引擎实现方式（Tair、ZK、未来支持Redis），支持使用方自主选择所需的一种或多种引擎。这样可以结合引擎特点，选择符合实际业务需求和系统架构的方式。
Cerberus分布式锁将不同引擎的接口抽象为一套，屏蔽了不同引擎的实现细节。使得使用方可以专注于业务逻辑，也可以任意选择并切换引擎而不必更改任何的业务代码。
如果使用方选择了一种以上的引擎，那么以配置顺序来区分主副引擎。以下是使用主引擎的推荐：
功能需求TairZK并发量高✔响应时间敏感✔临界区执行时间长✔公平锁✔非公平锁✔读写锁✔
特点二：使用灵活、学习成本低
下面是Cerberus的lock方法，这些方法和JUC的ReentrantLock的方式保持一致，使用非常灵活且不需要额外的学习时间。
void lock();
获取锁，如果锁被占用，将禁用当前线程，并且在获得锁之前，该线程将一直处于阻塞状态。boolean tryLock();
仅在调用时锁为空闲状态才获取该锁。
如果锁可用，则获取锁，并立即返回值 true。如果锁不可用，则此方法将立即返回值 false。boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
如果锁在给定的等待时间内空闲，并且当前线程未被中断，则获取锁。
如果在给定时间内锁可用，则获取锁，并立即返回值 true。如果在给定时间内锁一直不可用，则此方法将立即返回值false。void lockInterruptibly() throws InterruptedException;
获取锁，如果锁被占用，则一直等待直到线程被中断或者获取到锁。void unlock();
释放当前持有的锁。
特点三：支持一键降级
Cerberus提供了实时切换引擎的接口:
String switchEngine()
转换分布式锁引擎，按配置的引擎的顺序循环转换。
返回值：返回当前的engine名字，如："zk"。String switchEngine(String engineName)
转换分布式锁引擎，切换为指定的引擎。
参数：engineName - 引擎的名字，同配置bean的名字，"zk"/"tair"。
返回值：返回当前的engine名字，如："zk"。
当使用方选择了两种引擎，平时分布式锁会工作在主引擎上。一旦所依赖的主引擎出现故障，那么使用方可以通过自动或者手动方式调用该切换引擎接口，平滑的将分布式锁切换到另一个引擎上以将风险降到最低。自动切换方式可以利用Hystrix实现。手动切换推荐的一个方案则是使用美团点评基于Zookeeper的基础组件MCC，通过监听MCC配置项更改，来达到手动将分布式系统所有主机同步切换引擎的目的。
除此以外，Cerberus还提供了内置公用集群，免去搭建和配置集群的烦恼。Cerberus也有一套完善的应用授权机制，以此防止业务方未经评估使用，对集群造成影响。
目前，Cerberus分布式锁已经持续迭代了8个版本，先后在美团点评多个项目中稳定运行。
幂等性问题
所谓幂等，简单地说，就是对接口的多次调用所产生的结果和调用一次是一致的。扩展一下，这里的接口，可以理解为对外发布的HTTP接口或者Thrift接口，也可以是接收消息的内部接口，甚至是一个内部方法或操作。
那么我们为什么需要接口具有幂等性呢？设想一下以下情形：
在App中下订单的时候，点击确认之后，没反应，就又点击了几次。在这种情况下，如果无法保证该接口的幂等性，那么将会出现重复下单问题。
在接收消息的时候，消息推送重复。如果处理消息的接口无法保证幂等，那么重复消费消息产生的影响可能会非常大。
在分布式环境中，网络环境更加复杂，因前端操作抖动、网络故障、消息重复、响应速度慢等原因，对接口的重复调用概率会比集中式环境下更大，尤其是重复消息在分布式环境中很难避免。Tyler Treat也在《You Cannot Have Exactly-Once Delivery》一文中提到：
Within the context of a distributed system, you cannot have exactly-once message delivery.
分布式环境中，有些接口是天然保证幂等性的，如查询操作。有些对数据的修改是一个常量，并且无其他记录和操作，那也可以说是具有幂等性的。其他情况下，所有涉及对数据的修改、状态的变更就都有必要防止重复性操作的发生。通过间接的实现接口的幂等性来防止重复操作所带来的影响，成为了一种有效的解决方案。
GTIS
GTIS就是这样的一个解决方案。它是一个轻量的重复操作关卡系统，它能够确保在分布式环境中操作的唯一性。我们可以用它来间接保证每个操作的幂等性。它具有如下特点：
高效：低延时，单个方法平均响应时间在2ms内，几乎不会对业务造成影响；
可靠：提供降级策略，以应对外部存储引擎故障所造成的影响；提供应用鉴权，提供集群配置自定义，降低不同业务之间的干扰；
简单：接入简捷方便，学习成本低。只需简单的配置，在代码中进行两个方法的调用即可完成所有的接入工作；
灵活：提供多种接口参数、使用策略，以满足不同的业务需求。
实现原理
基本原理
GTIS的实现思路是将每一个不同的业务操作赋予其唯一性。这个唯一性是通过对不同操作所对应的唯一的内容特性生成一个唯一的全局ID来实现的。基本原则为：相同的操作生成相同的全局ID；不同的操作生成不同的全局ID。
生成的全局ID需要存储在外部存储引擎中，数据库、Redis亦或是Tair等等均可实现。考虑到Tair天生分布式和持久化的优势，目前的GTIS存储在Tair中。其相应的key和value如下：
key：将对于不同的业务，采用APP_KEY+业务操作内容特性生成一个唯一标识trans_contents。然后对唯一标识进行加密生成全局ID作为Key。value：current_timestamp + trans_contents，current_timestamp用于标识当前的操作线程。
判断是否重复，主要利用Tair的SETNX方法，如果原来没有值则set且返回成功，如果已经有值则返回失败。
内部流程
GTIS的内部实现流程为：
业务方在业务操作之前，生成一个能够唯一标识该操作的transContents，传入GTIS；GTIS根据传入的transContents，用MD5生成全局ID；GTIS将全局ID作为key，current_timestamp+transContents作为value放入Tair进行setNx，将结果返回给业务方；业务方根据返回结果确定能否开始进行业务操作；若能，开始进行操作；若不能，则结束当前操作；业务方将操作结果和请求结果传入GTIS，系统进行一次请求结果的检验；若该次操作成功，GTIS根据key取出value值，跟传入的返回结果进行比对，如果两者相等，则将该全局ID的过期时间改为较长时间；GTIS返回最终结果。
实现难点
GTIS的实现难点在于如何保证其判断重复的可靠性。由于分布式环境的复杂度和业务操作的不确定性，在上一章节分布式锁的实现中考虑的网络断开或主机宕机等等问题，同样需要在GTIS中设法解决。这里列出几个典型的场景：
如果操作执行失败，理想的情况应该是另一个相同的操作可以立即进行。因此，需要对业务方的操作结果进行判断，如果操作失败，那么就需要立即删除该全局ID；
如果操作超时或主机宕机，当前的操作无法告知GTIS操作是否成功。那么我们必须引入超时机制，一旦长时间获取不到业务方的操作反馈，那么也需要该全局ID失效；
结合上两个场景，既然全局ID会失效并且可能会被删除，那就需要保证删除的不是另一个相同操作的全局ID。这就需要将特殊的标识记录下来，并由此来判断。这里所用的标识为当前时间戳。
可以看到，解决这些问题的思路，也和上一章节中的实现有很多类似的地方。除此以外，还有更多的场景需要考虑和解决，所有分支流程如下:
使用说明
使用时，业务方只需要在操作的前后调用GTIS的前置方法和后置方法，如下图所示。如果前置方法返回可进行操作，则说明此时无重复操作，可以进行。否则则直接结束操作。
使用方需要考虑的主要是下面两个参数：
空间全局性：业务方输入的能够标志操作唯一性的内容特性，可以是唯一性的String类型的ID，也可以是map、POJO等形式。如订单ID等
时间全局性：确定在多长时间内不允许重复，1小时内还是一个月内亦或是永久。
此外，GTIS还提供了不同的故障处理策略和重试机制，以此来降低外部存储引擎异常对系统造成的影响。
目前，GTIS已经持续迭代了7个版本，距离第一个版本有近1年之久，先后在美团点评多个项目中稳定运行。
结语
在分布式环境中，操作互斥性问题和幂等性问题非常普遍。经过分析，我们找出了解决这两个问题的基本思路和实现原理，给出了具体的解决方案。
针对操作互斥性问题，常见的做法便是通过分布式锁来处理对共享资源的抢占。分布式锁的实现，很大程度借鉴了多线程和多进程环境中的互斥锁的实现原理。只要满足一些存储方面的基本条件，并且能够解决如网络断开等异常情况，那么就可以实现一个分布式锁。目前已经有基于Zookeeper和Redis等存储引擎的比较典型的分布式锁实现。但是由于单存储引擎的局限，我们开发了基于ZooKeeper和Tair的多引擎分布式锁Cerberus，它具有使用灵活方便等诸多优点，还提供了完善的一键降级方案。
针对操作幂等性问题，我们可以通过防止重复操作来间接的实现接口的幂等性。GTIS提供了一套可靠的解决方法：依赖于存储引擎，通过对不同操作所对应的唯一的内容特性生成一个唯一的全局ID来防止操作重复。
目前Cerberus分布式锁、GTIS都已应用在生产环境并平稳运行。两者提供的解决方案已经能够解决大多数分布式环境中的操作互斥性和幂等性的问题。值得一提的是，分布式锁和GTIS都不是万能的，它们对外部存储系统的强依赖使得在环境不那么稳定的情况下，对可靠性会造成一定的影响。在并发量过高的情况下，如果不能很好的控制锁的粒度，那么使用分布式锁也是不太合适的。总的来说，分布式环境下的业务场景纷繁复杂，要解决互斥性和幂等性问题还需要结合当前系统架构、业务需求和未来演进综合考虑。Cerberus分布式锁和GTIS也会持续不断地迭代更新，提供更多的引擎选择、更高效可靠的实现方式、更简捷的接入流程，以期满足更复杂的使用场景和业务需求。
<img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432110.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-14 20:57 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432110.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>聊一聊分布式锁的设计 （redis 分布式锁）</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432104.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 14 Dec 2016 10:31:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432104.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432104.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432104.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432104.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432104.html</trackback:ping><description><![CDATA[http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/<br /><br /><h2>起因</h2><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">前段时间，看到redis作者发布的一篇文章<a href="http://antirez.com/news/101" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《Is Redlock safe?》</a>，Redlock是redis作者基于redis设计的分布式锁的算法。文章起因是有一位分布式的专家写了一篇文章<a href="http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《How to do distributed locking》</a>，质疑Redlock的正确性。redis作者则在《Is Redlock safe?》文章中给予回应，一来一回甚是精彩。文本就为读者一一解析两位专家的争论。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">在了解两位专家的争论前，让我先从我了解的分布式锁一一道来。文章中提到的分布式锁均为排他锁。</p><h2><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u6570_u636E_u5E93_u9501_u8868" title="数据库锁表" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>数据库锁表</h2><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">我第一次接触分布式锁用的是mysql的锁表。当时我并没有分布式锁的概念。只知道当时有两台交易中心服务器处理相同的业务，每个交易中心处理订单的时候需要保证另一个无法处理。于是用mysql的一张表来控制共享资源。表结构如下：</p><pre style="overflow: auto; font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 13px; margin-top: 20px; margin-bottom: 20px; padding: 15px; color: #c5c8c6; line-height: 1.6; text-align: justify; background: #1d1f21;"><code style="font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 1em; word-break: break-all; text-shadow: none; padding: 0px; background: none;">CREATE TABLE `lockedOrder` (   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主码',   `type` tinyint(8) unsigned NOT NULL DEFAULT '0' COMMENT '操作类别',   `order_id` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的order_id',   `memo` varchar(1024) NOT NULL DEFAULT '',   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间，自动生成',   PRIMARY KEY (`id`),   UNIQUE KEY `uidx_order_id` (`order_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的订单'; </code></pre><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">order_id记录了订单号，type和memo用来记录下是那种类型的操作锁定的订单，memo用来记录一下操作内容。这张表能完成分布式锁的主要原因正是由于把order_id设置为了<code style="font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 1em; word-break: break-all; text-shadow: #ffffff 0px 1px; padding: 0px 0.3em; background: #eeeeee;">UNIQUE KEY</code>，所以同一个订单号只能插入一次。于是对锁的竞争就交给了数据库，处理同一个订单号的交易中心把订单号插入表中，数据库保证了只有一个交易中心能插入成功，其他交易中心都会插入失败。lock和unlock的伪代码也非常简单：</p><pre style="overflow: auto; font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 13px; margin-top: 20px; margin-bottom: 20px; padding: 15px; color: #c5c8c6; line-height: 1.6; text-align: justify; background: #1d1f21;"><code style="font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 1em; word-break: break-all; text-shadow: none; padding: 0px; background: none;">def lock ：     exec sql: insert into lockedOrder(type,order_id,memo) values (type,order_id,memo)     if result == true :         return true     else :         return false  def unlock ：     exec sql: delete from lockedOrder where order_id='order_id' </code></pre><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">读者可以发现，这个锁从功能上有几个问题：</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">数据库锁实现只能是非阻塞锁，即应该为tryLock，是尝试获得锁，如果无法获得则会返回失败。要改成阻塞锁，需要反复执行insert语句直到插入成功。由于交易中心的使用场景，只要一个交易中心处理订单就行了，所以这里不需要使用阻塞锁。</li><li style="list-style: circle;"><p style="margin: 0px 0px 25px;">这把锁没有过期时间，如果交易中心锁定了订单，但异常宕机后，这个订单就无法锁定了。这里为了让锁能够失效，需要在应用层加上定时任务，去删除过期还未解锁的订单。clear_timeout_lock的伪代码很简单，只要执行一条sql即可。</p><pre style="overflow: auto; font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 13px; margin-top: 20px; margin-bottom: 20px; padding: 15px; color: #c5c8c6; line-height: 1.6; background: #1d1f21;"><code style="font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 1em; word-break: break-all; text-shadow: none; padding: 0px; background: none;">def clear_timeout_lock :     exec sql : delete from lockedOrder where update_time &lt;  ADDTIME(NOW(),'-00:02:00') </code></pre><p style="margin: 0px 0px 25px;">这里设置过期时间为2分钟，也是从业务场景考虑的，如果订单处理时间可能超过2分钟的话，这个时候还需要加大。</p></li><li style="list-style: circle;"><p style="margin: 0px 0px 25px;">这把锁是不能重入的，意思就是即使一个交易中心获得了锁，在它为解锁前，之后的流程如果有再去获取锁的话还会失败，这样就可能出现死锁。这个问题我们当时没有处理，如果要处理这个问题的话，需要增加字段，在insert的时候，把该交易中心的标识加进来，这样再获取锁的时候， 通过select，看下锁定的人是不是自己。lock的伪代码版本如下：</p><pre style="overflow: auto; font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 13px; margin-top: 20px; margin-bottom: 20px; padding: 15px; color: #c5c8c6; line-height: 1.6; background: #1d1f21;"><code style="font-family: 'Input Mono', 'PT Mono', Consolas, Monaco, Menlo, monospace; font-size: 1em; word-break: break-all; text-shadow: none; padding: 0px; background: none;">def lock ：     exec sql: insert into lockedOrder(type,order_id,memo) values (type,order_id,memo)     if result == true :         return true     else :         exec sql : select id from lockedOrder where order_id='order_id' and memo = 'TradeCenterId'         if count &gt; 0 :             return true         else              return false </code></pre><p style="margin: 0px 0px 25px;">在锁定失败后，看下锁是不是自己，如果是自己，那依然锁定成功。不过这个方法解锁又遇到了困难，第一次unlock就把锁给释放了，后面的流程都是在没锁的情况下完成，就可能出现其他交易中心也获取到这个订单锁，产生冲突。解决这个办法的方法就是给锁加计数器，记录下lock多少次。unlock的时候，只有在lock次数为0后才能删除数据库的记录。</p></li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">可以看出，数据库锁能实现一个简单的避免共享资源被多个系统操作的情况。我以前在盛大的时候，发现盛大特别喜欢用数据库锁。盛大的前辈们会说，盛大基本上实现分布式锁用的都是数据库锁。在并发量不是那么恐怖的情况下，数据库锁的性能也不容易出问题，而且由于数据库的数据具有持久化的特性，一般的应用也足够应付。但是除了上面说的数据库锁的几个功能问题外，数据库锁并没有很好的应付数据库宕机的场景，如果数据库宕机，会带来的整个交易中心无法工作。当时我也没想过这个问题，我们整个交易系统，数据库是个单点，不过数据库实在是太稳定了，两年也没出过任何问题。随着工作经验的积累，构建高可用系统的概念越来越强，系统中是不允许出现单点的。现在想想，通过数据库的同步复制，以及使用vip切换Master就能解决这个问题。</p><h2><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u7F13_u5B58_u9501" title="缓存锁" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>缓存锁</h2><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">后来我开始接触缓存服务，知道很多应用都把缓存作为分布式锁，比如redis。使用缓存作为分布式锁，性能非常强劲，在一些不错的硬件上，redis可以每秒执行10w次，内网延迟不超过1ms，足够满足绝大部分应用的锁定需求。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">redis锁定的原理是利用setnx命令，即只有在某个key不存在情况才能set成功该key，这样就达到了多个进程并发去set同一个key，只有一个进程能set成功。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">仅有一个setnx命令，redis遇到的问题跟数据库锁一样，但是过期时间这一项，redis自带的expire功能可以不需要应用主动去删除锁。而且从 Redis 2.6.12 版本开始，redis的set命令直接直接设置NX和EX属性，NX即附带了setnx数据，key存在就无法插入，EX是过期属性，可以设置过期时间。这样一个命令就能原子的完成加锁和设置过期时间。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">缓存锁优势是性能出色，劣势就是由于数据在内存中，一旦缓存服务宕机，锁数据就丢失了。像redis自带复制功能，可以对数据可靠性有一定的保证，但是由于复制也是异步完成的，因此依然可能出现master节点写入锁数据而未同步到slave节点的时候宕机，锁数据丢失问题。</p><h2><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u5206_u5E03_u5F0F_u7F13_u5B58_u9501_u2014Redlock" title="分布式缓存锁&#8212;Redlock" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>分布式缓存锁&#8212;Redlock</h2><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">redis作者鉴于单点redis作为分布式锁的可能出现的锁数据丢失问题，提出了Redlock算法，该算法实现了比单一节点更安全、可靠的分布式锁管理（DLM）。下面我就介绍下Redlock的实现。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">Redlock算法假设有N个redis节点，这些节点互相独立，一般设置为N=5，这N个节点运行在不同的机器上以保持物理层面的独立。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">算法的步骤如下：</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">1、客户端获取当前时间，以毫秒为单位。</li><li style="list-style: circle;">2、客户端尝试获取N个节点的锁，（每个节点获取锁的方式和前面说的缓存锁一样），N个节点以相同的key和value获取锁。客户端需要设置接口访问超时，接口超时时间需要远远小于锁超时时间，比如锁自动释放的时间是10s，那么接口超时大概设置5-50ms。这样可以在有redis节点宕机后，访问该节点时能尽快超时，而减小锁的正常使用。</li><li style="list-style: circle;">3、客户端计算在获得锁的时候花费了多少时间，方法是用当前时间减去在步骤一获取的时间，只有客户端获得了超过3个节点的锁，而且获取锁的时间小于锁的超时时间，客户端才获得了分布式锁。</li><li style="list-style: circle;">4、客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。</li><li style="list-style: circle;">5、如果客户端获取锁失败了，客户端会依次删除所有的锁。</li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">使用Redlock算法，可以保证在挂掉最多2个节点的时候，分布式锁服务仍然能工作，这相比之前的数据库锁和缓存锁大大提高了可用性，由于redis的高效性能，分布式缓存锁性能并不比数据库锁差。</p><h3><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u5206_u5E03_u5F0F_u4E13_u5BB6_u8D28_u7591Redlock" title="分布式专家质疑Redlock" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>分布式专家质疑Redlock</h3><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">介绍了Redlock，就可以说起文章开头提到了分布式专家和redis作者的争论了。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">该专家提到，考虑分布式锁的时候需要考虑两个方面：性能和正确性。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">如果使用高性能的分布式锁，对正确性要求不高的场景下，那么使用缓存锁就足够了。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">如果使用可靠性高的分布式锁，那么就需要考虑严格的可靠性问题。而Redlock则不符合正确性。为什么不符合呢？专家列举了几个方面。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">现在很多编程语言使用的虚拟机都有GC功能，在Full GC的时候，程序会停下来处理GC，有些时候Full GC耗时很长，甚至程序有几分钟的卡顿，文章列举了HBase的例子，HBase有时候GC几分钟，会导致租约超时。而且Full GC什么时候到来，程序无法掌控，程序的任何时候都可能停下来处理GC，比如下图，客户端1获得了锁，正准备处理共享资源的时候，发生了Full GC直到锁过期。这样，客户端2又获得了锁，开始处理共享资源。在客户端2处理的时候，客户端1 Full GC完成，也开始处理共享资源，这样就出现了2个客户端都在处理共享资源的情况。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><img src="http://weizijun.cn/images/lock_unsafe-lock.png" alt="Alt text" style="border: 1px solid #dddddd; display: block; margin: 0px; max-width: 100%; height: auto; box-sizing: border-box; padding: 3px;" /></p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">专家给出了解决办法，如下图，看起来就是MVCC，给锁带上token，token就是version的概念，每次操作锁完成，token都会加1，在处理共享资源的时候带上token，只有指定版本的token能够处理共享资源。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><img src="http://weizijun.cn/images/lock_fencing-tokens.png" alt="Alt text" style="border: 1px solid #dddddd; display: block; margin: 0px; max-width: 100%; height: auto; box-sizing: border-box; padding: 3px;" /></p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">然后专家还说到了算法依赖本地时间，而且redis在处理key过期的时候，依赖gettimeofday方法获得时间，而不是monotonic clock，这也会带来时间的不准确。比如一下场景，两个客户端client 1和client 2，5个redis节点nodes (A, B, C, D and E)。</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">1、client 1从A、B、C成功获取锁，从D、E获取锁网络超时。</li><li style="list-style: circle;">2、节点C的时钟不准确，导致锁超时。</li><li style="list-style: circle;">3、client 2从C、D、E成功获取锁，从A、B获取锁网络超时。</li><li style="list-style: circle;">4、这样client 1和client 2都获得了锁。</li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">总结专家关于Redlock不可用的两点：</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">1、GC等场景可能随时发生，并导致在客户端获取了锁，在处理中超时，导致另外的客户端获取了锁。专家还给出了使用自增token的解决方法。</li><li style="list-style: circle;">2、算法依赖本地时间，会出现时钟不准，导致2个客户端同时获得锁的情况。</li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">所以专家给出的结论是，只有在有界的网络延迟、有界的程序中断、有界的时钟错误范围，Redlock才能正常工作，但是这三种场景的边界又是无法确认的，所以专家不建议使用Redlock。对于正确性要求高的场景，专家推荐了Zookeeper，关于使用Zookeeper作为分布式锁后面再讨论。</p><h3><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#redis_u4F5C_u8005_u89E3_u7591Redlock" title="redis作者解疑Redlock" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>redis作者解疑Redlock</h3><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">redis作者看到这个专家的文章后，写了一篇博客予以回应。作者很客气的感谢了专家，然后表达出了对专家观点的不认同。</p><blockquote style="margin: 0px; padding: 0px 15px; color: #666666; border-left-width: 4px; border-left-color: #dddddd; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><p style="margin: 0px 0px 25px;">I asked for an analysis in the original Redlock specification here:&nbsp;<a href="http://redis.io/topics/distlock" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">http://redis.io/topics/distlock</a>. So thank you Martin. However I don&#8217;t agree with the analysis.</p></blockquote><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">redis作者关于使用token解决锁超时问题可以概括成下面五点：</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">观点1，使用分布式锁一般是在，你没有其他方式去控制共享资源了，专家使用token来保证对共享资源的处理，那么就不需要分布式锁了。</li><li style="list-style: circle;">观点2，对于token的生成，为保证不同客户端获得的token的可靠性，生成token的服务还是需要分布式锁保证服务的可靠性。</li><li style="list-style: circle;">观点3，对于专家说的自增的token的方式，redis作者认为完全没必要，每个客户端可以生成唯一的uuid作为token，给共享资源设置为只有该uuid的客户端才能处理的状态，这样其他客户端就无法处理该共享资源，直到获得锁的客户端释放锁。</li><li style="list-style: circle;">观点4、redis作者认为，对于token是有序的，并不能解决专家提出的GC问题，如上图所示，如果token 34的客户端写入过程中发送GC导致锁超时，另外的客户端可能获得token 35的锁，并再次开始写入，导致锁冲突。所以token的有序并不能跟共享资源结合起来。</li><li style="list-style: circle;">观点5、redis作者认为，大部分场景下，分布式锁用来处理非事务场景下的更新问题。作者意思应该是有些场景很难结合token处理共享资源，所以得依赖锁去锁定资源并进行处理。</li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">专家说到的另一个时钟问题，redis作者也给出了解释。客户端实际获得的锁的时间是默认的超时时间，减去获取锁所花费的时间，如果获取锁花费时间过长导致超过了锁的默认超时间，那么此时客户端并不能获取到锁，不会存在专家提出的例子。</p><h3><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u518D_u6B21_u5206_u6790Redlock" title="再次分析Redlock" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>再次分析Redlock</h3><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">看了两位专家你来我回的争辩，相信读者会对Redlock有了更多的认识。这里我也想就分布式专家提到的两个问题结合redis作者的观点，说说我的想法。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">第一个问题我概括为，在一个客户端获取了分布式锁后，在客户端的处理过程中，可能出现锁超时释放的情况，这里说的处理中除了GC等非抗力外，程序流程未处理完也是可能发生的。之前在说到数据库锁设置的超时时间2分钟，如果出现某个任务占用某个订单锁超过2分钟，那么另一个交易中心就可以获得这把订单锁，从而两个交易中心同时处理同一个订单。正常情况，任务当然秒级处理完成，可是有时候，加入某个rpc请求设置的超时时间过长，一个任务中有多个这样的超时请求，那么，很可能就出现超过自动解锁时间了。当初我们的交易模块是用C++写的，不存在GC，如果用java写，中间还可能出现Full GC，那么锁超时解锁后，自己客户端无法感知，是件非常严重的事情。我觉得这不是锁本身的问题，上面说到的任何一个分布式锁，只要自带了超时释放的特性，都会出现这样的问题。如果使用锁的超时功能，那么客户端一定得设置获取锁超时后，采取相应的处理，而不是继续处理共享资源。Redlock的算法，在客户端获取锁后，会返回客户端能占用的锁时间，客户端必须处理该时间，让任务在超过该时间后停止下来。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">第二个问题，自然就是分布式专家没有理解Redlock。Redlock有个关键的特性是，获取锁的时间是锁默认超时的总时间减去获取锁所花费的时间，这样客户端处理的时间就是一个相对时间，就跟本地时间无关了。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">由此看来，Redlock的正确性是能得到很好的保证的。仔细分析Redlock，相比于一个节点的redis，Redlock提供的最主要的特性是可靠性更高，这在有些场景下是很重要的特性。但是我觉得Redlock为了实现可靠性，却花费了过大的代价。</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">首先必须部署5个节点才能让Redlock的可靠性更强。</li><li style="list-style: circle;">然后需要请求5个节点才能获取到锁，通过Future的方式，先并发向5个节点请求，再一起获得响应结果，能缩短响应时间，不过还是比单节点redis锁要耗费更多时间。</li><li style="list-style: circle;">然后由于必须获取到5个节点中的3个以上，所以可能出现获取锁冲突，即大家都获得了1-2把锁，结果谁也不能获取到锁，这个问题，redis作者借鉴了raft算法的精髓，通过冲突后在随机时间开始，可以大大降低冲突时间，但是这问题并不能很好的避免，特别是在第一次获取锁的时候，所以获取锁的时间成本增加了。</li><li style="list-style: circle;">如果5个节点有2个宕机，此时锁的可用性会极大降低，首先必须等待这两个宕机节点的结果超时才能返回，另外只有3个节点，客户端必须获取到这全部3个节点的锁才能拥有锁，难度也加大了。</li><li style="list-style: circle;">如果出现网络分区，那么可能出现客户端永远也无法获取锁的情况。</li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">分析了这么多原因，我觉得Redlock的问题，最关键的一点在于Redlock需要客户端去保证写入的一致性，后端5个节点完全独立，所有的客户端都得操作这5个节点。如果5个节点有一个leader，客户端只要从leader获取锁，其他节点能同步leader的数据，这样，分区、超时、冲突等问题都不会存在。所以为了保证分布式锁的正确性，我觉得使用强一致性的分布式协调服务能更好的解决问题。</p><h2><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u66F4_u597D_u7684_u5206_u5E03_u5F0F_u9501_u2014zookeeper" title="更好的分布式锁&#8212;zookeeper" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>更好的分布式锁&#8212;zookeeper</h2><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">提到分布式协调服务，自然就想到了zookeeper。zookeeper实现了类似paxos协议，是一个拥有多个节点分布式协调服务。对zookeeper写入请求会转发到leader，leader写入完成，并同步到其他节点，直到所有节点都写入完成，才返回客户端写入成功。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">zookeeper还有几个特质，让它非常适合作为分布式锁服务。</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">zookeeper支持watcher机制，这样实现阻塞锁，可以watch锁数据，等到数据被删除，zookeeper会通知客户端去重新竞争锁。</li><li style="list-style: circle;">zookeeper的数据可以支持临时节点的概念，即客户端写入的数据是临时数据，在客户端宕机后，临时数据会被删除，这样就实现了锁的异常释放。使用这样的方式，就不需要给锁增加超时自动释放的特性了。</li></ul><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">zookeeper实现锁的方式是客户端一起竞争写某条数据，比如/path/lock，只有第一个客户端能写入成功，其他的客户端都会写入失败。写入成功的客户端就获得了锁，写入失败的客户端，注册watch事件，等待锁的释放，从而继续竞争该锁。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">如果要实现tryLock，那么竞争失败就直接返回false即可。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">zookeeper实现的分布式锁简单、明了，分布式锁的关键技术都由zookeeper负责实现了。可以看下《从Paxos到Zookeeper:分布式一致性原理与实践》书里贴出来的分布式锁实现步骤</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><img src="http://weizijun.cn/images/lock_zookeeper.png" alt="Alt text" style="border: 1px solid #dddddd; display: block; margin: 0px; max-width: 100%; height: auto; box-sizing: border-box; padding: 3px;" /></p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">需要使用zookeeper的分布式锁功能，可以使用curator-recipes库。Curator是Netflix开源的一套ZooKeeper客户端框架，curator-recipes库里面集成了很多zookeeper的应用场景，分布式锁的功能在org.apache.curator.framework.recipes.locks包里面，<a href="http://colobu.com/2014/12/12/zookeeper-recipes-by-example-2" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《跟着实例学习ZooKeeper的用法： 分布式锁》</a>文章里面详细的介绍了curator-recipes分布式锁的使用，想要使用分布式锁功能的朋友们不妨一试。</p><h2><a href="http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/#u603B_u7ED3" title="总结" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word;"></a>总结</h2><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">文章写到这里，基本把我关于分布式锁的了解介绍了一遍。可以实现分布式锁功能的，包括数据库、缓存、分布式协调服务等等。根据业务的场景、现状以及已经依赖的服务，应用可以使用不同分布式锁实现。文章介绍了redis作者和分布式专家关于Redlock，虽然最终觉得Redlock并不像分布式专家说的那样缺乏正确性，不过我个人觉得，如果需要最可靠的分布式锁，还是使用zookeeper会更可靠些。curator-recipes库封装的分布式锁，java应用也可以直接使用。而且如果开始依赖zookeeper，那么zookeeper不仅仅提供了分布式锁功能，选主、服务注册与发现、保存元数据信息等功能都能依赖zookeeper，这让zookeeper不会那么闲置。</p><p style="margin: 0px 0px 25px; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;">参考资料：</p><ul style="list-style: square; color: #555555; font-family: Lato, 'PingFang SC', 'Microsoft YaHei', sans-serif; line-height: 28px; text-align: justify; background-color: #ffffff;"><li style="list-style: circle;">[1]<a href="http://redis.io/topics/distlock" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《Distributed locks with Redis》</a></li><li style="list-style: circle;">[2]<a href="http://antirez.com/news/101" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《Is Redlock safe?》</a></li><li style="list-style: circle;">[3]<a href="http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《How to do distributed locking》</a></li><li style="list-style: circle;">[4]<a href="http://colobu.com/2014/12/12/zookeeper-recipes-by-example-2" target="_blank" rel="external" style="color: #555555; text-decoration: none; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #cccccc; word-wrap: break-word; background-color: transparent;">《跟着实例学习ZooKeeper的用法： 分布式锁》</a></li><li style="list-style: circle;">[5]《从Paxos到Zookeeper:分布式一致性原理与实践》</li></ul><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432104.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-14 18:31 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432104.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> mutex 和 spinlock 对比</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432087.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 14 Dec 2016 05:52:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432087.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432087.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432087.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432087.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432087.html</trackback:ping><description><![CDATA[<span style="font-size: 12px;">http://blog.csdn.net/dong976209075/article/details/8004325<br /></span><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;">理论上：</span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">mutex和spinlock都是用于多进程/线程间访问公共资源时保持同步用的，只是在lock失败的时候处理方式有所不同。首先，当一个thread 给一个mutex上锁失败的时候，thread会进入sleep状态，从而让其他的thread运行，其中就包裹已经给mutex上锁成功的那个thread，被占用的lock一旦释放，就会去wake up 那个sleep的thread。其次，当一个thread给一个spinlock上锁失败的时候，thread会在spinlock上不停的轮讯，直到成功，所以他不会进入sleep状态（当然，时间片用完了，内核会自动进行调度）。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;"><br /></span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;">存在的问题：</span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">无论是mutex还是spinlock，如果一个thread去给一个已经被其他thread占用的锁上锁，那么从此刻起到其他thread对此锁解锁的时间长短将会导致mutex和spinlock出现下面的问题。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">mutex的问题是，它一旦上锁失败就会进入sleep，让其他thread运行，这就需要内核将thread切换到sleep状态，如果mutex又在很短的时间内被释放掉了，那么又需要将此thread再次唤醒，这需要消耗许多CPU指令和时间，这种消耗还不如让thread去轮讯。也就是说，其他thread解锁时间很短的话会导致CPU的资源浪费。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">spinlock的问题是，和上面正好相反，如果其他thread解锁的时间很长的话，这种spinlock进行轮讯的方式将会浪费很多CPU资源。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;"><br /></span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;">解决方法：</span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">对于single-core/single-CPU，spinlock将一直浪费CPU资源，如果采用mutex，反而可以立刻让其他的thread运行，可能去释放mutex lock。对于multi-core/mutil-CPU，会存在很多短时间被占用的lock，如果总是去让thread sleep，紧接着去wake up，这样会浪费很多CPU资源，从而降低了系统性能，所以应该尽量使用spinlock。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;"><br /></span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><span style="font-size: 18px;"><strong>现实情况：</strong></span></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">由于程序员不太可能确定每个运行程序的系统CPU和core的个数，所以也不可能去确定使用那一种lock。因此现在的<a href="http://lib.csdn.net/base/operatingsystem" title="操作系统知识库" target="_blank" style="color: #df3434; text-decoration: none; font-weight: bold;">操作系统</a>通常不太区分mutex和spinlock了。实际上，大多数现代操作系统已经使用了混合mutex（hybrid mutex）和混合spinlock（hybrid spinlock）。说白了就是将两者的特点相结合。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><br /></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">hydrid mutex：在一个multi-core系统上，hybrid mutex首先像一个spinlock一样，当thread加锁失败的时候不会立即被设置成sleep，但是，当过了一定的时间（或则其他的策略）还没有获得lock，就会被设置成sleep，之后再被wake up。而在一个single-core系统上，hybrid mutex就不会表现出spinlock的特性，而是如果加锁失败就直接被设置成sleep。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><br /></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">hybrid spinlock：和hybrid mutex相似，只不过，thread加锁失败后在spinlock一段很短的时间后，会被stop而不是被设置成sleep，stop是正常的进程调度，应该会比先让thread sleep然后再wake up的开销小一些。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><span style="font-size: 18px;"><strong><br /></strong></span></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><strong><span style="font-size: 18px;">总结：</span></strong></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;">写程序的时候，如果对mutex和spinlock有任何疑惑，请选择使用mutex。</p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><br /></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><br /></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><br /></p><p style="color: #362e2b; font-family: Arial; line-height: 26px;"><span style="font-size: 12px;">原文参考：<a href="http://stackoverflow.com/questions/5869825/when-should-one-use-a-spinlock-instead-of-mutex" style="color: #6a3906; text-decoration: none;">http://stackoverflow.com/questions/5869825/when-should-one-use-a-spinlock-instead-of-mutex</a></span></p><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432087.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-14 13:52 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/14/432087.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>