1、前言 2、分布式存在问题 + zookeeper三个功能 + zookeeper结构 + zookeeper四大功能
第一,zookeeper的引入,分布式架构存在的问题:使用分布式系统就没法避免对节点管理的问题(须要实时感知节点的状态、对节点进行统一管理等等),因为这些问题处理起来可能相对麻烦和提升了系统的复杂性,ZooKeeper做为一个可以通用解决这些问题的中间件就应运而生了。node
第二,开发中用到zookeeper的三个地方(kafka 注册中心 分布式锁)
一、Kafka:Kafka使用ZooKeeper进行分布式部署的broker(每一台kafka就是一个broker)的管理,Kafka使用ZooKeeper管理本身的元数据配置。
二、注册中心:和Eureka同样,能够做为注册中心、配置中心。
三、分布式锁:ZooKeeper能够做为分布式锁的一种实现。mysql
3、Zookeeper结构:ZNode + 监听器第三,zookeeper结构:树型数据结构,ZNode四种类型节点 + 监听器
第四,zookeeper四个功能(两种结构实现):统一配置管理、统一命名服务、分布式锁、集群管理面试
ZooKeeper的数据结构,跟Unix文件系统很是相似,能够看作是一颗树,每一个节点叫作ZNode。每个节点能够经过路径来标识,结构图以下:redis
那ZooKeeper这颗"树"有什么特色呢??
ZooKeeper的节点咱们称之为Znode,Znode分为两种类型:
短暂/临时(Ephemeral):当客户端和服务端断开链接后,所建立的Znode(节点)会自动删除
持久(Persistent):当客户端和服务端断开链接后,所建立的Znode(节点)不会删除sql
ZooKeeper和Redis同样,也是C/S结构(分红客户端和服务端)数据库
在上面咱们已经简单知道了ZooKeeper的数据结构了,ZooKeeper的树形数据结构须要配合监听器才能完成四个功能。常见的监听场景有如下两项:编程
监听Znode节点的数据变化
监听子节点的增减变化后端
3、ZooKeeper四个功能(统一配置管理、统一命名服务、分布式锁、集群管理)小结:经过监听+Znode节点(持久/短暂[临时]),ZooKeeper就能够完成四个功能。bash
问题:好比咱们如今有三个系统A、B、C,他们有三份配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,而后,这三份配置又很是相似,不少的配置项几乎都同样。
此时,若是咱们要改变其中一份配置项的信息,极可能其余两份都要改。而且,改变了配置项的信息极可能就要重启系统网络
理论指望:咱们但愿把ASystem.yml、BSystem.yml、CSystem.yml相同的配置项抽取出来成一份公用的配置common.yml,而且即使common.yml改了,也不须要系统A、B、C重启。
实际作法:咱们能够将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变动,若是变动了,及时响应。
对于上图的解释:
zookeeper中根节点为 “/” ,而后下面一个 “/configuration” 节点,我么讲application-common.properties,即配置文件的公共部分放在这个节点上,而后全部使用到这个application-common.properties做为公共配置的SOA服务,监听这个节点的数据变化就好,zk.subscribeDataChanges订阅, handleDataChange监听节点的数据变化,zookeeper节点中数据变化时响应;handleDataDeleted监听节点的删除,zookeeper节点被删除响应。
问题:统一配置存在的意义?为何要用统一配置?
回答:后端使用微服务开发,分布式部署,分布式部署不可能一个个修改配置文件application.properties/application.yaml。咱们作项目时用到的配置好比数据库配置等…咱们都是写死在项目里面,若是须要更改,那么也是的修改配置文件而后再投产上去,那么问题来了,若是作集群的呢,有100台机器,这时候作修改那就太不切实际了;那么就须要用到统一配置管理啦。
问题2:理论上,如何实现统一配置?
解决思路
1.把公共配置抽取出来
2.对公共配置进行维护
3.修改公共配置后应用不须要从新部署
问题3:实践上,统一配置的具体方案(采用zookeeper实现统一配置)?
回答 :
步骤1:公共配置抽取存放于zookeeper中并落地数据库( 下面代码:第一个main,前半部分 )
步骤2:对公共配置修改后发布到zookeeper中并落地数据库(下面代码:第一个main,后半部分)
步骤3:对应用开启配置实时监听,zookeeper配置文件一旦被修改,应用可实时监听到并获取(下面代码:第二个main)
如图:
public class Config implements Serializable{ // 实现了Serializable接口,就必定涉及网络传输和磁盘读写 仅仅一个bean 好懂 private static final long serialVersionUID = 1L; // 显式指定版本号和隐式默认版本号 private String userNm; private String userPw; public Config() { } public Config(String userNm, String userPw) { this.userNm = userNm; this.userPw = userPw; } public String getUserNm() { return userNm; } public void setUserNm(String userNm) { this.userNm = userNm; } public String getUserPw() { return userPw; } public void setUserPw(String userPw) { this.userPw = userPw; } @Override public String toString() { return "Config [userNm=" + userNm + ", userPw=" + userPw + "]"; } }
public class ZkConfigMag { // 提供三个工具方法,读库 写库 写zk private Config config; /** * 从数据库加载配置 */ public Config downLoadConfigFromDB(){ //getDB 这里省略了数据库操做 config = new Config("nm", "pw"); // 建立一个config对象 return config; } /** * 配置文件上传到数据库 这里省略了 good */ public void upLoadConfigToDB(String nm, String pw){ if(config==null)config = new Config(); config.setUserNm(nm); config.setUserPw(pw); //updateDB 配置文件上传到数据库,这里省略了数据库操做 } /** * 配置文件同步到zookeeper good */ public void syncConfigToZk(){ ZkClient zk = new ZkClient("localhost:2181"); // 链接zk if(!zk.exists("/zkConfig")){ zk.createPersistent("/zkConfig",true); // 不存在就建立一个持久化的节点 } zk.writeData("/zkConfig", config); // 写数据,将config对象写入到zk里面去,难怪Config类,要实现 可序列化 接口 zk.close(); } }
public class ZkConfigTest { // 第一,修改 public static void main(String[] args) { ZkConfigMag mag = new ZkConfigMag(); // 新建一个ZkConfigMag 对象 Config config = mag.downLoadConfigFromDB(); // 读库 设置局部变量config System.out.println("....加载数据库配置...."+config.toString()); //打印下从数据库中读出的config mag.syncConfigToZk(); // 将config写入到zk 读库会设置config System.out.println("....同步配置文件到zookeeper...."); // 成功写zk //歇会,这样看比较清晰 try { Thread.sleep( 10000); // 给看清楚,10s } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } mag.upLoadConfigToDB("cwhcc", "passwordcc"); // 写数据库 System.out.println("....修改配置文件...."+config.toString()); mag.syncConfigToZk(); // 写zk System.out.println("....同步配置文件到zookeeper...."); } }
public class ZkGetConfigClient { // 第二,监听zookeeper private Config config; public Config getConfig() { ZkClient zk = new ZkClient("localhost:2181"); // 链接zk config = (Config)zk.readData("/zkConfig"); // 读取zk,设置变量config System.out.println("加载到配置:"+config.toString()); // 打印下 //监听配置文件修改 zk.subscribeDataChanges("/zkConfig", new IZkDataListener(){ // 监听localhost:2181的zkConfig节点, 一旦监听到,调用方法 handleDataChange handleDataDeleted @Override public void handleDataChange(String arg0, Object arg1) throws Exception { config = (Config) arg1; System.out.println("监听到配置文件被修改:"+config.toString()); // config修改,监听打印 } @Override public void handleDataDeleted(String arg0) throws Exception { config = null; System.out.println("监听到配置文件被删除"); // config删除,监听打印 } }); return config; } public static void main(String[] args) { ZkGetConfigClient client = new ZkGetConfigClient(); // 建立一个当前类对象 client.getConfig(); // 当前类对象.getConfig() System.out.println(client.config.toString()); // 打印当前类对象的config for(int i = 0;i<10;i++){ // 遍历10次 System.out.println(client.config.toString()); // 打印当前类对象的config try { Thread.sleep(1000); // 每次休息1s } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
第一个main,修改zookeeper配置
第二个main,监听zookeeper配置的修改
统一命名服务定义:相似域名,就是咱们为zookeeper某一部分的资源给它取一个名字,别人经过这个名字就能够拿到对应的资源。
域名的使用:如今我有一个域名www.csdn.com,但我这个域名下有多台机器(一个局域网ip对应一个机器):
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
别人是经过www.csdn.com访问到个人机器,而不是经过IP去访问。
统一命名服务的使用:如今zookeeper集群中有一个命名服务 /myService,但这个命名服务下有多台机器(一个局域网ip对应一个机器):
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
别人访问 zookeeper节点/myService 便可访问到个人机器,而不是经过IP去访问。如图:
金手指:从锁到分布式锁
分布式锁文章连接
分布式锁和进程内的锁本质上是同样的。
一、要互斥,同一时刻只能被一台机器上的一个线程得到。
二、最好支持阻塞,而后唤醒,这样那些等待的线程不用循环重试。
三、最好能够重入(本文没有涉及,参见《编程世界的那把锁》)
四、得到锁和释放锁速度要快
五、对于分布式锁,须要找到一个集中的“地方”(数据库,Redis, Zookeeper等)来保存锁,这个地方最好是高可用的。
六、考虑到“不可靠的”分布式环境, 分布式锁须要设定过时时间
七、CAS的思想很重要。
问题:ZooKeeper如何实现分布式锁?
标准答案:使用指定节点名,zookeeper节点的惟一性来实现分布式锁的互斥。
下面来看看:系统A、B、C都去访问/locks节点
访问的时候会建立带顺序号的临时/短暂(EPHEMERAL_SEQUENTIAL)节点,好比,系统A建立了id_000000节点,系统B建立了id_000002节点,系统C建立了id_000001节点。
分布式锁理论规则:
拿到/locks节点下的全部子节点(id_000000,id_000001,id_000002),判断本身建立的是否是最小的那个节点
(1)若是是,则拿到锁执行。而后,执行完操做后,把建立的节点给删掉
(2)若是不是,则监听比本身要小1的节点变化
分布式锁实践流程:
(1)系统A拿到/locks节点下的全部子节点,通过比较,发现本身(id_000000),是全部子节点最小的,因此获得锁;
(2)系统B拿到/locks节点下的全部子节点,通过比较,发现本身(id_000002),不是全部子节点最小的。因此监听比本身小1的节点id_000001的状态;
(3)系统C拿到/locks节点下的全部子节点,通过比较,发现本身(id_000001),不是全部子节点最小的。因此监听比本身小1的节点id_000000的状态;
(4)等到系统A执行完操做之后,将本身建立的节点删除(id_000000)。经过监听,系统C发现id_000000节点已经删除了,发现本身已是最小的节点了,因而顺利拿到锁;
(5)系统C执行完以后,释放锁,系统B成为最小节点,加锁执行,执行完释放锁。
4、面试金手指集群状态1:使用zookeeper监听集群中其余节点状态(临时节点)
以三个系统A、B、C为例,在ZooKeeper中建立临时节点便可:
只要系统A挂了,那/groupMember/A这个节点就会删除,经过监听groupMember下的子节点,系统B和C就可以感知到系统A已经挂了。(新增也是同理)
集群状态2:动态选主(临时顺序节点)
除了可以感知节点的上下线变化,ZooKeeper还能够实现动态选举Master的功能。(若是集群是主从架构模式下)
可是注意,若是想要实现动态选举Master的功能,Znode节点的类型要求是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)。选主阶段,Zookeeper会每次选举最小编号的做为Master,若是Master挂了,天然对应的Znode节点就会删除。而后让新的最小编号做为Master,这样就能够实现动态选举的功能了。
第一,zookeeper的引入,分布式架构存在的问题:使用分布式系统就没法避免对节点管理的问题(须要实时感知节点的状态、对节点进行统一管理等等),因为这些问题处理起来可能相对麻烦和提升了系统的复杂性,ZooKeeper做为一个可以通用解决这些问题的中间件就应运而生了。
第二,开发中用到zookeeper的三个地方(kafka 注册中心 分布式锁)
一、Kafka:Kafka使用ZooKeeper进行分布式部署的broker(每一台kafka就是一个broker)的管理,Kafka使用ZooKeeper管理本身的元数据配置。
二、注册中心:和Eureka同样,能够做为注册中心、配置中心。
三、分布式锁:ZooKeeper能够做为分布式锁的一种实现。
第三,zookeeper结构:树型数据结构,ZNode四种类型节点 + 监听器
第四,zookeeper四个功能(两种结构实现):统一配置管理、统一命名服务、分布式锁、集群管理
ZNode:ZooKeeper这颗"树"有什么特色呢??
ZooKeeper的节点咱们称之为Znode,Znode分为两种类型:
短暂/临时(Ephemeral):当客户端和服务端断开链接后,所建立的Znode(节点)会自动删除
持久(Persistent):当客户端和服务端断开链接后,所建立的Znode(节点)不会删除
ZooKeeper和Redis同样,也是C/S结构(分红客户端和服务端)
监听器:ZooKeeper的树形数据结构须要配合监听器才能完成四个功能。常见的监听场景有如下两项:
(1)监听Znode节点的数据变化
(2)监听子节点的增减变化
问题:好比咱们如今有三个系统A、B、C,他们有三份配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,而后,这三份配置又很是相似,不少的配置项几乎都同样。
此时,若是咱们要改变其中一份配置项的信息,极可能其余两份都要改。而且,改变了配置项的信息极可能就要重启系统
理论指望:咱们但愿把ASystem.yml、BSystem.yml、CSystem.yml相同的配置项抽取出来成一份公用的配置common.yml,而且即使common.yml改了,也不须要系统A、B、C重启。
实际作法:咱们能够将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变动,若是变动了,及时响应。
zookeeper中根节点为 “/” ,而后下面一个 “/configuration” 节点,我么讲application-common.properties,即配置文件的公共部分放在这个节点上,而后全部使用到这个application-common.properties做为公共配置的SOA服务,监听这个节点的数据变化就好,zk.subscribeDataChanges订阅, handleDataChange监听节点的数据变化,zookeeper节点中数据变化时响应;handleDataDeleted监听节点的删除,zookeeper节点被删除响应。
问题:统一配置存在的意义?为何要用统一配置?
回答:后端使用微服务开发,分布式部署,分布式部署不可能一个个修改配置文件application.properties/application.yaml。咱们作项目时用到的配置好比数据库配置等…咱们都是写死在项目里面,若是须要更改,那么也是的修改配置文件而后再投产上去,那么问题来了,若是作集群的呢,有100台机器,这时候作修改那就太不切实际了;那么就须要用到统一配置管理啦。
问题2:理论上,如何实现统一配置?
解决思路
1.把公共配置抽取出来
2.对公共配置进行维护
3.修改公共配置后应用不须要从新部署
问题3:实践上,统一配置的具体方案(采用zookeeper实现统一配置)?
回答 :
步骤1:公共配置抽取存放于zookeeper中并落地数据库( 下面代码:第一个main,前半部分 )
步骤2:对公共配置修改后发布到zookeeper中并落地数据库(下面代码:第一个main,后半部分)
步骤3:对应用开启配置实时监听,zookeeper配置文件一旦被修改,应用可实时监听到并获取(下面代码:第二个main)
统一命名服务定义:相似域名,就是咱们为zookeeper某一部分的资源给它取一个名字,别人经过这个名字就能够拿到对应的资源。
域名的使用:如今我有一个域名www.csdn.com,但我这个域名下有多台机器(一个局域网ip对应一个机器):
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
别人访问www.csdn.com便可访问到个人机器,而不是经过IP去访问。
统一命名服务的使用:如今zookeeper集群中有一个命名服务 /myService,但这个命名服务下有多台机器(一个局域网ip对应一个机器):
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
别人访问 zookeeper节点/myService 便可访问到个人机器,而不是经过IP去访问。
金手指:从锁到分布式锁
分布式锁文章连接
分布式锁和进程内的锁本质上是同样的。
一、要互斥,同一时刻只能被一台机器上的一个线程得到。
二、最好支持阻塞,而后唤醒,这样那些等待的线程不用循环重试。
三、最好能够重入(本文没有涉及,参见《编程世界的那把锁》)
四、得到锁和释放锁速度要快
五、对于分布式锁,须要找到一个集中的“地方”(数据库,Redis, Zookeeper等)来保存锁,这个地方最好是高可用的。
六、考虑到“不可靠的”分布式环境, 分布式锁须要设定过时时间
七、CAS的思想很重要。
问题:ZooKeeper如何实现分布式锁?
标准答案:使用指定节点名,zookeeper节点的惟一性来实现分布式锁的互斥。
下面来看看:系统A、B、C都去访问/locks节点,访问的时候会建立带顺序号的临时/短暂(EPHEMERAL_SEQUENTIAL)节点,好比,系统A建立了id_000000节点,系统B建立了id_000002节点,系统C建立了id_000001节点。
分布式锁理论规则:
拿到/locks节点下的全部子节点(id_000000,id_000001,id_000002),判断本身建立的是否是最小的那个节点
(1)若是是,则拿到锁。而后,执行完操做后,把建立的节点给删掉
(2)若是不是,则监听比本身要小1的节点变化
分布式锁实践流程:
(1)系统A拿到/locks节点下的全部子节点,通过比较,发现本身(id_000000),是全部子节点最小的,因此获得锁;
(2)系统B拿到/locks节点下的全部子节点,通过比较,发现本身(id_000002),不是全部子节点最小的。因此监听比本身小1的节点id_000001的状态;
(3)系统C拿到/locks节点下的全部子节点,通过比较,发现本身(id_000001),不是全部子节点最小的。因此监听比本身小1的节点id_000000的状态;
(4)等到系统A执行完操做之后,将本身建立的节点删除(id_000000)。经过监听,系统C发现id_000000节点已经删除了,发现本身已是最小的节点了,因而顺利拿到锁;
(5)系统C执行完以后,释放锁,系统B成为最小节点,加锁执行,执行完释放锁。
集群状态1:使用zookeeper监听集群中其余节点状态(临时节点+监听器节点删除)
以三个系统A、B、C为例,在ZooKeeper中建立临时节点便可:只要系统A挂了,那/groupMember/A这个节点就会删除,经过监听groupMember下的子节点,系统B和C就可以感知到系统A已经挂了。(新增也是同理)
集群状态2:动态选主(临时顺序节点+监听器节点删除)
除了可以感知节点的上下线变化,ZooKeeper还能够实现动态选举Master的功能。(若是集群是主从架构模式下)
可是注意,若是想要实现动态选举Master的功能,Znode节点的类型要求是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)。选主阶段,Zookeeper会每次选举最小编号的做为Master,若是Master挂了,天然对应的Znode节点就会删除。而后让新的最小编号做为Master,这样就能够实现动态选举的功能了。
统一配置功能(永久节点+监听器节点数据修改):
(1)节点四种类型,要使用永久节点,不能一个客户端断开链接就删除节点;
(2)要使用监听器(两个功能中的数据修改功能),application-common.properties配置改变,客户端要能够监听到;
(3)实践:此功能eureka的注册中心。
统一命名服务(永久节点):
(1)使用统一命名 /myService,相似于域名,固然要使用永久节点。
第三,分布式锁(临时顺序节点+监听器节点删除)
(1)使用节点:指定节点名具备惟一性,建立节点就是获取锁,实现互斥,finally中删除节点就是释放锁,别的SOA客户端服务能够来建立这个节点名的节点了,再次开始竞争;
(2)使用临时节点:占用节点的客户端由于网络缘由异常宕机,断开链接,可是没有执行finally中释放锁,使用临时节点,断开链接就能够删除锁了。
(3)使用临时有序节点:避免全部的其余节点都监视一个节点,当这个节点释放的时候,形成羊群效应网络崩溃,使用临时有序节点,每个节点监视前一个节点就好,集群式监听变成链式监听;
(4)使用监听器(两个功能中的销毁功能):其余SOA客户端服务须要监听节点销毁。
(5)实践:此功能用来实现分布式锁(分布式锁三种:mysql、zookeeper、redis)。
第四,集群管理-监听集群状态(临时节点+监听器节点删除)
(1)临时节点:为何要建立临时节点,将一个系统抽象为一个zookeeper上的一个节点,当这个系统宕机或断开与zookeeper的链接,这个系统就没有意义了,因此这个节点应该删除,这是集群管理-监听集群状态的现实业务需求。为了知足“当这个系统宕机或断开与zookeeper的链接,这个系统就没有意义了,因此这个节点应该删除”,因此,zookeeper为这个系统的抽象新建的节点,就是临时节点。
(2)监听节点删除:当zookeeper要完成“集群管理-监听集群状态”的功能,zookeeper上的一个节点就是一个系统的抽象,由于是临时节点,当这个系统宕机或断开链接,节点被删除,其余节点(其余系统的抽象)固然要能感知到。
(3)实践:kafka使用zookeeper管理。
5、小结第五,集群管理-集群选主(临时有序节点+监听器节点删除)
(1)临时节点:为何要建立临时节点,将一个系统抽象为一个zookeeper上的一个节点,当这个系统宕机或断开与zookeeper的链接,这个系统就没有意义了,因此这个节点应该删除,这是集群管理-监听集群状态的现实业务需求。为了知足“当这个系统宕机或断开与zookeeper的链接,这个系统就没有意义了,因此这个节点应该删除”,因此,zookeeper为这个系统的抽象新建的节点,就是临时节点。
(2)临时有序节点:选主须要,选主三比较:一、比较 epoche纪元(zxid高32bit),若是其余节点的纪元比本身的大,选举 epoch大的节点(理由:epoch 表示年代,epoch越大表示数据越新)代码:(newEpoch > curEpoch);
二、比较 zxid, 若是纪元相同,就比较两个节点的zxid的大小,选举 zxid大的节点(理由:zxid 表示节点所提交事务最大的id,zxid越大表明该节点的数据越完整)代码:(newEpoch == curEpoch) && (newZxid > curZxid);
三、比较 serviceId,若是 epoch和zxid都相等,就比较服务的serverId,选举 serviceId大的节点(理由: serviceId 表示机器性能,他是在配置zookeeper集群时肯定的,因此咱们配置zookeeper集群的时候能够把服务性能更高的集群的serverId设置大些,让性能好的机器担任leader角色)代码 :(newEpoch == curEpoch) && ((newZxid == curZxid) && (newId > curId))。(2)监听节点删除:当zookeeper要完成“集群管理-监听集群状态”的功能,zookeeper上的一个节点就是一个系统的抽象,由于是临时节点,当这个系统宕机或断开链接,节点被删除,其余节点(其余系统的抽象)固然要能感知到。
(3)实践:kafka使用zookeeper管理。
zookeeper两个结构(ZNode + 监听器) + zookeeper四个功能(统一配置管理 + 统一命名服务 + 分布式锁 + 集群管理),完成了。
每天打码,每天进步!!!