《从Paxos到zookeeper:分布式一致性原理与实践》读书笔记

Paxos,发音近似 帕克索斯。

问题的提出

并发的定义(来自《深入理解计算机系统》):
如果逻辑控制流在时间上重叠,那么他们就是并发的。
本书的并发,指更新操作的并发,即有多个线程同时更新内存中变量的值。
数据复制的延时问题。数据一致性指对一个副本数据进行更新的同时,必须确保也能够更新其他的副本,否则不同副本之间的数据将不再一致。
一致性级别:

  • 强一致性:理论上基本不存在。读出的数据就是写入的数据,这个时间间隔小到一定程度就不是一致的;
  • 弱一致性:分会话一致性(对一个客户端会话而言)和用户一致性(对一个用户而言)
  • 最终一致性:弱一致性的特例。

第一章 分布式架构

集中式架构到分布式架构;
《分布式系统概念与设计》对分布式系统的定义:
硬件或者软件组件发布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。
分布式系统的特征:

  • 分布性
  • 对等性:数据副本
  • 并发性
  • 缺乏全局时钟
  • 故障总是会发生

分布式系统的典型问题:

  • 通信异常
  • 网络分区,即脑裂,集群中只有部分节点参与通信的状态;
  • 三态:成功、失败&超时
  • 节点故障

从ACID到CAP/BASE
标准SQL规范定义四个事务隔离级别:未授权读取、授权读取、可重复读取、串行化;
分布式事务:事务的参与者、支持事务的服务器、资源服务器、以及事务管理器分别位于分布式系统的不同节点上。

第二章 一致性协议

  1. 二阶段协议
  2. 三阶段协议
  3. Paxos协议

二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持原子性和一致性而设计的一种算法。通常,二阶段提交也被称为是一种协议。
在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交。二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
2.1协议说明:
阶段一:提交事务请求
1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。

2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。

3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个“YES”消息;如果参与者节点的事务操作实际执行失败,则它返回一个"NO”消息。

阶段一也叫投票阶段。

阶段二:执行事务请求
协调者会根据各参与者的反馈情况来决定最终是否可以进行事物提交操作,所以分为以下两种情况:
执行事物提交
当协调者节点从所有参与者节点获得的相应消息都为“YES”时:
)协调者节点向所有参与者节点发出”正式提交(commit)”的请求。

2)参与者节点接受到COMMIT请求后正式完成操作,并释放在整个事务期间内占用的资源。

3)参与者节点向协调者节点发送"ACK”消息。

4)协调者节点受到所有参与者节点反馈的"ACK”消息后,完成事务。

中断事物

如果任一参与者节点在第一阶段返回的响应消息为“NO”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时,那么就会中断事物。
1)协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。

2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。

3)参与者节点向协调者节点发送"ACK”消息。

4)协调者节点受到所有参与者节点反馈的"ACK”消息后,完成事务中断。

不管最后结果如何,第二阶段都会结束当前事务。

流程图如下:

简单来看:二阶段提交将一个事物的处理过程分为了投票和执行阶段,是一个先尝试再提交的过程,是一个强一致性的算法。
2.2优缺点:
优点:原理简单,实现方便。
缺点:
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

4、太过保守:二阶段提交缺乏较为完善的容错机制,任意一个节点的失败导致整个事物的失败。

三、三阶段提交(3PC)
三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
3.1协议说明
将二阶段提交协议的“提交事务请求”一分为二,形成了CanCommit、PreCommit、DoCommit三个阶段。示意图如下:

3.1.1CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

1.事务询问

协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。

2.响应反馈

参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

3.1.2PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。

执行事务预提交

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。

2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。

3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

中断事物
假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

1.发送中断请求 协调者向所有参与者发送abort请求。

2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

3.1.3docommit阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。

3.响应反馈 事务提交完之后,向协调者发送Ack响应。

4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。

中断事务

协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

1.发送中断请求 协调者向所有参与者发送abort请求

2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。

3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息

4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

