消息订阅应用非常广泛,像spring config中,当配置发生改变时其他需要第一时间发现并且更新自己的配置信息;其实像之前说到的master选举也是一样,在我看来也是消息订阅的一种特例,当主节点宕机时,其他节点需要立即感应,并且同时立马进行主节点竞选
其实这一篇与master竞选原理一致,都是监听一个节点的状态,master节点选举主要监听的是主节点的移除事件,而消息订阅需要更具不同的场景进行不同的处理,就像配置文件一样,主要监听节点更新事件,下边看实现:
1.定义配置数据类,但是在这个例子中并没怎么用到,这里只是做一个封装,真是创建中肯定是需要的
public class ConfigData {
private String cid;
private String nodeName;
public ConfigData() {
}
public ConfigData(String cid, String nodeName) {
this.cid = cid;
this.nodeName = nodeName;
}
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
}
2.定义客户端工具类,用于处理一些公用事件,如父节点创建,打开关闭客户端等
/**
* @ClassName ClientUtil
* @Author mjlft
* @Date 2020/1/21 10:20
* @Version 1.0
* @Description TODO
*/
public class ClientUtil {
/**
* 判断父节点是否存在,如果不存在则创建
* @param path
* @throws Exception
*/
public static void createParentPath(String path, CuratorFramework client) throws Exception {
if(path.length() <= 1){
return;
}
Stat stat = client.checkExists().forPath(path);
if(stat == null){
String parentPath = getparrentpath(path);
if(parentPath.equals("/")){
return;
}
createParentPath(parentPath, client);
client.create().forPath(path);
}
}
public static String getparrentpath(String path){
int lastIndex = path.lastIndexOf("/");
String parentPath = path.substring(0, lastIndex);
return parentPath;
}
//开启客户端
public static void start(CuratorFramework client) {
if (client != null) {
client.start();
}
}
//关闭客户端
public static void stop(CuratorFramework client) {
if (client != null) {
client.close();
}
}
}
3.抽象一个服务类,我们把每个节点对应一个服务
public class Service {
public final static String CONFIG_PATH = "/config";
public final static String WORK_PATH = "/services";
protected CuratorFramework client;
protected ConfigData configData;
//开启客户端
public void start() {
ClientUtil.start(this.client);
}
//关闭客户端
public void stop() {
System.out.println(this.configData.getNodeName() + "节点宕机");
ClientUtil.stop(this.client);
}
}
4.定义一个配置服务类,对应配置节点,也是我们需要订阅的节点,当该节点发生变化时,其他订阅节点可以感知到并且做出对应的响应
public class ConfigService extends Service {
public ConfigService() {
}
public ConfigService(ConfigData configData, CuratorFramework client) {
this.configData = configData;
this.client = client;
}
//节点初始化
public void init() throws Exception {
String path = Service.CONFIG_PATH;
//首先判断当前节点是否已经存在
Stat stat = client.checkExists().forPath(path);
if (stat == null) {
String parentpath = ClientUtil.getparrentpath(path);
ClientUtil.createParentPath(parentpath, client);
//如果不存在就创建一个新的节点
client.create().withMode(CreateMode.EPHEMERAL).forPath(path, configData.getNodeName().getBytes());
} else {
//如果已经存在,则更新数据
client.setData().forPath(path, configData.getNodeName().getBytes());
}
System.out.println("配置服务上线");
}
public void changeCommand(String command) throws Exception {
client.setData().forPath(Service.CONFIG_PATH, command.getBytes());
}
}
5.定义一个工作服务器类,也就是订阅者,他们来订阅我们的配置节点,当配置节点发生变化时,他们能感知到,我们需要在这些节点上注册监听事件
/**
* @ClassName WorkService
* @Author mjlft
* @Date 2020/1/20 21:58
* @Version 1.0
* @Description TODO
*/
public class WorkService extends Service {
public WorkService() {
}
public WorkService(ConfigData nodeData, CuratorFramework client) {
this.configData = nodeData;
this.client = client;
}
//节点初始化
public void init() throws Exception {
String path = Service.WORK_PATH + "/" + configData.getNodeName();
//首先判断当前节点是否已经存在
Stat stat = client.checkExists().forPath(path);
if (stat == null) {
String parentpath = ClientUtil.getparrentpath(path);
ClientUtil.createParentPath(parentpath, client);
//如果不存在就创建一个新的节点
client.create().withMode(CreateMode.EPHEMERAL).forPath(path, configData.getNodeName().getBytes());
} else {
//如果已经存在,则更新数据
client.setData().forPath(path, configData.getNodeName().getBytes());
}
System.out.println(configData.getNodeName() + ": 上线");
}
/**
* 注册选举事件
*
* @throws Exception
*/
public void register() throws Exception {
final ConfigData node = this.configData;
//注册监听事件
final TreeCache treeCache = TreeCache.newBuilder(this.client, CONFIG_PATH).setCacheData(true).setMaxDepth(2).build();
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
//如果节点被删除
if (treeCacheEvent.getType().equals(TreeCacheEvent.Type.NODE_REMOVED)) {
System.out.println(Thread.currentThread().getName() + " : 配置节点故障");
}
if(treeCacheEvent.getType().equals(TreeCacheEvent.Type.NODE_ADDED)){
System.out.println(Thread.currentThread().getName() + " : 配置节点恢复,可以同步数据");
}
if(treeCacheEvent.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)){
String command = new String(curatorFramework.getData().forPath(Service.CONFIG_PATH));
System.out.println(Thread.currentThread().getName() + " : 服务端更改命令为: " + command);
}
}
});
treeCache.start();
}
}
7.最后时测试类,通过控制台输入更改配置节点数据,每次更改所有来订阅的节点都能感知到
public static void main(String[] args) throws Exception {
RetryPolicy retryPolicy = new RetryNTimes(3, 100);
List<WorkService> workServices = new ArrayList<>(16);
CuratorFramework config = CuratorFrameworkFactory.newClient("192.168.1.107:2181, 192.168.1.107:2182",
30*60*1000, 5*1000, retryPolicy);
ConfigData configNodeData = new ConfigData("config_", "config");
ConfigService commandService = new ConfigService(configNodeData, config);
commandService.start();
commandService.init();
for(int i = 0; i < 10; i ++){
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.107:2181, 192.168.1.107:2182",
30*60*1000, 5*1000, retryPolicy);
ConfigData nodeData = new ConfigData("cid_"+i, "nodeName_" + i);
WorkService workService = new WorkService(nodeData, client);
workServices.add(workService);
workService.start();
workService.init();
workService.register();
}
while (true){
Scanner sc = new Scanner( System.in );
String nextCommand = sc.nextLine();
commandService.changeCommand(nextCommand);
}
}
8.测试结果:
配置服务上线
nodeName_0: 上线
Curator-TreeCache-0 : 配置节点恢复,可以同步数据
nodeName_1: 上线
Curator-TreeCache-1 : 配置节点恢复,可以同步数据
nodeName_2: 上线
Curator-TreeCache-2 : 配置节点恢复,可以同步数据
nodeName_3: 上线
Curator-TreeCache-3 : 配置节点恢复,可以同步数据
nodeName_4: 上线
Curator-TreeCache-4 : 配置节点恢复,可以同步数据
nodeName_5: 上线
Curator-TreeCache-5 : 配置节点恢复,可以同步数据
nodeName_6: 上线
Curator-TreeCache-6 : 配置节点恢复,可以同步数据
nodeName_7: 上线
Curator-TreeCache-7 : 配置节点恢复,可以同步数据
nodeName_8: 上线
Curator-TreeCache-8 : 配置节点恢复,可以同步数据
nodeName_9: 上线
Curator-TreeCache-9 : 配置节点恢复,可以同步数据
create
Curator-TreeCache-8 : 服务端更改命令为: create
Curator-TreeCache-3 : 服务端更改命令为: create
Curator-TreeCache-1 : 服务端更改命令为: create
Curator-TreeCache-2 : 服务端更改命令为: create
Curator-TreeCache-6 : 服务端更改命令为: create
Curator-TreeCache-0 : 服务端更改命令为: create
Curator-TreeCache-4 : 服务端更改命令为: create
Curator-TreeCache-7 : 服务端更改命令为: create
Curator-TreeCache-9 : 服务端更改命令为: create
Curator-TreeCache-5 : 服务端更改命令为: create
update
Curator-TreeCache-2 : 服务端更改命令为: update
Curator-TreeCache-8 : 服务端更改命令为: update
Curator-TreeCache-3 : 服务端更改命令为: update
Curator-TreeCache-1 : 服务端更改命令为: update
Curator-TreeCache-9 : 服务端更改命令为: update
Curator-TreeCache-7 : 服务端更改命令为: update
Curator-TreeCache-4 : 服务端更改命令为: update
Curator-TreeCache-5 : 服务端更改命令为: update
Curator-TreeCache-0 : 服务端更改命令为: update
Curator-TreeCache-6 : 服务端更改命令为: update
delete
Curator-TreeCache-2 : 服务端更改命令为: delete
Curator-TreeCache-8 : 服务端更改命令为: delete
Curator-TreeCache-3 : 服务端更改命令为: delete
Curator-TreeCache-1 : 服务端更改命令为: delete
Curator-TreeCache-5 : 服务端更改命令为: delete
Curator-TreeCache-9 : 服务端更改命令为: delete
Curator-TreeCache-4 : 服务端更改命令为: delete
Curator-TreeCache-7 : 服务端更改命令为: delete
Curator-TreeCache-6 : 服务端更改命令为: delete
Curator-TreeCache-0 : 服务端更改命令为: delete
来源:CSDN
作者:mjlfto
链接:https://blog.csdn.net/mjlfto/article/details/104060398