NIO/Concurrent包/zookeeper/Avro-zookeeper

  1. 简介
    1. 简介
      1. 1. Zookeeper是根据谷歌的论文《The Chubby Lock Service for Loosely Couple Distribute System 》所做的开源实现
        2. Zookeeper是Apache Hadoop的子组件之一,但是不仅仅支持Hadoop,还支持绝大部分的分布式集群
        3. Zookeeper是一个分布式的协调服务框架,用于解决分布式环境下的一些常见问题:集群管理、统一命名服务,信息配置管理,分布式锁等等
        

         

    2. 分布式存在的问题
    3. 	1. 死锁:至少有一个线程把持了资源,但是由于线程之间的相互等待,所以线程不耗费CPU
      	2. 活锁:所有的线程都没有把持资源而导致资源产生了浪费。而且由于线程之间一直在调度,导致CPU一直处于被占用状态。
      	3. 分布式环境下,需要引入监控和管理节点来保证服务器之间的任务调度
      	4. 为了防止单一监控节点带来单点问题,所以需要引入多个监控节点
      	5. 为了防止多个监控节点之间的任务调度不同,需要在其中选举出一个主监控节点
      	6. 确定一套选举算法
      	7. 为了防止主节点宕机而导致所有数据丢失,需要将监控节点的数据进行统一
      	8. 监控节点以及服务节点的统一配置
      

       

    4. 特点
      	1. Zookeeper是一个树状结构(Znode树)
      	2. 树状结构(Znode树)的根节点为 /
      	3. Zookeeper的每一个节点称之为是znode节点
      	4. 所有的znode节点都是从根节点开始计算
      	5. 每一个znode节点都必须存储数据
      	6. 每一个持久的znode节点都可以挂载子节点
      	7. 每一个znode节点的路径都是唯一的。所以基于这一个特点,可以做集群的统一命名服务
      	8. Znode树是维系在内存中的,即每一个znode节点中的数据也是维系在内存中,这样做的目的是方便快速查找
      	9. 不能利用Zookeeper存储海量数据,原因:
      		a. Znode树维系在内存中,并且多个Zookeeper存储的是相同的数据造成内存的浪费;
      		b. Zookeeper是做分布式的协调服务而不是做存储服务
      	10. Zookeeper提供了持久化机制,持久化的目录由zoo.cfg中的dataDir属性来决定
      	11. Zookeeper会为每一次的事务(增加、删除、更新)提供一个全局的递增的事务id
      

       

    5. 节点类型
      类型	                 解释
      PERSISTENT	             持久节点
      EPHEMERAL	             临时节点
      PERSISTENT_SEQUENTIAL	 持久顺序节点
      EPHEMERAL_SEQUENTIAL	 临时顺序节点
      

       

    6. 特性总结
      	1. 数据一致性:客户端不论连接到哪个Zookeeper节点上,展示给它都是同一个视图,即查询的数据都是一样的。这是Zookeeper最重要的性能
      	2. 原子性:对于事务决议的更新,只能是成功或者失败两种可能,没有中间状态。 要么都更新成功,要么都不更新。即,要么整个集群中所有机器都成功应用了某一事务,要么都没有应用,一定不会出现集群中部分机器应用了改事务,另外一部分没有应用的情况。 
      	3. 可靠性:一旦Zookeeper服务端成功的应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会一直保留下来,除非有另一个事务又对其进行了改变。
      	4. 实时性:Zookeeper保证客户端将在非常短的时间间隔范围内获得服务器的更新信息,或者服务器失效的信息,或者指定监听事件的变化信息。(前提条件是:网络状况良好) 
      	5. 顺序性:如果在一台服务器上消息a在消息b前发布,则在所有服务器上消息a都将在消息b前被发布。客户端在发起请求时,都会跟一个递增的命令号,根据这个机制,Zookeeper会确保客户端执行的顺序性。底层指的是Zxid。可以通过事务log来看。
      	6. 过半性:Zookeeper集群必须有半数以上的机器存活才能正常工作。因为只有满足过半性,才能满足选举机制选出leader。因为只有过半,在做事务决议时,事务才能更新。所以一般来说,zookeeper集群的数量最好是奇数个
      

       

  2. 安装
    1. 单机安装
      1. 安装步骤
        	1. 关闭Linux(CentOS6.X版本)的防火墙
        	临时关闭防火墙:service iptables stop
        	永久关闭防火墙:chkconfig iptables off
        	2. 下载安装JDK
        	3. 下载或者上传Zookeeper的安装包
        	4. 解压Zookeeper的安装包:tar  -xvf zookeeper-3.4.8
        	5. 进入Zookeeper的安装目录下的子目录conf目录中:cd zookeeper-3.4.8/conf
        	6. 将conf目录下的zoo_sample.cfg文件复制为zoo.cfg。Zookeeper在启动的时候会自动寻找zoo.cfg,根据其中的配置来启动服务:cp  zoo_sample.cfg zoo.cfg
        	7. 编辑zoo.cfg文件:vim zoo.cfg
        	8. 修改其中的属性dataDir,指定数据的存储目录:dataDir=/home/software/zookeeper-3.4.8/tmp
        	9. 保存并且关闭zoo.cfg
        	10. 进入Zookeeper的安装目录下的子目录bin目录中:cd ../bin
        	11. 执行zkServer.sh文件,来启动Zookeeper服务器端:sh zkServer.sh start
        	12. 执行zkCli.sh文件,来启动进入Zookeeper客户端:sh zkCli.sh
        

         

      2. 注意事项
        	1. 当服务器端启动成功之后,可以执行jps命令查看是否有Zookeeper的QuorumPeer进程
        	2. 或者在服务器端启动成功之后,可以执行一下命令查看服务器端状态:sh zkServer.sh status
        	如果出现了Standalone,说明启动成功
        

         

    2. 集群安装
      1. 安装步骤
        	1. 关闭Linux(版本是Centos6.X)的防火墙
        	临时关闭防火墙:service iptables stop
        永久关闭防火墙:chkconfig iptables off
        	2. 下载安装JDK
        	3. 下载或者上传Zookeeper的压缩包
        	4. 解压压缩包:tar -xvf zookeeper-3.4.8
        	5. 进入Zookeeper的安装目录下的子目录conf目录中:cd zookeeper-3.4.8/conf
        	6. 将conf目录下的zoo_sample.cfg复制为zoo.cfg。Zookeeper在启动的时候会自动寻找zoo.cfg,根据其中的配置来启动存储数据:cp zoo_sample.cfg zoo.cfg
        	7. 编辑zoo.cfg文件:vim zoo.cfg
        	8. 修改其中的属性dataDir,指定数据的存储目录:dataDir=/home/software/zookeeper-3.4.8/tmp
        	9. 在zoo.cfg文件的末添加要构建集群的服务器地址
        格式:server.编号=IP地址:原子广播端口:选举端口
        例如:
        server.1=10.8.42.133:2888:3888
        server.2=10.8.42.134:2888:3888
        server.3=10.8.42.135:2888:3888
        需要注意的是:
        		a. 编号要求是数字并且不能重复
        		b. 原子广播端口号和选举端口号只要不和当前已经使用的端口号冲突即可
        	10. 关闭并且保存zoo.cfg
        	11. 在dataDir的指定目录下创建数据存储目录:mkdir tmp
        	12. 进入数据存储目录:cd tmp
        	13. 在数据存储目录下编辑新的文件,文件名为myid:vim myid
        	14. 在myid文件中填入当前服务器所对应的编号。例如当前服务器的地址为10.8.42.133,则在zoo.cfg文件中指定编号为1,那么就在myid文件中添加数字1
        	15. 保存退出myid
        	16. 将要配置的集群中的其他服务器按照上述步骤配置。或者可以将当前服务器中的配置拷到其他服务器上,修改对应的myid
        格式 scp -r 目录或者文件 IP:目录
        例如:scp -r zookeeper-3.4.8 10.8.42.134:/home/software
        	17. 集群全部配置好之后,依次启动每一台服务器
        	18. 进入Zookeeper安装目录下的bin目录:cd ../bin
        	19. 执行zkServer.sh文件,启动Zookeeper的服务器:sh zkServer.sh start
        	20. 执行zkCli.sh文件,启动Zookeeper的客户端:sh zkCli.sh
        

         

      2. 注意事项
        	1. 在Zookeeper集群中,如果单独启动一台服务器,是无法对外提供服务的
        	2. 当把集群中的服务器都启动之后,可以利用:sh zkServer.sh status
        来查看当前节点的状态,如果出现leader或者follower,则说明启动成功
        

         

    3. 配置信息
      参数	说明
      clientPort	客户端连接服务器端的端口,即Zookeeper的对外服务端口,一般默认为2181
      dataDir	数据目录,即存储快照文件的目录
      dataLogDir	1. 事务日志输出目录。在不指定的情况下,和dataDir一致
      	2.  正常运行过程中,针对所有事务操作,在返回客户端ACK的响应前,Zookeeper会确保已经将本次事务操作的事务日志写到磁盘上,只有这样,事务才会生效
      tickTime	Zookeeper中的一个时间单元。Zookeeper中所有时间都是以这个时间单元为基础,进行整数倍配置
      initLimit	1. follower在启动过程中,会从leader同步所有最新数据,然后确定自己能够对外服务的起始状态。leader允许follower在 initLimit 时间内完成这个工作
      	2. 如果Zookeeper集群的数据量确实很大了,follower在启动的时候,从leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了
      	3.  默认是:10*ticktime
      syncLimit	1. 表示leader 与 follower 之间发送消息,请求和应答时间长度
      	2. 如果follower 在设置的时间内不能与leader 进行通信,那么此 follower 将被丢弃。
      	3. 默认是:5*ticktime
      	4. 当集群网络环境不好时,可以适当调大
      minSessionTimeout maxSessionTimeout	1. Session超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间
      	2. 默认的Session超时时间是在2 * tickTime ~ 20 * tickTime 这个范围
      snapCount	1. snapshot文件存储的快照数量。即在Zookeeper中每进行snapCount次事务之后,Zookeeper会生成一个新的快照文件snapshot.*,同时创建一个新的日志文件log.*
      	2. 默认值为100000
      	3. 在产生新的leader的时候,也会产生新的快照文件以及对应的事务日志
      autopurge.purgeInterval	1. 表示清理快照文件和事务日志的间隔时间
      	2. 默认单位是小时,默认数量是0,即不开启
      	3. 从Zookeeper的3.4.0版本开始,Zookeeper提供了自动清理快照和事务日志的功能。
      autopurge.snapRetainCount	表示在清理快照文件的时候需要保留的文件个数
      server.x=hostname:端口号1:端口号2	1. 配置集群中的主机
      	2. x是主机编号,即myid
      	3. 端口号1是原子广播端口,用于leader和follower之间的通信
      	4. 端口号2是选举端口,用于选举过程中的通信。
      jute.maxbuffer	用于控制每一个节点的最大存储的数据量。 默认是1M
      globalOutstandingLimit	1. 最大请求堆积数。默认是1000
      	2. Zookeeper运行的时候, 尽管server已经没有空闲来处理更多的客户端请求了,但是还是允许客户端将请求提交到服务器上来,以提高吞吐性能。而为了防止server内存溢出,用该属性限制请求堆积数。
      preAllocSize	1. 预先开辟磁盘空间,用于后续写入事务日志
      	2. 默认是64M,即每个事务日志大小就是64M。
      leaderServers	默认情况下,leader是会接受客户端连接,并提供正常的读写服务。但是,如果需要leader专注于集群中机器的事务协调(原子广播),那么可以将这个参数设置为no,这样一来,会提高整个zk集群性能。
      maxClientCnxns	控制的每一台zk服务器能处理的客户端并发请求数。默认是60。
      

       

  3. 指令
    1. 常见指令
      1. Zookeeper服务端指令
        指令	说明
        sh zkServer.sh start	启动服务器端
        sh zkServer.sh stop	停止服务器端
        sh zkServer.sh restart	重启服务器端
        sh zkServer.sh status	查看服务器端的状态
        sh zkCli.sh	启动客户端
        

         

      2. Zookeeper客户端指令
        指令	说明
        create /node01 "hello"	在根节点下创建节点node01,并且数据为"hello"
        create -e /node02 ''	在根节点下创建临时节点node02,并且数据为空。该节点在客户端关闭之后会删除
        create -s /node03 "hi"	在根节点下创建顺序节点/node03000000X,并且数据为"hi"
        create -e -s /node04 ''	在根节点下创建临时顺序节点/node040000000X,并且数据为空。该节点在客户端关闭之后会删除
        get /node01	获取/node01的数据及节点信息
        delete /node01	删除/node01。注意,如果/node01有子节点,那么不能删除
        rmr /node01	删除/node01及其子节点
        set /node01 "zk"	将/node01中的数据更新为"zk"
        ls /	查看/下的所有的子节点
        quit	推出客户端
        

         

      3. 集群指令
        1. NC安装使用
          	1. 简介:netcat 是一个小型的网络通信工具,可以发起TCP请求。可以通过Linux nc 工具来查看Zookeeper集群服务状态
          	2. 上传netcat安装包
          	3. 安装netcat:rpm -ivh nc-1.84-24.el6.x86_64.rpm
          

           

        2. Zooleeper集群指令
          	1. 查看节点状态:echo stat|nc 10.8.42.135 2181
          	2. 查看节点是否运行:echo ruok|nc 10.8.42.136 2181
          测试是否启动了该Server,若回复imok表示已经启动。
          	3. 查看节点配置信息:echo conf|nc 10.8.42.135 2181
          	4. 关闭指定节点:echo kill|nc 10.8.42.136 2181
          

           

  4. Zookeeper信息
    1. 节点信息
      名称	说明
      cZxid	创建该节点所分配的全局事务id
      ctime	创建时间
      mZxid	更新该节点数据所分配的全局事务id
      mtime	更新时间
      pZxid	子节点的最新事务id
      cversion	子节点版本,表示对子节点的更改次数
      dataversion	数据版本,数据每发生一次变化,数据版本增加1
      aclVersion	ACL的更新次数,实际上是权限更新的次数
      ephemeralOwner	如果不是临时节点,则此值为0;如果是临时节点,则此值是其会话id
      dataLength	数据的字节个数
      numChildren	子节点的个数
      

       

    2. 事务日志
      1. 概述
        	1. 事务日志指Zookeeper系统在正常运行过程中,针对所有的事务操作,在返回客户端ACK的响应前,Zookeeper会保证已经将本次更新操作的事务日志已经写到磁盘上
        	2. Zookeeper的事务日志为二进制文件,不能通过vim等工具直接访问。其实可以通过zookeeper自带的jar包读取事务日志文件
        	
        

         

      2. 查看事务log
        	1. 进入Zookeeper的安装目录的lib目录下:cd zookeeper-3.4.8/lib
        	2. 将lib目录下的slf4j-api-1.6.1.jar复制到数据目录的version-2目录下:cp slf4j-api-1.6.1.jar ../tmp/version-2/
        	3. 回到Zookeeper的安装目录:cd ..
        	4. 将Zookeeper的安装目录下的zookeeper-3.4.8.jar复制到数据目录下的version-2目录下:cp zookeeper-3.4.8.jar tmp/version-2/
        	5. 进入数据目录下的version-2目录下:cd tmp/version-2/
        	6. 执行: java -cp .:zookeeper-3.4.8.jar:slf4j-api-1.6.1.jar org.apache.zookeeper.server.LogFormatter ./log.100000001
        查看日志文件
        

         

    3. 快照
      1. 概述
        1. 快照指Zookeeper系统在所存储的部分节点信息写到了磁盘上。
        2. Zookeeper的快照文件为二进制文件,不能通过vim等工具直接访问。其实可以通过Zookeeper自带的jar包读取事务日志文件
        
        

         

      2. 查看快照文件
        	1. 进入Zookeeper的安装目录的lib目录下:cd zookeeper-3.4.8/lib
        	2. 将lib目录下的slf4j-api-1.6.1.jar复制到数据目录的version-2目录下:cp slf4j-api-1.6.1.jar ../tmp/version-2/
        	3. 回到Zookeeper的安装目录:cd ..
        	4. 将Zookeeper的安装目录下的zookeeper-3.4.8.jar复制到数据目录下的version-2目录下:cp zookeeper-3.4.8.jar tmp/version-2/
        	5. 进入数据目录下的version-2目录下:cd tmp/version-2/
        	6. 执行: java -cp .:zookeeper-3.4.8.jar:slf4j-api-1.6.1.jar org.apache.zookeeper.server.SnapshotFormatter ./log.100000001
        查看快照文件
        

         

  5. API操作
    一、POM文件
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
         
        <groupId>cn.tedu</groupId>
        <artifactId>zookeeper</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
         
        <name>zk</name>
        <url>http://maven.apache.org</url>
         
        <dependencies>
            <dependency>
                <!-- 单元测试 -->
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.10</version>
            </dependency> 
            <dependency>
                <!-- 日志记录 -->
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-simple</artifactId>
                <version>1.6.4</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <!-- Zookeeper -->
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>3.4.7</version>
            </dependency>
        </dependencies>
        <build>
        </build>
    </project>
    
    二、连接操作
    @Test
    public void connect() throws IOException, InterruptedException {
     
        CountDownLatch cdl = new CountDownLatch(1);
        // connectString - 连接地址+端口号
        // sessionTimeout - 会话超时时间 - 表示连接超时时间,单位默认为毫秒
        // watcher - 监控者 - 监控连接状态
        // Zookeeper本身是一个非阻塞式连接
        ZooKeeper zk = new ZooKeeper("117.50.90.232:2181", 5000, new Watcher() {
     
            // 监控连接状态
            public void process(WatchedEvent event) {
     
                if (event.getState() == KeeperState.SyncConnected) {
                    System.out.println("连接成功~~~");
                }
                cdl.countDown();
            }
        });
        cdl.await();
     
    }
    
    三、创建节点
    @Test
    public void create() throws KeeperException, InterruptedException {
        // path - 节点路径
        // data - 数据
        // acl - acl策略
        // createMode - 节点类型
        // 返回值表示节点的实际路径
        String str = zk.create("/node08", "hello,1807".getBytes(), Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT_SEQUENTIAL);
        System.out.println(str);
     
    }
    
    四、删除节点
    @Test
    public void delete() throws InterruptedException, KeeperException {
     
        // path - 节点路径
        // version - 数据版本
        // 在删除节点的时候回比较指定的数据版本和节点的实际数据版本是否一致
        // 如果一致则删除,如果不一致则放弃该操作
        zk.delete("/node04", 0);
        // -1表示强制执行
        zk.delete("/node05", -1);
     
    }
    
    五、更新节点数据
     @Test
    public void set() throws KeeperException, InterruptedException{
        // path - 节点路径
        //data - 数据
        // version - 数据版本
        // 返回节点信息
        Stat s = zk.setData("/node02", "hi,zk".getBytes(), -1);
        System.out.println(s);
    }
    
    六、获取节点的数据
     @Test
    public void get() throws KeeperException, InterruptedException, UnsupportedEncodingException {
         
        // path - 节点路径
        // watch - 监控
        // stat - 节点信息
        Stat s =  new Stat();
        byte[] data = zk.getData("/node01", null, s );
        System.out.println(new String(data, "utf-8"));
         
    }
    
    七、判断节点存在
    @Test
    public void exist() throws KeeperException, InterruptedException{
         
        // path - 节点路径
        // watch - 监控者
        // 返回的节点的信息
        Stat s = zk.exists("/node07", null);
        System.out.println(s);
    }
    
    八、获取子节点
    @Test
    public void getChidren() throws KeeperException, InterruptedException {
         
        // path - 节点路径
        // watch - 监控者
        // 返回子节点的路径
        List<String> nodes = zk.getChildren("/" ,null);
        for (String p : nodes) {
            System.out.println(p);
        }
    }
    
    九、监听节点数据变化
     @Test
    public void dataChanged() throws KeeperException, InterruptedException {
     
        CountDownLatch cdl = new CountDownLatch(1);
     
        // 获取的是监听之前的数据
        // 获取数据和监听变化实际上是两个线程
        byte[] data = zk.getData("/node01", new Watcher() {
     
            @Override
            public void process(WatchedEvent event) {
     
                if (event.getType() == EventType.NodeDataChanged)
                    System.out.println("节点数据发生变化~~~");
                cdl.countDown();
            }
        }, null);
        cdl.await();
        System.out.println(new String(data));
     
    }
    
    十、监听子节点是否发生变化
     @Test
    public void childChanged() throws KeeperException, InterruptedException {
     
        CountDownLatch cdl = new CountDownLatch(1);
     
        zk.getChildren("/node01", new Watcher() {
     
            @Override
            public void process(WatchedEvent event) {
                if (event.getType() == EventType.NodeChildrenChanged)
                    System.out.println("子节点产生变化~~~");
                cdl.countDown();
            }
        });
        cdl.await();
     
    }
    
    十一、监听节点的变化
     @Test
    public void nodeChanged() throws KeeperException, InterruptedException {
     
        CountDownLatch cdl = new CountDownLatch(1);
     
        zk.exists("/node06", new Watcher() {
     
            @Override
            public void process(WatchedEvent event) {
     
                if (event.getType() == EventType.NodeCreated)
                    System.out.println("这个节点被创建~~~");
                else if (event.getType() == EventType.NodeDeleted)
                    System.out.println("这个节点被删除~~~");
                cdl.countDown();
            }
        });
     
        cdl.await();
     
    }

     

  6. 选举机制
    一、选举机制
    	1. 第一阶段:数据恢复阶段
    每台Zookeeper服务器在启动的时候,都会从本地的数据目录中找到自己所拥有的最大事务id。
    	2. 第二阶段:选举阶段
    每一个Zookeeper的服务器都会推荐自己当leade并且提交选举协议:
    		a. 自己所拥有的最大事务id - Zxid
    		b. 自己的选举id - myid
    		c. 逻辑时钟值,作用是确保每一台Zookeeper服务器都会处在同一轮选举中
    	3. 节点状态
    		a. Looking - 选举状态
    		b. follower - 追随者
    		c. leader - 领导者
    		d. observer - 观察者
    
    二、PK原则
    	1. 在选举的时候会先比较两个节点的最大事务id即Zxid。Zxid越大,则说明事务越新。Zxid较大的会胜出
    	2. 如果Zxid一致,则比较选举id,选举id较大的胜出。
    	3. 满足过半性:即超过一半的节点才能成为leader
    
    三、过半性
    	1. 过半选举:即只有一个节点胜过一半的节点之后才能成为leader
    	2. 过半服务:即只有Zookeeper集群中超过一半的节点存活才能对外提供服务
    	3. 过半操作:即只有Zookeeper集群中超过一半的节点同意才会提交请求
    

     

  7. ZAB协议
    一、概述
    	1. ZAB(Zookeeper Atomic Broadcast)协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的原子广播协议
    	2. ZAB协议是一种特别为ZooKeeper设计的崩溃可恢复的原子消息广播算法。这个算法是一种类2PC算法,在2PC基础上做的改进
    	3. ZAB协议包括两种基本的模式,分别是:
    		a. 消息原子广播(保证数据一致性)
    		b. 崩溃恢复(解决2pc算法的单点问题)
    
    二、消息原子广播
    	1. 在ZooKeeper中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议,ZooKeeper实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性,实现分布式数据一致性的这一过程称为消息广播(原子广播)
    	2. ZAB协议的消息广播过程使用的是原子广播协议,类似于一个二阶段提交过程。但是相较于2PC算法,不同的是ZAB协议引入了过半性思想
    	3. ZooKeeper使用一个单一的主进程(leader服务器)来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程(follower或observer)上去。即:所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为leader服务器,而余下的其他服务器则成为follower服务器或observer
    	4. 具体流程:
    		a. 针对客户端的事务请求,leader服务器会先将该事务写到本地的log文件中
    		b. 然后,leader服务器会为这次请求生成对应的事务Proposal并且为这个事务Proposal分配一个全局递增的唯一的事务ID,即Zxid
    		c. leader服务器会为每一个follower服务器都各自分配一个单独的队列,将需要广播的事务Proposal依次放入队列中,发送给每一个follower
    		d. 每一个follower在收到队列之后,会从队列中依次取出事务Proposal,写道本地的事务日志中。如果写成功了,则给leader返回一个ACK消息
    		e. 当leader服务器接收到半数的follower的ACK相应之后,就会广播一个Commit消息给所有的follower以通知其进行事务提交,同时leader自身也进行事务提交
    		f. leader在收到Commit消息后完成事务提交
    	5. 当然,在这种简化了的二阶段提交模型下,是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题
    	6. 整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易地保证消息广播过程中消息接收与发送的顺序性
    
    三、崩溃恢复
    	1. 当leader服务器出现崩溃、重启等场景,或者因为网络问题导致过半的follower不能与leader服务器保持正常通信的时候,Zookeeper集群就会进入崩溃恢复模式
    	2. 进入崩溃恢复模式后,只要集群中存在过半的服务器能够彼此正常通信,那么就可以选举产生一个新的leader
    	3. 每次新选举的leader会自动分配一个全局递增的编号,即epochid
    	4. 当选举产生了新的leader服务器,同时集群中已经有过半的机器与该leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和leader服务器的数据状态保持一致
    	5. 当集群中已经有过半的follower服务器完成了和leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式了
    	6. 当一台同样遵守ZAB协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中
    

     

  8. 2PC算法
    一、概述
    	1. 2PC,是Two-Phase Commit的缩写,即二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法
    	2. 通常,二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统数据的一致性
    	3. 目前,绝大部分的关系型数据库都是采用二阶段提交协议来完成分布式事务处理的,利用该协议能够非常方便地完成所有分布式事务参与者的协调,统一决定事务的提交或回滚,从而能够有效地保证分布式数据一致性
    
    二、提交过程
    	1. 阶段一:提交事务请求+执行事务。由于在形式上近似是协调者组织各参与者对一次事务操作的投票表态过程,因此二阶段提交协议的阶段一也被称为“投票阶段”,即各参与者投票表明是否要继续执行接下去的事务提交操作。
    		a. 事务询问。协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应
    		b. 执行事务。各参与者节点执行事务操作,并将Undo和Redo信息记入事务日志中
    		c. 各参与者向协调者反馈事务询问的响应。如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行
    	2. 阶段二:事务提交。在阶段二中,协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,包含以下两种可能:
    		a. 执行事务提交。假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务提交:
    			i. 发送提交请求。协调者向所有参与者节点发出Commit请求
    			ii. 事务提交。参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源
    			iii. 反馈事务提交结果。参与者在完成事务提交之后,向协调者发送Ack消息
    			iv. 完成事务。协调者接收到所有参与者反馈的Ack消息后,完成事务
    		b. 中断事务。假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务:
    			i. 发送回滚请求。协调者向所有参与者节点发出Rollback请求
    			ii. 事务回滚。参与者接收到Rollback请求后,会利用其在阶段一中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源
    			iii. 反馈事务回滚结果。参与者在完成事务回滚之后,向协调者发送Ack消息
    			iv. 中断事务。协调者接收到所有参与者反馈的Ack消息后,完成事务中断
    	
    	
    	
    三、优缺点
    	1. 优点:原理简单,实现方便
    	2. 缺点:
    		a. 同步阻塞:二阶段提交协议存在的最明显也是最大的一个问题就是同步阻塞,这会极大地限制分布式系统的性能。在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,将无法进行其他任何操作
    		b. 单点问题:协调者的角色在整个二阶段提交协议中处于核心地位,因此一旦协调者出现问题,那么整个二阶段提交流程将无法运转
    		c. 太过保守:如果在协调者指示参与者进行事务提交询问的过程中,参与者出现故障而导致协调者始终无法获取到所有参与者的响应信息的话,这时协调者只能依靠其自身的超时机制来判断是否需要中断事务,这样的策略显得比较保守。换句话说,二阶段提交协议没有设计较为完善的容错机,任意一个节点的失败都会导致整个事务的失败
    

     

  9. 观察者
    一、概述
    	1. 对于当前的Zookeeper架构而言,如果添加了更多的投票成员,则会导致写入性能下降:一个写入操作要求共识协议至少是整体的一半,因此投票的成本随着投票者越多会显著增加
    	2. 在Zookeeper中引入了一个新的Zookeeper节点类型叫做observer(观察者),它帮助处理这个问题并进一步完善了Zookeeper的可扩展性
    	3.  观察者不参与投票,它只监听投票的结果,但是观察者可以和追随者一样运行 ,即客户端可能链接他们并发送读取和写入请求。 观察者像追随者一样转发这些请求到领导者,而他们只是简单的等待监听投票的结果
    	4. 观察者的数量的多少不影响投票的性能。因此观察者不是Zookeeper集群整体的主要组件。 因此如果观察者产生故障或者从集群断开连接都不会影响Zookeeper服务的可用性
    	5. 在实际使用中,观察者可以连接到比追随者更不可靠的网络。事实上,观察者可以用于从其他数据中心和Zookeeper服务通信。观察者的客户端会看到快速的读取,因为所有的读取都在本地,并且写入导致最小的网络开销,因为投票协议所需的消息数量更小
    
    二、配置
    	1. 进入到Zookeeper安装目录下的conf目录:cd zookeeper-3.4.8/conf
    	2. 编辑zoo.cfg文件:vim zoo.cfg
    	3. 在zoo.cfg中添加如下属性:peerType=observer
    	4. 在要配置为观察者的主机后添加观察者标记。例如:
    server.1=10.8.42.133:2888:3888
    server.2=10.8.42.134:2888:3888
    server.3=10.8.42.135:2888:3888:observer #表示将该节点设置为观察者
    	5. 保存退出文件
    	6. 进入到Zookeeper的bin目录下启动该服务器端
    cd ../bin
    sh zkServer.sh start
    

     

  10. 扩展阅读
    1. Cannal
      一、概述
      	1. Canal是阿里巴巴于2013年1月正式开源的一个由纯Java语言编写的基于MySQL数据库Binlog实现的增量订阅和消费组件
      	2. 目前项目主页地址为:https://github.Com/alibaba/canal 。由项目主要负责人,同时也是资深的开源爱好者agapple持续维护
      	3. 项目名Canal取自“管道”的英文单词,寓意数据的流转,是一个定位为基于MySQL数据库的Binlog增量日志来实现数据库镜像、实时备份和增量数据消费的通用组件
      	4. 早期的数据库同步业务,大多都是使用MySQL数据库的触发器机制(即Trigger)来获取数据库的增量变更。不过从2010年开始,阿里系下属各公司开始逐步尝试基于数据库的日志解析来获取增量变更,并在此基础上实现数据的同步,由此衍生出了数据库的增量订阅和消费业务——Canal项目也由此诞生
      	5. Canal的工作原理相对比较简单,其核心思想就是模拟MySQL Slave的交互协议,将自己伪装成一个MySQL的Slave机器,然后不断地向Master服务器发送Dump请求。Master收到Dump请求后,就会开始推送相应的Binary Log给该Slave(也就是Canal)。Canal收到Binary Log,解析出相应的Binary Log对象后就可以进行二次消费了,其基本工作原理如下图所示。:
      
      
      二、Canal Server主备切换设计
      	1. 在Canal的设计中,基于对容灾的考虑,往往会配置两个或更多个Canal Server来负责一个MySQL数据库实例的数据增量复制
      	2. 另一方面,为了减少Canal Server的Dump请求对MySQLMaster所带来的性能影响,就要求不同的Canal Server上的instance在同一时刻只能有一个处于Running状态,其他的instance都处于Standby状态,这就使得Canal必须具备主备自动切换的能力
      	3. 在Canal中,整个主备切换过程控制主要是依赖于ZooKeeper来完成的,如下图所示:
      	
      		a. 尝试启动:每个Canal Server在启动某个Canal instance的时候都会首先向ZooKeeper进行一次尝试启动判断。具体的做法是向ZooKeeper创建一个相同的临时节点,哪个Canal Server创建成功了,那么就让哪个Server启动。以 “example”这个instance为例来说明,所有的Canal Server在启动的时候,都会去创建 /otter/canal/destinations/example/running 节点,并且无论有多少个Canal Server同时并发启动,ZooKeeper都会保证最终只有一个Canal Server能够成功创建该节点。 
      		b. 启动instance:假设最终IP地址为10.20.144.51的Canal Server成功创建了该节点,那么它就会将自己的机器信息写入到该节点中去: {"active":true,"address":"10.20.144.51:11111","cid":1}并同时启动instance。而其他Canal Server由于没有成功创建节点,于是就会将自己的状态置为Standby,同时 /otter/canal/destinations/example/running节点注册Watcher监听,以监听该节点的变化情况。
      		c. 主备切换:Canal Server在运行过程中,难免会发生一些异常情况导致其无法正常工作,这个时候就需要进行主备切换了。基于ZooKeeper临时节点的特性,当原本处于Running状态的Canal Server因为挂掉或网络等原因断开了与ZooKeeper的连接,那么/otter/canal/destinations/example/running节点就会在一段时间后消失 。由于之前处于Standby状态的所有Canal Server已经对该节点进行了监听,因此它们在接收到ZooKeeper发送过来的节点消失通知后,会重复进行步骤1——以此实现主备切换。 
      	4. 在主备切换设计过程中最容易碰到的一个问题,就是“假死”。所谓假死状态是指,Canal Server所在服务器的网络出现闪断,导致ZooKeeper认为其会话失效,从而释放了Running节点——但此时Canal Server对应的JVM并未退出,其工作状态是正常的
      	5. 在Canal的设计中,为了保护假死状态的Canal Server,避免因瞬间Running节点失效导致instance重新分布带来的资源消耗,所以设计了一个策略:
      		a. 状态为Standby的Canal Server在收到Running节点释放的通知后,会延迟一段时间抢占Running节点,而原本处于Running状态的instance,即Running节点的拥有者可以不需要等待延迟,直接取得Running节点
      		b.  这样就可以尽可能地保证假死状态下一些无谓的资源释放和重新分配
      		c. 目前延迟时间的默认 值为5秒,即Running节点针对假死状态的保护期为5秒
      
      三、Canal Client的HA设计
      	1. Canal Client在进行数据消费前,首先当然需要找到当前正在提供服务的Canal Server,即Master
      	2. 在上面“主备切换”部分中,针对每一个数据复制实例,例如example,都会 /otter/canal/destinations/example/running节点中记录下当前正在运行的Canal Server。因此,Canal Client只需要连接ZooKeeper,并从对应的节点上读取Canal Server信息即可
      	3. 从ZooKeeper中读取出当前处于Running状态的Server。Canal Client在启动的时候,会首先从/otter/canal/destinations/example/running节点上读取出当前处于Running状态的Server。同时,客户端也会将自己的信息注册到ZooKeeper的 /otter/canal/destinations/example/1001/running节点上,其中“1001”代表了该客户端的唯一标识,其节点内容如下: {"active":true,"address":"10.12.48.171:50544","clientId":1001} 
      	4. 注册Running节点数据变化的监听。由于Canal Server存在挂掉的风险,因此Canal Client还会对/otter/canal/destinations/example/running节点注册一个节点变化的监听,这样一旦发生Server的主备切换,Client就可以随时感知到
      	5. 连接对应的Running Server进行数据消费
      
      四、数据消费位点记录
      	1. 由于存在Canal Client的重启或其他变化,为了避免数据消费的重复性和顺序错乱,Canal必须对数据消费的位点进行实时记录
      	2. 数据消费成功后,Canal Server会在ZooKeeper中记录下当前最后一次消费成功的Binary Log位点,一旦发生Client重启,只需要从这最后一个位点继续进行消费即可
      	3. 具体的做法是在ZooKeeper的/otter/canal/destinations/example/1001/cursor节点中记录下客户端消费的详细位点信息: {"@type":"com.alibaba.otter.canal.protocol.position.LogPosition","identity":{"slaveId":-1,"sourceAddress":{"address":"10.20.144.15","port":3306}},"postion":"included":false,"journalName":"mysqlbin.002253","position":2574756,"timestamp":1363688722000}}
      

       

    2. Dubbo
      一、概述
      	1. Dubbo是阿里巴巴于2011年10月正式开源的一个由Java语言编写的分布式服务框架,致力于提供高性能和透明化的远程服务调用方案和基于服务框架展开的完整SOA服务治理方案
      	2. 目前项目主页地址为:https://github.com/alibaba/dubbo。 
      
      二、核心组成
      	1. 远程通信:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型、序列化,以及“请求-响应”模式的信息交换方式
      	2. 集群容错:提供基于接口方法的远程过程透明调用,包括对多协议的支持,以及对软负载均衡、失败容错、地址路由和动态配置等集群特性的支持
      	3. 自动发现:提供基于注册中心的目录服务,使服务消费方能动态地查找服务提供方,使地址透明,使服务提供方可以平滑地增加或减少机器
      	4. Dubbo框架还包括负责服务对象序列化的Serialize组件、网络传输组件Transport、协议层Protocol以及服务注册中心Registry等,其整体模块组成和协作方式如下图所示:
      
      
      三、注册中心
      	1. 注册中心是RPC框架最核心的模块之一,用于服务的注册和订阅
      	2. 在Dubbo的实现中,对注册中心模块进行了抽象封装,因此可以基于其提供的外部接口来实现各种不同类型的注册中心,例如数据库、 ZooKeeper和Redis等
      	3. 在Dubbo注册中心的整体架构设计中,ZooKeeper上服务的节点设计如下图所示 
      	
      	/dubbo:这是Dubbo在ZooKeeper上创建的根节点。
      	/dubbo/com.foo.BarService:这是服务节点,代表了Dubbo的一个服务。
      	/dubbo/com.foo.BarService/providers:这是服务提供者的根节点,其子节点代表了每一个服务的真正提供者。
      	/dubbo/com.foo.BarService/conssumers:这是服务消费者的根节点,其子节点代表了每一个服务的真正消费者。
      	4. 以“com.foo.BarService”这个服务为例,来说明Dubbo基于ZooKeeper实现的注册中心的工作流程:
      		a. 服务提供者:服务提供者在初始化启动的时候,会首先在ZooKeeper的/dubbo/com.foo.BarService/providers节点下创建一个子节点,并写入自己的URL地址,这就代表了“com.foo.BarService”这个服务的一个提供者
      		b. 服务消费者:服务消费者会在启动的时候,读取并订阅ZooKeeper上/dubbo/com.foo.BarService/providers节点下的所有子节点,并解析出所有提供者的URL地址来作为该服务地址列表,然后开始发起正常调用。同时,服务消费者还会在ZooKeeper的 /dubbo/com.foo.BarService/consumers节点下创建一个临时节点,并写入自己的URL地址,这就代表了“com.foo.BarService”这个服务的一个消费者
      		c. 监控中心:监控中心是Dubbo中服务治理体系的重要一部分,其需要知道一个服务的所有提供者和订阅者,及其变化情况。因此,监控中心在启动的时候,会通过ZooKeeper的/dubbo/com.foo.BarService节点来获取所有提供者和消费者的URL地址,并注册Watcher来监听其子节点变化
      		d. 另外需要注意的是,所有提供者在ZooKeeper上创建的节点都是临时节点,利用的是临时节点的生命周期和客户端会话相关的特性,因此一旦提供者所在的机器出现故障导致该提供者无法对外提供服务时,该临时节点就会自动从ZooKeeper上删除,这样服务的消费者和监控中心都能感知到服务提供者的变化
      	5. 在ZooKeeper节点结构设计上,以服务名和类型作为节点路径,符合Dubbo订阅和通知的需求,这样保证了以服务为粒度的变更通知,通知范围易于控制,即使在服务的提供者和消费者变更频繁的情况下,也不会对ZooKeeper造成太大的性能影响。
      
      

       

    3. Metamorphosis
      一、概述
      	1. Metamorphosis是阿里巴巴中间件团队的killme2008和wq163于2012年3月开源的一个Java消息中间件
      	2. 目前项目主页地址为:https://github.com/killme2008/Metamorphosis,由开源爱好者及项目的创始人killme2008和wq163持续维护
      	3. Metamorphosis是一个高性能、高可用、可扩展的分布式消息中间件,其思路起源于LinkedIn的Kafka,但并不是Kafka的一个简单复制
      	4. Metamorphosis具有消息存储顺序写、吞吐量大和支持本地XA事务等特性,适用于大吞吐量、顺序消息、消息广播和日志数据传输等分布式应用场景,目前在淘宝和支付宝都有着广泛的应用,其系统整体部署结构如下图所示: 
      	
      	5. 和传统的消息中间件采用推(Push)模型所不同的是,Metamorphosis是基于拉(Pull)模型构建的,由消费者主动从Metamorphosis服务器拉取数据并解析成消息来进行消费,同时大量依赖ZooKeeper来实现负载均衡和Offset的存储。 
      
      二、生产者的负载均衡
      	1. 和Kafka系统一样,Metamorphosis假定生产者、 Broker和消费者都是分布式的集群系统
      	2. 生产者可以是一个集群,多台机器上的生产者可以向相同的Topic发送消息
      	3. 服务器Broker通常也是一个集群,多台Broker组成一个集群对外提供一系列的Topic消息服务
      	4. 生产者按照一定的路由规则向集群里某台Broker发送消息,消费者按照一定的路由规则拉取某台Broker上的消息
      	5. 每个Broker都可以配置一个Topic的多个分区,但是在生产者看来,会将一个Topic在所有Broker上的所有分区组成一个完整的分区列表来使用
      	6. 在创建生产者的时候,客户端会从ZooKeeper上获取已经配置好的Topic对应的Broker和分区列表,生产者在发送消息的时候必须选择一台Broker上的一个分区来发送消息,默认的策略是一个轮询的路由规则,如下图所示:
      	
      	7. 生产者在通过ZooKeeper获取分区列表之后,会按照Broker Id和Partition的顺序排列组织成一个有序的分区列表,发送的时候按照从头到尾循环往复的方式选择一个分区来发送消息
      	8. 考虑到Broker服务器软硬件配置基本一致,因此默认的轮询策略已然足够
      	9. 在Broker因为重启或者故障等因素无法提供服务时,Producer能够通过ZooKeeper感知到这个变化,同时将失效的分区从列表中移除,从而做到Fail Over
      	10. 需要注意的是,因为从故障到生产者感知到这个变化有一定的延迟,因此可能在那一瞬间会有部分的消息发送失败。
      
      三、消费者的负载均衡
      	1. 消费者的负载均衡则会相对复杂一些,这里讨论的是单个分组内的消费者集群的负载均衡,不同分组的负载均衡互不干扰
      	2. 消费者的负载均衡跟Topic的分区数目和消费者的个数紧密相关,分以下几个场景来讨论:
      		a. 消费者数和Topic分区数一致:如果单个分组内的消费者数目和Topic总的分区数目相同,那么每个消费者负责消费一个分区中的消息,一一对应,如下图所示: 
      		
      		b. 消费者数大于Topic分区数:如果单个分组内的消费者数目比Topic总的分区数目多,则多出来的消费者不参与消费,如下图所示:
      		
      		c. 消费者数小于Topic分区数:如果分组内的消费者数目比Topic总的分区数目小,则有部分消费者需要额外承担消息的消费任务,具体如下图所示:
      		
      		d. 当分区数目(n)大于单个Group的消费者数目(m)的时候,则有n%m个消费者需要额外承担1/n的消费任务,我们假设n无限大,那么这种策略还是能够达到负载均衡的目的的
      	3. 综上所述,单个分组内的消费者集群的负载均衡策略如下:
      		a. 每个分区针对同一个Group只能挂载一个消费者,即每个分区至多同时允许被一个消费者进行消费
      		b. 如果同一个Group的消费者数目大于分区数目,则多出来的消费者将不参与消费
      		c. 如果同一个Group的消费者数目小于分区数目,则有部分消费者需要额外承担消费任务
      	4. Metamorphosis的客户端会自动处理消费者的负载均衡,将消费者列表和分区列表分别排序,然后按照上述规则做合理的挂载
      	5. 从上述内容来看,合理地设置分区数目至关重要。如果分区数目太小,则有部分消费者可能闲置;如果分区数目太大,则对服务器的性能有影响
      	6. 在某个消费者发生故障或者发生重启等情况时,其他消费者会感知到这一变化(通过ZooKeeper的“节点变化”通知),然后重新进行负载均衡,以保证所有的分区都有消费者进行消费。 
      
      四、消息消费位点Offset存储
      	1. 为了保证生产者和消费者在进行消息发送与接收过程中的可靠性和顺序性,同时也是为了尽可能地保证避免出现消息的重复发送和接收,Metamorphosis会将消息的消费记录Offset记录到 ZooKeeper上去,以尽可能地确保在消费者进行负载均衡的时候,能够正确地识别出指定分区的消息进度。
      

       

    4. otter
      一、概述
      	1. Otter是阿里巴巴于2013年8月正式开源的一个由纯Java语言编写的分布式数据库同步系统,主要用于异地双A机房的数据库数据同步,致力于解决长距离机房的数据同步及双A机房架构下的数据一致性问题
      	2. 目前项目主页地址为https://github.com/alibaba/otter,由项目主要负责人,同时也是资深的开源爱好者agapple持续维护
      	3. 项目名Otter取自“水獭”的英文单词,寓意数据搬运工,是一个定位为基于数据库增量日志解析,在本机房或异地机房的MySQL/Oracle数据库之间进行准实时同步的分布式数据库同步系统
      	4. Otter的第一个版本可以追溯到2004年,初衷是为了解决阿里巴巴中美机房之间的数据同步问题,从4.0版本开始开源,并逐渐演变成一个通用的分布式数据库同步系统。其基本架构如下图所示。 
      	
      	5. 在Otter中也是使用ZooKeeper来实现一些与分布式协调相关的功能
      
      二、 分布式SEDA模型调度
      概述
      	1. 为了更好地提高整个系统的扩展性和灵活性,在Otter中将整个数据同步流程抽象为类似于ETL的处理模型,具体分为四个阶段(Stage):
      		a. Select:数据接入
      		b. Extract:数据提取
      		c. Transform:数据转换
      		d. Load:数据载入
      	2. 其中Select阶段是为了解决数据来源的差异性,比如可以接入来自Canal的增量数据,也可以接入其他系统的数据源。 Extract/Transform/Load阶段则类似于数据仓库的ETL模型,具体可分为 数据Join、数据转化和数据Load等过程 
      	3. 同时,为了保证系统的高可用性,SEDA的每个阶段都会有多个节点进行协同处理。如下图所示是该SEDA模型的示意图:
      	
      Stage管理
      	1. Stage管理主要就是维护一组工作线程,在接收到Schedule的Event任务信号后,分配一个工作线程来进行任务处理,并在任务处理完成后,反馈信息到Schedule
      Schedule调度
      	1. Schedule调度主要是指基于ZooKeeper来管理Stage之间的任务消息传递
      	2. 具体实现逻辑:
      		a. 创建节点。Otter首先会为每个Stage在ZooKeeper上创建一个节点,例如/seda/stage/s1,其中s1即为该Stage的名称,每个任务事件都会对应于该节点下的一个子节点,例如/seda/stage/s1/RequestA
      		b. 任务分配。当s1的上一级Stage完成RequestA任务后,就会通知“Schedule调度器”其已完成了该请求。根据预先定义的Stage流程,Schedule调度器便会在Stage s1的目录下创建一个RequestA的子节点,告知s1有一个新的请求需要其处理——以此完成一次任务的分配
      		c. 任务通知。每个Stage都会有一个Schedule监听线程,利用ZooKeeper的Watcher机制来关注ZooKeeper中对应Stage节点的子节点变化,比如关注s1就是关注/seda/stage/s1的子节点的变化情况。此时,如果步骤2中调度器在s1的节点下创建了一个RequestA,那么ZooKeeper就会通过Watcher机制通知到该Schedule线程,然后Schedule就会通知Stage进行任务处理——以此完成一次任务的通知
      		d. 任务完成。 当s1完成了RequestA任务后,会删除s1目录下的RequestA任务,代表处理完成,然后继续步骤2,分配下一个Stage的任务
      	3. 在上面的第3步中,还有一个需要注意的细节是,在真正的生产环境部署中,往往都会由多台机器共同组成一个Stage来处理Request,因此就涉及多个机器节点之间的分布式协调。如果s1有多个节点协同处理,每个节点都会有该Stage的一个Shedule线程,其在s1目录变化时都会收到通知。在这种情况下,往往可以采取抢占式的模式,尝试在RequestA目录下创建一个lock节点,谁创建成功就可以代表当前谁抢到了任务,而没抢到该任务的节点,便会关注该lock节点的变化(因为一旦该lock节点消失,那么代表当前抢到任务的节点可能出现了异常退出,没有完成任务),然后继续抢占模型
      
      三、中美跨机房ZooKeeper集群的部署
      	1. 由于Otter主要用于异地双A机房的数据库同步,致力于解决长距离机房的数据同步及双A机房架构下的数据一致性问题,因此其本身就有面向中美机房服务的需求,也就会有每个机房都要对ZooKeeper进行读写操作的需求。于是,希望可以部署一个面向全球机房服务的ZooKeeper集群,保证读写数据一致性。这里就需要使用ZooKeeper的Observer功能
      	2. 从3.3.0版本开始,ZooKeeper新增了Observer模式,该角色提供只读服务,且不参与事务请求的投票,主要用来提升整个ZooKeeper集群对非事务请求的处理能力。因此,借助ZooKeeper的Observer特性,Otter将ZooKeeper集群进行了三地部署。杭州机房部署Leader/Follower集群,为了保障系统高可用,可以部署3个机房。每个机房的部署实例可为1/1/1或者3/2/2的模式。美国机房部署Observer集群,为了保证系统高可用,可以部署2个机房,每个机房的部署实例可以为1/1。青岛机房部署Observer集群。 下图所示是ZooKeeper集群三地部署示意图。
      	
      	3. 当美国机房的客户端发起一个非事务请求时,就直接从部署在美国机房的Observer ZooKeeper读取数据即可,这将大大减少中美机房之间网络延迟对ZooKeeper操作的影响。而如果是事务请求,那么美国机房的Observer就会将该事务请求转发到杭州机房的Leader/Follower集群上进行投票处理,然后再通知美国机房的Observer,最后再由美国机房的Observer负责响应客户端
      	4. 上面这个部署结构,不仅大大提升了ZooKeeper集群对美国机房客户端的非事务请求处理能力,同时,由于对事务请求的投票处理都是在杭州机房内部完成,因此也大大提升了集群对事务请求的处理能力
      

       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宰祖宣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值