注意:
阶段三,可能会出现:协调者出现问题,协调者和参与者网络出现故障。无论哪种异常都会导致参与者无法及时接收到来自协调者的doCommit或者rebort请求时,参与者会在等待超时之后,会继续进行事务的提交。

3.2优缺点:
优点:3PC主要解决的单点故障问题,并减少阻塞范围,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。

缺点:引入新问题,如果出现网络分区,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

对于2PC、3PC无法彻底解决分布式一致性问题后,就是paxos。

第三章 Paxos的工程实践

第四章 zookeeper和paxos

zookeeper提供分布式协调服务,提供诸如统一命名服务、配置管理和分布式锁、分布式消息等分布式的基础服务。zookeeper的zab(zookeeper atomic broadCast),是paxos的一种改进。zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用可以基于zookeeper实现发布\订阅、负载均衡、命名服务、分布式协调\通知、集群管理/master选举、分布式锁、和分布式队列等功能。

zookeeper可以保证各种分布式一致性问题:顺序一致性、原子性、单一视图、可靠性、实时性。

zookeeper的设计目标: 提供简单的数据模型、可以构建集群、顺序访问、高性能。

简单的数据模型:zookeeper提供了一个共享的、树型结构的命名空间来进行相互协调,这些数据结构都存在内存中,可以提高吞吐量。

构建分布式集群:

顺序访问:来自于client的事务,zookeeper都会分配一个全局唯一的递增ID,这个ID反应了客户端执行的顺序。

高性能:zookeeper将全部数据结构存在内存中,并直接服务于客户端的非事务请求。

zookeeper概念:

集群角色

在分布式系统中,典型的应用是master/slave 主从模式的架构,提供全局写操作的节点为master,通过异步复制的节点叫做slave。在zookeeper中,提出了leader、Follower、Observer。follower选举产生leader,observer提供读服务,所以zookeeper适用于读多写少的服务。

回话角色:

回话(session):当zookeeper客户端启动的时候,会与zookeeper建立一个长连接,zookeeper能够通过向client发送心跳检测client的状态,通过zookeeper管理zookeeper的client(zookeeper的client为分布式应用的各个节点)。

数据节点:

在分布式应用中,一个是zookeeper管理的节点即为应用节点-机器节点,另一个节点类型为数据节点-znode,管理zookeeper数据模型的数据单元(zookeeper管理节点的数据)。zookeeper为树型数据模型(Znode Tree) , 通过/分割的路径就是一个ZNode,比如/foo/path1 , 为一个ZNode节点,保存数据内容和属性信息。

版本:

zookeeper存放znode的版本。

watcher:

zookeeper允许用户注册事件,在某个时候触发事件,分配给合适的client执行事件,watcher机制是zookeeper实现分布式协调服务的重要特征。

ACL机制:
zookeeper提供类似linux的权限控制机制。

第五章 使用zookeeper

部署:单机模式,伪分布模式和分布式集群模式;
客户端脚本,zkCli.sh,命令:

  • create:-s 顺序节点,-e临时节点,默认情况下不加参数是持久节点;
  • ls:只能读取指定节点的第一级的所有子节点;第一次部署的zk,默认在根节点下面有一个/zookeeper的保留节点;
  • get:
  • set:set命令后面有一个可选的version参数,用于指定本次更新操作是基于ZNode的哪一个数据版本进行的。
  • delete:不能有子节点存在,否则不能删除;

