《从PAXOS到ZOOKEEPER分布式一致性原理与实践》读书笔记


1. 分布式架构

1.1 从集中式到分布式

1.2 从ACID到CAP/BASE

1.2.1 CAP定理

  1. Consistency一致性:数据的强一致性
  2. Availability可用性:请求在一定时间内返回正确结果
  3. Partition tolerance分区容错性:分布式系统在遇到任何网络分区故障时候,仍然需要保证能对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障

一个分布式系统无法同时满足以上三个需求,只能满足其中两项。

1.2.3 BASE定理

  1. Basically Available基本可用:出现不可预知故障时,允许损失部分可用性

  2. Soft state软状态:数据存在中间状态,即允许不同节点同步数据副本之间存在延时

  3. Eventually Consistent最终一致性:不需要实时保证数据强一致性

2. 一致性协议

2.1 2PC与3PC

2PC分为两个阶段

  1. 准备阶段

事务协调者向每个参与者发送准备消息,每个参与者要么直接返回失败消息(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志但不提交,可以进一步分为以下三步:

  1. 协调者向所有节点询问是否可以执行提交操作(vote),并开始等待各参与者几点的响应

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

  3. 参与者响应协调者节点发起的询问。如果参与者节点的实务操作实际执行成功,则它返回一个ack同意消息;如果事务操作实际执行失败,则它返回一个"终止"消息。

  4. 提交阶段

如果协调者收到了参与者的失败消息或者超时,直接向每个参与者发送回滚(Rollback)消息,否则发送(Commit)消息,参与者根据协调者的指令执行提交或者回滚操作,释放所有在事务处理中使用的锁资源。

2PC缺点如下:

  1. 同步阻塞问题,执行过程中所有参与节点都是事务阻塞型的,参与者占用公共资源时,其他第三方节点访问公共资源时不得不处于阻塞状态。
  2. 单点故障,由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去。
  3. 数据不一致,在提交阶段中,协调者向参与者发送commit指令后发生了局部网络异常或者在发送commit请求的过程中协调者发生了故障,会导致只有一部分参与者接收到commit请求,于是整个分布式系统出现了数据不一致的现象。

3PC三阶段提交把2PC的准备阶段一分为二,总共有CanCommit、PreCommit、DoCommit三个阶段。

  1. CanCommit:和2PC的第一阶段类似,协调者向参与者发送commit请求,参与者如果可以提交返回yes响应,否则返回no响应。

  2. PreCommit:协调者根据参与者的反应情况来决定是否可以记录事务的PreCommit操作。根据响应情况,有以下可能。

    • 假如协调者从所有参与者获得的反馈都是yes响应,执行事务
    • 假如有任何一个参与者将协调者发送了No响应,或者等待超时后协调者都没有接到参与者的响应,则执行事务中断。
  3. DoCommit:该阶段进行真正的事务提交,可以分为执行提交和中断事务两种执行情况。

    执行提交的过程如下:

    1. 协调者接收到参与者发送的ack响应后,将从预提交状态进入提交状态,并向所有参与者发送DoCommit请求。
    2. 事务提交参与者接收到DoCommit请求后,执行正式的事务提交,并在完成事务提交后释放所有事务资源。
    3. 事务提交完成后,向协调者发送ack响应。
    4. 协调者接收到所有参与者的ack响应后,完成事务。

    中断事务过程如下:

    1. 协调者向所有参与者发送abort请求。
    2. 参与者接收到abort请求后,利用其在第2阶段记录的undo信息来执行事务的回滚操作,完成回滚后释放所有事务资源
    3. 参与者完成事务回滚后,向协调者发送ack消息。
    4. 协调者接收到参与者反馈的ack消息后,执行事务的中断。

3PC优点:相较于2PC,3PC最大的优点是降低了参与者的阻塞范围,并且能够在出现单点故障后继续达成一致。

3PC缺点:去除阻塞的同时引入了新问题,就是在参与者接收到PreCommit后,如果网络出现分区,此时协调者所在的节点和参与者无法进行正常的网络通信,这种情况下,参与者依然会进行实物的提交,这必然出现数据的不一致性。

2.2 Paxos算法

3. Paxos的工程实践

3.1 Chubby

3.2 Hypertable

4. Zookeeper与Paxos

4.1 Zookeeper

设计目标

  1. 简单的数据模型:ZNode
  2. 可有构建集群
  3. 顺序访问:对于来自客户端的每个更新请求,Zookeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事物操作的先后顺序,应用程序可以使用Zookeeper这个特性实现更高层次的同步原语。
  4. 高性能:全量数据存储在内存中,服务于客户端所有非事务请求,因此适用于以读操作为主的应用场景。

基本概念

  1. 集群角色(Leader Follower Observer)

  2. 会话session:客户端与服务器建立TCP连接,

  3. 数据节点ZNode:持久和临时两种节点,持久节点需要主动移除,临时节点在会话失效时自动移除

  4. 版本

    zk为每个ZNode维护一个Stat的数据结构,记录了Znode的三个数据版本:

    1. version:ZNode的版本
    2. cversion:ZNode的子节点的版本
    3. aversion:ZNode的ACL版本
  5. Watcher:事件监听器

  6. ACL(Access Control Lists)

    zk采用ACL策略进行权限控制,类似UNIX文件系统权限控制

    • create:创建子节点的权限
    • read:获取节点数据和子节点列表的权限
    • write:更新节点数据的权限
    • delete:删除子节点的权限
    • admin:设置节点ACL的权限

4.2 Zookeeper的ZAB协议

​ ZAB(ZooKeeper Atomic Broadcast原子消息广播协议)协议是为Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。用于实现分布式数据一致性,主备模式的系统架构来保持集群各个副本之间数据一致性。zk使用一个单一的主进程来接收处理客户端所有事务请求,采用ZAB广播协议,将数据状态变更以事务Proposal的形式广播到所有副本进程。

所有事务请求必须由一个全局唯一的服务器来协调处理(Leader服务器),余下的服务器成为Follower服务器。Leader负责将一个客户端事务请求转换成一个事务Proposal,并分发给集群所有Follower。之后Leader需要等待所有Follower的反馈,一旦超过半数Follower服务器进行正确的反馈后,Leader会再次向所有Follower分发Commit消息,要求其将前一个Proposal进行提交。

4.2.1消息广播

使用原子广播协议,类似于二阶段提交过程。

Proposal Ack Commit

4.2.2崩溃恢复

5. 使用Zookeeper

5.1部署与运行

  • 大多数主流操作系统都能正常运行Linux、win、macOS
  • Zookeeper使用Java语言编写,需要java环境支持。
  1. 集群模式
  2. 单机模式

5.2客户端脚本

  • 创建节点和数据

    #创建节点和数据
    create [-s] [-e] path data acl
    #[-s] [-e]分别指定节点特性:顺序或者临时节点。默认是持久节点。
    #acl用来进行权限控制
    
    create /zk-book 12345
    
  • 读取节点信息

    #列出指定节点下一级所有子节点
    ls path
    #如:
    ls /
    
    #get命令,获取指定节点数据内容和属性信息。
    get path
    
    [zk: localhost:2181(CONNECTED) 22] get /zk-book/child
    1234556
    cZxid = 0x100000008
    ctime = Sat Apr 27 16:09:25 CST 2019
    mZxid = 0x100000008
    mtime = Sat Apr 27 16:09:25 CST 2019
    pZxid = 0x100000008
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 7
    numChildren = 0
    
  • 更新节点信息

    #set命令更新指定节点数据内容
    set path data [version]
    
    [zk: localhost:2181(CONNECTED) 10] set /zl-book 345
    cZxid = 0x100000004
    ctime = Sat Apr 27 16:04:57 CST 2019
    mZxid = 0x100000005
    mtime = Sat Apr 27 16:08:02 CST 2019
    pZxid = 0x100000004
    cversion = 0
    dataVersion = 1
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 3
    numChildren = 0
    
  • 删除节点

    #delete命令删除指定节点
    #要删除某一个指定节点,该节点必须没有子节点存在
    delete path [version]
    
    delete /zk-book
    

5.3Java客户端API使用

5.4开源客户端

  1. ZkClient
  2. Curator

6. Zookeeper应用场景

6.1 典型应用场景及实现

6.1.1 数据发布/订阅

数据发布/订阅(Publish/Subscribe)系统,即配置中心。一般有两种设计模式,推和拉,各有优缺点。

zookeeper采用推拉相结合的方式:客户端向服务器注册自己关注的节点,一旦节点数据发生改变,服务端向相应的客户端发送Watcher事件通知,客户端收到消息通知后,需要主动到服务端获取最新的数据。

6.1.2 负载均衡

Dynamic DNS

6.1.3 命名服务

全局唯一id,利用zk创建顺序节点。

6.1.4 分布式协调/通知

利用watcher注册与异步通知机制,实现分布式环境下不同机器之间的协调与通知,实现对数据变更的实时处理。

心跳检测

  • ping
  • TCP长连接
  • zk指定节点下创建临时子节点,不同机器可以根据临时节点判断客户机器是否存活

工作进度汇报

  • 通过临时节点判断任务机器是否存活
  • 各个任务机器实时将自己的进度写到这个临时节点上,以便中心系统实时获取任务进度

系统调度

控制台修改节点数据,将数据变更以时间通知的形式发送给对应的订阅客户端

6.1.5 集群管理

  • 集群监控:集群状态收集
  • 集群控制:操作与控制集群

6.1.6 Master选举

多主机同时创建节点,创建成功者为master

6.1.7 分布式锁

  1. 排它锁(数据只对一个事务可见)

    创建临时节点,创建成功则获取到锁,创建失败,监听ZNode状态

  2. 共享锁(数据对所有事务可见)

    创建临时顺序节点,是否是序号最小节点,是则获取到锁,否则监听节点状态

6.1.8 分布式队列

FIFO,创建临时顺序节点,如果是序号最小的节点,执行任务,否则监听节点状态

Barrier,创建临时节点,如果到达10个,执行处理

6.2 Zookeeper在大型分布式系统中的应用

6.2.1 Hadoop

6.2.2 HBase

6.2.3 Kafka

  • Broker注册

    使用Zookeeper进行Broker的注册,每个Broker创建临时节点(宕机),Broker ID,/broker/ids/1/broker/ids/2

  • Topic注册

    Topic节点,/broker/topics/[topic]

    临时节点注册Broker ID:/broker/topics/[topic]/3->2,表明Broker ID为3的服务器,对于[topic]消息,提供了两个分区进行消息存储

  • Producer负载均衡

7. Zookeeper技术内幕

7.1 系统模型

7.1.1 数据模型

ZNode是Zookeeper中数据的最小单元,类似UNIX文件系统路径,每个ZNode可以保存数据,也可以挂载子节点,构成树。

事务操作是指能够改变Zookeeper服务器状态的操作,一般包括节点创建与删除、数据内容更新和客户端会话创建与失效等。Zookeeper为每一个事务请求分配一个全局唯一的事务ID(ZXID,64位的数字),从ZXID中可以识别出Zookeeper处理更新操作的全局顺序。

7.1.2 节点特性

节点类型,三种:

  • 持久节点(persistent)

    数据节点被创建后,一直存在于Zookeeper上,知道有删除操作主动清除。

  • 临时节点(ephemeral)

    临时节点生命周期和客户端会话绑定在一起,客户端会话失效,节点被自动清除。

    Zookeeper规定不能基于临时节点创建子节点,即临时节点只能作为叶子节点。

  • 顺序节点(sequential)

    父节点为第一级自己诶但维护一份顺序,记录子节点创建的先后顺序。数字后缀的上限是整型的最大值。

通过组合,总共有四种:

  1. 持久节点
  2. 持久顺序节点
  3. 临时节点
  4. 临时顺序节点

状态信息

搜狗截图20190626110851.png

7.1.3 版本——保证分布式数据原子性操作

每个数据节点有三种版本信息,对节点的任何更新操作都会引起版本号变化。

乐观锁:更新操作前,比较version是否发生改变,无改变则更新。

7.1.4 Watcher——数据变更通知

public interface Watcher {
    void process(WatchedEvent var1);
}
public class WatchedEvent {
    private final KeeperState keeperState;
    private final EventType eventType;
    private String path;
}

public static enum KeeperState {
            /** @deprecated */
            @Deprecated
            Unknown(-1),
            Disconnected(0),
            /** @deprecated */
            @Deprecated
            NoSyncConnected(1),
            SyncConnected(3),
            AuthFailed(4),//授权失败
            ConnectedReadOnly(5),
            SaslAuthenticated(6),
            Expired(-112);
}

 public static enum EventType {
            None(-1),
            NodeCreated(1),
            NodeDeleted(2),
            NodeDataChanged(3),//版本变化
            NodeChildrenChanged(4);//新增或删除子节点,子节点内容变化不会触发
 }
  1. 客户端进行Watcher注册

  2. 服务端处理Watcher

  3. 客户端回调Watcher

7.1.5 ACL——报障数据安全

比UGO(User Group Others)更细粒度的权限控制方式,可以针对任意用户和组进行细粒度的权限控制。

  • 权限模式(scheme)
  • 授权对象(id)
  • 权限(permission)

通常使用scheme:id:permission来标识一个有效的ACL信息。

权限模式:Scheme

  • IP

    通过IP粒度来进行权限控制,如“ip:192.168.0.110”。也支持按照网段方式配置,如“ip:192.168.0.1/24”,表示对192.168.0.*这个IP段进行权限控制

  • Digest

    最常用,类似“username:password”形式的权限标识,便于区分不同应用进行权限控制,Zookeeper会对其进行两次编码处理,分辨是SHA-1算法加密和BASE-64编码。

    foo:zk-book->foo:kWN6aNSbjcKWPqjiV7cg0N24raU=

  • World

    数据节点的访问权限对所有用户开放,特殊的digest模式,“world:anyone

  • Super

    超级用户可以对任意Zookeeper上的节点进行任何操作

授权对象 ID

权限赋予的用户或一个指定实体,如IP地址或是机器。在不同权限模式下,授权对象是不同的

权限模式 授权对象
IP IP地址或者IP段,如ip:192.168.0.110
Digest 自定义,通常是username:BASE64(SHA-1(username:password)),如foo:zk-book->foo:kWN6aNSbjcKWPqjiV7cg0N24raU=
World 只有一个id:anyone
Super 与Digest一致

权限 Permission

  • create(C):数据节点的创建权限,创建子节点的权限
  • read(R):数据节点的读取权限,获取节点数据和子节点列表的权限
  • write(W):数据节点的更新权限,更新节点数据的权限
  • delete(D):子节点的删除权限,删除子节点的权限
  • admin(A):数据节点的权限管理,设置节点ACL的权限

权限扩展体系Pluggable Zookeeper Authentication

ACL管理

设置权限

7.2 序列化与协议

7.2.1 Jute介绍

Zookeeper使用Jute进行序列化和反序列化

7.3 客户端

核心组件:

  • Zookeeper实例:客户端的入口
  • ClientWatchManager:客户端Watcher管理器
  • HostProvider:服务器地址列表管理器
  • ClientCnxn:客户端核心现场,其内部包含两个线程,SendThread和EventThread。前者是一个IO线程,负责Zookeeper客户端与服务端之间的网络IO通信,后者是一个事件线程,负责对服务端事件进行处理。

7.3.1 一次会话创建的过程

初始化阶段:

  1. 初始化Zookeeper对象,创建ClientWatchManager
  2. 如果构造方法传入了watcher,作为默认watcher,保存在ClientWatchManager
  3. 设置Zookeeper服务器地址列表,服务器地址存在HostProvider中
  4. 创建ClientCnxn,初始化客户端的两个核心队列outgoingQueue和pendingQueue,分别是客户端请求发送队列和服务端响应的等待队列。ClientCnxn连接器的底层IO处理器是ClientCnxnSocket。
  5. 初始化SendThread和EventThread,将ClientCnxnSocket分配给SendThread作为底层网络IO处理器,并初始化EventThread的到处理事件队列waitingEvents,存放所有等待被客户端处理的事件

会话创建阶段:

  1. 启动SendThread和EventThread

  2. 获取一个服务器地址。

    SendThread从HostProvider随机获取一个地址,然后委托给ClientCnxnSocket创建于服务器的TCP连接。

  3. 创建TCP连接。

    ClientCnxnSocket创建TCP长连接

  4. 构造ConnectRequest请求。

    SendThread根据客户端实际设置,构造ConnectRequest请求,代表客户端试图与服务器创建一个会话。Zookeeper客户端将该请求包装成IO层的Packet对象,放入outgoingQueue

  5. 发送请求。

ClientCnxnSocket从outgoingQueue去除一个待发送的Packet对象,序列化成ByteBuffer,发送给服务端。

响应处理阶段:

  1. 接收服务端响应

  2. 处理response

    ClientCnxnSocket接收服务端响应进行反序列化,得到ConnectResponse对象,获取服务端分配的会话sessionId。

  3. 连接成功

  4. 生成时间:SyncConnected-None

  5. 查询Watcher

  6. 处理事件

7.3.2 服务器地址列表

IP地址配置形式:192.168.0.1:2181:192.168.0.2:2181:192.168.0.3:2181/app/demo

public final class ConnectStringParser {
    private static final int DEFAULT_PORT = 2181;
    //Chroot :客户端隔离命名空间
    private final String chrootPath;//   /app/demo  or  null
    //服务器地址和端口封装到serverAddresses中
    private final ArrayList serverAddresses = new ArrayList();
}

HostProvider:地址列表管理器

public interface HostProvider {
    //服务器地址列表个数
    int size();
    //返回一个服务器地址
    InetSocketAddress next(long var1);
    //回调方法,客户端与服务器连接成功,调用该方法通知HostProvider
    void onConnected();
}
public final class StaticHostProvider implements HostProvider {
    private final List serverAddresses = new ArrayList(5);
    private int lastIndex = -1;
    private int currentIndex = -1;
}

7.3.3 ClientCnxn:网络IO

Packet

Packet是ClientCnxn内部定义的一个对协议层的封装,是Zookeeper请求与响应的载体。

static class Packet {
    RequestHeader requestHeader;//
    ReplyHeader replyHeader;
    Record request;//
    Record response;
    ByteBuffer bb;
    String clientPath;
    String serverPath;
    boolean finished;
    AsyncCallback cb;
    Object ctx;
    WatchRegistration watchRegistration;
    public boolean readOnly;//
}

outgoingQueue

存储需要发送到服务端的Packet集合

pendingQueue

存储已经从客户端发送到服务端的,但是需要等待服务端响应的Packet集合

ClientCnxnSocket:底层socket通信层

7.4 会话

7.4.3 会话管理

分桶策略

将类似的会话放在同一区块中进行管理,以便于Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。分块原则是每个会话的“下一次超时时间点(ExpirationTime:指会话最近一次可能超时的时间点)”,

新创建的会话:ExpirationTime = CurrentTime + SessionTimeOut

Zookeeper的leader服务器定时进行会话超时检测,时间间隔是ExpirationInterval毫秒,默认值是tickTime的值(2000)

ExpirationTime_ = CurrentTime + SessionTimeOut

ExpirationTime = (ExpirationTime_ /ExpirationInterval +1) x ExpirationInterval

7.5 服务器启动

7.6 Leader选举

7.7 各服务器角色

7.7.1 Leader

  • 事务请求(会改变服务器状态的请求)的唯一调度的处理者,保证集群事务处理的顺序性
  • 集群内部个服务器的调度者

请求处理链

使用责任链处理每个客户端的请求。

LearnerHandler

Leader服务器会与每一个learner建立一个TCP长连接,同时为每个learner创建一个LearnerHandler实体。

7.7.2 Follower

  • 处理客户端非事务请求,转发事务请求给Leader服务器
  • 参与事务请求Proposal的投票
  • 参与Leader选举投票

请求处理链

使用责任链处理每个客户端的请求,不需要处理事务请求。

7.7.3 Observer

观察Zookeeper集群最新状态并且同步,工作与Follower基本一致,区别在于observer不参与任何形式的投票,包括事务请求Proposal的投票和Leader选举投票。

Observer只提供非事务请求,通常用于在不影响集群事务处理能力的前提下提升集群非事务处理能力。

7.7.4 集群间消息通信

Zookeeper消息类型可以分为四类:数据同步型,服务器初始化型,请求处理型,会话管理型。

7.8 请求处理

7.9 数据存储

7.9.1 内存数据

DataTree DataNode

7.9.2 磁盘数据

8. Zookeeper运维

你可能感兴趣的:(《从PAXOS到ZOOKEEPER分布式一致性原理与实践》读书笔记)