基于zookeeper实现分布式的发布订阅

    client 注册自己的id与关注的任务标识到zookeeper。server读取client的注册信息到本地,发布任务时,根据任务标识选取对应的client,且在任务节点产生子节点,子节点的名称即为客户端id+任务标识,子节点的内容为任务参数。client监控任务节点,如果发现自己需要执行的节点,则获取对应的信息,且删除节点。

 Zkclient类


/**
 * @author wenjs
 */
public class ZKClient {
    /**
     * zookeeper地址
     */
    final String CONNECT_ADDR = "localhost:2181";
    /**
     * session超时时间
     */
    final int SESSION_OUTTIME = 10000;//ms

    /**
     * zk 客户端
     */
    final ZkClient zkClinet;

    /**
     * 客户端id
     */
    final String clientId;

    /**
     * 客户端注册路径
     */
    final String clientPath;

    /**
     * 服务端发布任务路径
     */
    final String taskPath;

    /**
     * 心跳频率
     */
    final int delay = 10;

    /**
     * 心跳
     */
    final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    /**
     * 接受的任务标识
     */
    String[] tasks;

    /**
     * 任务处理类
     */
    TaskHandler handler;

    /**
     *
     * @param clientId  客户端id,唯一
     * @param clientPath 客户端注册路径
     * @param taskPath 检测任务路径
     * @param handler 任务处理器
     */
    public ZKClient(String clientId, String clientPath, String taskPath, TaskHandler handler) {
        this.clientId = clientId;
        this.clientPath = clientPath;
        this.taskPath = taskPath;
        this.handler = handler;

        this.zkClinet = new ZkClient(CONNECT_ADDR, SESSION_OUTTIME);
        this.zkClinet.setZkSerializer(new MyZkSerializer());

        createPath(clientPath);
        createPath(taskPath);

        heart();
    }

    public void createPath(String path) {
        String paths[] = path.split("/");
        String currentPath = "";
        for (int i = 0; i < paths.length; i++) {
            if (!"".equals(paths[i])) {
                currentPath += "/" + paths[i];
                createPathPersisten(currentPath);
            }
        }
    }

    public void createPathPersisten(String path) {
        if (!zkClinet.exists(path)) {
            zkClinet.createPersistent(path);
        }
    }

    public void createTask(String... task) {
        tasks = task;
        doCreateTask();
    }

    public void doCreateTask() {
        List<String> taskIds = new ArrayList<>();

        String currentClinetPath = clientPath + "/" + clientId;
        if (!zkClinet.exists(currentClinetPath)) {
            zkClinet.createEphemeral(currentClinetPath);
        } else {
            String jsonStr = zkClinet.readData(currentClinetPath);
            taskIds.addAll(JSONObject.parseArray(jsonStr, String.class));
        }

        if (tasks != null) {
            taskIds.addAll(Arrays.asList(tasks));
            zkClinet.writeData(currentClinetPath, JSONObject.toJSONString(taskIds));
        }

    }

    public void work() {
        //对父节点添加监听子节点变化。
        zkClinet.subscribeChildChanges(taskPath, new IZkChildListener() {
            @Override
            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                currentChilds.forEach(p -> {
                    String[] split = p.split("::");
                    if (split[0].equals(clientId)) {
                        handler.process("clientId:" + clientId + ":执行任务:" + split[1] + ";任务参数:" + zkClinet.readData(taskPath + "/" + p)
                                + ";节点编号:" + split[2]);
                        zkClinet.delete(taskPath + "/" + p);
                    }
                });

            }
        });
        zkClinet.subscribeDataChanges(taskPath, new IZkDataListener() {
            @Override
            public void handleDataDeleted(String path) throws Exception {
                System.out.println("删除的节点为:" + path);
            }

            @Override
            public void handleDataChange(String path, Object data) throws Exception {
                System.out.println("变更的节点为:" + path + ",变更内容为:" + data);
            }
        });
    }

    public void close() {
        zkClinet.close();
    }

    public void heart() {
        scheduledExecutorService.schedule(new HeartThread(), delay, TimeUnit.SECONDS);
    }

    public static class MyZkSerializer implements ZkSerializer {
        /**
         * 序列化,将对象转化为字节数组
         */
        @Override
        public byte[] serialize(Object obj) throws ZkMarshallingError {
            return String.valueOf(obj).getBytes(Charsets.UTF_8);
        }

        /**
         * 反序列化,将字节数组转化为UTF_8字符串
         */
        @Override
        public Object deserialize(byte[] bytes) throws ZkMarshallingError {
            return new String(bytes, Charsets.UTF_8);
        }
    }

    public class HeartThread implements Runnable {

        @Override
        public void run() {
            System.out.println(ZKClient.this.clientId + "心跳++++++++++++++++++++++++++++++++++++++++++++++++++");
            ZKClient.this.doCreateTask();
            ZKClient.this.scheduledExecutorService.schedule(new HeartThread(), delay, TimeUnit.SECONDS);
        }
    }

    public static void main(String[] args) throws IOException {

        ZKClient clinet1 = new ZKClient("client1", WorkPath.clientPath, WorkPath.taskPath, new DefaultTaskHandler());
        clinet1.createTask("task1", "task2");
        clinet1.work();

        ZKClient clinet2 = new ZKClient("client2", WorkPath.clientPath, WorkPath.taskPath, new DefaultTaskHandler());
        clinet2.createTask("task2", "task3");
        clinet2.work();

        System.out.println("客户端启动成功");
        System.in.read();
        clinet1.close();
        clinet2.close();
    }
}
ZKService类