Java客户端工具
zookeeper的客户端和服务端会话建立是一个异步的过程;
create()方法有同步和异步两种,都不支持递归创建节点,节点内容只支持字节数组byte[]类型,即不负责序列化工作,可以使用Hessian或者Kryo等专门的序列化工具;异步和同步的区别,异步方式下节点的创建过程(包括网络通信和服务端节点创建过程)是异步的,在使用同步接口时需要注意可能抛出的异常,但是异步接口本身不会抛出异常,所有的异常都是在回调函数中通过ResultCode响应码来体现。
delete,同样有同步和异步之分;
getChildren,zk客户端在获取到指定节点的子节点列表之后,还需要订阅这个子节点列表的变化通知,通过注册watcher来实现。当有子节点被添加或者是删除时,服务端会向客户端发送一个EventType.NodeChildrenChanged类型的事件通知(只发送一次,watcher会失效,故客户端需要反复注册watcher),此时客户端需要主动重新去获取列表才能获取到最新的列表。子节点列表都是数据节点的相对节点路径;
getData,节点的数据内容或者是节点的数据版本变化,都是节点的变化;
setData,CAS:对于值V,每次更新前都会比对其值是否是预期值A,只有符合预期,才会将V值原子化地更新到新值B。
exists;
权限控制,zk提供的几种权限控制模式scheme:world、auth、digest、ip&super。当客户端对一个数据节点添加权限信息后,对于删除操作而言,其作用范围是其子节点

开源客户端:ZkClient & Curator
ZkClient,提供Session超时重连,watcher反复注册;使用listener来实现watcher的反复注册,注册一次一直有效;deleteRecursive接口,提供递归删除子节点功能;
Curator
fluent风格,提供各种应用场景(共享锁服务,master选举,分布式计算器)的抽象封装;重试策略ExponentialBackoffRetry;zk所有的非叶子节点必须是持久节点;BackgroundCallback接口,用来处理异步接口调用之后服务端返回的结果信息;CuratorEventType:CREATE、DELETE、EXISTS、GET_DATA/SET_DATA、CHILDREN、SYNC/GET_ACL/WATCHED、CLOSING等。响应码,KeeperException.Code类,0(OK)、-4(ConnectionLoss)、 -100(NodeExists) 、 -112(SessionExpired);
典型使用场景
引入依赖curator-recipes;

  • 事件监听,Curator提供Cache,对事件监听的包装,本地缓存视图和远程zookeeper视图的对比过程,分为两类,节点监听NodeCache和子节点监听PathChildrenCache,PathChildrenCacheEvent类定义所有的事件类型,包括新增子节点CHILD_ADDED、子节点数据变更CHILD_UPDATED、子节点删除CHILD_REMOVED。对于节点本身的变更,不能通知到客户端。和其他zk客户端工具一样,Curator也不能对二级子节点进行事件监听。
  • Master选举,LeaderSelector.autoRequeue()和start()方法,构造函数传入监听器,LeaderSelectorListenerAdapter,LeaderSelectorListener.takeLeaderShip方法。
  • 分布式锁,InterProcessMutex,提供方法acquire和release;
  • 分布式计数器,应用场景如统计网站的在线人数。思路:指定一个zk数据节点作为计数器,多个应用实例在分布式锁的控制下,通过更新该数据节点的内容来实现计数功能。DistributedAtomicInteger类;
  • 分布式barrier,JDK自带CyclicBarrier,用来控制多线程之间同步。DistributedBarrier提供setBarrier、waitOnBarrier、removeBarrier方法;除了主线程来出发Barrier释放之外,还提供另一种线程自发触发Barrier释放的模式:enter和leave方法;
  • 工具类,ZkPaths,构建ZNode路径,递归创建和删除节点;EnsurePath,一直能够保证数据节点存在的机制,静默的节点创建方式;TestingServer,在依赖包Curator-test包里面,允许开发人员自定义zk服务器对外服务的端口和dataDir路径,不指定dataDir的情况下,默认在系统的临时目录java.io.tempdir下创建临时目录作为数据存储目录;TestingCluster,模拟集群的工具,构造函数传入模拟的集群的节点数目;

第六章 zookeeper的典型应用场景

