Shoulder-DB 中提出了高性能序号生成器 Shoulder-Sequence
,其实现参考了美团的 leaf
、滴滴的 tinyId
等同类技术实现,那这些开源技术有什么区别,该如何选择呢?
滴滴 tinyId:
特点:有极度轻量的客户端(几乎纯java原生)、token 实现、无监控但有监控的扩展设计,客户端可获取当前运行状态
评价:
-
模型、逻辑清晰,但需要手动初始化 db (代码中无 insert),
-
序列步幅长度、min、max、loadThreshold 等都在数据库中初始化时配置。
-
锁:几乎都在初始化中,初始化后,内存号段用完时,同步锁粒度与同类产品更大。
-
但考虑到tinyId中有到阈值后异步预加载nextBuffer,故 synchronize中大概率只有内存操作,也就不会对性能造成太大影响。
-
反而减少了大量的代码判断逻辑和循环,易于维护。
-
滴滴做了很聪明的抉择:但在绝大多数场景下,刚刚提到的内容对于运行时无弊端,反而减少了大量判断逻辑,代码理解更简单可维护性更好。
-
在绝大多数场景,甚至可以不改代码仅需修改配置直接适用(有基本的鉴权)。
-
企业大规模适用时,建议自行开发 id 分配器的申请逻辑(insert代码)。
-
缓存id无过期
-
提供了静态工具类
美团 leaf:
特点:提供获取 sequence 的 HTTP 接口、提供轻量状态监控、双 buffer 缓冲
评价:
-
源码为 demo 级别,极度轻量和简化
-
锁:除了初始化有锁,初始化后,内存号段用完时优先空循环等待
-
与 tinyId 相比锁力度较细,进入同步前优先调用 waitAndSleep(空循环 1w次 or sleep 10ms)+ volatile 代替 synchronized
-
逻辑更复杂,在用光 && 异步加载未及时完成 && 并发获取id 情况下性能可能更优(线程未切换),也可能会更差(等待10ms),同时 CPU 占用将更高。
-
不必太多关注这块性能和实现
-
缓存id默认每60s过期一次
Shoulder 的 sequence
特点:SpringBoot 开箱即用,提供轻量状态监控、双 buffer 缓冲,细粒度锁、高性能、兼容性强、扩展性好
评价:
-
与美团 leaf 类似,但有更细粒度的锁,理论并发更好【最追求极限性能】、且判断更完善,定义更清晰,手动指定事物可见性,对不同数据库兼容性更好。
-
判断是否达到阈值,仅需要由异步线程判断,获取id时无须判断
-
额外新增定时打印状态
-
缓存id可定制过期时间
-
支持多数据库
-
提供了建议的流水号规则,并考虑到系统升级,版本切换等大型长期运行项目中需要考虑的内容
-
代码相对更多,多在:
-
完善的缓存/模型设计
-
一套代码通过不同配置可兼容不同数据库(如同时有 mysql / oracle)
-
完善的逻辑判断(各种极端情况)
-
完善的报错与日志打印
-
通过配置可指定表名、字段名
-
更完备的sequence定义:max、step、initValue等
-
整体比较
-
性能上几乎都一样
-
锁的粒度都是 sequenceName 级别,几乎集中在初始化,初始化后 && 合理的参数配置下均几乎无锁
-
加载实现上均有 双 buffer 缓冲 + 异步检测是否需要加载
-
使用上几乎一样
-
几乎都是Generator.nextId()
-
id 缓存略有差别
-
tinyId 缓存id不支持过期
-
leaf 默认过期短,修改需要改代码
-
shoulder 默认过期长,支持自定义
差异:
-
tinyId、leaf 开源是为了提升影响力,提供分布式环境下全局id生成思路。 tinyId 提供了静态工具类,代码更简单,leaf 提供了非常简单的监控接口
-
Shoulder 直接提供了完备的解决方按,不仅仅提供了基础的序列生成能力,还提供了流水号设计的思路,更完善的能力:排查日志、监控埋点、定制数据库名称(便于符合不同组织定义的数据库命名规范)