分布式系统

转载: http://blog.csdn.net/gugemichael/article/details/36688043node

写的挺好的,推荐你们看一下。mysql


”分布式系统设计“系列第一篇文章,这篇文章主要介绍一些入门的概念和原理,后面带来一些高可用、数据分布的实践方法!!linux


各位亲,若是大家以为本文有还不错的地方,请点击“投一票”支持本文,多谢!web

http://vote.blog.csdn.net/Article/Details?articleid=36688043     算法


==> 分布式系统中的概念
==> 分布式系统与单节点的不一样
==> 分布式系统特性
==> 分布式系统设计策略
==> 分布式系统设计实践


【分布式系统中的概念】

        三元组    

                其实,分布式系统说白了,就是不少机器组成的集群,靠彼此之间的网络通讯,担当的角色可能不一样,共同完成同一个事情的系统。若是按”实体“来划分的话,就是以下这几种:
                一、节点 -- 系统中按照协议完成计算工做的一个逻辑实体,多是执行某些工做的进程或机器
                二、网络 -- 系统的数据传输通道,用来彼此通讯。通讯是具备方向性的。
                三、存储 -- 系统中持久化数据的数据库或者文件存储。

                如图
                

        状态特性

                各个节点的状态能够是“无状态”或者“有状态的”.sql

                通常认为,节点是偏计算和通讯的模块,通常是无状态的。这类应用通常不会存储本身的中间状态信息,好比Nginx,通常状况下是转发请求而已,不会存储中间信息。另外一种“有状态”的,如MySQL数据库,状态和数据所有持久化到磁盘等介质。数据库

                “无状态”的节点通常咱们认为是可随意重启的,由于重启后只须要马上工做就好。“有状态”的则不一样,须要先读取持久化的数据,才能开始服务。因此,“无状态”的节点通常是能够随意扩展的,“有状态”的节点须要一些控制协议来保证扩展。

        系统异常

                异常,可认为是节点由于某种缘由不能工做,此为节点异常。还有由于网络缘由,临时、永久不能被其余节点所访问,此为网络异常。在分布式系统中,要有对异常的处理,保证集群的正常工做。


【分布式系统与单节点的不一样】


          一、从linux write()系统调用提及

         众所周知,在unix/linux/mac(类Unix)环境下,两个机器通讯,最经常使用的就是经过socket链接对方。传输数据的话,无非就是调用write()这个系统调用,把一段内存缓冲区发出去。可是能够进一步想一下,write()以后能确认对方收到了这些数据吗?

         答案确定是不能,缘由就是发送数据须要走内核->网卡->链路->对端网卡->内核,这一路径太长了,因此只能是异步操做。write()把数据写入内核缓冲区以后就返回到应用层了,具体后面什么时候发送、怎么发送、TCP怎么作滑动窗口、流控都是tcp/ip协议栈内核的事情了。

         因此在应用层,能确认对方受到了消息只能是对方应用返回数据,逻辑确认了此次发送才认为是成功的。这就却别与单系统编程,大部分系统调用、库调用只要返回了就说明已经确认完成了。

         二、TCP/IP协议是“不可靠”的

         教科书上明确写明了互联网是不可靠的,TCP实现了可靠传输。何来“不可靠”呢?先来看一下网络交互的例子,有A、B两个节点,之间经过TCP链接,如今A、B都想确认本身发出的任何一条消息都能被对方接收并反馈,因而开始了以下操做:
         A->B发送数据,而后A须要等待B收到数据的确认,B收到数据后发送确认消息给A,而后B须要等待A收到数据的确认,A收到B的数据确认消息后再次发送确认消息给B,而后A又去须要等待B收到的确认。。。死循环了!!

         其实,这就是著名的“拜占庭将军”问题:编程

         http://baike.baidu.com/link?url=6iPrbRxHLOo9an1hT-s6DvM5kAoq7RxclIrzgrS34W1fRq1h507RDWJOxfhkDOcihVFRZ2c7ybCkUosWQeUoS_缓存


         因此,通讯双方是“不可能”同时确认对方受到了本身的信息。而教科书上定义的实际上是指“单向”通讯是成立的,好比A向B发起Http调用,收到了HttpCode 200的响应包,这只能确认,A确认B收到了本身的请求,而且B正常处理了,不能确认的是B确认A受到了它的成功的消息。


         三、不可控的状态


         在单系统编程中,咱们对系统状态是很是可控的。好比函数调用、逻辑运算,要么成功,要么失败,由于这些操做被框在一个机器内部,cpu/总线/内存都是能够快速获得反馈的。开发者能够针对这两个状态很明确的作出程序上的判断和后续的操做。
         而在分布式的网络环境下,这就变得微妙了。好比一次rpc、http调用,可能成功、失败,还有多是“超时”,这就比前者的状态多了一个不可控因素,致使后面的代码不是很容易作出判断。试想一下,用A用支付宝向B转了一大笔钱,当他按下“确认”后,界面上有个圈在转啊转,而后显示请求超时了,而后A就抓狂了,不知道到底钱转没转过去,开始确认本身的帐户、确认B的帐户、打电话找客服等等。

         因此分布式环境下,咱们的其实要时时刻刻考虑面对这种不可控的“第三状态”设计开发,这也是挑战之一

         四、视”异常“为”正常“

         单系统下,进程/机器的异常几率十分小。即便出现了问题,能够经过人工干预重启、迁移等手段恢复。但在分布式环境下,机器上千台,每几分钟均可能出现宕机、死机、网络断网等异常,出现的几率很大。因此,这种环境下,进程core掉、机器挂掉都是须要咱们在编程中认为随时可能出现的,这样才能使咱们整个系统健壮起来,因此”容错“是基本需求。

         异常能够分为以下几类:服务器

         节点错误:

                  通常是因为应用致使,一些coredump和系统错误触发,通常从新服务后可恢复。

        硬件错误:

                  因为磁盘或者内存等硬件设备致使某节点不能服务,须要人工干预恢复。 

        网络错误:

                  因为点对点的网络抖动,暂时的访问错误,通常拓扑稳定后或流量减少能够恢复。

        网络分化:

                  网络中路由器、交换机错误致使网络不可达,可是网络两边都正常,这类错误比较难恢复,而且须要在开发时特别处理。【这种状况也会比较前面的问题较难处理】



