雪花算法,生成分布式唯一ID
2.3 基于算法实现 【转载】
这里介绍下Twitter的Snowflake算法——snowflake,它把时间戳,工作机器id,序列号组合在一起,以保证在分布式系统中唯一性和自增性。


snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,在同一毫秒内最多可以生成 1024 X 4096 = 4194304个全局唯一ID。
优点:不依赖数据库,完全内存操作速度快
缺点:不同服务器需要保证系统时钟一致
snowflake的C#版本的简单实现:

public class SnowflakeIdWorker
{
/// <summary>
/// 开始时间截
/// 1288834974657 是(Thu, 04 Nov 2010 01:42:54 GMT) 这一时刻到1970-01-01 00:00:00时刻所经过的毫秒数。
/// 当前时刻减去1288834974657 的值刚好在2^41 里,因此占41位。
/// 所以这个数是为了让时间戳占41位才特地算出来的。
/// </summary>
public const long Twepoch = 1288834974657L; /// <summary>
/// 工作节点Id占用5位
/// </summary>
const int WorkerIdBits = 5; /// <summary>
/// 数据中心Id占用5位
/// </summary>
const int DatacenterIdBits = 5; /// <summary>
/// 序列号占用12位
/// </summary>
const int SequenceBits = 12; /// <summary>
/// 支持的最大机器Id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
/// </summary>
const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits); /// <summary>
/// 支持的最大数据中心Id,结果是31
/// </summary>
const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits); /// <summary>
/// 机器ID向左移12位
/// </summary>
private const int WorkerIdShift = SequenceBits; /// <summary>
/// 数据标识id向左移17位(12+5)
/// </summary>
private const int DatacenterIdShift = SequenceBits + WorkerIdBits; /// <summary>
/// 时间截向左移22位(5+5+12)
/// </summary>
public const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits; /// <summary>
/// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
/// </summary>
private const long SequenceMask = -1L ^ (-1L << SequenceBits); /// <summary>
/// 毫秒内序列(0~4095)
/// </summary>
private long _sequence = 0L; /// <summary>
/// 上次生成Id的时间截
/// </summary>
private long _lastTimestamp = -1L; /// <summary>
/// 工作节点Id
/// </summary>
public long WorkerId { get; protected set; } /// <summary>
/// 数据中心Id
/// </summary>
public long DatacenterId { get; protected set; } /// <summary>
/// 构造器
/// </summary>
/// <param name="workerId">工作ID (0~31)</param>
/// <param name="datacenterId">数据中心ID (0~31)</param>
public SnowflakeIdWorker(long workerId, long datacenterId)
{
WorkerId = workerId;
DatacenterId = datacenterId; if (workerId > MaxWorkerId || workerId < 0)
{
throw new ArgumentException(String.Format("worker Id can't be greater than {0} or less than 0", MaxWorkerId));
}
if (datacenterId > MaxDatacenterId || datacenterId < 0)
{
throw new ArgumentException(String.Format("datacenter Id can't be greater than {0} or less than 0", MaxDatacenterId));
}
} private static readonly object _lockObj = new Object(); /// <summary>
/// 获得下一个ID (该方法是线程安全的)
/// </summary>
/// <returns></returns>
public virtual long NextId()
{
lock (_lockObj)
{
//获取当前时间戳
var timestamp = TimeGen(); //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < _lastTimestamp)
{
throw new InvalidOperationException(String.Format(
"Clock moved backwards. Refusing to generate id for {0} milliseconds", _lastTimestamp - timestamp));
} //如果是同一时间生成的,则进行毫秒内序列
if (_lastTimestamp == timestamp)
{
_sequence = (_sequence + 1) & SequenceMask;
//毫秒内序列溢出
if (_sequence == 0)
{
//阻塞到下一个毫秒,获得新的时间戳
timestamp = TilNextMillis(_lastTimestamp);
}
} //时间戳改变,毫秒内序列重置
else
{
_sequence = 0;
} //上次生成ID的时间截
_lastTimestamp = timestamp; //移位并通过或运算拼到一起组成64位的ID
return ((timestamp - Twepoch) << TimestampLeftShift) |
(DatacenterId << DatacenterIdShift) |
(WorkerId << WorkerIdShift) | _sequence;
}
} /// <summary>
/// 生成当前时间戳
/// </summary>
/// <returns>毫秒</returns>
private static long GetTimestamp()
{
return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
} /// <summary>
/// 生成当前时间戳
/// </summary>
/// <returns>毫秒</returns>
protected virtual long TimeGen()
{
return GetTimestamp();
} /// <summary>
/// 阻塞到下一个毫秒,直到获得新的时间戳
/// </summary>
/// <param name="lastTimestamp">上次生成Id的时间截</param>
/// <returns></returns>
protected virtual long TilNextMillis(long lastTimestamp)
{
var timestamp = TimeGen();
while (timestamp <= lastTimestamp)
{
timestamp = TimeGen();
}
return timestamp;
}
}

测试:

[TestClass]
public class SnowflakeTest
{
[TestMethod]
public void MainTest()
{
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 1000; i++)
{
Trace.WriteLine(string.Format("{0}-{1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ffffff"), idWorker.NextId()));
}
}
}

