分布式唯一ID:雪花ID Snowflake .Net版
先抄个雪花ID介绍,雪花算法:
雪花ID是用一个64位的整形数字来做ID,对应.net中的long,数据库中的bigint,雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。
自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。
算法描述:
- 最高位是符号位,始终为0,不可用。
- 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
- 10位的机器标识,10位的长度最多支持部署1024个节点。
- 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
好了,回归本人自己介绍:时钟回拨
雪花ID严重依赖系统当前时间,当系统时间被人为反后调整时,算法会出问题,可能会出重复ID.Snowflake原算法是在检测到系统时间被回调后直接抛异常.本代码在时钟回拨后,会将生成的ID时间戳停留在最后一次时间戳上(每当序列溢出时会往前走一毫秒),等待系统时间追上后即可以避过时钟回拨问题.
这种处理方式的优点是时钟回拨后不会异常,能一直生成出雪花ID,但缺点是雪花ID中的时间戳不是系统的当前时间,会是回拨前的最后记录的一次时间戳,但相差也不大.不知道有没有什么生产系统会对这个时间戳要求非常严格,无法使用这种补救方式的?
当然停掉系统后的时钟回拨是无法处理的,这种还是会有可能出现重复ID的.
介绍完毕,下面直接上源码吧,,本源码除了生成雪花ID外,还提供解析雪花ID的方法.
源码git地址: https://gitee.com/itsm/learning_example/tree/master/snowflake-雪花Id
public class SnowflakeId
{ // 开始时间截((new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)-Jan1st1970).TotalMilliseconds)
private const long twepoch = 1577836800000L; // 机器id所占的位数
private const int workerIdBits = ; // 数据标识id所占的位数
private const int datacenterIdBits = ; // 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
private const long maxWorkerId = -1L ^ (-1L << workerIdBits); // 支持的最大数据标识id,结果是31
private const long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 序列在id中占的位数
private const int sequenceBits = ; // 数据标识id向左移17位(12+5)
private const int datacenterIdShift = sequenceBits + workerIdBits; // 机器ID向左移12位
private const int workerIdShift = sequenceBits; // 时间截向左移22位(5+5+12)
private const int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
private const long sequenceMask = -1L ^ (-1L << sequenceBits); // 数据中心ID(0~31)
public long datacenterId { get; private set; } // 工作机器ID(0~31)
public long workerId { get; private set; } // 毫秒内序列(0~4095)
public long sequence { get; private set; } // 上次生成ID的时间截
public long lastTimestamp { get; private set; } /// <summary>
/// 雪花ID
/// </summary>
/// <param name="datacenterId">数据中心ID</param>
/// <param name="workerId">工作机器ID</param>
public SnowflakeId(long datacenterId,long workerId )
{
if (datacenterId > maxDatacenterId || datacenterId < )
{
throw new Exception(string.Format("datacenter Id can't be greater than {0} or less than 0", maxDatacenterId));
}
if (workerId > maxWorkerId || workerId < )
{
throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0", maxWorkerId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = 0L;
this.lastTimestamp = -1L;
} /// <summary>
/// 获得下一个ID
/// </summary>
/// <returns></returns>
public long NextId()
{
lock (this)
{
long timestamp = GetCurrentTimestamp();
if (timestamp > lastTimestamp) //时间戳改变,毫秒内序列重置
{
sequence = 0L;
}
else if (timestamp == lastTimestamp) //如果是同一时间生成的,则进行毫秒内序列
{
sequence = (sequence + ) & sequenceMask;
if (sequence == ) //毫秒内序列溢出
{
timestamp = GetNextTimestamp(lastTimestamp); //阻塞到下一个毫秒,获得新的时间戳
}
}
else //当前时间小于上一次ID生成的时间戳,证明系统时钟被回拨,此时需要做回拨处理
{
sequence = (sequence + ) & sequenceMask;
if (sequence > )
{
timestamp = lastTimestamp; //停留在最后一次时间戳上,等待系统时间追上后即完全度过了时钟回拨问题。
}
else //毫秒内序列溢出
{
timestamp = lastTimestamp + ; //直接进位到下一个毫秒
}
//throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp));
} lastTimestamp = timestamp; //上次生成ID的时间截 //移位并通过或运算拼到一起组成64位的ID
var id= ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
return id;
}
} /// <summary>
/// 解析雪花ID
/// </summary>
/// <returns></returns>
public static string AnalyzeId(long Id)
{
StringBuilder sb = new StringBuilder(); var timestamp = (Id >> timestampLeftShift) ;
var time = Jan1st1970.AddMilliseconds(timestamp + twepoch);
sb.Append(time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:fff")); var datacenterId = (Id ^ (timestamp << timestampLeftShift)) >> datacenterIdShift;
sb.Append("_" + datacenterId); var workerId = (Id ^ ((timestamp << timestampLeftShift) | (datacenterId << datacenterIdShift))) >> workerIdShift;
sb.Append("_" + workerId); var sequence = Id & sequenceMask;
sb.Append("_" + sequence); return sb.ToString();
} /// <summary>
/// 阻塞到下一个毫秒,直到获得新的时间戳
/// </summary>
/// <param name="lastTimestamp">上次生成ID的时间截</param>
/// <returns>当前时间戳</returns>
private static long GetNextTimestamp(long lastTimestamp)
{
long timestamp = GetCurrentTimestamp();
while (timestamp <= lastTimestamp)
{
timestamp = GetCurrentTimestamp();
}
return timestamp;
} /// <summary>
/// 获取当前时间戳
/// </summary>
/// <returns></returns>
private static long GetCurrentTimestamp()
{
return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
} private static readonly DateTime Jan1st1970 = new DateTime(, , , , , , DateTimeKind.Utc);
}
分布式唯一ID:雪花ID Snowflake .Net版的更多相关文章
- 分布式SnowFlakeID(雪花ID)原理和改进优化
最近在研究分布式框架的组件和整体设计思路.所有的问题,一旦涉及分布式难度就呈几何倍数的提升.包括最常见的ID生成也是,单机情况下,使用数据库自增ID.UUID都是简单易行的选择 但在分布式环境下,就需 ...
- 雪花算法(snowflake)delphi版
雪花算法简单描述: + 最高位是符号位,始终为0,不可用. + 41位的时间序列,精确到毫秒级,41位的长度可以使用69年.时间位还有一个很重要的作用是可以根据时间进行排序. + 10位的机器标识,1 ...
- Twitter的分布式自增ID算法snowflake(雪花算法) - C#版
概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的.有些时候我们希望能使用一种简 ...
- 分布式唯一ID生成方案选型!详细解析雪花算法Snowflake
分布式唯一ID 使用RocketMQ时,需要使用到分布式唯一ID 消息可能会发生重复,所以要在消费端做幂等性,为了达到业务的幂等性,生产者必须要有一个唯一ID, 需要满足以下条件: 同一业务场景要全局 ...
- 分布式ID生成系统 UUID与雪花(snowflake)算法
Leaf——美团点评分布式ID生成系统 -https://tech.meituan.com/MT_Leaf.html 网游服务器中的GUID(唯一标识码)实现-基于snowflake算法-云栖社区-阿 ...
- 分布式系统-主键唯一id,订单编号生成-雪花算法-SnowFlake
分布式系统下 我们每台设备(分布式系统-独立的应用空间-或者docker环境) * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作 ...
- 分布式唯一ID生成算法-雪花算法
在我们的工作中,数据库某些表的字段会用到唯一的,趋势递增的订单编号,我们将介绍两种方法,一种是传统的采用随机数生成的方式,另外一种是采用当前比较流行的“分布式唯一ID生成算法-雪花算法”来实现. 一. ...
- 分布式唯一id:snowflake算法思考
匠心零度 转载请注明原创出处,谢谢! 缘起 为什么会突然谈到分布式唯一id呢?原因是最近在准备使用RocketMQ,看看官网介绍: 一句话,消息可能会重复,所以消费端需要做幂等.为什么消息会重复后续R ...
- snowflake 分布式唯一ID生成器
本文来自我的github pages博客http://galengao.github.io/ 即www.gaohuirong.cn 摘要: 原文参考运维生存和开源中国上的代码整理 我的环境是pytho ...
随机推荐
- 给tomcat容器配置SSL的记录,包含项目完整部署过程
给tomcat容器配置SSL(https) 昨天公司有一个旧的项目要部署, 服务器(OS是windows 10) 数据库都是新买的, 写个博客记录一下 1, 下载证书(以阿里云为例子) 参考链接: h ...
- NIO 中文乱码自我解决的简单DEMO
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStrea ...
- 洛谷P3377 【模板】左偏树(可并堆) 题解
作者:zifeiy 标签:左偏树 这篇随笔需要你在之前掌握 堆 和 二叉树 的相关知识点. 堆支持在 \(O(\log n)\) 的时间内进行插入元素.查询最值和删除最值的操作.在这里,如果最值是最小 ...
- windows下如何安装Composer?
Composer 不是一个包管理器,它仅仅是一个依赖管理工具.它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目 ...
- CentOS7.0下安装FTP服务的方法
http://www.jb51.net/article/106604.htm 本篇文章主要介绍了CentOS7.0下安装FTP服务的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟 ...
- Redux action 状态
action 不同的状态,设置不同的action.type [就是一个名字],返回对应的数据 不同的状态返回不同的 接口数据
- 2018-7-31-C#-判断两条直线距离
title author date CreateTime categories C# 判断两条直线距离 lindexi 2018-07-31 14:38:13 +0800 2018-05-08 10: ...
- dotnet core 使用 CoreRT 将程序编译为 Native 程序
现在微软有一个开源项目 CoreRT 能通过将托管的 .NET Core 编译为单个无依赖的 Native 程序 这个项目现在还没发布,但是能尝试使用,可以带来很多的性能提升 使用 CoreRT 发布 ...
- linux 使用 gdb
gdb 对于看系统内部是非常有用. 在这个级别精通调试器的使用要求对 gdb 命令有信心, 需要理解目标平台的汇编代码, 以及对应源码和优化的汇编码的能力. 调试器必须把内核作为一个应用程序来调用. ...
- Linux 内核 NuBus 总线
另一个有趣的, 但是几乎被忘记的, 接口总线是 NuBus. 它被发现于老的 Mac 计算机(那 些有 M68K CPU 家族的). 所有的这个总线是内存映射的(象 M68K 的所有东西), 并且设备 ...