• [Done]SnowFlake 分布式环境下基于ZK构WorkId


    Twitter 的 Snowflake  大家应该都熟悉的,先上个图:

    时间戳 序列号一般不会去改造,主要是工作机器id,大家会进行相关改造,我厂对工作机器进行了如下改造(估计大家都差不多吧,囧~~~):

    机房号,不同的机房搞个初始化配置即可(当然若机房数量多也可考虑分布式动态获取),

    主要是机器编号,在如何动态获取,以下workId获取方式供参考:

    public class WorkIdBuilder {
    
        private final static Logger logger            = LoggerFactory.getLogger(WorkIdBuilder.class);
    
        // 省略字段和get set 方法public void close() {
            if (null != client && null == client.getState()) {
                client.close();
            }
            client = null;
        }
    
        public void init() {
            if (StringUtils.isBlank(this.appName)) {
                logger.error("应用名称不能为空!");
                throw new RuntimeException("应用名称不能为空!");
            }
            if (client != null) {
                close();
            }
            client = CuratorFrameworkFactory.builder()
                    .connectString(address)
                    .connectionTimeoutMs(connectionTimeout)
                    .sessionTimeoutMs(sessionTimeout)
                    .canBeReadOnly(false)
                    .retryPolicy(new ExponentialBackoffRetry(baseSleepTimeOut, Integer.MAX_VALUE))
                    .build();
    
            client.start();
            buildWorkId(this.appName);
        }
    
        // 序号集,当前最大支持 256 个节点,每个节点去占用编号,通过InterProcessLock来控制分布式环境下的获取
        private static Set<Integer> OrderIdSet ;
        static {
            OrderIdSet = new HashSet<>();
            for(int i = 0; i < MAX_ORDER; i++){
                OrderIdSet.add(i);
            }
        }
    
        /***
         * 获取workId
         * @param appPath 应用名称
         */
        private void buildWorkId(final String appPath){
            // 检测client是否已经连接上
            if (null == client) {
                throw new RuntimeException("本节点注册到ZK异常。");
            }
    
            // lockPath,用于加锁,注意要与nodePath区分开
            final String lockPath = this.ROOT_NAME +"/" + this.appName ;
            // nodePath 用于存放集群各节点初始路径
            final String nodePath = this.ROOT_NAME +"/" + this.appName + this.NODE_NAME;
    
            // InterProcessMutex 分布式锁(加锁过程中lockPath会自动创建)
            InterProcessLock interProcessLock = new InterProcessMutex(client, lockPath);
            try {
                // 加锁 此处逻辑非常重要
                if (!interProcessLock.acquire(lockTimeOut, TimeUnit.MILLISECONDS)) {
                    throw new TimeoutException("ZK分布式锁 加锁超时,超时时间: " + lockTimeOut);
                }
    
                // nodePath 第一次需初始化,永久保存, 或者节点路径为临时节点,则设置为永久节点
                if (null == client.checkExists().forPath(nodePath)) {
                    client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(nodePath);
                }
    
                // 获取nodePath下已经创建的子节点
                List<String> childPath = client.getChildren().forPath(nodePath);
                Set<Integer> nodeIdSet = new LinkedHashSet<>();
                if(CollectionUtils.isNotEmpty(childPath)){
                    for(String path : childPath){
                        try {
                            nodeIdSet.add(Integer.valueOf(path));
                        }
                        catch (Exception e){
                            logger.warn("路径由不合法操作创建,注意["+nodePath+"]仅用于构建workId");
                            // ignore
                        }
                    }
                }
                // 遍历所有id,构建workId,主要是判断可用id是否已经被集群中其他节点占用
                for (Integer order : OrderIdSet) {
                    if (!nodeIdSet.contains(order)) {
                        final String currentNodePath = nodePath + "/" + order;
                        String nodeDate = String.format("[ip:%s,hostname:%s,pid:%s]",
                                                        InetAddress.getLocalHost().getHostAddress(),
                                                        InetAddress.getLocalHost().getHostName(),
                                                        ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
                        // 事务提交, 应用断开zk连接时候,删除该节点数据,此处CreateMode = EPHEMERAL  (非常重要)
                        // 当本节点zk断开时,其他client.getChildren().forPath(nodePath)进行操作时,子节点逻辑已释放,orderId可复用
                        client.inTransaction()
                                .create().withMode(CreateMode.EPHEMERAL).forPath(currentNodePath)
                                .and().setData().forPath(currentNodePath,nodeDate.getBytes("UTF-8"))
                                .and().commit();
                        long pathCreateTime = client.checkExists().forPath(currentNodePath).getCtime();
    
                        // 以下逻辑主要用于检测断开重连情况
                        TreeCache treeCache = new TreeCache(client, currentNodePath);
                        // 添加监听器
                        treeCache.getListenable().addListener(new TreeCacheListener() {
    
                            public void childEvent(CuratorFramework curatorFramework,
                                                   TreeCacheEvent treeCacheEvent) throws Exception {
                                long pathTime;
                                try {
                                    pathTime = curatorFramework.checkExists().forPath(currentNodePath).getCtime();
                                } catch (Exception e) {
                                    pathTime = 0;
                                }
    
                                // 如果pathTime != pathCreateTime, 那么只能一种情况:
                                // 当前应用与zk失去联系,且/nodePath/{currentNodePath}不存在或者被其它应用占据了(表象为pathCreateTime变化)
                                // 无论哪种情况,当前应用都要重新注册节点
                                if (pathCreateTime != pathTime) {
                                    logger.info("从ZK断开,再次注册...") ;
                                    // 关闭之前旧的treeCache
                                    try{
                                        treeCache.close();
                                    }
                                    catch (Exception e){
                                        logger.warn("treeCache关闭失败");
                                    }
                                    // 再次注册
                                    finally {
                                        buildWorkId(appPath);
                                    }
                                }
                            }
                        });
                        treeCache.start();
                        this.workerId = order;
                        logger.info("基于ZK成功构建 workId:{}",this.workerId);
                        return;
                    }
                }
                throw new RuntimeException("获取WorkId失败,共["+this.MAX_ORDER+"]个可用WorkId, 已全部用完。 ");
            } catch (Exception e) {
                logger.error("获取分布式WorkId异常",e);
            } finally {
                // 构建成功后释放锁
                if(interProcessLock != null) {
                    try {
                        interProcessLock.release();
                    } catch (Exception e) {
                        logger.warn("释放锁失败");
                    }
                }
            }
        }
    
    }

    供参考。

    以上。

  • 相关阅读:
    CentOS 7.0关闭默认防火墙启用iptables防火墙
    Linux下安装jdk1.8
    Linux下的tar压缩解压缩命令详解
    centos7上安装redis
    通过克隆虚拟机来创建多个虚拟机
    深度学习的网络资料
    在ubuntu中添加widows启动项的简单方法
    循环神经网络RNN的基本介绍
    统计学习——随机过程
    spark机制理解(一)
  • 原文地址:https://www.cnblogs.com/do-your-best/p/9801518.html
Copyright © 2020-2023  润新知