分布式是一门计算机科学,而分布式服务器就是将数据、程序等不同类型的数据分布在不同的服务器。
一般情况下,使用分布式架构搭建一个网站至少需要一台服务器存放数据库,一台服务器存放网站程序。
景安河南最大的多线服务器托管商!提供专业的双机热备、负载均衡等增值服务
本文基于对redis、zookpeer、rocketmq、elasticsearch学习总结,对于分布式系统学习,一定绕不开一个点,那就是CAP定理。什么是CAP定理,我这里简单的复制摘抄一下百度上的文案。
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
说明一下上面的三个要素各代表的含义:
CAP定理说明上述的三个要素不能兼顾,最多只能满足其中的两个要素,在分布式系统中,一般都是保证分区容错性,而在一致性和可用性之间做取舍。因此存在CP、AP两种分布式集群的实现。
CP集群,即满足一致性和分区容错性,如zookpeer
AP集群,即满足可用性和分区容错性,如redis-cluster
下面,针对与上述的CP和AP问题,我们展开话题。
对于分布式系统,学习了解多了之后,发现其内在的解决方案基本上都是一样的,所谓万变不离其中。总结一下大体在于以下几步:
数据分片,很多分布式系统尤其是中间件服务,一般都会涉及高并发,数据量大的问题,如redis-cluster、recketmq,以及被大家熟知的Elasticsearch。针对于大数据量高并发的问题,若不做处理,服务器的性能将会成为服务的瓶颈,解决的方案之一便是数据分片,将大数据量在集群中按照一定的规则分片,使数据按照一定的规则分布集群的不同服务器上,以减轻单个服务器的压力,保证服务集群的可用性。
redis-cluster的数据分片是通过redis-cluster的哈希槽来实现的,redis-cluster有16384个哈希槽,这个数量是固定的,根据集群中服务器的数量可以手动的调配每个服务上存放的hash槽的数量,哈希槽之间是相互独立的,因此对集群的扩展提供了便利。
rocketmq的分片和topic紧密相关,在使用rocketmq中,无论是消息的生产者还是消费者都需要注册订阅一个topic。在rocketmq集群中,集群中的broker保存这个topic下数据的一部分,也就是topic的其中一个数据分片。当然,rocketmq不仅将一个topic下的数据分片到多个broker上,而且,一个broker上的topic数据还可以被分为多个queue,这是因为rocketmq中,一个queue只能被一个consumer消费,若是consumer的数量多于queue的数量,没有绑定queue的consumer将不能消费数据。
elasticsearch的数据分片在我看来和mysql的分库分表原理是一样的,elasticsearch中,每一个索引都相当于mysql的一个表,将一个索引分成多个shard放在不同的节点上,每个shard存储一部分数据。elasticsearch将数据进行分片,这样可以支持集群的横向扩展,同时,多个节点提供服务可以提高系统的效率和吞吐量。
综上所述,数据分片的一般都有两个好处,一个是支持集群的横向扩展,而是提升服务的吞吐量和性能。数据分片解决了以上两个问题,但是若是集群中一个节点发生宕机,或者因为网络原因和集群断开链接,那么这部分的数据分片甚至整个集群都会不可用,如何解决这个问题,就需要用到数据备份和主备切换。
数据分片的策略 了解了数据分片之后,需要了解以下数据分片的策略,根据集群提供服务的性质不同,可以采用的数据分片策略也各有不同,下面是我学习后的总结:
说到这里,会发现其实这种分片策略和负载均衡的策略还是挺相似的。
数据备份,举个例子来说,我有两台电脑A、电脑B,A用于工作,B用于游戏,我写了一篇文章,保存在电脑上电脑上,若是某一天我的电脑A磁盘坏了,那我这篇文章就找不到了,即便我现在还有电脑B,我也没有办法在对文章进行编辑。但是若是我在之前,就将文章拷贝了一份放在电脑B上,那么现在,我用电脑B就可以对文件进行编辑修改。
举这个例子,我的目的就是为了说明数据备份对于集群可用性的意义,例子中,我的两台电脑可以认为是集群中两台服务器,两台服务器一开始提供的服务可能不相同,A电脑提供的就是编辑文章的服务,数据备份的意义就在于,当原本提供服务的服务器宕机损坏,集群中另外的服务器仍然可以根据已经备份的数据提供相同的服务,而不会影响到用户的工作。
数据备份的目的就是不发生单点问题的措施之一,但是若是数据备份的策略不合适,备份的时机不对,那么备份的数据时效性也是问题。还是从例子出发,这里的文章每次都是我手动从A电脑拷贝到B电脑,这是我的备份策略,若是我选择每天晚上才拷贝一次,那么若是A电脑在我拷贝之前坏了,当天的文章编辑数据就丢失了,采用手动的方式备份,这种备份方式耗时耗力且不可控,而在分布式集群中,不同的系统采用了不同的备份策略,下面一一来说明。
首先明确一点,在分布式集群中,不可能采用人工手动备份,一定是系统程序按照一定的规则自动备份,就好像我将AB连在一起,写个程序,让A电脑自动把文章同步到B电脑。数据备份的方式分为两种:
这里以redis-cluster和zookeeper举例。
在redis-cluster中,当一台新的slave节点加入时,会出发数据同步,需要将主节点的数据同步到从节点。这时根据从节点的状态有两种同步方案:完整重同步 和 部分重同步
完整重同步既是将主节点的全部数据都复制给新的slave节点。大致流程为,当一个新的节点加入进来时,发送PSYNC命令给主节点并携带slave节点自身的信息(重点是复制偏移量),主节点会根据slave传过来的信息判断是完整重同步还是部分重同步,如何判断与数据同步时的复制缓冲区有关,更细节不展开介绍。
相对于redis-cluster,zookeeper中的数据同步有四种方式,和redis-cluster完整重同步和部分重同步相似的SNAP(全量同步)和DIFF(增量同步),以及zk事务处理相关的TRUNC(仅回滚同步)、TRUNC+DIFF(回滚+增量同步)
当节点已经加入集群,成为集群中的从节点,只要不断开连接,一般都只需要进行增量同步,不过系统同步的范围和方式有所差异,大致分为下面六种:
下面还是以具体服务来举例: redis-cluster中,主从复制采用的是异步复制的方式,master节点在做数据变更之后,会由一个异步线程将数据变更同步给slave节点,这是通过push的方式。当redis28之后,slave会周期的获取最新的数据,加入了pull方式。无论是master还是slave,在进行数据同步时,不会阻塞正常的应用请求。所以redis-cluster的主从复制,是异步备份+最终一致性的备份。
elasticsearch的主从复制可以手动设置同步备份或者异步备份,数据备份时不要求强一致性,而是主分片(primary shard)会维护一份需要同步的(replica shard)分片列表,这个分片列表同步完成,则认为数据备份完成,需要注意的是,这里的主从复制不是节点的更新数据,而是分片的更新数据。
rocketmq的主从复制和elasticsearch类似,也可以分为同步备份和异步备份,不同的是rocketetmq的数据备份采用的是pull的方式,从节点会通过HAConnection链接主动向主节点发送待拉取数据偏移量,待主节点返回节点更新数据信息,更新从节点数据偏移量,如此重复。
zookeeper的数据备份则是通过ZAB协议,通过消息广播的方式同步数据到从节点。
当数据备份后,主从节点上就有了相同的数据,为了提升服务的性能,那么可以采用读写分离的方式。主节点提供数据写服务,从节点提供读服务,可以有效的分担主节点的服务器压力。可以进行数据分片的系统,如:redis、rocketmq、elasticsearch,一般都可以配置一主多从、多主多从的集群架构。
读写分离之后,主节点提供写服务,从节点只提供读服务,因此若是主节点发生宕机,从节点依然可以提供读服务,但是服务无法更新数据,这时候就要进行主从切换。早起,主从切换可以由人工手动完成,不过随着技术发展,主从切换已经成为集群的必备功能。想要实现主从切换,必须要解决两个问题:
解决这个问题,需要额外再引入一个角色,相当于是一个监视者的角色,能够长期的对主节点进行监视,若是只有一个监视者,可能会发生误判,所以还需要一套机制去保证当监视者说主节点宕机,那么主节点是真的宕机,否则集群会出现脑裂问题。
以redis为例,在redis的哨兵模式中,这个监视者的角色是一个个哨兵实例,而在redis-cluster架构中,这个监视者的角色是redis实例自己。
在redis哨兵模式中,哨兵集群中的哨兵实例会定期和redis实例进行通信(ping),监视redis实例的在线情况,若是其中一台哨兵发现redis实例master故障,那么该哨兵会将该master状态改为主观下线,并通知其他哨兵,当哨兵集群中达到配置数量的哨兵实例认为该master都为主观下线状态,这时会将master修改为客观下线状态,并开始触发后续的故障转移。
在redis-cluster模式中,集群中的每一个节点都可以和其他节点通讯(ping),当某一个节点A发现主节点B下线了,A会将该主节点B设为疑似下线状态。集群中的节点会通过互发消息维护信息,当另一个节点C收到A的消息时,会将A对B节点的判断记录在C节点的维护信息下,这个信息可以理解为A说C疑似下线了。若是有其他节点发送C的状态信息,A同样也会记录。当某一个节点如C发现记录的B节点信息中,超过半数的主节点都认为B下线了,那么C就会将B节点状态修改为已下线状态,并广播消息给集群的其他节点,开始后续的故障转移。
上面就是redis的两种分布式模式故障检测的方案。大致可以归结为,监视节点会和被监视节点进行通讯,感知被监视节点的状态;监视节点之间也会进行通讯,同步信息。为了防止集群出现脑裂,对于某个主节点的故障判断会十分的谨慎,需要达到一定数量的监视节点都认为主节点故障时,才会认为主节点真的故障,从而触发故障转移。
在rocketmq集群模式中,nameserver扮演着监视者的角色(不同于其他系统,nameserver并不负责集群的主从切换,rocketmq 45之前不支持自动主从切换,45之后,通过dledger实现自动的故障转移)。在elasticsearch集群中,elasticsearch实例本身在扮演监视者角色。zookeeper也是实例本身扮演监视者的角色。
故障转移就是当集群发现集群中的主节点/从节点发生故障之后的处理,从节点比较简单,直接将从节点下线即可,主节点的故障转移流程比较复杂,各个系统根据系统的功能和架构有不同的实现方式,共同点是选举出的主节点一定是集群中数据最新的最完善的节点。
选举过程大致如下:
首先选举成功的条件时集群中具有投票权限的超过半数的节点投票一致,通过某一个节点成为主节点。
开始一轮选举时,定义为一个纪元,用一个自增的id表示。
候选节点将带着纪元id,以及自身信息作为投票申请广播给集群给可投票的节点。
具有投票权限的节点投票只要满足两个条件:1自身在最新纪元没有给投过票 2节点发送过来的投票申请时最新纪元的(如何判断时最新纪元,则是判断一下节点之前通过申请的纪元id是否小于当前申请的纪元id)。
半数以上的投票节点通过某一个候选节点成为leader节点,则leader产生。
若是一个纪元没有产生主节点,则候选节点进入随机的休眠,并且开启下一个纪元,知道产生leader节点。
在zk集群经过崩溃恢复模式之后,需要保证:1已经提交的事务不能丢失 2未被提交的事务不能出现。如何保证以上两点,zk服务集群中维护了zxid,zxid也可以看作是一个自增的id,集群中每产生一个新事物,zxid就会增加。zxid有64位,前32位维护了集群主节点变更情况,每重新选举出一个新的主节点则增加,后32位维护在新的主节点集群下事务的id,产生一个新事物则增加。
ZAB的选举模式有很多种,我主要了解了默认,也是推荐的FastLeaderElection模式,在这个模式下,我会以集群中一台参与选举的服务器的视角来模拟选主的过程;
我是一台zk服务器,我现在很慌,因为我的leader服务器不见了,作为一个有梦想的follower,我也要参加leader的选举,为了这次选举我要准备:myid(在集群中标识是这台服务器的id),zxid(本台服务器保存的最新事务id),logicClock(本台服务器发起的第几轮投票)
首先我会自己选自己,这得自信。于是我将自身的选举信息[myid, zxid]放到自己的收票箱,然后将我的选举信息还有我的选举轮次logicClock广播给其他服务器进行PK
作为一个有原则的服务器,我们的选举也是有原则的,当我收到别人的选举信息时,我也会将他和我自己的选举信息进行PK,PK的原则如下:
经过这一系列的PK,终于选出了我心中的leader服务器,要广播给其他服务器。
超过半数的服务器都同意某一台服务器成为leader,选举结束了。
如何解决多台客户端连接在不同服务器,互相发送消息问题!描述:
客户端A 和客户端B 都订阅同一个Topic ,后台Websocket收到消息后,将消息发送至Redis中,同时服务端会监听该渠道内的消息,监听到消息后,会将消息推送至对应的客户端。
暂未完成,敬请期待!
当我们在生产线上用一台服务器来提供数据服务的时候,我会遇到如下的两个问题:
1)一台服务器的性能不足以提供足够的能力服务于所有的网络请求。
2)我们总是害怕我们的这台服务器停机,造成服务不可用或是数据丢失。
于是我们不得不对我们的服务器进行扩展,加入更多的机器来分担性能上的问题,以及来解决单点故障问题。 通常,我们会通过两种手段来扩展我们的数据服务:
1)数据分区:就是把数据分块放在不同的服务器上(如:uid % 16,一致性哈希等)。
2)数据镜像:让所有的服务器都有相同的数据,提供相当的服务。
对于第一种情况,我们无法解决数据丢失的问题,单台服务器出问题时,会有部分数据丢失。所以,数据服务的高可用性只能通过第二种方法来完成——数据的冗余存储(一般工业界认为比较安全的备份数应该是3份,如:Hadoop和Dynamo)。 但是,加入更多的机器,会让我们的数据服务变得很复杂,尤其是跨服务器的事务处理,也就是跨服务器的数据一致性。这个是一个很难的问题。 让我们用最经典的Use Case:“A帐号向B帐号汇钱”来说明一下,熟悉RDBMS事务的都知道从帐号A到帐号B需要6个 *** 作:
从A帐号中把余额读出来。
对A帐号做减法 *** 作。
把结果写回A帐号中。
从B帐号中把余额读出来。
对B帐号做加法 *** 作。
把结果写回B帐号中。
为了数据的一致性,这6件事,要么都成功做完,要么都不成功,而且这个 *** 作的过程中,对A、B帐号的其它访问必需锁死,所谓锁死就是要排除其它的读写 *** 作,不然会有脏数据的问题,这就是事务。那么,我们在加入了更多的机器后,这个事情会变得复杂起来:
1)在数据分区的方案中:如果A帐号和B帐号的数据不在同一台服务器上怎么办?我们需要一个跨机器的事务处理。也就是说,如果A的扣钱成功了,但B的加钱不成功,我们还要把A的 *** 作给回滚回去。这在跨机器的情况下,就变得比较复杂了。
2)在数据镜像的方案中:A帐号和B帐号间的汇款是可以在一台机器上完成的,但是别忘了我们有多台机器存在A帐号和B帐号的副本。如果对A帐号的汇钱有两个并发 *** 作(要汇给B和C),这两个 *** 作发生在不同的两台服务器上怎么办?也就是说,在数据镜像中,在不同的服务器上对同一个数据的写 *** 作怎么保证其一致性,保证数据不冲突?
同时,我们还要考虑性能的因素,如果不考虑性能的话,事务得到保证并不困难,系统慢一点就行了。除了考虑性能外,我们还要考虑可用性,也就是说,一台机器没了,数据不丢失,服务可由别的机器继续提供。 于是,我们需要重点考虑下面的这么几个情况:
1)容灾:数据不丢、节点的Failover
2)数据的一致性:事务处理
3)性能:吞吐量 、 响应时间
前面说过,要解决数据不丢,只能通过数据冗余的方法,就算是数据分区,每个区也需要进行数据冗余处理。这就是数据副本:当出现某个节点的数据丢失时可以从副本读到,数据副本是分布式系统解决数据丢失异常的唯一手段。所以,在这篇文章中,简单起见,我们只讨论在数据冗余情况下考虑数据的一致性和性能的问题。简单说来:
1)要想让数据有高可用性,就得写多份数据。
2)写多份的问题会导致数据一致性的问题。
3)数据一致性的问题又会引发性能问题
这就是软件开发,按下了葫芦起了瓢。
一致性模型
说起数据一致性来说,简单说有三种类型(当然,如果细分的话,还有很多一致性模型,如:顺序一致性,FIFO一致性,会话一致性,单读一致性,单写一致性,但为了本文的简单易读,我只说下面三种):
1)Weak 弱一致性:当你写入一个新值后,读 *** 作在数据副本上可能读出来,也可能读不出来。比如:某些cache系统,网络游戏其它玩家的数据和你没什么关系,VOIP这样的系统,或是百度搜索引擎(呵呵)。
2)Eventually 最终一致性:当你写入一个新值后,有可能读不出来,但在某个时间窗口之后保证最终能读出来。比如:DNS,电子邮件、Amazon S3,Google搜索引擎这样的系统。
3)Strong 强一致性:新的数据一旦写入,在任意副本任意时刻都能读到新值。比如:文件系统,RDBMS,Azure Table都是强一致性的。
从这三种一致型的模型上来说,我们可以看到,Weak和Eventually一般来说是异步冗余的,而Strong一般来说是同步冗余的,异步的通常意味着更好的性能,但也意味着更复杂的状态控制。同步意味着简单,但也意味着性能下降。 好,让我们由浅入深,一步一步地来看有哪些技术:
Master-Slave
首先是Master-Slave结构,对于这种加构,Slave一般是Master的备份。在这样的系统中,一般是如下设计的:
1)读写请求都由Master负责。
2)写请求写到Master上后,由Master同步到Slave上。
从Master同步到Slave上,你可以使用异步,也可以使用同步,可以使用Master来push,也可以使用Slave来pull。 通常来说是Slave来周期性的pull,所以,是最终一致性。这个设计的问题是,如果Master在pull周期内垮掉了,那么会导致这个时间片内的数据丢失。如果你不想让数据丢掉,Slave只能成为Read-Only的方式等Master恢复。
当然,如果你可以容忍数据丢掉的话,你可以马上让Slave代替Master工作(对于只负责计算的节点来说,没有数据一致性和数据丢失的问题,Master-Slave的方式就可以解决单点问题了) 当然,Master Slave也可以是强一致性的, 比如:当我们写Master的时候,Master负责先写自己,等成功后,再写Slave,两者都成功后返回成功,整个过程是同步的,如果写Slave失败了,那么两种方法,一种是标记Slave不可用报错并继续服务(等Slave恢复后同步Master的数据,可以有多个Slave,这样少一个,还有备份,就像前面说的写三份那样),另一种是回滚自己并返回写失败。(注:一般不先写Slave,因为如果写Master自己失败后,还要回滚Slave,此时如果回滚Slave失败,就得手工订正数据了)你可以看到,如果Master-Slave需要做成强一致性有多复杂。
Master-Master
Master-Master,又叫Multi-master,是指一个系统存在两个或多个Master,每个Master都提供read-write服务。这个模型是Master-Slave的加强版,数据间同步一般是通过Master间的异步完成,所以是最终一致性。 Master-Master的好处是,一台Master挂了,别的Master可以正常做读写服务,他和Master-Slave一样,当数据没有被复制到别的Master上时,数据会丢失。很多数据库都支持Master-Master的Replication的机制。
另外,如果多个Master对同一个数据进行修改的时候,这个模型的恶梦就出现了——对数据间的冲突合并,这并不是一件容易的事情。看看Dynamo的Vector Clock的设计(记录数据的版本号和修改者)就知道这个事并不那么简单,而且Dynamo对数据冲突这个事是交给用户自己搞的。就像我们的SVN源码冲突一样,对于同一行代码的冲突,只能交给开发者自己来处理。(在本文后后面会讨论一下Dynamo的Vector Clock)
Two/Three Phase Commit
这个协议的缩写又叫2PC,中文叫两阶段提交。在分布式系统中,每个节点虽然可以知晓自己的 *** 作时成功或者失败,却无法知道其他节点的 *** 作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的 *** 作结果并最终指示这些节点是否要把 *** 作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)