zookeeper实现消息订阅

家住魔仙堡 提交于 2020-01-21 14:29:43

消息订阅应用非常广泛,像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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!