本文是读《Spring Boot2精髓-从构建小系统到架构分布式大系统》的读书笔记。
ZooKeeper 就是这样一款协调器。协调器本身也是分布式的,以保证协调器的高可用,所以也称为分布式协调器 。 分布式协调器是分布式系统和大数据系统必备的一个基础服务 。
16.1 ZooKeeper
ZooKeeper (下面简称 zk )有以下特点:
- 简单的 API 和数据结构完成协调服务, zk 提供了易于理解的数据结构来完成协调服务, 其 Java API 非常简单。 Curator进一步封装了这些 API ,直接提供了分布式协调服务而 不需要关心细节 。
- 分布式,不会出现单点故障, 一般来说,至少部署 3 台 zk 以避免单点故障 。 客户端(指 Spring Boot 应用)如果连接的 zk 窑机, 客户端将自动连接到另外一台。
- 保证操作的时序性, zk 对每次更新都有时间戳记录,从而保证操作的时序性,保证可 以完成更高层次的协调服务,如分布式锁 。
- 性能测试结果, zk 本身的性能非常好,既可以处理分布式系统的管理协调任务,如选 举领导 ,也能胜任高井发量的业务协调处理,如业务处理的分布式锁 。
ZooKeeper的数据结构
zk 提供的命名 空 间 ( name space )类似文件系统,每一个节点都是通过路径来表示的,不同的是,节点可以包含一定的数据 (2MB 字节),这些节点可以用来存放业务信息,如配置信息等 。
安装ZooKeeper
https://zookeeper.apache.org/
为了启动 ZooKeeper,进入 conf 目录,创建一个文件,命名为 zoo.cfg,内容如下:
tickTime=2000
dataDir= .. /data
clientPort=2181
然后进入 Bin 目录,运行 zkServer:
D: \apache\zookeeper- 3 . 4 . 8\ bin>zkServer
使用 Ctrl+C
停止 zkServer 。
ZooKeeper 的基本命令
服务器启动成功,我们可以运行 zkCli
来连接到 ZooKeeper 服务器上进行操作 。
ls , 查看目录 。
create ,创建节点 。
create -e,创建临时节点, 一旦用户会话结束,则节点自动删除 。
create -s,创建带有序列号的节点,带有-s 的 create 命令会自动为节点增加一个序列号
递增后缀,可以通过这个判断节点创建的先后顺序。
get ,获取节点数据 。
delete ,删 除节点 。
set path data , 设置节点数据 。
watch 操作
领导选取演示
分布式锁演示
实现分布式锁可以利用节点唯一性,比如创建一个/locks/xxx 的节点,这里的 xxx 是任意名字,例如对应到业务逻辑的合同号等。
服务注册演示
16.2 Spring Boot 集成 ZooKeeper
Curator 是 Apache 提供的一个访问 zk 的工具包,封装了这些低级别操作 , 同时也提供一些高级服务,比如分布式锁、领导选取等
集成 Curator
pom中添加依赖
<dependency>
<groupid>org.apache . curator</groupid>
<artifactid>curator-recipes</artifactid>
<version >2 .12 . 0</version>
</dependency>
@Configuration
public class ZookeeperConf {
@Value (”${ zk . url } ”)
private String zkUrl;
@Bean
public CuratorFramework getCuratorFramework() {
RetryPolicy retryPolicy =new ExponentialBackoffRetry(lOOO, 3);
CuratorFramework client= CuratorFrameworkFactory.newClient(zkUrl, retryPolicy) ;
client.start() ;
return client ;
}
}
Curator API
Curator API 是链式调用风格,遇到 forPath 接口就触发 ZooKeeper 调用,比如创建一个节点:
这样,当节点变化时,将通知 Curator,只需要添加一个 CuratorListener
即可。
Curator API 支持异步执行,通过在调用链式方法中加入backgroud()实现。异步的执行结果也将通过CuratorListener 通知,除了上面提到的CuratorEventType.WATCHED,还支持后台执行的CREATE 、DELETE 、EXISTS等操作。
16.3 实现分布式锁
Curator 提供了zk场景的绝大部分实现,使用Curator,就不必关心其内部算法,Curator 提供了 InterProcessMutex 来实现分布式锁,InterProcessMutex 用 acquire 方法获取锁,以及用 release释放锁,同其他锁一样, release 方法需要放在 finally 代码块中 ,确保锁能正确释放 。
package com.bee.sample.ch16.service.impl;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bee.sample.ch16.service.OrderService;
@Service
public class OrderServiceImpl implements OrderService {
Log log = LogFactory.getLog(OrderServiceImpl.class);
@Autowired
CuratorFramework zkClient ;
String lockPath = "/lock/order";
//处理某种订单类型
public void makeOrderType(String type) {
String path = lockPath+"/"+type;
log.info("try do job for "+type);
try{
InterProcessMutex lock = new InterProcessMutex(zkClient, path);
if ( lock.acquire(10, TimeUnit.HOURS) ){
try {
//模拟用时5秒
Thread.sleep(1000*5);
//即使获得分布式锁,在实际业务处理过程中,也应该检查数据是否已经被处理
log.info("do job "+type+" done");
}
finally{
lock.release();
}
}
}catch(Exception ex){
//zk异常
ex.printStackTrace();
}
}
}
16.4 服务注册
Curator 提供了 一个服务注册与发现的封装库,需要在 porn 中 添加以下依赖:
通过 Service Discovery 注册服务
获取服务
获取服务也是调用 ServiceDiscovery 类来实现的,通过调用 queryForInstances 可以获得当前所有可用服务。
领导选取
分布式应用中,有时候必须选出一个节点来分配任务给其他节点,或者负责协调其他节点,比如有多台机器协作处理一批任务,那这些任务应该由哪台机器来分配呢?必须选出 一个领导节点来负责。
使用 Curator,无须关心 zk 的领导节点的选取算法,通过 LeaderSelector
即可实现领导的选取