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("释放锁失败");
                }
            }
        }
    }

}

 

供参考。

以上。

 

posted on 2018-10-17 00:09  大辉_FFf  阅读(1119)  评论(0编辑  收藏  举报