/**
 * zk的服务端,实现单点任务发布功能。
 * 1、检测  "/isky/client" 路径注册了多少个客户端(客户端id),
 * 2、定时在 "/isky/service/task" 路劲下生成任务节点
 *
 * @author wenjs
 */
public class ZKService {
    /**
     * zookeeper地址
     */
    final String CONNECT_ADDR = "localhost:2181";
    /**
     * session超时时间
     */
    final int SESSION_OUTTIME = 10000;//ms

    /**
     * zk 客户端
     */
    final ZkClient zkClinet;
    /**
     * 客户端id
     */
    final String serviceId;
    /**
     * 客户端信息路径
     */
    final String clientPath;
    /**
     * 任务发布路径
     */
    final String taskPath;

    /**
     * 任务生产者
     */
    ProductWorker productWorker;
    /**
     * 任务发布者
     */
    ConsumerWorker consumerWorker;

    /**
     * 任务队列
     */
    public LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();

    public ZKService(String serviceId, String clientPath, String taskPath) {
        this.serviceId = serviceId;
        this.clientPath = clientPath;
        this.taskPath = taskPath;

        this.zkClinet = new ZkClient(CONNECT_ADDR, SESSION_OUTTIME);
        this.zkClinet.setZkSerializer(new ZKClient.MyZkSerializer());

        productWorker = new ProductWorker(this);
        consumerWorker = new ConsumerWorker(this);

        createPath(clientPath);
        createPath(taskPath);
    }

    public void createPath(String path) {
        String paths[] = path.split("/");
        String currentPath = "";
        for (int i = 0; i < paths.length; i++) {
            if (! "".equals(paths[i]) ) {
                currentPath += "/" + paths[i];
                createPathPersisten(currentPath);
            }
        }
    }

    public void createPathPersisten(String path) {
        if (!zkClinet.exists(path)) {
            zkClinet.createPersistent(path);
        }
    }

    /**
     * 指定某个客户端生成任务节点,让它消费
     *
     * @param clientId
     * @param task
     */
    public void createTask(String clientId, String task) {
        String flag = clientId + "::" + task + "::";
        String param = "this is param";

        String currentClinetPath = taskPath + "/" + flag;
        zkClinet.createEphemeralSequential(currentClinetPath, param);
    }

    public void work() {
        //对父节点添加监听子节点变化。
        zkClinet.subscribeChildChanges(taskPath, new IZkChildListener() {
            @Override
            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                System.out.println("parentPath:" + parentPath);
                System.out.println("当前任务被领取:" + currentChilds);
            }
        });

        start();
    }

    public void start() {
        productWorker.start();
        consumerWorker.start();
    }

    public void close() {
        zkClinet.close();
    }

    /**
     * 根据指定的任务,获取所有关注此任务的客户端
     * @param task
     * @return
     */
    public List<String> getClinet(String task) {
        List<String> candidateClients = new ArrayList<>();
        List<String> clients = zkClinet.getChildren(clientPath);
        if (!CollectionUtils.isEmpty(clients)) {
            clients.forEach(client -> {
                String data = zkClinet.readData(clientPath + "/" + client);

                if (!StringUtils.isEmpty(data) && data.contains(task)) {
                    candidateClients.add(client);
                }
            });
        }

        return candidateClients;
    }

    public static class ProductWorker extends Thread {
        final ZKService service;

        public ProductWorker(ZKService service) {
            this.service = service;
        }

        @Override
        public void run() {
            try {
                for (; ; ) {
                    List<String> tasks = new ArrayList<>();
                    tasks.add("task1");
                    tasks.add("task2");
                    tasks.add("task3");
                    service.queue.add(tasks.get((new Random()).nextInt(tasks.size())));
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class ConsumerWorker extends Thread {
        final ZKService service;

        public ConsumerWorker(ZKService service) {
            this.service = service;
        }

        @Override
        public void run() {
            try {
                for (; ; ) {
                    String task = service.queue.take();

                    System.out.println("产生任务:" + task);
                    List<String> clients = service.getClinet(task);
                    if (!CollectionUtils.isEmpty(clients)) {
                        // 在所有关注此任务的客户端中随机选取一个执行此任务
                        service.createTask(clients.get((new Random()).nextInt(clients.size())), task);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class MyZkSerializer implements ZkSerializer {
        /**
         * 序列化,将对象转化为字节数组
         */
        @Override
        public byte[] serialize(Object obj) throws ZkMarshallingError {
            return String.valueOf(obj).getBytes(Charsets.UTF_8);
        }

        /**
         * 反序列化,将字节数组转化为UTF_8字符串
         */
        @Override
        public Object deserialize(byte[] bytes) throws ZkMarshallingError {
            return new String(bytes, Charsets.UTF_8);
        }
    }

    public static void main(String[] args) throws IOException {
        ZKService server = new ZKService(UUID.randomUUID().toString(), WorkPath.clientPath, WorkPath.taskPath);
        server.work();

        System.out.println("服务端启动成功");
        System.in.read();
        server.close();
        System.out.println("服务端关闭");
    }
}

WorkPath类
/**
 * @author wenjs
 */
public interface WorkPath {

    String bathPath = "/isky";
    String clientPath = bathPath +"/client";
    String serverPath = bathPath +"/service";
    String taskPath = serverPath +"/task"; 
}

TaskHandler类
/**
 * @author wenjs
 */
public interface TaskHandler {

    /**
     * 任务处理
     * @param param
     */
    void process(String param);
}

DefaultTaskHandler类

/**
 * @author wenjs
 */
public class DefaultTaskHandler  implements TaskHandler{
    @Override
    public void process(String param) {

        System.out.println(param);
    }
}

 依赖引用

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.38</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.13</version>
</dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>25.1-jre</version>
</dependency>
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值