【分布式系统特性】

         CAP是分布式系统里最著名的理论,wiki百科以下

                Consistency(all nodes see the same data at the same time)
                Availability (a guarantee that every request receives a response about whether it was successful or failed)
                Partition tolerance (the system continues to operate despite arbitrary message loss or failure of part of the system)
                                (摘自 :http://en.wikipedia.org/wiki/CAP_theorem)


          早些时候,国外的大牛已经证实了CAP三者是不能兼得,不少实践也证实了。
          本人就不挑战权威了,感兴趣的同窗能够本身Google。本人以本身的观点总结了一下:

          一致性
                    描述当前全部节点存储数据的统一模型,分为强一致性和弱一致性:
                    强一致性描述了全部节点的数据高度一致,不管从哪一个节点读取,都是同样的。无需担忧同一时刻会得到不一样的数据。是级别最高的,实现的代价比较高
                    如图:
    
                              弱一致性又分为单调一致性和最终一致性:
                              一、单调一致性强调数据是按照时间的新旧,单调向最新的数据靠近,不会回退,如:
                                   数据存在三个版本v1->v2->v3,获取只能向v3靠近(如取到的是v2,就不可能再次得到v1)
                              二、最终一致性强调数据通过一个时间窗口以后,只要多尝试几回,最终的状态是一致的,是最新的数据
                                    如图:



                              强一致性的场景,就好像交易系统,存取钱的+/-操做必须是立刻一致的,不然会令不少人误解。
                              弱一致性的场景,大部分就像web互联网的模式,好比发了一条微博,改了某些配置,可能不会立刻生效,但刷新几回后就能够看到了,其实弱一致性就是在系统上经过业务可接受的方式换取了一些系统的低复杂度和可用性。


          可用性
                    保证系统的正常可运行性,在请求方看来,只要发送了一个请求,就能够获得恢复不管成功仍是失败(不会超时)!


         分区容忍性
                    在系统某些节点或网络有异常的状况下,系统依旧能够继续服务。
                    这一般是有负载均衡和副原本支撑的。例如计算模块异常可经过负载均衡引流到其余平行节点,存储模块经过其余几点上的副原本对外提供服务。

          扩展性
                    扩展性是融合在CAP里面的特性,我以为此处能够单独讲一下。扩展性直接影响了分布式系统的好坏,系统开发初期不可能把系统的容量、峰值都考虑到,后期确定牵扯到扩容,而如何作到快而不太影响业务的扩容策略,也是须要考虑的。(后面在介绍数据分布时会着重讨论这个问题)



【分布式系统设计策略】


          一、重试机制


          通常状况下,写一段网络交互的代码,发起rpc或者http,都会遇到请求超时而失败状况。多是网络抖动(暂时的网络变动致使包不可达,好比拓扑变动)或者对端挂掉。这时通常处理逻辑是将请求包在一个重试循环块里,以下:

[cpp]  view plain  copy
 print ?
  1. int retry = 3;  
  2. while(!request() && retry--)  
  3.     sched_yield();   // or usleep(100)  

          此种模式能够防止网络暂时的抖动,通常停顿时间很短,并重试屡次后,请求成功!但不能防止对端长时间不能链接(网络问题或进程问题)

          二、心跳机制

          心跳顾名思义,就是以固定的频率向其余节点汇报当前节点状态的方式。收到心跳,通常能够认为一个节点和如今的网络拓扑是良好的。固然,心跳汇报时,通常也会携带一些附加的状态、元数据信息,以便管理。以下图:
 


          但心跳不是万能的,收到心跳能够确认ok,可是收不到心跳却不能确认节点不存在或者挂掉了,由于多是网络缘由却是链路不通可是节点依旧在工做。
          因此切记,”心跳“只能告诉你正常的状态是ok,它不能发现节点是否真的死亡,有可能还在继续服务。(后面会介绍一种可靠的方式 -- Lease机制)


          三、副本


          副本指的是针对一份数据的多份冗余拷贝,在不一样的节点上持久化同一份数据,当某一个节点的数据丢失时,能够从副本上获取数据。数据副本是分布式系统解决数据丢失异常的仅有的惟一途径。固然对多份副本的写入会带来一致性和可用性的问题,好比规定副本数为3,同步写3份,会带来3次IO的性能问题。仍是同步写1份,而后异步写2份,会带来一致性问题,好比后面2份未写成功其余模块就去读了(下个小结会详细讨论若是在副本一致性中间作取舍)。


          四、中心化/无中心化


          系统模型这方面,无非就是两种:
          中心节点,例如mysql的MSS单主双从、MongDB Master、HDFS NameNode、MapReduce JobTracker等,有1个或几个节点充当整个系统的核心元数据及节点管理工做,其余节点都和中心节点交互。这种方式的好处显而易见,数据和管理高度统一集中在一个地方,容易聚合,就像领导者同样,其余人都服从就好。简单可行。
          可是缺点是模块高度集中,容易造成性能瓶颈,而且若是出现异常,就像群龙无首同样。

          无中心化的设计,例如cassandra、zookeeper,系统中不存在一个领导者,节点彼此通讯而且彼此合做完成任务。好处在于若是出现异常,不会影响总体系统,局部不可用。缺点是比较协议复杂,并且须要各个节点间同步信息。


【分布式系统设计实践】


          基本的理论和策略简单介绍这么多,后面本人会从工程的角度,细化说一下”数据分布“、"副本控制"和"高可用协议"

          在分布式系统中,不管是计算仍是存储,处理的对象都是数据,数据不存在于一台机器或进程中,这就牵扯到如何多机均匀分发数据的问题,此小结主要讨论"哈希取模",”一致性哈希“,”范围表划分“,”数据块划分“

          一、哈希取模:

                    哈希方式是最多见的数据分布方式,实现方式是经过能够描述记录的业务的id或key(好比用户 id),经过Hash函数的计算求余。余数做为处理该数据的服务器索引编号处理。如图:
                    

                    这样的好处是只须要经过计算就能够映射出数据和处理节点的关系,不须要存储映射。难点就是若是id分布不均匀可能出现计算、存储倾斜的问题,在某个节点上分布太重。而且当处理节点宕机时,这种”硬哈希“的方式会直接致使部分数据异常,还有扩容很是困难,原来的映射关系所有发生变动。

                    此处,若是是”无状态“型的节点,影响比较小,但遇到”有状态“的存储节点时,会发生大量数据位置须要变动,发生大量数据迁移的问题。这个问题在实际生产中,能够经过按2的幂的机器数,成倍扩容的方式来缓解,如图:


                   

                    不过扩容的数量和方式后收到很大限制。下面介绍一种”自适应“的方式解决扩容和容灾的问题。


          二、一致性哈希:

          一致性哈希 -- Consistent Hash 是使用一个哈希函数计算数据或数据特征的哈希值,令该哈希函数的输出值域为一个封闭的环,最大值+1=最小值。将节点随机分布到这个环上,每一个节点负责处理从本身开始顺
          时针至下一个节点的所有哈希值域上的数据,如图:
          

################################################3

          一致性哈希的优势在于能够任意动态添加、删除节点,每次添加、删除一个节点仅影响一致性哈希环上相邻的节点。 为了尽量均匀的分布节点和数据,一种常见的改进算法是引入虚节点的概念,系统会建立许多虚拟节点,个数远大于当前节点的个数,均匀分布到一致性哈希值域环上。读写数据时,首先经过数据的哈希值在环上找到对应的虚节点,而后查找到对应的real节点。这样在扩容和容错时,大量读写的压力会再次被其余部分节点分摊,主要解决了压力集中的问题如图:

 


 


          三、数据范围划分:

          有些时候业务的数据id或key分布不是很均匀,而且读写也会呈现汇集的方式。好比某些id的数据量特别大,这时候能够将数据按Group划分,从业务角度划分好比id为0~10000,已知8000以上的id可能访问量特别大,那么分布能够划分为[[0~8000],[8000~9000],[9000~1000]]。将小访问量的汇集在一块儿。
          这样能够根据真实场景按需划分,缺点是因为这些信息不能经过计算获取,须要引入一个模块存储这些映射信息。这就增长了模块依赖,可能会有性能和可用性的额外代价。

          四、数据块划分:


          许多文件系统常常采用相似设计,将数据按固定块大小(好比HDFS的64MB),将数据分为一个个大小固定的块,而后这些块均匀的分布在各个节点,这种作法也须要外部节点来存储映射关系。
          因为与具体的数据内容无关,按数据量分布数据的方式通常没有数据倾斜的问题,数据老是被均匀切分并分布到集群中。当集群须要从新负载均衡时,只需经过迁移数据块便可完成。

          如图:
          


          大概说了一下数据分布的具体实施,后面根据这些分布,看看工程中各个节点间如何相互配合、管理,一块儿对外服务。


                    一、paxos

  paxos不少人都据说过了,这是惟一一个被承认的在工程中证明的强一致性、高可用的去中心化分布式协议。
虽然论文里提到的概念比较复杂,但基本流程不难理解。本人能力有限,这里只简单的阐述一下基本原理:
Paxos 协议中,有三类角色: 
Proposer:Proposer 能够有多个,Proposer 提出议案,此处定义为value。不一样的 Proposer 能够提出不一样的甚至矛盾的 value,例如某个 Proposer 提议“将变量a设置为x1” ,另外一个 Proposer 提议“将变量a设置为x2” ,但对同一轮 Paxos过程,最多只有一个 value 被批准。 
Acceptor: 批准者。 Acceptor 有 N 个, Proposer 提出的 value 必须得到超过半数(N/2+1)的 Acceptor批准后才能经过。Acceptor 之间对等独立。 
Learner:学习者。Learner 学习被批准的 value。所谓学习就是经过读取各个 Proposer 对 value的选择结果, 若是某个 value 被超过半数 Proposer 经过, 则 Learner 学习到了这个 value。从而学习者须要至少读取 N/2+1 个 Accpetor,至多读取 N 个 Acceptor 的结果后,能学习到一个经过的 value。


  paxos在开源界里比较好的实现就是zookeeper(相似Google chubby),zookeeper牺牲了分区容忍性,在一半节点宕机状况下,zookeeper就不可用了。能够提供中心化配置管理下发、分布式锁、选主等消息队列等功能。其中前二者依靠了Lease机制来实现节点存活感知和网络异常检测。

                    二、Lease机制

  Lease英文含义是”租期“、”承诺“。在分布式环境中,此机制描述为:
  Lease 是由受权者授予的在一段时间内的承诺。受权者一旦发出 lease,则不管接受方是否收到,也不管后续接收方处于何种状态,只要 lease 不过时,受权者必定遵照承诺,按承诺的时间、内容执行。接收方在有效期内可使用颁发者的承诺,只要 lease 过时,接
收方放弃受权,再也不继续执行,要从新申请Lease。
                             如图:

                              

                             


  Lease用法举例1:
  现有一个相似DNS服务的系统,数据的规律是改动不多,大量的读操做。客户端从服务端获取数据,若是每次都去服务器查询,则量比较大。能够把数据缓存在本地,当数据有变更的时候从新拉取。如今服务器以lease的形式,把数据和lease一同推送给客户端,在lease中存放承诺该数据的不变的时间,而后客户端就能够一直放心的使用这些数据(由于这些数据在服务器不会发生变动)。若是有客户端修改了数据,则把这些数据推送给服务器,服务器会阻塞一直到已发布的全部lease都已经超时用完,而后后面发送数据和lease时,更新如今的数据

  这里有个优化能够作,当服务器收到数据更新须要等全部已经下发的lease超时的这段时间,能够直接发送让数据和lease失效的指令到客户端,减少服务器等待时间,若是不是全部的lease都失效成功,则退化为前面的等待方案(几率小)。



  Lease用法举例2:

  现有一个系统,有三个角色,选主模块Manager,惟一的Master,和其余salver节点。slaver都向Maganer注册本身,并由manager选出惟一的Master节点并告知其余slaver节点。当网络出现异常时,多是Master和Manager之间的链路断了,Master认为Master已经死掉了,则会再选出一个Master,可是原来的Master对其余网络链路可能都仍是正常的,原来的Master认为本身仍是主节点,继续服务。这时候系统中就出现了”双主“,俗称”脑裂“。
  解决这个问题的方式能够经过Lease,来规定节点能够当Master的时间,若是没有可用的Lease,则自动退化为Slaver。若是出现”双主“,原Master会由于Lease到期而放弃当Master,退化为Slaver,恢复了一个Master的状况。


                    三、选主算法

                    有些时候出于系统某些特性,能够在有取舍的状况下,实现一些相似Lease的选主的方案,可见本人另外一篇文章:

  http://blog.csdn.net/gugemichael/article/details/8964834