Apache ZooKeeper是Apache软件基金会的一个软件项目,他为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。ZooKeeper曾经是Hadoop的一个子项目,但如今是一个独立的顶级项目。java
ZooKeeper的架构经过冗余服务实现高可用性。所以,若是第一次无应答,客户端就能够询问另外一台ZooKeeper主机。ZooKeeper节点将它们的数据存储于一个分层的命名空间,很是相似于一个文件系统或一个前缀树结构。客户端能够在节点读写,从而以这种方式拥有一个共享的配置服务。node
使用ZooKeeper的公司包括Rackspace、雅虎和eBay,以及相似于像Solr这样的开源企业级搜索系统。算法
文件系统:zookeeper维护一个相似文件系统的数据结构,每一个子目录项如 NameService 都被称做为 znode,和文件系统同样,自由增长及删除,惟一不一样其可存储数据。Znode分为四种类型apache
Zookeeper角色分为三类,领导者:负责进行投票的发起和决议,更新系统状态。跟随者:Follower用于接收客户请求并向客户端返回结果,在选中过程当中参与投票。观察者:Observer能够接收客户端链接,将写请求转发给leader节点。但不参加投票过程,只同步leader状态。Observer目的在于扩展系统,提升读取速度。服务器
当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式须要从新选举出一个新的leader,让全部的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。先介绍basic paxos流程:网络
选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
选举线程首先向全部Server发起一次询问(包括本身);
选举线程收到回复后,验证是不是本身发起的询问(验证zxid是否一致),而后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
收到全部Server回复之后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
线程将当前zxid最大的Server设置为当前Server要推荐的Leader,若是此时获胜的Server得到n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置本身的状态,不然,继续这个过程,直到leader被选举出来。
经过流程分析咱们能够得出:要使Leader得到多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1.数据结构
每一个Server启动后都会重复以上流程。在恢复模式下,若是是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并按期进行快照,方便在恢复时进行状态恢复。架构
fast paxos流程是在选举过程当中,某Server首先向全部Server提议本身要成为leader,当其它Server收到提议之后,解决epoch和zxid的冲突,并接受对方的提议,而后向对方发送接受提议完成的消息,重复这个流程,最后必定能选举出Leader。eclipse
wget http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz tar zxvf zookeeper-3.4.8.tar.gz -C /usr/local/ cd $ZOOKEEPER_HOME cp conf/zoo_sample.cfg conf/zoo.cfg # 集群须要在zoo.cfg配置 server.1=192.168.1.148:2888:3888 server.2=192.168.1.149:2888:3888 server.3=192.168.1.150:2888:3888 # 在zookeeper的临时目录建立myid mkdir -p /tmp/zookeeper # 分别在不一样节点建立myid文件里面的数字对应节点的编号好比server.1就对应1,server.2就对应2 echo 1 > /tmp/zookeeper/myid # 最后分别启动集群上的节点 $ZOOKEEPER_HOME/bin/zkServer.sh start # 查看zookeeper的状态 $ZOOKEEPER_HOME/bin/zkServer.sh status # 中止zookeeper服务 $ZOOKEEPER_HOME/bin/zkServer.sh stop
启动zookeeper服务后到bin目录启动zookeeper的客户端$ZOOKEEPER_HOME/bin/zkCli.sh分布式
# 输入help [zk: localhost:2181(CONNECTED) 0] help ZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port
建立节点
[zk: localhost:2181(CONNECTED) 14] create /test test-data Created /test
查看节点
[zk: localhost:2181(CONNECTED) 1] ls / [abc, zookeeper, eclipse]
获取节点
[zk: localhost:2181(CONNECTED) 10] get /test test-update cZxid = 0x38 ctime = Sat Dec 22 10:11:46 CST 2018 mZxid = 0x39 mtime = Sat Dec 22 10:12:05 CST 2018 pZxid = 0x38 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 11 numChildren = 0
修改节点
[zk: localhost:2181(CONNECTED) 11] set /test test-update cZxid = 0x38 ctime = Sat Dec 22 10:11:46 CST 2018 mZxid = 0x3a mtime = Sat Dec 22 10:15:04 CST 2018 pZxid = 0x38 cversion = 0 dataVersion = 2 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 11 numChildren = 0
删除节点
[zk: localhost:2181(CONNECTED) 13] delete /test
/** * @author leone * @since 2018-06-16 **/ public class ZkClient { private final static Logger logger = LoggerFactory.getLogger(ZkClient.class); private final static String ZK_URL = "xxx.xxx.xxx.xxx:2181"; private final static int TIME_OUT = 5000; private static ZooKeeper zkClient = null; @Before public void init() throws Exception { zkClient = new ZooKeeper(ZK_URL, TIME_OUT, (WatchedEvent event) -> { // 收到事件通知后的回调函数(应该是咱们本身的事件处理逻辑) logger.info(event.getType() + "---" + event.getPath()); try { zkClient.getChildren("/", true); } catch (Exception e) { e.printStackTrace(); } }); } /** * 设置值 * * @throws Exception */ @Test public void testSetData() throws Exception { zkClient.setData("/eclipse", "world".getBytes(), -1); byte[] data = zkClient.getData("/eclipse", false, null); System.out.println(new String(data)); } /** * 建立节点 * * @throws Exception */ @Test public void testCreate() throws Exception { // 参数1:要建立的节点的路径 参数2:节点数据 参数3:节点的权限 参数4:节点的类型 zkClient.create("/eclipse/aaa", "aaaData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } /** * 测试某节点是否存在 * * @throws Exception */ @Test public void testExists() throws Exception { Stat stat = zkClient.exists("/eclipse", false); System.out.println(stat == null ? "not exist" : "exist"); } /** * 获取子节点 * * @throws Exception */ @Test public void testGetChild() throws Exception { List<String> children = zkClient.getChildren("/", true); for (String child : children) { System.out.println(child); } } /** * 删除节点 * * @throws Exception */ @Test public void testDelete() throws Exception { // 参数2:指定要删除的版本,-1表示删除全部版本 zkClient.delete("/abc", -1); } /** * 获取节点的数据 * * @throws Exception */ @Test public void testGetDate() throws Exception { byte[] data = zkClient.getData("/eclipse", false, null); System.out.println(new String(data)); } }