文档章节

分布式系统基本原理

ericSM
 ericSM
发布于 2019/04/08 16:36
字数 6162
阅读 9
收藏 0

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

三元组

其实,分布式系统说白了,就是很多机器组成的集群,靠彼此之间的网络通信,担当的角色可能不同,共同完成同一个事情的系统。如果按”实体“来划分的话,就是如下这几种:

  1. 节点 -- 系统中按照协议完成计算工作的一个逻辑实体,可能是执行某些工作的进程或机器
  2. 网络 -- 系统的数据传输通道,用来彼此通信。通信是具有方向性的。
  3. 存储 -- 系统中持久化数据的数据库或者文件存储。

状态特性

各个节点的状态可以是“无状态”或者“有状态的”.

一般认为,节点是偏计算和通信的模块,一般是无状态的。这类应用一般不会存储自己的中间状态信息,比如Nginx,一般情况下是转发请求而已,不会存储中间信息。另一种“有状态”的,如mysql等数据库,状态和数据全部持久化到磁盘等介质。

“无状态”的节点一般我们认为是可随意重启的,因为重启后只需要立刻工作就好。“有状态”的则不同,需要先读取持久化的数据,才能开始服务。所以,“无状态”的节点一般是可以随意扩展的,“有状态”的节点需要一些控制协议来保证扩展。

分布式系统的三个指标


这三个指标不可能同时做到。这个结论就叫做 CAP 定理
CAP三个特性只能满足其中两个,那么取舍的策略就共有三种:

CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。

CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。

 AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。

系统异常

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

##【分布式系统与单节点的不同】

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

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

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

2、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受到了它的成功的消息。

3、不可控的状态

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

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

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

单系统下,进程/机器的异常概率十分小。即使出现了问题,可以通过人工干预重启、迁移等手段恢复。但在分布式环境下,机器上千台,每几分钟都可能出现宕机、死机、网络断网等异常,出现的概率很大。所以,这种环境下,进程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)

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

一致性 描述当前所有节点存储数据的统一模型,分为强一致性和弱一致性: 强一致性描述了所有节点的数据高度一致,无论从哪个节点读取,都是一样的。无需担心同一时刻会获得不同的数据。是级别最高的,实现的代价比较高 如图:

弱一致性又分为单调一致性和最终一致性: 1、单调一致性强调数据是按照时间的新旧,单调向最新的数据靠近,不会回退,如: 数据存在三个版本v1->v2->v3,获取只能向v3靠近(如取到的是v2,就不可能再次获得v1) 2、最终一致性强调数据经过一个时间窗口之后,只要多尝试几次,最终的状态是一致的,是最新的数据 如图:

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

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

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

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

1、重试机制

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

int retry = 3; 
while(!request() && retry--)
sched_yield();   // or usleep(100)

此种模式可以防止网络暂时的抖动,一般停顿时间很短,并重试多次后,请求成功!但不能防止对端长时间不能连接(网络问题或进程问题)

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

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

3、副本

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

4、中心化/无中心化

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

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

【分布式系统设计实践】

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

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

1、哈希取模: 哈希方式是最常见的数据分布方式,实现方式是通过可以描述记录的业务的id或key(比如用户 id),通过Hash函数的计算求余。余数作为处理该数据的服务器索引编号处理。如图:


这样的好处是只需要通过计算就可以映射出数据和处理节点的关系,不需要存储映射。难点就是如果id分布不均匀可能出现计算、存储倾斜的问题,在某个节点上分布过重。并且当处理节点宕机时,这种”硬哈希“的方式会直接导致部分数据异常,还有扩容非常困难,原来的映射关系全部发生变更。

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


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

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

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

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

4、数据块划分:

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

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

1、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机制来实现节点存活感知和网络异常检测。

2、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的情况。

本文转载自:https://blog.csdn.net/armygan/article/details/79964678

上一篇: JVM内存模型和GC
下一篇: Spark 数据倾斜
ericSM
粉丝 18
博文 142
码字总数 154379
作品 0
南京
项目经理
私信 提问
程序猿DD/consul-distributed-lock

#基于Consul的分布式锁工具 在构建分布式系统的时候,我们经常需要控制对共享资源的访问。这个时候我们就涉及到分布式锁(也称为全局锁)的实现,本项目将基于Consul的KV存储来实现一套Java的...

程序猿DD
2017/04/19
0
0
2019年给自己定的几个任务和几个小目标

本帖写给自己,新的计划新的目标 1、熟读3本技术书籍和1本心灵鸡汤书籍。 2、深入理解JVM原理,多线程,并发。 3、深入学些NIO,Netty框架。 4、深入学习分布式基本原理(一致性,三高,CAP...

lemonLove
2019/02/12
45
0
准备做自由职业者,接全文检索,企业搜索单

休息了有些日子了,现在开始做自由职业者,接全文检索,企业搜索的单子,附本人简介,有项目的朋友照顾一下。 8年JAVA项目和产品开发经验 5年管理经验 4年软件产品管理经验大型JAVA企业级软件...

R3商业智能
2011/03/18
591
8
怎样才能正确了解自身的价位,合理的选择入职公司及深研技术节点?

如题,对于年前年后的跳槽大风波,引入了思考; java程序员 如何来给自己准备的定位,发挥自己的优势,正确的找到何时的公司? 这个问题应该得到重视,正所谓 "跳槽穷半年,换行穷三年", 公司好的谁也...

尾生
2016/12/30
1K
6
【上海】大众点评网 招聘资深mysql DBA

1. 计算机相关专业大学本科及以上学历,具有 3 年及以上的 MySQL 管理和开发经验; 2.精通MySQL的自动化运营和管理,有大规模MySQL运维经验优先; 3.精通MySQL架构规划、分布式设计部署、Sha...

jinkiyo
2011/11/03
690
1

没有更多内容

加载失败,请刷新页面

加载更多

dynamic-connectivity 动态连通性问题之 quick-union 算法

quick-union 的思想是:若对象 p 的 root_id 和对象 q 的 root_id 相等,则认为 p 和 q 连通。 若要将对象 p 和对象 q 连通(已知两对象未连通),则将 p 的 root_id 的值设为 q 的 root_id ...

Phpythoner_Alei
今天
33
0
OSChina 周六乱弹 —— 实在选不出来就唱国歌

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 @花间小酌 :#今日歌曲推荐# 分享阿冗的单曲《你的答案》。--祝大家在2020年都找到自己答案。 《你的答案》- 阿冗 手机党少年们想听歌,请使劲...

小小编辑
今天
12
1
Maven打包可执行Jar包的方法

在使用Java开发中,会使用到将工程打包成可执行的jar包的情况,那么在maven中怎么将项目中的依赖包都添加到jar中呢。在pom.xml中添加一下插件: <build><plugins><plugin><ar...

CapJes
今天
10
0
使用vue 开发地图类系统(openlayers.js)的注意。

使用vue 开发地图类系统的注意。 1、使用地图应该创建的对象 少使用 vue 的data 和计算属性(comments)存数据或是vuex。 为什么要要注意这个问题呢? 答:这个就要了解到vue的实现原理 。原理...

DY-Tao
昨天
7
0
web移动端学习:高德地图demo(一)

在高德地图开发中申请开发者资格,然后在控制台中新建应用,获得KEY; 新建模板HTML文件; <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>地图demo</title><scri......

dxiya
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部