- 数据发布/订阅(配置中心)
- 配置存储
- 配置获取
- 配置变更
- 负载均衡
- 动态DNS服务
- 域名配置
- 域名解析
- 域名变更
- 命名服务
- 命名配置
- 命名变更
- 分布式协调/通知
- 分布式锁
- 排他锁
- Zookeeper实现排他锁
- 获取锁
- 释放锁
- 共享锁
- ZooKeeper实现共享锁
- 获取锁
- 判断读写顺序
- 释放锁
- 改进分布式共享锁实现
数据发布/订阅(配置中心)
数据发布/订阅(Publish/Subscribe)系统,即所谓的配置中心。发布者将数据发布到Zookeeper的一个节点或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布/订阅系统一般有两种设计模式,分别为推(Push)和拉(Pull)模式。
推模式:服务器主动将数据更新发送给所有订阅的客户端。
拉模式:客户端主动发起请求获取最新数据,通常客户端采用定时轮询的方法拉取。
Zookeeper采用是推拉结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,服务器会向相应的客户端发送Watcher时间通知,客户端收取到通知后,需要主动到服务器获取最新的数据。
配置存储首先我们需要将初始化配置存储到Zookeeper上,一般情况下,我们可以在Zookeeper上选取一个数据节点用于配置信息的存储。
#Zookeeper节点路径 /config/dev/ #子节点 /config/dev/customer #子节点数据 jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:127.0.0.1:3306/customer jdbc.username=test jdbc.password=123456 .... #子节点 /config/dev/shop #子节点数据 jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:127.0.0.1:3306/shop jdbc.username=test jdbc.password=123456配置获取
集群中每台集群在启动初始化阶段,首先会从上面Zookeeper配置节点上读取配置信息,同时,客户端还需要在改节点上注册一个数据变更的Watcher监听,一旦节点数据发生变更,那么所有订阅的客户端都能够获取到数据变更通知。
配置变更客户端收到Watcher的数据变更通知,会去拉取最想的配置信息数据。实现自动更新配置。
负载均衡负载均衡(Load Balance)是一种相当常见的计算机网络技术,用来对多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源进行分配负载,以达到优化资源使用、最大化吞吐率、最小化响应时间和避免过载的目的。
动态DNS服务DNS(Domain Name System,域名系统),DNS系统可以看做是一个超大规模的分布式映射表,用于将域名和IP地址进行一一映射。
域名配置首先我们需要将初始化域名列表存储到Zookeeper上,一般情况下,我们可以在Zookeeper上选取一个数据节点用于域名列表的存储。
#Zookeeper节点路径 /dns #Zookeeper子节点,这个为临时节点 /dns/customer.test.com #节点数据 ##单个IP地址 192.168.2.1:8080 ##多个IP地址 192.168.1.1:8080,192.168.1.2域名解析
域名解析的过程中都是每一个应用自己复制的,通常应用都会首先从域名节获取一份IP地址和端口的配置,进行自行解析。同时,每个应用会在域名节点上注册一个数据变更Watcher监听,以便及时收到域名变更的通知。
域名变更当碰到需要变更域名列表中的IP地址或是端口变更是,只需要对指定的域名节点进行更新操作,Zookeeper会向所有订阅的客户端发送变更通知,应用在收到Watcher通知,拉取域名并重新解析。
命名服务较为场景的就是一些分布式服务器框架(RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息等。
命名配置首先在Zookeeper节点上创建/name节点,之后的子节点为对应服务在启动的时候在/name节点上创建临时子节点,并获取/name节点下所有的信息,并注册Watcher事件。
#Zookeeper节点路径 /name #Zookeeper子节点,这个为临时节点,为Customer服务注册的节点 /name/customer #Customer服务列表 节点为临时节点,且为服务自己创建的节点 #节点数据,如:操作系统、服务器情况等。 /name/customer/192.168.1.1:8080 /name/customer/192.168.1.2:8080命名变更
当碰到对应服务不可以用,因为是临时节点就会自动删除自己创建的节点,Zookeeper会触发Watcher通知所有注册了的服务器,命名变换,拉取最新的数据。当有服务上线也是同样的操作。这样就可以做到自动更新服务器列表。
分布式协调/通知对于分布式应用通常需要一个协调者来控制整个系统运行流程,如:分布式事务。引入这样一个协调者,可以将分布式协调的职责从应用中分离处理,从而减少系统之间的耦合性,提供系统的扩展性。
Zookeeper通过Watcher注册与异步通知机制,能够很好地实现分布式环境下不同服务,甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。
分布式锁分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一系统的不同主机之间共享一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这样情况下,就需要使用分布式锁。
排他锁排他锁(Exclusive Locks,X锁),又称为独占锁或写锁。
如:事物T1对数据对象O1加上排他锁,那么在整个加锁期间,只允许事物T1对O1进行读取和更新操作,其他任何事物都不能在这个数据进行任何类型操作—直到T1释放了排他锁。
Zookeeper实现排他锁ZooKeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功。
获取锁在需要获取排他锁时,所有的客户端都会试图通过调用create()接口,创建/exclusive_lock节点下创建临时子节点/exclusive_lock/lock,创建成功的客户端就认为获得了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变换情况。
释放锁释放锁的情况:
- 当前获取锁的客户端机器发送宕机,那么ZooKeeper上的临时节点就会自动移除。
- 真诚执行完成业务逻辑后,客户端就会主动删除创建的临时节点。
在释放锁之后,ZooKeeper会通知所有在/exclusive_lock节点上注册了Watcher监听的客户端。这些客户端在收取到通知之后,再次发起分布式锁获取。
共享锁共享锁(Shared Locks, S锁或读锁),如果事物T1对数据对象O1加上共享锁,那么当前事物只能对O1进行读取操作,其他事物也只能对这个这个对象加共享锁----直到该数据对象上所有共享锁都被释放。
ZooKeeper实现共享锁通过在/shared_lock节点下,创建一系列临时顺序子节点作为共享锁。
获取锁在需要获取共享锁是,所有客户端都会到/shared_lock这个节点下面创建一个临时顺序节点,如果当前是读请求,那么就创建如:/shared_lock/192.168.1.1-R-00000001的节点;如果是写请求,那么就创建如:/shared_lock/192.168.1.2-W-00000001的节点。
判断读写顺序根据共享锁的定义,不同的事物都可以同时对同一数据对象进行读取操作,而更新操作必须在当前没有任何事物进行读写操作的情况下进行。
- 创建玩节点后,获取/shared_lock节点下所有子节点,并对该节点注册了节点变更的Watcher监听。
- 确定自己的节点序号在所有节点中的顺序。
- 对于读请求:
如果没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑。
如果比自己序号小的子节点中有写请求,那么就需要进入等待。
对于写请求
如果自己不是需要最小的子节点,那么就需要进入等待。否则执行写逻辑。
- 接受到Watcher通知后,重复步骤1。
释放锁的逻辑和排他锁一致。
改进分布式共享锁实现在/shared_lock注册Watcher通知,会导致每个客户端收到很多不是自己关心的通知。因为每个锁竞争者,只关注比自己序号小的那个节点。
每个锁竞争者,只需要关注比自己序号小的那个节点是否存在即可,
- 客户端调用create()接口创建类似与/shared_lock/192.168.1.1-R-00000001的节点。
- 客户端调用getChildren()接口来获取/shared_lock所有已创建的子节点列表,这里不需要注册Watcher事件。
- 如果无法获取共享锁,那么就调用exist()来对比自己小的那么节点注册Watcher,具体分为:读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。 写请求:向比自己序号小的最后一个节点注册Watcher监听。
- 等待Watcher通知,继续进入步骤2。



