什么是分布式呢?node
起初,咱们的应用流量比较小,全部东西所有部署在一个服务器,好比所有丢给一个tomcat来处理,顶多作一个tomcat的多节点部署多分,再挂一台Nginx作一下负载均衡就OK了。可是随着业务功能复杂度上升,访问流程的上升,单体架构就不行了。这个时候就该分布式上场了,将业务模块作必定拆分,各业务组件分布在网络上不一样的计算机节点上,同时为了保证高可用性和性能,单个组件模块也会作集群部署。算法
分布式虽然爽了,可是随之而来的就是分布式带来的复杂性,好比在分布式系统中网络故障问题几乎是必然存在的;事务也再也不是数据库帮咱们保证了,由于可能每一个业务有本身的库,但不一样业务之间又有保证事务的需求,这是就须要考虑实现分布式事务了;还有数据一致性问题,集群中副本节点不能及时同步到主节点的数据,会有数据一致性问题须要解决。spring
实践须要理论的知道,一样地,在分布式软件开发领域内,也是有前辈大神们作了基础的理论研究。下面将要介绍的就是分布式相关的两个基础理论:CAP定理和BASE理论。数据库
在聊CAP定理前,咱们先简单了解下分布式事务。数据库的事务咱们知道。假如银行转帐,转出操做和转入操做在同一个数据库中,就很好实现了,只须要在方法上增长一个@Transactional,剩下的事情数据库会帮咱们作好。可是在分布式环境中,咱们对比现实中的银行转帐,夸行转帐,不止是夸库,更是夸不一样的银行系统的。在这种场景,咱们须要保证ACID的特性,就是须要分布式事务解决方案了。好了,这里只是作个告终。下面说CAP定理。tomcat
CAP定理是说,在一个分布式系统中,不可能同时知足一致性(Consistency)、可用性(Availiablity)和分区容错性(Partition tolerance)这三个基本的需求。最多只能知足其中的两项。安全
一致性bash
在分布式环境中,一致性是指数据在多个副本之间是否可以保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操做后,应该保持系统的数据仍然处于一致的状态。若是对第一个节点上的数据更新成功后,第二个节点上的数据并无获得相应的更新,那么若是从第二个节点读取数据,则获取到的就是旧数据(或者或是脏数据)。这就是典型的分布式数据不一致的场景。服务器
可用性网络
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每个操做请求老是能在有限的时间内返回结果。这里有限的时间就是系统响应时间session
分区容错性
分布式系统在遇到任何网络分区故障的时候,仍然须要保证可以对外提供知足一致性和可用性的服务,除非是整个网络环境发生了故障。
网络分区是指在分布式系统中,不一样节点分布在不一样的子网络中,因为一些特殊的缘由致使这些子网络之间出现了网络不连通的状况,但各个子网络的内部网络是正常的,从而致使整个系统的网络环境被切分红了若干个孤立的区域。
上面说到,一个分布式系统不可能同时知足CAP的特性。可是,须要说明的是,分区容错性P是一个最基本的需求。由于分布式系统中的组件必然部署在不一样的网络节点上,网络问题是必然会出现的一个问题。所以就剩下两种选择了,即CP和AP。系统架构须要在C和A之间寻求平衡。
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)的缩写。BASE是对CAP中一致性和可用性权衡的结果。是基于CAP定理逐步演化而来,其核心思想是即便没法作到强一致性,但每一个应用均可以根据自身的业务特色,采用适当的方式来使系统达到最终的一致性。
基本可用
分布式系统在出现不可预知的故障的时候,容许损失部分可用性。好比响应时间上的损失,原来0.2s返回的,如今可能须要2s返回。或者是部分功能上的损失,好比秒杀场景下部分用户可能会被引导到一个降级的页面。
软状态
是和硬状态相对的,是指容许系统中的数据存在中间的状态。可是中间的状态并不会影响系统的总体可用性。
最终一致性
是指系统中的全部数据副本,通过必定时间的同步后,最终可以达到一个一致的状态。而不是要实时保证系统数据的强一致性。
Apache Zookeeper是由Apache Hadoop的子项目发展而来,2011年正式成为Apache的顶级项目。Zookeeper为分布式应用提供了高效且可靠的分布式协调服务。在解决分布式数据一致性方面,Zookeeper并无使用Paxos算法(一种一致性算法),而是采用了ZAB(Zookeeper Atomic Broadcast)的一致性协议。
分布式应用程序能够基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调、集群管理、Master选举、分布式锁和分布式队列等功能。Zookeeper能够保证以下分布式一致性特性。
顺序一致性
同一个客户端发起的事务请求,最终将会严格的按照其发起的顺序被应用到Zookeeper中去。
原子性
全部事务请求的处理结果在整个集群中全部机器上的应用状况是一致的,也就是说,也么集群中的全部节点都应用了一个事务,要么都没有应用。
单一视图
不管客户端链接的是哪一个Zookeeper服务器,其看到的服务端数据模型都是一致的。
可靠性
一旦服务端成功的应用了一个事务,并完成对客户端的响应那么该事务所引发的服务端状态改变将会被一直保留下来,除非有另外一个事务又对其进行了变动。
实时性
Zookeeper仅仅保证在必定的时间段内,客户端最终必定可以从服务端读取到最新的数据状态。
Zookeeper中有数据节点的概念,咱们称之为ZNode,ZNode是Zookeeper中数据的最小单元。每一个ZNode上均可以保存数据,同时还能够挂载子节点,所以就构成了一个层次化的命名空间,咱们叫作树。相似于Linux文件系统中的目录树。
看图就明白了。
Zookeeper事务
Zookeeper中的事务,和数据库中具备ACID特性的事务有所区别。在Zookeeper中,事务是指可以改变Zookeeper服务器状态的操做,咱们叫作事务操做或者更新操做,通常包括数据节点的建立和删除、数据节点内容更新和客户端会话建立与失效操做。对于每个事务请求,Zookeeper都会为其分配一个全局惟一的事务ID,用ZXID来表示,一般是一个64位的数字。每个ZXID对应一次更新操做,从这些ZXID中能够间接地识别出Zookeeper处理这些更新操做请求的全局顺序。
数据节点的类型
在Zookeeper中,节点类型能够分为持久节点(PERSISTENT),临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)。在具体的节点建立中,经过组合,有下面四种组合节点类型:
持久节点
是最多见的一种节点类型,是指数据节点被建立后,就会一直存在于Zookeeper服务器中,直到有删除操做来主动删除这个节点。
持久顺序节点
和持久节点的特性是同样的,额外的特性表如今顺序性上。在Zookeeper中,每一个父节点都会为它的第一级子节点维护一份顺序,用于记录每一个子节点建立的顺序。基于这个顺序特性,在建立子节点的时候,能够设置这个标记,那么在建立节点过程当中,Zookeeper会自动为节点名加上一个数字后缀,做为一个新的完整的节点名。数字后缀的上限是整型的最大值。
临时节点
和持久节点不一样的是,临时节点的生命周期和客户端的会话绑定在一块儿,也就是说,若是客户端的会话失效,那么这个节点就会被自动清理掉。这里提到的是客户端会话失效,而非TCP链接断开。
临时有序节点
临时有序节点的特性和临时节点的特性同样,只是增长了有序的特性。
状态信息
在Zookeeper客户端中,咱们经过stat命令,能够查看节点的状态信息。
1[zk: localhost:2181(CONNECTED) 6] stat /test/node1
2cZxid = 0x8
3ctime = Sun May 03 21:12:24 CST 2020
4mZxid = 0xb
5mtime = Sun May 03 21:13:08 CST 2020
6pZxid = 0x8
7cversion = 0
8dataVersion = 1
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 0
下面简要介绍下状态信息中各字段含义:
1cZxid: Created ZXID ,表示数据节点被建立时的事务ID。
2ctime: Created Time,表示节点被建立的时间。
3mZxid: Modified ZXID,表示该节点最后一次被更新时的事务ID。
4mtime: Modified Time,表示该节点最后一次被更新的时间。
5pZxid: 表示该节点的子节点列表最后一次被修改时的事务ID,注意只有子节点列表变动了才会变动pZxid,子节点内容的变动不会影响pZxid。
6cversion: 子节点的版本号。
7dataVersion: 数据的版本号。
8aclVersion: acl权限的版本号。
9ephemeralOwner: 建立该临时节点的会话的sessionID,若是该节点是持久节点,那么这个属性值为0。
10dataLength: 数据内容的长度。
11numChildren: 当前节点的子节点个数。
上面状态中的version字段是一种乐观锁机制的保证,保证并发更新数据的安全性。
在Zookeeper中,引入了Watcher机制来实现这种分布式的通知功能。Zookeeper容许客户端向服务器注册一个Watcher监听,当服务端的一个特定事件触发了这个Watcher,那么就会向客户端发送一个事件通知来实现分布式的通知功能。这个过程能够看下面的图。
构成集群的每一台机器都有本身的角色,最典型的集群模式就是Master/Slave模式。在这种集群模式中,Master节点复杂读写操做,Slave负责提供读服务,并以异步的方式从Master同步数据。
而在Zookeeper集群中,没有使用传统的Master/Slave集群模式,而是引入了Leader、Follower和Observer三种角色。ZK集群中的全部机器经过一个Leader选举过程来选定一台机器做为“Leader”的机器。Leader服务器为客户端提供读和写服务。Follower和Observer都可以提供读服务,惟一区别在于Observer机器不参与Leader选举过程。
Leader
Leader服务器是整个zk集群工做机制中的核心,其主要工做是如下两个:
Follower
Follower服务器时集群状态的跟随者,其主要的工做有一下三个。
Observer
在zk集群中充当了一个观察者的角色,观察集群的最新状态并将这些状态变动同步过来。Observer服务器的工做原理和Follower服务器基本是一致的,对于非事务的请求均可以进行独立的处理,对于事务请求会转发给Leader处理。和Follower惟一的区别在于,Observer不参与任何形式的投票,包括事务请求Proposal投票和Leader选举投票。
安装好Zookeeper以后,就可使用zk自带的客户端脚原本进行操做了。进入Zookeeper的bin目录以后,直接执行以下命令:
1sh zkCli.sh
当看到出现下面这句话时,说明已经成功链接到了zkserver。
1WatchedEvent state:SyncConnected type:None path:null
进入客户端后,能够直接使用help命令看下支持哪些命令。
下面说一下在zk客户端中怎么操做节点和数据。
增长节点
使用create命令新建一个节点。命令格式以下:
1create [-s] [-e] [-c] [-t ttl] path [data] [acl]
-s是顺序特性,-e是临时节点。
好比执行下面命令
1[zk: localhost:2181(CONNECTED) 10] create /Java hello
2Created /Java
会在根节点下建立一个/Java节点,而且节点的数据内容是hello。默认建立的是持久节点。
能够继续在/Java节点下建立子节点,好比:
1[zk: localhost:2181(CONNECTED) 11] create /Java/spring 123
2Created /Java/spring
读取
使用ls命令能够看到指定节点下的全部子节点。只能看一级,不能列出子节点树。命令格式为:
1ls [-s] [-w] [-R] path
好比执行 ls / 命令,能够看下根节点下的子节点状况。
1[zk: localhost:2181(CONNECTED) 12] ls /
2[Java, aaa, happy, test, zookeeper]
可使用get 命令获取节点的数据内容,命令格式为:
1get [-s] [-w] path
好比咱们获取一下/Java节点中的数据内容:
1[zk: localhost:2181(CONNECTED) 14] get /Java
2hello
获取节点状态信息
使用stat命令能够获取节点的状态信息,命令格式以下:
1stat [-w] path
例如,咱们获取一下/Java节点的状态信息
1[zk: localhost:2181(CONNECTED) 15] stat /Java
2cZxid = 0x14
3ctime = Mon May 11 21:40:38 CST 2020
4mZxid = 0x14
5mtime = Mon May 11 21:40:38 CST 2020
6pZxid = 0x15
7cversion = 1
8dataVersion = 0
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 1
上面这些信息咱们在以前聊到Zookeeper数据节点是有讲过是什么意思,能够回顾下。
更新
使用set命令能够更新指定节点的数据内容。命令格式以下:
1 set [-s] [-v version] path data
好比咱们将/Java节点的内容更新为 world。
1[zk: localhost:2181(CONNECTED) 16] set /Java world
2[zk: localhost:2181(CONNECTED) 17] get /Java
3world
数据更新完成以后,咱们可使用stat命令,再看一下节点的状态信息,发现dataVersion已经由0变为1了。
1[zk: localhost:2181(CONNECTED) 18] stat /Java
2cZxid = 0x14
3ctime = Mon May 11 21:40:38 CST 2020
4mZxid = 0x16
5mtime = Mon May 11 21:52:19 CST 2020
6pZxid = 0x15
7cversion = 1
8dataVersion = 1
9aclVersion = 0
10ephemeralOwner = 0x0
11dataLength = 5
12numChildren = 1
由于刚才更新数据内容的操做致使数据版本升级。
删除
使用delete命令删除Zookeeper节点,用法以下:
1 delete [-v version] path
好比咱们将/Java节点删除掉。不过须要注意的是,要删除的节点必须没有子节点才能够。下面直接删除/Java节点是删除不掉的。由于它下面有子节点。
1[zk: localhost:2181(CONNECTED) 20] delete /Java
2Node not empty: /Java
3[zk: localhost:2181(CONNECTED) 21] ls /
4[Java, aaa, happy, test, zookeeper]
能够删除/Java/spring节点
1[zk: localhost:2181(CONNECTED) 22] ls /Java
2[spring]
3[zk: localhost:2181(CONNECTED) 23] delete /Java/spring
4[zk: localhost:2181(CONNECTED) 24] ls /Java
5[]
ZAB协议是为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。它并非Paxos算法的一种实现。
在Zookeeper中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议,Zookeeper实现了一种主备模式的系统架构来保证集群模中各副本之间数据的一致性。ZAB协议要知足下面一些核心需求。
ZAB协议的核心机制
其核心机制是定义了对于那些会改变Zookeeper服务器数据状态的事务请求的处理方式,即:
全部的事务请求必须由一个全局惟一的服务器来协调处理,这样的服务器称为Leader服务器,而余下的其余服务器是Follower(这里暂不说Observer,由于不参与投票)。Leader服务器复杂将一个客户端的事务请求转换成一个事务提议(Proposal),并将该Proposal分发给集群中全部的Follower服务器。以后Leader服务器须要等待全部Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确地反馈后,那么Leader就会再次向全部的Follower服务器分发Commit消息,要求其将前一个Proposal进行提交。
针对ZAB协议这里只作简要介绍,至于崩溃恢复和消息广播的具体内容不详细展开。
前面说到在ZK集群中,有一个Leader负责处理事务请求。Leader是经过一种选举算法选出来的一个Zookeeper服务器节点。下面简要介绍下Leader选举的过程。
Leader选举有两个时机:
一、服务器启动时Leader选举
二、运行期Leader节点挂了,须要选举新的Leader。
服务器启动时的Leader选举
这里以3台机器组成的集群为例子。Server1(myid为1)、Server2(myid为2)、Server3(myid为3)。myid是在zk集群中用来标识每一台机器的,不能重复。假设Server1最早启动,而后Server2,再Server3。
一、首先每台Server会发出一个投票
因为是初始的状况,每台机器都会选本身做为Leader。每次投票的最基本信息就是服务器的myid和ZXID,咱们用(myid,zxid)这种形式标识。那Server1发出的投票就是(1,0),Server2发出的投票就是(2,0),Server3(3,0)而后将各自的投票发送给集群中剩下的其余全部机器。
二、接收来自各个服务器的投票
每一个服务器都会接收来自其余服务器的投票。集群中每一个服务器在接收到投票后,首先会判断该投票的有效性,包括检查投票是不是本轮投票,是否来自LOOKING状态的服务器。
三、处理投票
主要是拿本身的投票和其余服务器发送过来的投票作一个PK,PK的规则以下:
这里对于Server1来讲,本身的投票是(1,0),收到的投票是(2,0),通过PK以后,Server1会更新本身的投票(2,0),而后将票从新发出去。而对于Server2来讲,不须要更新本身的投票信息。
四、统计投票信息
每次投票后,服务器都会统计全部的投票,判断是否已经有过半的机器收到了相同的投票信息。这里对于Server1,Server2来讲,都统计出集群中已经有两台机器接收了(2,0)这个投票信息。此时,就认为已经选举出了Leader
五、改变服务器状态
一旦肯定了Leader,每一个服务器都会更新本身的状态:若是是Follower,那么就变为FOLLOWING,若是是Leader,那么就变为LEADING。
运行期Leader节点挂了,须要选举新的Leader。
zk集群正常运行的过程当中,一旦选出了Leader,那它一直就是Leader,除非这个Leader挂了,才会进入新一轮的Leader选举,也就是下面要说的这种状况。这个过程其实和启动期间Leader选择过程基本是一致的。
一、变动服务器状态
当Leader挂了以后,余下的非Observer服务器都会将本身的服务器状态变为LOOKING,而后开始进入Leader选举流程。
二、每一个Server会发出一个投票
这里ZXID可能就会是不同的,由于是运行期,每台机器上的数据同步状况可能会有差别。
三、接收来自各个服务器的投票
四、处理投票
五、统计投票
六、改变服务器的状态
一、使用Zookeeper能够作配置中心
能够将配置信息集中存储在zk的节点中,客户端能够注册一些监听,一旦节点数据发生变动,服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个通知以后,能够主动到服务端获取最新的数据。
二、做为注册中心
Dubbo中就是默认用zk做为注册中心的。将服务的url信息注册到zk的节点上。利用临时节点和watcher机制实现服务的动态感知。
三、做为分布式锁
分布式锁是分布式场景下保证资源同步的一种方式。在zk中,全部客户端都在一个节点(好比/lock)下调用create()方法建立临时子节点,只会有一个建立成功,这个建立成功的就认为是获取了锁。其余客户端就须要在/lock节点上注册一个子节点变动的watcher监听。