算法概述

分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。

该项目地址(Scala实现):https://github.com/twitter/snowflake

python版项目地址:https://github.com/erans/pysnowflake

ID结构

Snowflake生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。

snowflake的结构如下(每部分用-分开):

注:上图的工作机器id(10比特)=数据中心(占左5比特)+ 机器ID(占右5比特)

Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 数据中心(占5比特)+ 机器ID(占5比特)+ 自增值(占12比特)

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)一共加起来刚好64位,为一个Long型(转换成字符串长度为18)。

1bit:不使用。

  • 因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。

41bit-时间戳:用来记录时间戳,毫秒级

  • 41位可以表示个毫秒的值。
  • 转化成单位年则是年。

10bit-工作机器id:用来记录工作机器id。

  • 可以部署在个节点,包含5位datacenterId和5位workerId
  • 5位(bit)可以表示的最大正整数是,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId

12bit-序列号:序列号,用来记录同毫秒内产生的不同id。

  • 12位(bit)可以表示的最大正整数是,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

算法特性

SnowFlake可以保证:

  • 所有生成的id按时间趋势递增
  • 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)

据说:snowflake每秒能够产生26万个ID。

算法代码(C#)

网上雪花算法的C#实现代码一大把,但大多是复制的同一份代码。而且,网上的C#版实现有很多错误

这里要提一下雪花算法(Snowflake)C#版本 压测Id重复严重,为这位博主默哀一下...

这里的算法实现代码是我参考原版(Scala实现)、Java版的代码用C#实现的,经测试未发现问题,可放心使用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Remoting.Contexts;
using System.Runtime.CompilerServices; namespace SnowflakeDemo
{
public sealed class IdWorker
{
/// <summary>
/// 起始的时间戳:唯一时间,这是一个避免重复的随机量,自行设定不要大于当前时间戳。
/// 一个计时周期表示一百纳秒,即一千万分之一秒。 1 毫秒内有 10,000 个计时周期,即 1 秒内有 1,000 万个计时周期。
/// </summary>
private static long StartTimeStamp = new DateTime(2020,7,1).Ticks/10000; /*
* 每一部分占用的位数
* 对于移位运算符 << 和 >>,右侧操作数的类型必须为 int,或具有预定义隐式数值转换 为 int 的类型。
*/
private const int SequenceBit = 12; //序列号占用的位数
private const int MachingBit = 5; //机器标识占用的位数
private const int DataCenterBit = 5; //数据中心占用的位数 /*
* 每一部分的最大值
*/
private static long MaxSequence = -1L ^ (-1L << SequenceBit);
private static long MaxMachingNum = -1L ^ (-1L << MachingBit);
private static long MaxDataCenterNum = -1L ^ (-1L << DataCenterBit); /*
* 每一部分向左的位移
*/
private const int MachingLeft = SequenceBit;
private const int DataCenterLeft = SequenceBit + MachingBit;
private const int TimeStampLeft = DataCenterLeft + DataCenterBit; private long dataCenterId; //数据中心
private long machineId; //机器标识
private long sequence; //序列号
private long lastTimeStamp = -1; //上一次时间戳 private long GetNextMill()
{
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp)
{
mill = getNewTimeStamp();
}
return mill;
} private long getNewTimeStamp()
{
return DateTime.Now.Ticks/10000;
} /// <summary>
/// 根据指定的数据中心ID和机器标志ID生成指定的序列号
/// </summary>
/// <param name="dataCenterId">数据中心ID</param>
/// <param name="machineId">机器标志ID</param>
public IdWorker(long dataCenterId, long machineId)
{
if (dataCenterId > MaxDataCenterNum || dataCenterId < 0)
{
throw new ArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
}
if (machineId > MaxMachingNum || machineId < 0)
{
throw new ArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
} /// <summary>
/// 产生下一个ID
/// </summary>
/// <returns></returns>
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public long NextId()
{
long currTimeStamp = getNewTimeStamp();
if (currTimeStamp < lastTimeStamp)
{
//如果当前时间戳比上一次生成ID时时间戳还小,抛出异常,因为不能保证现在生成的ID之前没有生成过
throw new Exception("Clock moved backwards. Refusing to generate id");
} if (currTimeStamp == lastTimeStamp)
{
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MaxSequence;
//同一毫秒的序列数已经达到最大
if (sequence == 0L)
{
currTimeStamp = GetNextMill();
}
}
else
{
//不同毫秒内,序列号置为0
sequence = 0L;
} lastTimeStamp = currTimeStamp; return (currTimeStamp - StartTimeStamp) << TimeStampLeft //时间戳部分
| dataCenterId << DataCenterLeft //数据中心部分
| machineId << MachingLeft //机器标识部分
| sequence; //序列号部分
} }
}

算法测试

测试代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading; namespace SnowflakeDemo
{
class Program
{
static void Main(string[] args)
{
IdWorker idworker = new IdWorker(1, 1); Console.WriteLine("开始单线程测试:");
Stopwatch sw1 = new Stopwatch();
sw1.Start();
for (int i = 0; i < 260000; i++)
{
idworker.NextId();
}
sw1.Stop();
TimeSpan ts = sw1.Elapsed;
Console.WriteLine("产生26万个ID需要{0}毫秒",ts.TotalMilliseconds); Console.WriteLine();
Console.WriteLine("开始多线程测试:");
int threadNum = 10;//测试线程数
int idNum = 100000;//每个线程产生的id数
long[,] idAllAry = new long[threadNum,idNum];
bool[] completeAry = new bool[threadNum];
double[] workTimeAry = new double[threadNum];
Thread[] thAry = new Thread[threadNum];
for (int i = 0; i < thAry.Length; i++)
{
thAry[i] = new Thread(new ParameterizedThreadStart(obj =>
{
int index = (int)obj;
Stopwatch sw2 = new Stopwatch();
sw2.Start(); for (int j = 0; j < idNum; j++)
{
idAllAry[index,j]=idworker.NextId();
}
completeAry[index] = true;
sw2.Stop();
workTimeAry[index] = sw2.Elapsed.TotalMilliseconds; }));
}
for (int i = 0; i < thAry.Length; i++)
{
thAry[i].Start(i);
}
Console.WriteLine(string.Format("运行{0}个线程,每个线程产生{1}个ID",threadNum,idNum)); while (completeAry.Where(c => !c).ToList().Count != 0)
{
Console.WriteLine("等待执行结果...");
Thread.Sleep(1000);
} Console.WriteLine(string.Format("单个线程产生ID耗时的最小为{0}毫秒,最大为{1}毫秒", workTimeAry.Min(), workTimeAry.Max())); List<long> idList = new List<long>();
for (int i = 0; i < threadNum; i++)
{
for (int j = 0; j < idNum; j++)
{
idList.Add(idAllAry[i, j]);
}
}
var qrepeatId = idList.GroupBy(x => x).Where(x => x.Count() > 1).ToList();
Console.WriteLine(string.Format("ID总数为{0},ID重复个数{1}", idList.Count, qrepeatId.Count)); foreach (var item in qrepeatId)
{
Console.WriteLine(item.Key);
}
Console.ReadLine();
}
}
}

测试结果:

开始单线程测试:
产生26万个ID需要972.9153毫秒 开始多线程测试:
运行10个线程,每个线程产生100000个ID等待执行结果…
待执行结果...
待执行结果...
待执行结果...
待执行结果...
单个线程产生ID耗时的最小为1895.3256毫秒,最大为3828.659毫秒
ID总数为1000000,ID重复个数0

参考文章:

Twitter的分布式自增ID算法snowflake(雪花算法) - C#版——博客园

一口气说出9种分布式ID生成方式,阿里面试官都懵了——知乎

雪花算法(SnowFlake)Java实现——简书

理解分布式id生成算法SnowFlake——segmentfault——讲解的较为细致

简单实用算法—分布式自增ID算法snowflake(雪花算法)的更多相关文章

  1. 分布式ID生成器 snowflake(雪花)算法

    在springboot的启动类中引入 @Bean public IdWorker idWorkker(){ return new IdWorker(1, 1); } 在代码中调用 @Autowired ...

  2. 分布式主键解决方案之--Snowflake雪花算法

    0--前言 对于分布式系统环境,主键ID的设计很关键,什么自增intID那些是绝对不用的,比较早的时候,大部分系统都用UUID/GUID来作为主键,优点是方便又能解决问题,缺点是插入时因为UUID/G ...

  3. 说起分布式自增ID只知道UUID?SnowFlake(雪花)算法了解一下(Python3.0实现)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_155 但凡说起分布式系统,我们肯定会对一些海量级的业务进行分拆,比如:用户表,订单表.因为数据量巨大一张表完全无法支撑,就会对其进 ...

  4. Twitter分布式自增ID算法snowflake原理解析

    以JAVA为例 Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个 ...

  5. Twitter分布式自增ID算法snowflake原理解析(Long类型)

    Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个Long类型的6 ...

  6. 详解Twitter开源分布式自增ID算法snowflake(附演算验证过程)

    详解Twitter开源分布式自增ID算法snowflake,附演算验证过程 2017年01月22日 14:44:40 url: http://blog.csdn.net/li396864285/art ...

  7. 分布式自增ID算法-Snowflake详解

    1.Snowflake简介 互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并 ...

  8. 分布式Snowflake雪花算法

    前言 项目中主键ID生成方式比较多,但是哪种方式更能提高的我们的工作效率.项目质量.代码实用性以及健壮性呢,下面作了一下比较,目前雪花算法的优点还是很明显的. 优缺点比较 UUID(缺点:太长.没法排 ...

  9. SnowFlake 雪花算法详解与实现 & MP中的应用

    BackGround 现在的服务基本是分布式,微服务形式的,而且大数据量也导致分库分表的产生,对于水平分表就需要保证表中 id 的全局唯一性. 对于 MySQL 而言,一个表中的主键 id 一般使用自 ...

  10. .Net Core ORM选择之路,哪个才适合你 通用查询类封装之Mongodb篇 Snowflake(雪花算法)的JavaScript实现 【开发记录】如何在B/S项目中使用中国天气的实时天气功能 【开发记录】微信小游戏开发入门——俄罗斯方块

    .Net Core ORM选择之路,哪个才适合你   因为老板的一句话公司项目需要迁移到.Net Core ,但是以前同事用的ORM不支持.Net Core 开发过程也遇到了各种坑,插入条数多了也特别 ...

随机推荐

  1. P4149 [IOI2011] Race 题解

    题目链接:Race 点分治基本题,从这题简单阐述点分治如何思考问题的.点分治常见的解决一类问题,就是树里面的一些路径类问题.比如一些计数是最常见的. 点分治的一个核心计数思想: 如图所见,对于某个点而 ...

  2. 痞子衡嵌入式:我入选了2023年度与非网(eefocus)最佳创作者Top15

    最近收到了「与非网」发来的 2023 年度最佳创作者 证书,证书做得一如既往地有质感,这是与非网第二次给痞子衡发证书了,足见与非网对痞子衡的认可. 与非网自 2021 年起,每年都会评选一次年度创作者 ...

  3. Vulkan学习苦旅01:最初的相遇(学习路线、参考资料与环境配置)

    提示:博主本人也在努力学习Vulkan中,文中可能有写错的地方,敬请大家批评指正. 这个世界只有两种人:会Vulkan的和不会Vulkan的,大概不存在"只会一点"的中间状态.学习 ...

  4. 记录一则exachk进程占用大量CPU资源

    有Exadata客户在进行exachk巡检之后反馈,发现系统中,exachk进程占用了大量CPU资源. 了解之前的变更,只是巡检之前升级了AHF,然后进行标准的exachk巡检. 现象: 目前机器整体 ...

  5. OGG常用运维命令

    1. 管理(MGR)进程命令 INFO MANAGER         返回有关管理器端口和进程id的信息. START MANAGER       开启管理进程 STATUS MANAGER    ...

  6. Zookeeper-ZKFC的原理和功能

    一.前言 HADOOP2 HA架构引入了ZKFC.Journalnode组件,本篇文章主要介绍ZKFC的功能和原理.HA架构支持两种切换方式: 手动切换:  通过命令实现主备之间的切换,可以用HDFS ...

  7. NC53074 Forsaken喜欢独一无二的树

    题目链接 题目 题目描述 ​ 众所周知,最小生成树是指使图中所有节点连通且边权和最小时的边权子集. ​ 不过最小生成树太简单了,我们现在来思考一个稍微复杂一点的问题. ​ 现在给定一个 \(n\) 个 ...

  8. NC50500 凸多边形的划分

    题目链接 题目 题目描述 给定一个具有N个顶点的凸多边形,将顶点从1至N标号,每个顶点的权值都是一个正整数.将这个凸多边形划分成N-2个互不相交的三角形,试求这些三角形顶点的权值乘积和至少为多少. 输 ...

  9. 【Android】使用ContentProvider实现跨进程通讯

    1 前言 ​ ContentProvider 即内容提供器,是 Android 四大组件之一,为 App 存取数据提供统一的对外接口,让不同的应用之间可以共享数据. ​ 如图,Server 端通过 C ...

  10. col命令

    col命令 在很多UNIX说明文件里,都有RLF控制字符,当我们把说明文件的内容输出成纯文本文件时,控制字符会变成乱码,col命令则能有效滤除这些控制字符. 语法 col [options] 参数 - ...