基于.NET Standard的分布式自增ID算法--美团点评LeafSegment
概述
前一篇文章讲述了最流行的分布式ID生成算法snowflake,本篇文章根据美团点评分布式ID生成系统文章,介绍另一种相对更容易理解和编写的分布式ID生成方式。
实现原理
Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话:
There are no two identical leaves in the world
"世界上没有两片相同的树叶"
设置数据表主键自增是最简单的方案,缺点也很明显:
强依赖数据库,无法提供高可用
ID生成强依赖单台服务,无法横向扩展
很容易想到,如果我的应用每次申请一批id,插入数据时顺序取一个使用,即将耗尽时再去获取一批新的id,如此即可在一定程度上减弱与数据库的关系,同时将单台扩展延伸为获取id的步长。
负责发放ID的服务既可以使用MySQL服务,也可以使用Redis等服务。
基于MySQL实现
首先我们建立一张数据库表
1 2 3 4 5 6 7 8 9 10 11 | DROP TABLE IF EXISTS `leafsegment`; CREATE TABLE `leafsegment` ( `biz_tag` varchar (255) NULL DEFAULT NULL , `max_id` bigint (20) NULL DEFAULT 0, `step` int (11) NULL DEFAULT 5000, ` desc ` varchar (255) NULL DEFAULT NULL , `update_time` datetime(0) NULL DEFAULT now() ); -- 添加一条初始化数据 INSERT INTO `leafsegment` VALUES ( 'test' , 0, 5000, '测试' , '2018-12-06 23:32:11' ); |
数据库表如下图
biz_tag:业务标记,不同业务使用不同的值,可以最大限度地利用ID
max_id:当前已经被申请走的最大Id
step:每次申请Id的步长
desc:业务内容描述
update_time:最新一次申请时间
应用如何获取一批有效ID呢?
1 2 3 4 | Begin UPDATE leafsegment SET max_id=max_id+step,update_time=now() WHERE biz_tag= 'test' SELECT biz_tag, max_id, step FROM leafsegment WHERE biz_tag= 'test' Commit |
在一个事务周期内完成max_id的更新,和最新数据的获取,天然解决了资源竞争问题。
而后,我们就可以在应用中将[max_id-step+1,max_id]闭区间的所有值作为ID来使用了。
基于Redis实现
Redis的实现更为简单,基本原理是利用了Redis的IncrBy命令实现原子加N,具体实现流程无须赘述。
代码实现
首先我们定义一个传递Step(步长)和MaxId(最大值)的DTO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /// <summary> /// 数据单元 /// </summary> public class DataVal { /// <summary> /// 当前最大Id /// </summary> public long MaxId { get ; set ; } = 1; /// <summary> /// 当前步长 /// </summary> public int Step { get ; set ; } = 1000; } |
这个类仅负责将ID生发器的数据传入核心类LeafSegment中。核心类的具体实现如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /// <summary> /// 美团的Leaf Segment 方案 /// </summary> public class LeafSegment { private long _currentStep = long .MaxValue >> 1; private readonly Func<DataVal> _idGetAction; private readonly ConcurrentQueue< long > _data = new ConcurrentQueue< long >(); private readonly AutoResetEvent _autoReset = new AutoResetEvent( false ); /// <summary> /// 美团的Leaf Segment 方案 /// </summary> /// <param name="idGetAction">Id生成策略</param> /// <param name="prefill">是否立即初始化数据</param> public LeafSegment(Func<DataVal> idGetAction, bool prefill= false ) { _idGetAction = idGetAction; if (prefill) { FillData(); } Loop(); } /// <summary> /// 获取下一个Id /// </summary> /// <returns></returns> public long NextId() { _autoReset.Set(); if (_data.TryDequeue( out var result)) { return result; } throw new Exception( "Resource not enough" ); } private void Loop() { ( new Thread(_ => { while ( true ) { _autoReset.WaitOne(); FillData(); } }) {IsBackground = true }).Start(); } private void FillData() { //数量小于步长一半时触发拉新 while (_data.Count < (_currentStep >> 1)) { var tmp = _idGetAction.Invoke(); _currentStep = tmp.Step; for ( var i = tmp.MaxId - tmp.Step + 1; i <= tmp.MaxId; i++) { _data.Enqueue(i); } } } } |
此处需要注意的是LeafSegment构造函数的第一个入参IdGetAction是一个返回DataVal的回调函数,因此外部实现中可以在该回调函数中返回所需ID序列;
第二个参数prefill,该参数控制实例化LeafSegment对象时,是否同步调用获取ID区段,如该值为false,将会由启动的线程稍后补充数据。
完整实现、使用Demo以及benchmark测试请参见源代码:https://github.com/sampsonye/nice
作者:散漫的小蜗牛
出处:http://www.cnblogs.com/leafly
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。如有问题,可以邮件:UpdateServer@163.com 非常感谢。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?