结果:

总之,GUID能满足大部分需要,但如果想要我们的程序精益求精,也可以考虑使用本文提到的方法,感谢阅读。
雪花算法,生成分布式唯一ID的更多相关文章
- 雪花算法生成全局唯一ID
系统中某些场景少不了全局唯一ID的使用,来保证数据的唯一性.除了通过数据库自带的自增id来保证 id 的唯一性,通常为了保证的数据的可移植性会选择通过程序生成全局唯一 id.百度了不少php相关的生成 ...
- 基于雪花算法生成分布式ID(Java版)
SnowFlake算法原理介绍 在分布式系统中会将一个业务的系统部署到多台服务器上,用户随机访问其中一台,而之所以引入分布式系统就是为了让整个系统能够承载更大的访问量.诸如订单号这些我们需要它是全局唯 ...
- 雪花算法生成分布式ID
分布式主键ID生成方案 分布式主键ID的生成方案有以下几种: 数据库自增主键 缺点: 导入旧数据时,可能会ID重复,导致导入失败 分布式架构,多个Mysql实例可能会导致ID重复 UUID 缺点: 占 ...
- 分布式唯一id:snowflake算法思考
匠心零度 转载请注明原创出处,谢谢! 缘起 为什么会突然谈到分布式唯一id呢?原因是最近在准备使用RocketMQ,看看官网介绍: 一句话,消息可能会重复,所以消费端需要做幂等.为什么消息会重复后续R ...
- 分布式唯一ID生成算法-雪花算法
在我们的工作中,数据库某些表的字段会用到唯一的,趋势递增的订单编号,我们将介绍两种方法,一种是传统的采用随机数生成的方式,另外一种是采用当前比较流行的“分布式唯一ID生成算法-雪花算法”来实现. 一. ...
- 分布式唯一ID生成方案选型!详细解析雪花算法Snowflake
分布式唯一ID 使用RocketMQ时,需要使用到分布式唯一ID 消息可能会发生重复,所以要在消费端做幂等性,为了达到业务的幂等性,生产者必须要有一个唯一ID, 需要满足以下条件: 同一业务场景要全局 ...
- 分布式唯一ID生成方案是什么样的?(转)
一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...
- 【系统设计】分布式唯一ID生成方案总结
目录 分布式系统中唯一ID生成方案 1. 唯一ID简介 2. 全局ID常见生成方案 2.1 UUID生成 2.2 数据库生成 2.3 Redis生成 2.4 利用zookeeper生成 2.5 雪花算 ...
- 分布式唯一ID的生成方案
分布式ID的特性 全局唯一 不能出现重复的ID,这是最基本的要求. 递增 有利于关系数据库索引性能. 高可用 既然是服务于分布式系统,为多个服务提供ID服务,访问压力一定很大,所以需要保证高可用. 信 ...
随机推荐
- (9)Go指针
区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针. 要搞明白Go语言中的指针需要先知道3个概念:指针地址.指针类型和指针取值. Go语言中的指针 任何程序数据载入内存后,在内存 ...
- 大量数据通过Phoenix插入到hbase报错记录(2)
错误: Caused by: java.sql.SQLException: ERROR (INT10): Unable to find cached index metadata 解决办法: 在hba ...
- Kubernetes集群部署(yum部署)
环境准备 Kubernetes-Master:192.168.37.134 #yum install kubernetes-master etcd flannel -y Kubernetes-n ...
- 微信小程序之使用checkbox
效果图如下: 实例代码如下:type_add.js // pages/detail_add/detail_add.js Page({ /** * 页面的初始数据 */ data: { selectDa ...
- 数据仓库DW、ODS、DM概念及其区别
整体结构 在具体分析数据仓库之前先看下一下数据中心的整体架构以及数据流向 数据中心整体架构.png DB 是现有的数据来源,可以为mysql.SQLserver.文件日志等,为数据仓库提供数据来源 ...
- Don't always upset yourself !
- [Beta]第三次 Scrum Meeting
[Beta]第三次 Scrum Meeting 写在前面 会议时间 会议时长 会议地点 2019/5/7 22:00 30min 大运村公寓6F寝室 附Github仓库:WEDO 例会照片 工作情况总 ...
- 打jar包和使用jar包
一.为什么要打jar包 二.如何查看jar包 三.如何开发jar包 四.拿到jar包之后,如何使用 一.为什么要打jar包 给别人用的时候一般给别人的是class文件.如果有很多类这么办?把类变成一个 ...
- GIS地理处理脚本案例教程——批量栅格分割-批量栅格裁剪-批量栅格掩膜-深度学习样本批量提取
GIS地理处理脚本案例教程--批量栅格分割-批量栅格裁剪-批量栅格掩膜-深度学习样本批量提取 商务合作,科技咨询,版权转让:向日葵,135-4855_4328,xiexiaokui#qq.com 关键 ...
- Android ADB常用命令使用
Android SDK: adb shell 命令的使用(am.pm.wm.screencap.monkey等) https://blog.csdn.net/xietansheng/article/d ...