基于zookeeper/curator的一个任务分片方案

為{幸葍}努か 提交于 2019-12-04 19:59:50

假如现在有一个表,表里记录的是一些延迟消息,也就是在未来某时刻需要发送出去的消息,表的记录数不定,如果很多,那么需要多台机器来执行,如果很少,那么一两台就够了,这时候需要一个领导选举/任务分片的工作,下面是一个方案,使用curator来实现。
上代码:

import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;

import java.util.List;
import java.util.UUID;

public class JobDistribute {
    private static final String ZK_ADDRESS = "127.0.0.1:2181";
    private static final String ZK_LEADER_PATH = "/task/exec/leader";
    private static final String ZK_TASK_SPLIT_PATH = "/task/split/worker";
    private static final String WORKERS_PARENT = "/task/split/";

    private static List<String> allJobs = Lists.newArrayList("a","b","c","d","e","f","g","h");

    public static void main(String[] args) throws Exception {
        CuratorFramework client = CuratorFrameworkFactory
                .newClient(ZK_ADDRESS,10000,5000,
                        new RetryNTimes(Integer.MAX_VALUE, 1000));
        client.start();
        String getJobPath = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(ZK_TASK_SPLIT_PATH);
        //监听任务分配节点的值变化,根据值执行任务
        final NodeCache nodeCache = new NodeCache(client, getJobPath, false);
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("nodeChanged:next time do those job ==============="+nodeCache.getCurrentData());
                //可以写入本地缓存,执行对应任务。。。。。
            }
        });
        nodeCache.start();
        System.out.println("getJobPath:"+getJobPath);
        final String id = UUID.randomUUID().toString();
        System.out.println("id:"+id);
        LeaderLatch leaderLatch = new LeaderLatch(client, ZK_LEADER_PATH, id);
        leaderLatch.addListener(new LeaderLatchListener() {
            @Override
            public void isLeader() {
                System.out.println("Currently run as leader");
                //成为leader之后要管理任务分片
                try{
                    splitJobs(client);
                } catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    PathChildrenCache cache = new PathChildrenCache(client,
                            WORKERS_PARENT.substring(0,WORKERS_PARENT.length()-1), true);
                    cache.start();
                    cache.getListenable().addListener(new PathChildrenCacheListener(){
                        @Override
                        public void childEvent(CuratorFramework curatorFramework,
                                               PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
                            System.err.println("Event TYpe :" + pathChildrenCacheEvent.getType());
                            if(pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED ||
                                    pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED){
                                //有新加入节点或者删除节点,重新分配任务
                                splitJobs(client);
                            }
                        }
                    });
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
            @Override
            public void notLeader() {
                System.out.println("Currently run as slave");
            }
        });
        leaderLatch.start();
        Thread.sleep(Integer.MAX_VALUE);
    }
    
    //这里写了个简单粗暴的方法,其实平分任务方法很多,可以双重循环,外层循环数量大的,内层循环数量小的,进行分配
    //如果希望一个任务尽量不被随意的转移,也可以有别的算法
    //这里是8份任务,最多四台机器同时执行。
    private static void splitJobs(CuratorFramework client) throws Exception {
        List<String> workers = client.getChildren().forPath(WORKERS_PARENT.substring(0,WORKERS_PARENT.length()-1));
        System.out.println("workers:"+workers);
        if(workers.size()==1){
            String path = WORKERS_PARENT+workers.get(0);
            client.setData().forPath(path, StringUtils.join(allJobs,",").getBytes());
        }else if(workers.size() ==2){
            String path0 = WORKERS_PARENT+workers.get(0);
            String path1 = WORKERS_PARENT+workers.get(1);
            client.setData().forPath(path0, StringUtils.join(allJobs.subList(0,4),",").getBytes());
            client.setData().forPath(path1, StringUtils.join(allJobs.subList(4,8),",").getBytes());
        }else if(workers.size() == 3){
            String path0 = WORKERS_PARENT+workers.get(0);
            String path1 = WORKERS_PARENT+workers.get(1);
            String path2 = WORKERS_PARENT+workers.get(2);
            client.setData().forPath(path0, StringUtils.join(allJobs.subList(0,3),",").getBytes());
            client.setData().forPath(path1, StringUtils.join(allJobs.subList(3,6),",").getBytes());
            client.setData().forPath(path2, StringUtils.join(allJobs.subList(6,8),",").getBytes());
        }else if(workers.size() == 4){
            String path0 = WORKERS_PARENT+workers.get(0);
            String path1 = WORKERS_PARENT+workers.get(1);
            String path2 = WORKERS_PARENT+workers.get(2);
            String path3 = WORKERS_PARENT+workers.get(3);
            client.setData().forPath(path0, StringUtils.join(allJobs.subList(0,2),",").getBytes());
            client.setData().forPath(path1, StringUtils.join(allJobs.subList(2,4),",").getBytes());
            client.setData().forPath(path2, StringUtils.join(allJobs.subList(4,6),",").getBytes());
            client.setData().forPath(path3, StringUtils.join(allJobs.subList(6,8),",").getBytes());
        }
        System.out.println("splitJobs ok");
    }
}

可以使用zkCli去查看对应节点的值,可以强制leader退出,过几秒,就会选举新的leader,然后分配任务。
这只是一个简陋的雏形,想要实现高可用,还需要做很多工作。这是最近在做延迟任务时想到的。如果加上一个定时任务轮询任务表,在加上jdk delayqueue,那么,就能实现一个延迟消息系统,或者延迟任务系统。OK。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!