分布式 ID 生成系统 Leaf 的设计思路,源码解读

发表于 2年以前  | 总阅读数:583 次

小伙伴们好呀, 今天来分享下最近研究的分布式 ID 生成系统 —— Leaf ,一起来思考下这个分布式ID的设计吧

什么是分布式ID?

ID 最大的特点是 唯一

而分布式 ID,就是指分布式系统下的 ID,它是 全局唯一 的。

为啥需要分布式ID呢?

这就和 唯一 息息相关了。

比如我们用 MySQL 存储数据,一开始数据量不大,但是业务经过一段时间的发展,单表数据每日剧增,最终突破 1000w,2000w …… 系统开始变慢了,此时我们已经尝试了 优化索引读写分离升级硬件升级网络 等操作,但是 单表瓶颈 还是来了,我们只能去 分库分表 了。

而问题也随着而来了,分库分表后,如果还用 数据库自增ID 的方式的话,那么在用户表中,就会出现 两个不同的用户有相同的ID 的情况,这个是不能接受的。

分布式ID全局唯一 的特点,正是我们所需要的。

分布式ID的生成方式

  • UUID
  • 数据库自增ID (MySQL,Redis)
  • 雪花算法

基本就上面几种了,UUID 的最大缺点就是太长,36个字符长度,而且无序,不适合。

而其他两种的缺点还有办法补救,可能这也是 Leaf 提供这两种生成 ID 方式的原因。

项目简介

Leaf ,分布式 ID 生成系统,有两种生成 ID 的方式:

  1. 号段模式
  2. Snowflake模式

号段模式

数据库自增ID 的基础上进行优化

  1. 增加一个 segement ,减少访问数据库的次数。
  2. 双 Buffer 优化,提前缓存下一个 Segement,降低网络请求的耗时(降低系统的TP999指标)

来自美团技术团队

biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度

没优化前,每次都从 db 获取,现在获取的频率和 step 字段相关。

双 Buffer 优化思路

号段模式源码解读

SegmentService 构造方法

作用

  1. 配置 dataSource
  2. 设置 MyBatis
  3. 实例化 SegmentIDGenImpl
  4. 执行 init 方法

这段代码我也忘了 哈哈,已经多久没直接用 mybatis 了,还是重新去官网翻看的。

mybatis 官网例子

实例化 SegmentIDGenImpl 时,其中有两个变量要留意下

  1. SEGMENT_DURATION,智能调节 step 的关键
  2. cache ,其中 SegmentBuffer 是双 Buffer 的关键设计。

这里先不展开,看看 init 方法先。

SegmentIDGenImpl init 方法

作用

  1. 执行 updateCacheFromDb 方法
  2. 开后台线程,每分钟执行一次 updateCacheFromDb() 方法

显然,核心在 updateCacheFromDb

updateCacheFromDb 方法

这里就直接看源码和我加的注释

private void updateCacheFromDb() {
        logger.info("update cache from db");
        StopWatch sw = new Slf4JStopWatch();
        try {
            // 执行 SELECT biz_tag FROM leaf_alloc 语句,获取所有的 业务字段。
            List<String> dbTags = dao.getAllTags();
            if (dbTags == null || dbTags.isEmpty()) {
                return;
            }
            // 缓存中的 biz_tag
            List<String> cacheTags = new ArrayList<String>(cache.keySet());
            // 要插入的 db 中的 biz_tag
            Set<String> insertTagsSet = new HashSet<>(dbTags);
            // 要移除的缓存中的 biz_tag 
            Set<String> removeTagsSet = new HashSet<>(cacheTags);

            // 缓存中有的话,不用再插入,从 insertTagsSet 中移除
            for (int i = 0; i < cacheTags.size(); i++) {
                String tmp = cacheTags.get(i);
                if (insertTagsSet.contains(tmp)) {
                    insertTagsSet.remove(tmp);
                }
            }

            // 为新增的 biz_tag 创建缓存 SegmentBuffer
            for (String tag : insertTagsSet) {
                SegmentBuffer buffer = new SegmentBuffer();
                buffer.setKey(tag);
                Segment segment = buffer.getCurrent();
                segment.setValue(new AtomicLong(0));
                segment.setMax(0);
                segment.setStep(0);
                cache.put(tag, buffer);
                logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer);
            }


            // db中存在的,从要移除的 removeTagsSet 移除。
            for (int i = 0; i < dbTags.size(); i++) {
                String tmp = dbTags.get(i);
                if (removeTagsSet.contains(tmp)) {
                    removeTagsSet.remove(tmp);
                }
            }

            // 从 cache 中移除不存在的 bit_tag。
            for (String tag : removeTagsSet) {
                cache.remove(tag);
                logger.info("Remove tag {} from IdCache", tag);
            }
        } catch (Exception e) {
            logger.warn("update cache from db exception", e);
        } finally {
            sw.stop("updateCacheFromDb");
        }
    }

执行完后,会出现这样的 log

Add tag leaf-segment-test from db to IdCache, SegmentBuffer SegmentBuffer{key='leaf-segment-test', segments=[Segment(value:0,max:0,step:0), Segment(value:0,max:0,step:0)], currentPos=0, nextReady=false, initOk=false, threadRunning=false, step=0, minStep=0, updateTimestamp=0}

