分布式唯一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 ...
随机推荐
- 「BZOJ3694」「FJ2014集训」最短路
「BZOJ3694」「FJ2014集训」最短路 首先树剖没得说了,这里说一下并查集的做法, 对于一条非树边,它会影响的点就只有u(i),v(i)到lca,对于lca-v的路径上所有点x,都可通过1-t ...
- python基础之逻辑题(3)
Python基础之逻辑题(3) 1.编写一个函数实现将IP地址转换成一个整数 2.求结果:---lambda 3.求a的结果 4.求下面nums的输出 5.求下面片段的输出 6.写出程序的结果:--- ...
- day8_python网络编程(实验版本)
1.简单的套接字通信 1.1.服务端 import socket # 买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 插 ...
- HDU 1251 裸的字典树、入门题
裸的字典树还是挺简单的. 四个基本操作建立.查找.插入.删除 建立新结点我是用的c++中 new操作.当然也可以用malloc,都方便 不过指针阿.地址阿.这其中关系什么的我貌似还不是很清楚阿. 因为 ...
- 7种最常见的Hadoop和Spark项目
7种最常见的Hadoop和Spark项目 如果您的Hadoop项目将有新的突破,那么它必定与下边介绍的七种常见项目很相像. 有一句古老的格言是这样说的,如果你向某人提供你的全部支持和金融支持去做一些不 ...
- Python--day49--ORM框架SQLAlchemy之relationship的使用(有时间要从新看,这里状态不好,没有仔细听)
小贴士: 迭代器:只有在循环的时候才一个一个往外拿 relationship
- Jackson-deserialization fails on circular dependencies(JackSon无限递归问题)
Ok, so I'm trying to test some stuffs with jackson json converter. I'm trying to simulate a graph be ...
- P1065 汪老师的烟
题目描述 汪老师有n根烟,他每吸完一根烟就把烟蒂保存起来,\(k(k>1)\) 个烟蒂可以换一个新的烟,那么 汪老师 最终能吸到多少根烟呢? 输入格式 每组测试数据一行包括两个整数 \(n,k( ...
- The Function() Constructor
Functions are usually defined using the function keyword, either in the form of a function definitio ...
- P1048 数组中的逆序对
题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 输入格式 第一行包含一个整数 \(n\) ,表示数组中的元素个数 ...