分布式协调服务-zookeeper分布式环境的特点分布性并发性程序运行过程中,并发性操作是很常见的。比如同一个分布式系统中的多个节点,同时访问一个共享资源。数据库、分布式存储无序性进程之间的消息通信,会出现顺序不一致问题分布式环境下面临的问题网络通信网络本身的不可靠性,因此会涉及到一些网络通信问题网络分区(脑裂)当网络发生异常导致分布式系统中部分节点之间的网络延时不断增大,最终导致组成分布式架构的所有节点,只有部分节点能够正常通信三态在分布式架构里面,成功、失败、超时分布式事务ACID(原子性、一致性、隔离性、持久性)中心化和去中心化冷备或者热备分布式架构里面,很多的架构思想采用的是:当集群发生故障的时候,集群中的人群会自动“选举”出一个新的领导。最典型的是: zookeeper / etcd经典的CAP/BASE理论CAPC(一致性 Consistency): 所有节点上的数据,时刻保持一致可用性(Availability):每个请求都能够收到一个响应,无论响应成功或者失败分区容错 (Partition-tolerance):表示系统出现脑裂以后,可能导致某些server与集群中的其他机器失去联系CP / APCAP理论仅适用于原子读写的Nosql场景,不适用于数据库系统BASE基于CAP理论,CAP理论并不适用于数据库事务(因为更新一些错误的数据而导致数据出现紊乱,无论什么样的数据库高可用方案都是徒劳) ,虽然XA事务可以保证数据库在分布式系统下的ACID特性,但是会带来性能方面的影响;eBay尝试了一种完全不同的套路,放宽了对事务ACID的要求。提出了BASE理论Basically available : 数据库采用分片模式, 把100W的用户数据分布在5个实例上。如果破坏了其中一个实例,仍然可以保证80%的用户可用soft-state: 在基于client-server模式的系统中,server端是否有状态,决定了系统是否具备良好的水平扩展、负载均衡、故障恢复等特性。Server端承诺会维护client端状态数据,这个状态仅仅维持一小段时间, 这段时间以后,server端就会丢弃这个状态,恢复正常状态Eventually consistent:数据的最终一致性初步认识zookeeperzookeeper是一个开源的分布式协调服务,是由雅虎创建的,基于google chubby。zookeeper是什么分布式数据一致性的解决方案zookeeper能做什么 数据的发布/订阅(配置中心:disconf) 负载均衡(dubbo利用了zookeeper机制实现负载均衡) 命名服务 master选举(kafka、hadoop、hbase) 分布式队列 分布式锁zookeeper的特性顺序一致性 从同一个客户端发起的事务请求,最终会严格按照顺序被应用到zookeeper中原子性 所有的事务请求的处理结果在整个集群中的所有机器上的应用情况是一致的,也就是说,要么整个集群中的所有机器都成功应用了某一事务、要么全都不应用可靠性 一旦服务器成功应用了某一个事务数据,并且对客户端做了响应,那么这个数据在整个集群中一定是同步并且保留下来的实时性 一旦一个事务被成功应用,客户端就能够立即从服务器端读取到事务变更后的最新数据状态;(zookeeper仅仅保证在一定时间内,近实时)zookeeper安装单机环境安装下载zookeeper的安装包http://apache.fayea.com/zooke…解压zookeeper tar -zxvf zookeeper-3.4.10.tar.gzcd 到 ZK_HOME/conf , copy一份zoo.cfg cp zoo_sample.cfg zoo.cfgsh zkServer.sh {start|start-foreground|stop|restart|status|upgrade|print-cmd}sh zkCli.sh -server ip:port默认端口 2181127.0.0.1:2181 zkServer.sh start-foreground集群环境建议使用奇数对于复制模式,至少需要三台服务器,强烈建议您使用奇数个服务器。如果您只有两台服务器,那么您处于这样的情况:如果其中一台服务器出现故障,则没有足够的机器来构成多数仲裁。两台服务器本质上 不如 单一服务器稳定,因为有两个单点故障。zookeeper集群, 包含三种角色:leader : 所有写和更改操作接受,转发到其他节点,接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部的数据交换(同步)follower : 直接为客户端服务并参与提案的投票,同时与Leader进行数据交换(同步)observer: 直接为客户端服务但不参与提案的投票,同时也与Leader进行数据交换(同步)observerobserver 是一种特殊的zookeeper节点。可以帮助解决zookeeper的扩展性(如果大量客户端访问我们zookeeper集群,需要增加zookeeper集群机器数量。从而增加zookeeper集群的性能。 导致zookeeper写性能下降, zookeeper的数据变更需要半数以上服务器投票通过。造成网络消耗增加投票成本)observer不参与投票。 只接收投票结果。不属于zookeeper的关键部位。在zoo.cfg里面增加在集群中的每一台服务器必须感知其他服务器(1,2,3表示id,取值范围 1-255)peerType=observerserver.1=host:2181:3181:observerserver.2=host:2181:3181server.3=host:2181:3181quorumListenOnAllIPs=true第一步: 修改配置文件 server.id=host:port:port id的取值范围: 1~255; 用id来标识该机器在集群中的机器序号 2181是zookeeper的端口; //3306 3181表示leader选举的端口 server.1=host:2181:3181 server.2=host:2181:3181 server.3=host:2181:3181第二步:创建myid 在每一个服务器的dataDir目录下创建一个myid的文件,文件就一行数据,数据内容是每台机器对应的server ID的数字第三步:启动zookeeper分布式系统里面的特点分布式系统架构存在的问题中心化和去中心化CAP和BASEzookeeper的安装 单机环境安装/集群环境安装zookeeper的特性1. zookeeper的客户端使用2. zoo.cfg里面配置信息的讲解3. zookeeper的一些常见概念模型4. zookeeper java客户端的使用集群的角色: leader :followerobserver:避免写性能下降,不需要参与写集群的搭建修改zoo.cfg 129/135/136 server.id=ip:port:port server.1=host:2888:3181 2888表示follower节点与leader节点交换信息的端口号 3181 如果leader节点挂掉了, 需要一个端口来重新选举。 server.2=host:2888:3181 server.3=host:2888:3181zoo.cfg中有一个dataDir = /tmp/zookeeper $dataDir/myid 添加一个myid文件。启动服务 如果需要增加observer节点zoo.cfg中 增加 ;peerType=observerserver.1=host:2888:3181 server.2=host:2888:3181 server.3=host:2888:3181:observerzoo.cfg配置文件分析tickTime=2000 zookeeper中最小的时间单位长度 (ms)initLimit=10 follower节点启动后与leader节点完成数据同步的时间syncLimit=5 leader节点和follower节点进行心跳检测的最大延时时间dataDir=/tmp/zookeeper 表示zookeeper服务器存储快照文件的目录dataLogDir 表示配置 zookeeper事务日志的存储路径,默认指定在dataDir目录下clientPort 表示客户端和服务端建立连接的端口号: 2181zookeeper中的一些概念数据模型zookeeper的数据模型和文件系统类似,每一个节点称为:znode. 是zookeeper中的最小数据单元。每一个znode上都可以保存数据和挂载子节点。 从而构成一个层次化的属性结构节点特性持久化节点 : 节点创建后会一直存在zookeeper服务器上,直到主动删除持久化有序节点 :每个节点都会为它的一级子节点维护一个顺序临时节点 : 临时节点的生命周期和客户端的会话保持一致。当客户端会话失效,该节点自动清理,临时节点下面不能挂子节点临时有序节点 : 在临时节点上多勒一个顺序性特性会话Watcherzookeeper提供了分布式数据发布/订阅,zookeeper允许客户端向服务器注册一个watcher监听。当服务器端的节点触发指定事件的时候会触发watcher。服务端会向客户端发送一个事件通知watcher的通知是一次性,一旦触发一次通知后,该watcher就失效ACLzookeeper提供控制节点访问权限的功能,用于有效的保证zookeeper中数据的安全性。避免误操作而导致系统出现重大事故。CREATE READWRITEDELETEADMINzookeeper的命令操作1. create [-s]【-e】 path data acl -s 表示节点是否有序 -e 表示是否为临时节点 默认情况下,是持久化节点 path需要/开头2. get path [watch] 获得指定 path的信息3.set path data [version] 修改节点 path对应的data 乐观锁的概念 每个节点创建好之后会有一个dataversion,默认是0,如果后续此值更改之后,dataversion会更改,我们可以通过version来保证乐观锁 数据库里面有一个 version 字段去控制数据行的版本号4.delete path [version] 删除节点stat信息 cversion = 0 子节点的版本号 aclVersion = 0 表示acl的版本号,修改节点权限 dataVersion = 1 表示的是当前节点数据的版本号 czxid 节点被创建时的事务ID mzxid 节点最后一次被更新的事务ID pzxid 当前节点下的子节点最后一次被修改时的事务ID ctime = Sat Aug 05 20:48:26 CST 2017 创建时间 mtime = Sat Aug 05 20:48:50 CST 2017 修改时间 cZxid = 0x500000015 ctime = Sat Aug 05 20:48:26 CST 2017 mZxid = 0x500000016 mtime = Sat Aug 05 20:48:50 CST 2017 pZxid = 0x500000015 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 创建临时节点的时候,会有一个sessionId 。 该值存储的就是这个sessionid,客户端有一定时间生效时间,具有重试机制,过一段时间判断是否有会话,如果没有,则删除 dataLength = 3 数据值长度 numChildren = 0 子节点数java API的使用导入jar包<dependency>    <groupId>org.apache.zookeeper</groupId>    <artifactId>zookeeper</artifactId>    <version>3.4.8</version></dependency>public class CreateNodeDemo implements Watcher { private final static String CONNECT_STRING = “host:2181,” + “host:2181,” + “host:2181”; private static CountDownLatch countDownLatch = new CountDownLatch(1); private static ZooKeeper zooKeeper = null; private static Stat stat = new Stat(); public static void main(String[] args) throws IOException, InterruptedException, KeeperException { zooKeeper = new ZooKeeper(CONNECT_STRING, 5000, new CreateNodeDemo()); countDownLatch.await(); System.out.println(zooKeeper.getState()); String path = zooKeeper.create( “/java1”, “123”.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //watcher是一次性的,所以使用一次之后需要重新注册 zooKeeper.getData("/java1", true, stat); System.out.println(“节点创建成功:” + path); zooKeeper.setData("/java1", “231”.getBytes(), -1); zooKeeper.delete("/java1", -1); zooKeeper.create("/java1/dd", “da”.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Thread.sleep(3000); List<String> children = zooKeeper.getChildren("/java1", true); for (String child : children) { System.out.println(child); } //权限模式 } public void process(WatchedEvent watchedEvent) { if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { if (watchedEvent.getType() == watchedEvent.getType() && null == watchedEvent.getPath()) { countDownLatch.countDown(); System.out.println(watchedEvent.getState() + “–>” + watchedEvent.getType()); } if (watchedEvent.getType() == Event.EventType.NodeCreated) { try { System.out.println(“创建节点” + watchedEvent.getPath() + “改变后的值是” + zooKeeper.getData(watchedEvent.getPath(), true, stat)); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } if (watchedEvent.getType() == Event.EventType.NodeDeleted) { System.out.println(“删除节点” + watchedEvent.getPath()); } if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) { try { System.out.println(“子节点改变” + watchedEvent.getPath() + “改变后的值是” + zooKeeper.getData(watchedEvent.getPath(), true, stat)); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } if (watchedEvent.getType() == Event.EventType.NodeDataChanged) { System.out.println("————–"); try { System.out.println(“数据改变” + watchedEvent.getPath() + “改变后的值是” + zooKeeper.getData(watchedEvent.getPath(), true, stat)); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(watchedEvent.getPath()); System.out.println(watchedEvent.getType()); }}权限控制模式schema:授权对象ip : hostDigest : username:passwordworld : 开放式的权限控制模式,数据节点的访问权限对所有用户开放。 world:anyonesuper :超级用户,可以对zookeeper上的数据节点进行操作连接状态KeeperStat.Expired 在一定时间内客户端没有收到服务器的通知, 则认为当前的会话已经过期了。KeeperStat.Disconnected 断开连接的状态KeeperStat.SyncConnected 客户端和服务器端在某一个节点上建立连接,并且完成一次version、zxid同步KeeperStat.authFailed 授权失败事件类型NodeCreated 当节点被创建的时候,触发NodeChildrenChanged 表示子节点被创建、被删除、子节点数据发生变化NodeDataChanged 节点数据发生变化NodeDeleted 节点被删除None 客户端和服务器端连接状态发生变化的时候,事件类型就是Nonezkclient<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version></dependency>提供了递归创建父节点递归删除节点的功能curatorCurator本身是Netflix公司开源的zookeeper客户端;curator提供了各种应用场景的实现封装curator-framework 提供了fluent风格api,curator-replice 提供了实现封装curator连接的重试策略ExponentialBackoffRetry() 衰减重试 RetryNTimes 指定最大重试次数RetryOneTime 仅重试一次RetryUnitilElapsed 一直重试知道规定的时间zookeeper的实际应用场景zookeeper能够实现哪些场景订阅发布watcher机制统一配置管理(disconf)分布式锁redis setnxzookeeper 节点特性数据库 负载均衡ID生成器分布式队列统一命名服务master选举分布式锁master选举