(转)基于.NET Standard的分布式自增ID算法--Snowflake
转自:https://www.lmlphp.com/user/1222/article/item/15683/
概述
本篇文章主要讲述分布式ID生成
算法中最出名的Snowflake
算法。搞.NET开发的,数据库主键最常见的就是int类型的自增主键和GUID类型的uniqueidentifier。
那么为何还要引入snowflake呢?
INT自增主键
自增主键是解决主键生成的最简单方案,它有如下优势:
- 数据库本身负责主键生成,效率高
- 数据库本身保证主键顺序递增,方便存储和检索
相对应的,它也有如下缺点:
- 严重依赖数据库服务
- 强顺序递增,不易横向扩展
- 分库分表很难处理
- 不方便导入数据
- 上层应用在插入数据时,如果需要获知主键,必须再次查询
总结来说,INT自增主键在单机性能和主键严格递增上由很大的优势,但是在扩展性和分布式数据库上有较大限制
GUID主键
GUID(全局唯一标识符,Globally Unique Identifier)为128位(16字节),它使用太网卡地址、纳秒级时间、芯片ID码和许多可能的数字根据算法动态生成,理论上可以有2^128个结果,
所以产生2个相同的ID的几率非常小。
它的优点如下:
- 应用生成,解放服务器压力
- 生成的ID可以做到全库唯一,方便数据库分库分表、数据导入
缺点也很明显:
- 16字节太长,浪费空间
- 非顺序递增,增加数据库存储和检索开销
在做数据库主键选则时,如果系统较小,业务逻辑相对简单,可以考虑使用自增主键;如果业务复杂,涉及到分库分表分布式等,建议考虑GUID。如果认为GUID的缺点太影响使用,
可以考虑马上开始重点介绍的分布式ID生成算法 Snowflake
Snowflake是由Twitter提出并首先使用的分布式ID生成算法,使用它来生成分布式趋势递增的Id。
-
分布式
Id有分布式系统的节点自己生成 -
趋势递增
主键非严格顺序递增的,而是根数时间顺序递增,这在一定程度上保证了数据存储和索引的效率
算法讲解
总长度为64位长整型(8字节)
1位:首字节固定为0,来保证所有生成的数据都是正数
41位:第2到第42位工41字节,用于生成毫秒级时间戳,计算大概(2^41−1)/(1000∗60∗60∗24∗365)=69 年,对于一般系统来说绝对够用。
10位: 第43位到第52位为工作机ID,可表示2^10=1024台设备,一般高5位表示机房Id(datacenterId),低5位表示工作节点ID(workid)
12位:第53位到第64位表示序列号,2^12-1=4095
综上算法,表示单机每毫秒可以提供4095个Id,所有机器每毫秒可生成4095*1024=4194304个Id。
它的优点如下:
- 应用生成,解放服务器压力
- 生成的ID可以做到全库唯一,方便数据库分库分表、数据导入
- 8字节,长整型,节省空间
- 趋势递增,方便数据存储和查询
基于.NET Standard的分布式自增ID算法--美团点评LeafSegment
概述
前一篇文章讲述了最流行的分布式ID生成算法snowflake,本篇文章根据美团点评分布式ID生成系统文章,介绍另一种相对更容易理解和编写的分布式ID生成方式。
实现原理
Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话:
设置数据表主键自增是最简单的方案,缺点也很明显:
-
强依赖数据库,无法提供高可用
-
ID生成强依赖单台服务,无法横向扩展
很容易想到,如果我的应用每次申请一批id,插入数据时顺序取一个使用,即将耗尽时再去获取一批新的id,如此即可在一定程度上减弱与数据库的关系,同时将单台扩展延伸为获取id的步长。
负责发放ID的服务既可以使用MySQL服务,也可以使用Redis等服务。
基于MySQL实现
首先我们建立一张数据库表
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呢?
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
/// <summary> /// 数据单元 /// </summary> public class DataVal { /// <summary> /// 当前最大Id /// </summary> public long MaxId { get; set; } = 1; /// <summary> /// 当前步长 /// </summary> public int Step { get; set; } = 1000; }
这个类仅负责将ID生发器的数据传入核心类LeafSegment中。核心类的具体实现如下代码:
/// <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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?