读《从Paxos到Zookeeper 分布式一致性原理与实践》笔记之客户端

1.    Zookeeper技术内幕 

1.1. 客户端

1.1.1.  服务器地址列表

        Zookeeper构造方法中传入的地址,使用逗号分隔的多个IP地址和端口的字符串,

                192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181

        Zookeeper客户端在连接服务器的过程中,是如何从这个服务器列表中选择服务器机器的呢?是按序访问,还是随机访问呢?

        Zookeeper客户端内部在接收到这个服务器地址列表后,会将其首先放入一个ConnectStringParser对象中封装起来。ConnectStringParser是一个服务器地址列表的解析器,该类的基本结构如下:

public final class ConnectStringParser {
    /*
    * 根目录
    * 如:connectString=100.73.17.29:2181,100.73.17.30:2181,100.73.17.31:2181/checksystem/faceaudit
    * chrootPath = /checksystem/faceaudit
    * */
    private final String chrootPath;
    /*存储所有的zookeeper服务器地址*/
    private final ArrayList<InetSocketAddress> serverAddresses 
                     = new ArrayList<InetSocketAddress>();
}
        ConnectStringParser做两个主要的处理,解析chrootPath和解析服务器地址列表。

Chroot:客户端隔离命名空间

        在3.2.0之后版本的zookeeper中,添加了“Chroot”特性,该特性允许每个客户端为自己设置已给命名空间。如果一个zookeeper客户端设置了Chroot,那么该客户端对服务器的任何操作,都将会被限制在自己的命名空间下。

        客户端可以通过在connectString中添加后缀的方式来设置Chroot,如下所示:

                192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181/apps/X

        将这样一个connectString传入客户端的ConnectStringParser后就能够解析出Chroot并保存在chrootPath属性中。

解析服务器地址

        针对ConnectStringParser.serverAddresses集合中哪些没有被解析的服务器地址,staticHostProvider首先会对这些地址逐个进行解析,然后再放入serverAddresses结合中去。同时使用Collections工具类的shuffle方法来将这个服务器地址列表进行随机的打算。

获取可用的服务器地址

        通过调用staticHostProvider的next()方法,能够从staticHostProvider中获取一个可用的服务器地址。这个next()方法并非简单地从serverAddresses中一次获取一个服务器地址,而是现将随机打散后的服务器地址列表拼装成一个环形的循环队列,如下图,注意这个随机过程是一次性的,也就是说,之后的使用过程中一直是按照这样的顺利来获取服务器地址的。


        举个例子来说,假如客户端传入这样一个地址列表:“host1,host2,host3,host4,host5”。经过一轮随机打散后,可能的一种顺序变成了“host2,host4,host1,host5,host1”,并且形成了上图的循环队列。此外HostProvider还会为该环形队列创建两个游标:currentIndex俄lastIndex。currentIndex标识环形队列中当前遍历到的那个元素位置,lastIndex则表示当前正在使用的服务器地址位置。初始化的时候,curre和lastIndex的值都为-1。

        在每次尝试获取一个服务器地址的时候,都会首先将currentIndex游标向前移动1位,如果发现游标移动超过了整个地址列表的长度,那么就重置为0,回到开始的位置重新开始,这样一来,就实现了循环队列。当然对于那些服务器地址列表提供的比较少的场景,staticHostProvider中做了一个小技巧,就是如果发现当前游标的位置和上次已经使用过的地址位置一样,即当currentIndex和lastIndex游标值相同时,就进行spinDelay毫秒时间的等待。        

        总的来时,staticHostProvider就是不断从上图所示的环形地址列表队列中去获取已给地址,整个过程非常类似于“Round Robin”的调度策略。

对于HostProvider的几个设想

        staticHostProvider只是zookeeper官方提供的对于地址列表管理器的默认实现方式,也是最通用和最简单的一种实现方式。读者如果有些要的话,满足接口要求的前提下,可以实现自己的服务器地址列表管理器。

1.配置文件方式

        实现从配置文件中加载服务器地址列表。

2.动态变更的地址列表管理器

        从DNS或一个配置管理中心上解析出zookeeper服务器地址列表。

3.实现同机房优先策略

        在目前大规模的分布式系统设计中,多机房的情况下,考虑引入“同机房优先”的策略。所谓的“同机房优先”是指服务的消费者优先小飞同一个机房中提供的服务。举个例子来说,一个服务F在杭州机房和北京机房中都有部署,那么对于杭州机房中的服务消费者,会优先调用杭州机房中的服务,对于北京机房的客户端也一样。

        对于zookeeper继群来说,为了达到容灾要求,通常会将集群中的机器分开部署在多个机房中,这样就会面临网络延迟问题。对于这种情况,就可以实现一个能够优先和同机房zookeeper服务器创建会话的HostProvider。

1.1.2.  请求发送与响应接收

请求发送

 

        在正常情况下(即客户端与服务端之间的TCP连接正常且会话有效的情况下),会从 outgoingQueue队列中提取出一个可发送的Packet对象,同时生成一个客户端请求序号xid并将其设置到Packet请求头中去,然后将其序列化后进行发送。

        请求发送完毕后,会立即将该Packet保存到pendingQueue队列中,以便等待服务端响应返回后进行相应的处理,如上图。

响应接收

        客户端获取到来自服务端的完整响应数据后,根据不同的客户端请求类型,会进行不同的处理。

        l  如果检测到当前客户端还尚未进行初始化,那么说明当前客户端与服务端之间正在进行会话创建,那么就直接将接收到的ByteBuffer(incomingBuffer)序列化成 ConnectResponse 对象。

        l  如果当前客户端已经处在正常的会话周期,并且接收到的服务端响应是一个事件, 那么ZooKeeper客户端会将接收到的ByteBuffer (incomingBuffer)序列化成WatcherEvent对象,并将该事件放入待处理队列中。

        l  如果是一个常规的请求响应(指的是Create, GetData和Exist等操作请求),那么会从pendingQueue队列中取出一个Packet来进行相应的处理。ZooKeeper 客户端首先会通过检验服务端响应中包含的XID值来确保请求处理的顺序性,然后再将接收到的ByteBuffer (incomingBuffer)序列化成相应的Response对象。

SendThread

        SendThread是客户端ClientCnxn内部一个核心的I/O调度线程,用于管理客户端和服务端之间的所有网络I/O操作。在ZooKeeper客户端的实际运行过程中,一方面, SendThread维护了客户端与服务端之间的会话生命周期,其通过在—定的周期频率内向服务端发送一个PING包来实现心跳检测。同时,在会话周期内,如果客户端与服务端之间出现TCP连接断开的怙况,那么就会自动且透明化地完成重连操作。

        另一方面,SendThread管理了客户端所有的请求发送和响应接收操作,其将上层客户端API操作转换成相应的请求协议并发送到服务端,并完成对同步调用的返回和异步调用的回调。同时,SendThread还负责将来自服务端的事件传递给EventThread去处理。

EventThread

        EventThread是客户端ClientCnxn内部的另一个核心线程,负责客户端的事件处理,并触发客户端注册的Watcher监听。EventThread中有一个waitingEvents队列,用于临时存放那些需要被触发的Object,包括那些客户端注册的Watcher和异步接口中注册的回调器AsyncCallback。同时,EventThread会不断地从waitingEvents这个队列中取出Object, 识别出具体类型(Watcher或者AsyncCallback),并分别调用process和processResult接口方法来实现对事件的触发和回调。

猜你喜欢

转载自blog.csdn.net/lihuayong/article/details/53705255