实战:10 分钟掌握分布式 ID 之雪花算法
JAVA葵花宝典
共 396字,需浏览 1分钟
·
2020-08-22 22:48
实战:10 分钟掌握分布式 ID 之雪花算法
一个在生产每天经过1亿+数据量验证的id生成器
背景
1.为什么要使用雪花算法生成 ID
-- 保证 id 全局唯一
-- 保证 id 自增长
-- uuid 无序且过长
雪花算法 ID 组成
1: 1位标识部分:
--- 在 java 中由于 long 的最高位是符号位,正数是 0,负数是 1,一般生成的 ID 为正数,所以为 0;
2: 41 位时间戳部分:
--- 这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的 ID 从更小值开始;41 位的时间戳可以使用 69 年,(1L<< 41) / (1000L _ 60 _ 60 _ 24 _ 365) = 69 年;
3: 10 位workid:
Twitter 实现中使用前 5 位作为数据中心标识,后 5 位作为机器标识,可以部署 1024 个节点。我这里的实现根据服务名生产的,意思就是说每个服务只要不超过 1024 个节点就不会有问题,实际生产中我也没有见过某个服务有 1024 个节点的。
4: 12 位序列号部分:
--- 支持同一毫秒内同一个节点可以生成 4096 个 ID。意思就是说某个服务 1ms 能生成 4096 个 id,如果你单表的 TPS 超过 4096\*60s,那可能就会出问题了,实际生产这么大的 TPS 我是没有见过的。
Zookeeper生成workid
雪花算法生成ID网络上方法很多,很多重复的东西我就不赘述了,这里简明扼要的说一下ZK生成workid。
这是生成的ID的截图。父节点是workid,只要有引用id生成器jar的服务都会在workid下面生成一个文件夹,以服务名命名,有多少个节点该节点下就会有多个文件,该节点下的id一定是不会重复的。下面我贴一下java版生成workid的核心代码
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(5000, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("ZK分布式锁 加锁超时,超时时间: " + 5000);
}
// nodePath 第一次需初始化,永久保存, 或者节点路径为临时节点,则设置为永久节点
if (null == client.checkExists().forPath(nodePath)) {
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(nodePath);
}
// 获取nodePath下已经创建的子节点
List
childPath = client.getChildren().forPath(nodePath); Set
nodeIdSet = new LinkedHashSet<>(); if (!CollectionUtils.isEmpty(childPath)) {
for (String path : childPath) {
try {
nodeIdSet.add(Integer.valueOf(path));
} catch (Exception e) {
log.warn("路径由不合法操作创建,注意[" + nodePath + "]仅用于构建workId");
}
}
}
// 遍历所有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]);
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(currentNodePath, nodeDate.getBytes("UTF-8"));
} catch (Exception e) {
log.debug("节点[{}]无法创建,可能是已存在", currentNodePath);
continue;
}
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) {
log.info("从ZK断开,再次注册...");
// 关闭之前旧的treeCache
try {
treeCache.close();
} catch (Exception e) {
log.warn("treeCache关闭失败");
}
// 再次注册
finally {
buildWorkId(appPath);
}
}
}
});
treeCache.start();
workerId = order;
log.info("基于ZK成功构建 workId:{}", workerId);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
log.error("获取分布式WorkId异常", e);
} finally {
// 构建成功后释放锁
if (interProcessLock != null) {
try {
interProcessLock.release();
} catch (Exception e) {
log.warn("释放锁失败");
}
}
}
}
核心代码我已经贴出来了,对雪花算法有一定了解的同学,使用的时候在需要id生成器的地方引用就好了,还有部分非核心代码,这个ID生成器已经在生产验证了每天1亿+的数据量,如果你真的需要可以联系我,把代码都给你。zookeeper其实是一个很实用的工具,还有分布式锁的实现及应用,下篇文章会给大家带来用zk生成分布式锁。
END
喜欢请扫码关注
评论