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

实战: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<String> childPath = client.getChildren().forPath(nodePath);
            Set<Integer> 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 - 点击原文获取电商实战落地项目视频资料

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值