主要包括:数据发布/订阅、负载均衡、命名服务、分布式协调/通知(注册功能)、集群管理、master选举、分布式锁、分布式消息队列。
zk实现数据发布/订阅服务:
发布/订阅通常有两种模式:推模式push/拉模式pull。推模式之后,服务端主动将数据更新发送给所有订阅的客户端;拉模式,客户端主动发起请求去获取最新数据,定时轮询拉取。zookeeper是推拉结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,则服务端就会向相应的客户端发送watcher事件通知,客户端接收到消息通知之后,需要主动去服务端获取最新数据。主要应用于分布式中,配置文件的全局通知、更新。
全局配置信息的3个特性:
数据量比较小;数据内容在运行时发生动态变化;集群内各机器共享,配置一致。
配置获取:在机器启动过程中,zookeeper的客户端会获取到其他客户端配置文件的改变。zk客户端通过向zk注册一个watcher监听,监听到客户端配置文件改变时,通知其他客户端信息改变。
配置更新:在系统运行过程中,当客户端系统配置文件改变时,zk通知其他注册的客户端,客户端在收到变更通知后,更新获取的数据。

zk的负载均衡:
负载均衡主要有硬件负载均衡和软件负载均衡,通过负载均衡达到计算机集群cpu、内存、网络连接、I/O进行资源配置。zookeeper主要通过软件进行负载均衡,zookeeper通过域名配置的功能,为IP配置相应的域名。
DNS系统可以看做一个超大规模的分布式映射表,域名和IP之间一一映射,向域名注册服务商申请域名注册,缺陷在于只能注册有限的域名;实际开发中,往往使用本地host文件绑定来实现域名解析的工作,缺陷在于:当应用的机器规模扩大之后,需要到每个机器上去逐个进行变更,需要消耗大量时间,不能保证实时性。
域名配置:
域名解析:
域名变更:
问题在于:

升级版——自动化的DNS服务
域名注册:
域名解析:
域名探测:

命名服务:
zk提供的命名服务功能和JNDI技术比较相似,通过一个资源引用的方式来实现对资源的定位和使用。分布式环境下,上层应用仅仅需要一个全局唯一的名字,通过这个唯一主键来定位资源。UUID的缺点:1. 长度过长;2. 含义不明。

分布式协调服务:
在分布式系统中,通过引入分布式系统协调者来控制整个分布式应用的运行,比如分布式任务的执行、分布式机器的协调工作。一般的做法是通过多个client在一个watcher上注册(dubbo使用zk的注册功能。),当client上数据发生变化时,所有watcher的订阅者会收到相应的数据变化通知。

Master选举:

分布式锁:

zk通过分布式锁控制客户端对于共享资源的访问,当访问共享资源时,需要分布式锁保证资源的一致性。

分布式队列:

第七章 zookeeper技术内幕

第八章 zookeeper运维

zk配置参数:

四字命令
使用有两种方式:1. 使用telnet命令登录zk的对外服务端口,直接使用四字命令;2.使用nc命令:echo conf | nc localhost 2181
命令概览:

  1. conf,输出zk服务器运行时的基本配置信息,如clientPort,dataDir,ticjTime;
  2. cons,输出当前服务器上所有的客户端连接详细信息,client IP,Session ID,最近一次与服务器交互的操作类型;
  3. crst,重置所有的客户端连接统计信息;
  4. dump,输出当前集群所有的会话信息;
  5. envi,输出zk所在服务器的运行时环境信息;
  6. ruok,输出当前zk服务器是否在运行,are you ok?正常则输出imok,否则没有任何响应。仅仅只能表明当前服务器是否在运行,确切地讲,端口2181端口且四字命令执行流程正常,不能代表zk服务器是否运行正常。不常用。也不是很有用。
  7. stat,输出zk服务器运行时状态信息;
  8. srvr,和stat类似,区别是仅仅输出服务器自身信息;
  9. srst,重置所有服务端的统计信息;
  10. schs,输出当前服务器管理的watcher的概要信息;
  11. wchc,输出当前服务器管理的watcher的详细信息,以会话为单位进行归组,同时列出被该会话注册watcher的节点路径;
  12. wchp,类似wchc,不同在于以节点路径为单位进行归组;
  13. mntr,输出信息比stat更详细,服务器的统计信息,k-v键值对,可以进行监控;

