第一,zookeeper的引入,分布式架构存在的问题:使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了。
第二,开发中用到zookeeper的三个地方(kafka 注册中心 分布式锁) 1、Kafka:Kafka使用ZooKeeper进行分布式部署的broker(每一台kafka就是一个broker)的管理,Kafka使用ZooKeeper管理自己的元数据配置。 2、注册中心:和Eureka一样,可以作为注册中心、配置中心。 3、分布式锁:ZooKeeper可以作为分布式锁的一种实现。
第三,zookeeper结构:树型数据结构,ZNode四种类型节点 + 监听器 第四,zookeeper四个功能(两种结构实现):统一配置管理、统一命名服务、分布式锁、集群管理
ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识,结构图如下:
那ZooKeeper这颗"树"有什么特点呢?? ZooKeeper的节点我们称之为Znode,Znode分为两种类型: 短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除 持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除
ZooKeeper和Redis一样,也是C/S结构(分成客户端和服务端)
在上面我们已经简单知道了ZooKeeper的数据结构了,ZooKeeper的树形数据结构需要配合监听器才能完成四个功能。常见的监听场景有以下两项:
监听Znode节点的数据变化 监听子节点的增减变化
小结:通过监听+Znode节点(持久/短暂[临时]),ZooKeeper就可以完成四个功能。
问题:比如我们现在有三个系统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) 如图:
第一个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去访问。如图:
金手指:从锁到分布式锁 分布式锁文章链接 分布式锁和进程内的锁本质上是一样的。 1、要互斥,同一时刻只能被一台机器上的一个线程获得。 2、最好支持阻塞,然后唤醒,这样那些等待的线程不用循环重试。 3、最好可以重入(本文没有涉及,参见《编程世界的那把锁》) 4、获得锁和释放锁速度要快 5、对于分布式锁,需要找到一个集中的“地方”(数据库,Redis, Zookeeper等)来保存锁,这个地方最好是高可用的。 6、考虑到“不可靠的”分布式环境, 分布式锁需要设定过期时间 7、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,这样就可以实现动态选举的功能了。
第一,zookeeper的引入,分布式架构存在的问题:使用分布式系统就无法避免对节点管理的问题(需要实时感知节点的状态、对节点进行统一管理等等),由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了。
第二,开发中用到zookeeper的三个地方(kafka 注册中心 分布式锁) 1、Kafka:Kafka使用ZooKeeper进行分布式部署的broker(每一台kafka就是一个broker)的管理,Kafka使用ZooKeeper管理自己的元数据配置。 2、注册中心:和Eureka一样,可以作为注册中心、配置中心。 3、分布式锁: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去访问。
金手指:从锁到分布式锁 分布式锁文章链接 分布式锁和进程内的锁本质上是一样的。 1、要互斥,同一时刻只能被一台机器上的一个线程获得。 2、最好支持阻塞,然后唤醒,这样那些等待的线程不用循环重试。 3、最好可以重入(本文没有涉及,参见《编程世界的那把锁》) 4、获得锁和释放锁速度要快 5、对于分布式锁,需要找到一个集中的“地方”(数据库,Redis, Zookeeper等)来保存锁,这个地方最好是高可用的。 6、考虑到“不可靠的”分布式环境, 分布式锁需要设定过期时间 7、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管理。
第五,集群管理-集群选主(临时有序节点+监听器节点删除) (1)临时节点:为什么要创建临时节点,将一个系统抽象为一个zookeeper上的一个节点,当这个系统宕机或断开与zookeeper的连接,这个系统就没有意义了,所以这个节点应该删除,这是集群管理-监听集群状态的现实业务需求。为了满足“当这个系统宕机或断开与zookeeper的连接,这个系统就没有意义了,所以这个节点应该删除”,所以,zookeeper为这个系统的抽象新建的节点,就是临时节点。 (2)临时有序节点:选主需要,选主三比较:
1、比较 epoche纪元(zxid高32bit),如果其他节点的纪元比自己的大,选举 epoch大的节点(理由:epoch 表示年代,epoch越大表示数据越新)代码:(newEpoch > curEpoch); 2、比较 zxid, 如果纪元相同,就比较两个节点的zxid的大小,选举 zxid大的节点(理由:zxid 表示节点所提交事务最大的id,zxid越大代表该节点的数据越完整)代码:(newEpoch == curEpoch) && (newZxid > curZxid); 3、比较 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四个功能(统一配置管理 + 统一命名服务 + 分布式锁 + 集群管理),完成了。
天天打码,天天进步!!!