写点什么

不能错过的分布式 ID 生成器(Leaf-),积累总结

用户头像
极客good
关注
发布于: 刚刚
  • max_id:当前业务号段的最大值,用于计算下一个号段

  • step:步长,也就是每次获取 ID 的数量

  • description:对于业务的描述,没啥好说的


将 Leaf 项目下载到本地:https://github.com/Meituan-Dianping/Leaf


修改一下项目中的leaf.properties文件,添加数据库配置


leaf.name=com.sankuai.leaf.opensource.testleaf.segment.enable=trueleaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/xin-master?useUnicode=tru


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


e&characterEncoding=utf8leaf.jdbc.username=junkangleaf.jdbc.password=junkangleaf.snowflake.enable=false


注意leaf.snowflake.enableleaf.segment.enable 是无法同时开启的,否则项目将无法启动。


配置相当的简单,直接启动LeafServerApplication后就 OK 了,接下来测试一下,leaf是基于Http请求的发号服务, LeafController 中只有两个方法,一个号段接口,一个 snowflake 接口,key就是数据库中预先插入的业务biz_tag


@RestControllerpublic?class?LeafController?{private?Logger?logger?=?LoggerFactory.getLogger(LeafController.class);


@Autowiredprivate?SegmentService?segmentService;@Autowiredprivate?SnowflakeService?snowflakeService;


/***?号段模式*?@param?key*?@return*/@RequestMapping(value?=?"/api/segment/get/{key}")public?String?getSegmentId(@PathVariable("key")?String?key)?{return?get(key,?segmentService.getId(key));}


/***?雪花算法模式*?@param?key*?@return*/@RequestMapping(value?=?"/api/snowflake/get/{key}")public?String?getSnowflakeId(@PathVariable("key")?String?key)?{return?get(key,?snowflakeService.getId(key));}


private?String?get(@PathVariable("key")?String?key,?Result?id)?{Result?result;if?(key?==?null?||?key.isEmpty())?{throw?new?NoKeyException();}result?=?id;if?(result.getStatus().equals(Status.EXCEPTION))?{throw?new?LeafServerException(result.toString());}return?String.valueOf(result.getId());}}


访问:http://127.0.0.1:8080/api/segment/get/leaf-segment-test,结果正常返回,感觉没毛病,但当查了一下数据库表中数据时发现了一个问题。




通常在用号段模式的时候,取号段的时机是在前一个号段消耗完的时候进行的,可刚刚才取了一个 ID,数据库中却已经更新了max_id,也就是说leaf已经多获取了一个号段,这是什么鬼操作?



Leaf为啥要这么设计呢?


Leaf 希望能在 DB 中取号段的过程中做到无阻塞!


当号段耗尽时再去 DB 中取下一个号段,如果此时网络发生抖动,或者 DB 发生慢查询,业务系统拿不到号段,就会导致整个系统的响应时间变慢,对流量巨大的业务,这是不可容忍的。


所以Leaf在当前号段消费到某个点时,就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做很大程度上的降低了系统的风险。


那么某个点到底是什么时候呢?


这里做了一个实验,号段设置长度为step=10max_id=1



当我拿第一个 ID 时,看到号段增加了,1/10




当我拿第三个 Id 时,看到号段又增加了,3/10




Leaf采用双buffer的方式,它的服务内部有两个号段缓存区segment。当前号段已消耗 10%时,还没能拿到下一个号段,则会另启一个更新线程去更新下一个号段。


简而言之就是Leaf保证了总是会多缓存两个号段,即便哪一时刻数据库挂了,也会保证发号服务可以正常工作一段时间。



通常推荐号段(segment)长度设置为服务高峰期发号 QPS 的 600 倍(10 分钟),这样即使 DB 宕机,Leaf 仍能持续发号 10-20 分钟不受影响。


优点:


  • Leaf 服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。

  • 容灾性高:Leaf 服务内部有号段缓存,即使 DB 宕机,短时间内 Leaf 仍能正常对外提供服务。


缺点:


  • ID 号码不够随机,能够泄露发号数量的信息,不太安全。

  • DB 宕机会造成整个系统不可用(用到数据库的都有可能)。

二、Leaf-snowflake

Leaf-snowflake基本上就是沿用了 snowflake 的设计,ID 组成结构:正数位(占 1 比特)+ 时间戳(占 41 比特)+ 机器ID(占 5 比特)+ 机房ID(占 5 比特)+ 自增值(占 12 比特),总共 64 比特组成的一个 Long 类型。


Leaf-snowflake不同于原始 snowflake 算法地方,主要是在 workId 的生成上,Leaf-snowflake依靠Zookeeper生成workId,也就是上边的机器ID(占 5 比特)+ 机房ID(占 5 比特)。Leaf中 workId 是基于 ZooKeeper 的顺序Id来生成的,每个应用在使用 Leaf-snowflake 时,启动时都会都在 Zookeeper 中生成一个顺序 Id,相当于一台机器对应一个顺序节点,也就是一个 workId。



Leaf-snowflake启动服务的过程大致如下:


  • 启动 Leaf-snowflake 服务,连接 Zookeeper,在 leaf_forever 父节点下检查自己是否已经注册过(是否有该顺序子节点)。

  • 如果有注册过直接取回自己的 workerID(zk 顺序节点生成的 int 类型 ID 号),启动服务。

  • 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的 workerID 号,启动服务。


Leaf-snowflake对 Zookeeper 是一种弱依赖关系,除了每次会去 ZK 拿数据以外,也会在本机文件系统上缓存一个workerID文件。一旦 ZooKeeper 出现问题,恰好机器出现故障需重启时,依然能够保证服务正常启动。


启动Leaf-snowflake模式也比较简单,起动本地 ZooKeeper,修改一下项目中的leaf.properties文件,关闭leaf.segment模式,启用leaf.snowflake模式即可。


leaf.segment.enable=false#leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/xin-master?useUnicode=true&characterEncoding=utf8

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
不能错过的分布式ID生成器(Leaf-),积累总结