JMX
四字命令仅仅用于监控,但实际上侧重于监,不能用于控制;想要实现控制,需要JMX支持,zk3.3.0以上版本,使用标准JMX来对外提供运行时数据信息和管控接口;zk默认开启JMX功能。
找到zkServer.sh文件,找到zoomain配置信息,可以考虑修改authenticate=false(本地测试开发时)
ZOOMAIN=" -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$JMXPORT -Dcom.sun.management.jmxremote.authenticate=$JMXAUTH -Dcom.sun.management.jmxremote.ssl=$JMXSSL -Dzookeeper.jmx.log4j.disable=$JMXLOG4J org.apache.zookeeper.server.quorum.QuorumPeerMain"

第二种方式,通过JConsole连接zk
JConsole基于JMX,图形化界面,需要多摸索摸索即可;TODO
Leader服务器对外提供四个操作:
followerInfo:获取所有follower的运行时信息;
resetLatency:重置所有与客户端请求处理延时相关的统计信息;
resetMaxLatency:重置客户端请求处理的最大延时统计;
resetStatistics:重置所有客户端请求处理延时统计,以及所有客户端数据包发送与接收的统计信息。
InMemoryDataTree,zk服务器的内存数据库。

监控:实时监控与数据统计

构建高可用集群
容灾和扩容两方面,5台机器与6台机器构成的zk集群,容灾能力没有差别,基于此,zk集群一般是奇数个节点。容灾主要就是面对单点问题,单点的不仅仅是一个服务器,有可能是一个机房。故而有三机房部署与两机房部署方案(略);至于扩容和缩容,实际上就是zk集群的重启问题,两种方式:1. 全部重启,不推荐;2. 逐台重启;

日常运维

  1. 数据与日志管理,dataDir和dataLogDir两个目录分别用于存储快照数据和事务日志。zk不会自己清理这两个目录,需要自己人工清理。三种清理方式:1. 纯shell脚本,每天定时执行即可;2. 使用清理工具PurgeTxnLog,zk提供,该工具限制至少需要保留3个快照数据文件。java -cp zookeeper-3.4.5.jar :lib/slf4j-api-1.6.1.jar:lib/slf4j-log4j12-1.6.1.jar:lib/log4j-1.2.15.jar:conf org.apache.zookeeper.server.PurgeTxnLog /home/admin/taokeeper/zk_data /home/admin/taokeeper/zk_data -n 15,至多保留15个文件。3. 使用清理脚本zkCleanup.sh,基于PurgeTxnLog。自动清理机制,zk3.4.0版本之后,配置参数autoperge.snapRetainCount和autoperge.purgeInterval。
  2. Too many connections,maxCilentCnxns参数。
  3. 磁盘管理,挂载到单独的磁盘。

附脚本:

#!/bin/bash
#snapshot file dir
dataDir=/home/nileader/taokeeper/zk_data/version-2
# tran log dir
dataLogDir=/home/nileader/taokeeper/zk_log/version-2
#zk log dir
logDir=/home/nileader/taokeeper/logs
# leave 60 files
count=60
count=$[$count+1]
ls -t $dataLogDir/log.* | tail -n +$count | xargs rm -f
ls -t $dataDir/snapshot.* | tail -n +$count | xargs rm -f
ls -t $logDir/zookeeper.log.* | tail -n +$count | xargs rm -f

附录

Windows平台安装zk及部署集群;
源码构建;
源码阅读:src/java/generated目录是实体类和协议层类;zookeeperMain主类;jute组件进行序列化和反序列化;网络通信的客户端类:ClientCnxn和ClientCnxnSocket和ClientCnxnSocketNIO,服务端四个类:NIOServerCnxn、NIOServerCnxnFactory、NettyServerCnxn、NettyServerCnxnFactory;权限认证:digest,IP和SASL;

猜你喜欢

转载自blog.csdn.net/lonelymanontheway/article/details/82833148