环境
- zookeeper
- docker
- springcloud
安装
#搜索镜像
docker search zookeeper
#拉取镜像
docker pull zookeeper
#运行镜像
docker run -itd -p 2181:2181 2e2
常用实践
配置yml
server:
port: 8602
spring:
application:
name: client2-service
cloud:
nacos:
#192.168.82.83:8848
server-addr: 127.0.0.1:8848
zookeeper:
connect-string: 127.0.0.1:2181
session-timeout: 2000
基础应用
引入依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.2</version>
</dependency>
ZookeeperConfig
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.CountDownLatch;
/**
* @author shock wave 2
* @version 1.0
* @description: TODO
* @date 2021/8/10 16:34
*/
@Configuration
@Slf4j
public class ZookeeperConfig {
@Value("${spring.cloud.zookeeper.connect-string}")
private String connectString;
@Value("${spring.cloud.zookeeper.session-timeout}")
private int sessionTimeout;
public String getConnectString() {
return connectString;
}
public void setConnectString(String connectString) {
this.connectString = connectString;
}
public int getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
@Bean(name = "zkClient")
public ZooKeeper zkClient() {
ZooKeeper zooKeeper = null;
try {
final CountDownLatch countDownLatch = new CountDownLatch(1);
zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 如果收到了服务端的响应事件,说明连接成功
if (Event.KeeperState.SyncConnected == event.getState()) {
countDownLatch.countDown();
}
}
});
countDownLatch.await();
log.info(" 初始化ZooKeeper连接状态: {}",zooKeeper.getState());
} catch (Exception e) {
log.error(" 初始化Zookeeper连接状态异常: {}",e.getMessage());
}
return zooKeeper;
}
}
ZoopeekerController
package org.example.controller;
import org.apache.commons.lang.NullArgumentException;
import org.example.config.CustomWatcher;
import org.example.entity.ZoopeekerNode;
import org.example.utils.Result;
import org.example.utils.ZkUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author shock wave 2
* @version 1.0
* @description: TODO
* @date 2021/8/10 16:33
*/
@RestController
@RequestMapping("zoopeeker")
public class ZoopeekerController {
@Autowired
private ZkUtil zkUtil;
@PostMapping("createNode")
public void createNode(@RequestBody ZoopeekerNode zoopeekerNode) {
switch (zoopeekerNode.getType()){
case "per":
zkUtil.createPerNode(zoopeekerNode.getPath(),zoopeekerNode.getData());
break;
case "tmp":
zkUtil.createTmpNode(zoopeekerNode.getPath(),zoopeekerNode.getData());
break;
case "custom":
zkUtil.createNode(zoopeekerNode.getPath(),zoopeekerNode.getData(),zoopeekerNode.getAcl(),zoopeekerNode.getCreateMode());
break;
default:
throw new NullArgumentException("缺少主要参数");
}
}
@GetMapping("getNode")
public Result getNode(@RequestParam String path) {
CustomWatcher customWatcher = new CustomWatcher();
return Result.success(zkUtil.getData(path,customWatcher));
}
@PostMapping("updateNode")
public Result updateNode(@RequestBody ZoopeekerNode zoopeekerNode) {
return Result.success(zkUtil.updateNode(zoopeekerNode.getPath(),zoopeekerNode.getData()));
}
}
ZkUtil
package org.example.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author shock wave 2
* @version 1.0
* @description: TODO
* @date 2021/8/10 17:18
*/
@Slf4j
@Component
public class ZkUtil {
@Autowired
private org.apache.zookeeper.ZooKeeper zkClient;
/**
* 创建持久化节点
* -- 客户端断开连接后,节点数据持久化在磁盘上,不会被删除。
*
* @param path 路径
* @param data 数据
*/
public boolean createPerNode(String path, String data) {
try {
// 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
zkClient.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
return true;
} catch (Exception e) {
log.error("创建持久化节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
return false;
}
}
/**
* 创建临时节点
* -- 客户端断开连接后,节点将被删除。
*
* @param path 路径
* @param data 数据
*/
public boolean createTmpNode(String path, String data) {
try {
// 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
zkClient.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
return true;
} catch (Exception e) {
log.error("创建临时节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
return false;
}
}
/**
* 创建自定义节点
*
* @param path 路径
* @param data 数据
* @param acl 节点权限
* @param createMode 节点类型
*/
public boolean createNode(String path, String data, List<ACL> acl, CreateMode createMode) {
try {
// 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
zkClient.create(path, data.getBytes(), acl, createMode);
return true;
} catch (Exception e) {
log.error("创建节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
return false;
}
}
/**
* 修改节点
*
* @param path 路径
* @param data 数据
*/
public boolean updateNode(String path, String data) {
try {
// zk的数据版本是从0开始计数的。如果客户端传入的是-1,则表示zk服务器需要基于最新的数据进行更新。如果对zk的数据节点的更新操作没有原子性要求则可以使用-1.
// version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
zkClient.setData(path, data.getBytes(), -1);
return true;
} catch (Exception e) {
log.error("修改节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
return false;
}
}
/**
* 删除节点
*
* @param path 路径
*/
public boolean deleteNode(String path) {
try {
// version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
zkClient.delete(path, -1);
return true;
} catch (Exception e) {
log.error("删除节点异常,路径: {}, 异常: {}", path, e);
return false;
}
}
/**
* 判断指定节点是否存在
*
* @param path 路径
* @param needWatch 指定是否复用zookeeper中默认的Watcher
* @return 结果
*/
public Stat exists(String path, boolean needWatch) {
try {
return zkClient.exists(path, needWatch);
} catch (Exception e) {
log.error("判断指定节点是否存在异常,路径: {}, 异常: {}", path, e);
return null;
}
}
/**
* 检测结点是否存在 并设置监听事件
* 三种监听类型: 创建,删除,更新
*
* @param path 路径
* @param watcher 传入指定的监听类
*/
public Stat exists(String path, Watcher watcher) {
try {
return zkClient.exists(path, watcher);
} catch (Exception e) {
log.error("判断指定节点是否存在异常,路径: {}, 异常: {}", path, e);
return null;
}
}
/**
* 获取当前节点的子节点(不包含孙子节点)
*
* @param path 父节点path
*/
public List<String> getChildren(String path) throws KeeperException, InterruptedException {
List<String> list = zkClient.getChildren(path, false);
return list;
}
/**
* 获取指定节点的值
*
* @param path 路径
*/
public String getData(String path, Watcher watcher) {
try {
Stat stat = new Stat();
byte[] bytes = zkClient.getData(path, watcher, stat);
return new String(bytes);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
curator分布式锁
依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
CuratorFrameworkConfig
package org.example.config;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.api.CuratorEventType;
import org.apache.curator.framework.api.CuratorListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.WatchedEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author shock wave 2
* @version 1.0
* @description: TODO
* @date 2021/8/12 17:32
*/
@Configuration
public class CuratorFrameworkConfig {
@Value("${spring.cloud.zookeeper.connect-string}")
private String connectString;
@Bean
public CuratorFramework curatorFramework(){
// ExponentialBackoffRetry是种重连策略,每次重连的间隔会越来越长,1000毫秒是初始化的间隔时间,3代表尝试重连次数。
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
// 创建client
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(connectString, retry);
// 添加watched 监听器
curatorFramework.getCuratorListenable().addListener(new MyCuratorListener(){
});
curatorFramework.start();
return curatorFramework;
}
public class MyCuratorListener implements CuratorListener {
@Override
public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception {
CuratorEventType type = event.getType();
if(type == CuratorEventType.WATCHED){
WatchedEvent watchedEvent = event.getWatchedEvent();
String path = watchedEvent.getPath();
System.out.println(watchedEvent.getType()+" -- "+ path);
// 重新设置改节点监听
if(null != path){
client.checkExists().watched().forPath(path);
}
}
}
}
}
api
@GetMapping("curatorTest")
public Result curatorTest() {
//大概原理是,使用zookeeper生成有序临时结点,每次判断是否是序号最小的结点,是的话获取锁
//不是的话,需要获取当前序号的上一个结点
String lockPath = "/lock/test/";
String lockName = lockPath + UUID.randomUUID().toString();
//TODO 获取分布式锁
InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex (curatorFramework, lockName);
try{
//获取锁资源
boolean flag = lock.acquire(10, TimeUnit.HOURS);
if(flag){
log.info("线程:{},获取到了锁",Thread.currentThread().getName());
//TODO 获得锁之后可以进行相应的处理 睡一会
Thread.sleep(500);
log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
}
}catch (Exception e){
log.info("错误信息:{}",e.getMessage());
}finally {
try {
lock.release();
log.info("=========lockName:{}==============={}释放了锁",lockName,Thread.currentThread().getName());
} catch (Exception e) {
log.info("错误信息:{}",e.getMessage());
}
}
return Result.success();
}
zookeeper作为注册中心
- zookeeper可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(IP+端口)去访问具体的服务提供者。
- zookeeper提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除。
- 服务消费者会去监听相应路径(/HelloWorldService/1.0.0),一旦路径上的数据有任务变化(增加或减少),zookeeper都会通知服务消费方、服务提供者地址列表已经发生改变,从而进行更新。
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
启动类
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author shock wave 2
* @version 1.0
* @description: TODO
* @date 2021/8/9 17:14
*/
@SpringBootApplication
//会自动生成相应的结点
@EnableDiscoveryClient
public class Client2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(Client2ServerApplication.class, args);
}
}