最后 init 方法结束后,会将 initOk 设置为 true


项目启动完毕后,我们就可以调用这个 API 了。

如图,访问 LeafController 中的 Segment API,可以获取到一个 id。

SegmentIDGenImpl get 方法

可以看到,init 不成功会报错。

以及会直接从 cache 中查找这个 key(biz_tag) , 没有的话会报错。

拿到这个 SegmentBuffer 时,还得看看它 init 了 没有,没有的话用双检查锁的方式去更新

先来看下一眼 SegmentBuffer 的结构

SegmentBuffer 类

⭐updateSegmentFromDb 方法

这里就是更新缓存的方法了,主要是更新 Segment 的 value , max,step 字段。

可以看到有三个 if 分支,下面展开说

分支一:初始化

第一次,buffer 还没 init,如上图,执行完后会更新 SegmentBuffer 的 step 和 minStep 字段。

分支二:第二次更新

这里主要是更新这个 updateTimestamp ,它的作用看分支三

分支三:剩下的更新

这里就比较有意思了,就是说如果这个号段在 15分钟 内用完了,那么它会扩大这个 step (不超过 10w),创建一个更大的 MaxId ,降低访问 DB 的频率。

那么,到这里,我们完成了 updateSegmentFromDb 方法,更新了 Segment 的 value , max,step 字段。

但是,我们不是每次 get 都走上面的流程,它还得走这个缓存方法

⭐getIdFromSegmentBuffer 方法

显然,这是另一个重点。

如图,在死循环中,先获取读锁,拿到当前的号段 Segment,进行判断

  • 使用超过 10% 就开新线程去更新下一个号段
  • 没超过则将 value (AtomicLong 类型)+1 ,小于 maxId 则直接返回。

这里要重点留意 读写锁的使用 ,比如 开新线程时,使用了这个 写锁 ,里面的 nextReady 等变量使用了 volatile 修饰

这里的核心就是切换 Segment。

至此,号段模式结束。

优缺点

信息安全如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。—— 《Leaf——美团点评分布式ID生成系统》

美团可以看到,这个号段模式的最大弊端就是 信息不安全,所以在使用时得三思,能不能用到这些业务中去。


Snowflake模式

雪花算法,核心就是将 64bit 分段,用来表示时间,机器,序列号等。

41-bit的时间可以表示(1L<<41)/(1000L360024*365)=69年的时间,10-bit机器可以分别表示1024台机器。

12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为 2^12 * 1000 = 409.6w/s

这里使用 Zookeeper 持久顺序节点的特性自动对 snowflake 节点配置 wokerID,不用手动配置。

时钟回拨问题

img

Snowflake模式源码解读

这部分源码就不一一展开了,直接展示核心代码

SnowflakeZookeeperHolder init 方法

这里要注意调整这个 connectionTimeoutMs 和 sessionTimeoutMs ,不然两种模式都启动的话,这个 zk 的 session 可能会超时,造成启动失败。

图中流程

  1. 看看 zk 节点存不存在,不存在就创建
  2. 同时将 worker id 保存到本地。
  3. 创建定时任务,更新 znode。

znode

worker Id

定时任务

SnowflakeIDGenImpl get 方法

这里直接看代码和注释了

@Override
    public synchronized Result get(String key) {
        long timestamp = timeGen();
        //  发生了回拨,此刻时间小于上次发号时间
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    //时间偏差大小小于5ms,则等待两倍时间
                    wait(offset << 1);
                    timestamp = timeGen();
                    //还是小于,抛异常并上报
                    if (timestamp < lastTimestamp) {
                        return new Result(-1, Status.EXCEPTION);
                    }
                } catch (InterruptedException e) {
                    LOGGER.error("wait interrupted");
                    return new Result(-2, Status.EXCEPTION);
                }
            } else {
                return new Result(-3, Status.EXCEPTION);
            }
        }
        if (lastTimestamp == timestamp) {
            // sequenceMask = ~(-1L << 12 ) = 4095 二进制即 12 个1
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                //seq 为0的时候表示是下一毫秒时间开始对seq做随机
                sequence = RANDOM.nextInt(100);
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //如果是新的ms开始
            sequence = RANDOM.nextInt(100);
        }
        lastTimestamp = timestamp;
        // timestampLeftShift = 22, workerIdShift = 12 
        long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
        return new Result(id, Status.SUCCESS);
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }

API 效果

生成 ID

反解 ID

至此,这个 Snowflake 模式也了解完毕了。

总结

看完上面两种模式,我觉得两种模式都有它适用的场景,号段模式更适合对内使用(比如 用户ID),而如果你这个 ID 会被用户看到,暴露出去有其他风险(比如爬虫恶意爬取等),那就得多斟酌了,。而订单号 就更适合用 snowflake 模式。

分布式ID 的特点

  1. 全局唯一
  2. 趋势递增(有序一直很重要,粗略有序还是严格有序就看情况了)
  3. 可反解(可选)
  4. 信息安全(可选)

参考资料

  • Github 地址:https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md
  • Leaf——美团点评分布式ID生成系统:https://tech.meituan.com/2017/04/21/mt-leaf.html
  • 分布式id生成方案总结:https://www.cnblogs.com/javaguide/p/11824105.html

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/BvLW3LTrTfW4-s3zPPRi6A

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237239次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8075次阅读
 目录