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("释放锁失败"); } } } } }
供参考。
以上。