实战:10 分钟掌握分布式 ID 之雪花算法

JAVA葵花宝典

共 396字,需浏览 1分钟

 · 2020-08-22

实战: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

喜欢请扫码关注

- END -点击原文获取电商实战落地项目视频资料



浏览 63
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报