算法概述

分布式系统中,有一些需要使用全局唯一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. python list 转二叉树 及遍历

    from __future__ import annotations from typing import Union from collections import deque class Tree ...

  2. Win12不会取代Win11!真正目标是Google

    Windows 11之后自然应该是Windows 12,但这一次不太一样. 据多个消息源确认,Windows的下一个重大版本将不会是Windows 11的直接升级版,而是更专注于云和Web,同时大力接 ...

  3. delphi TDBLookupComboboxEh 的项目设置

  4. 【架构师视角系列】QConfig配置中心系列之架构设计(一)

    目录 声明 配置中心系列文章 一.架构 基础模型 架构图 架构分层 运行规则 模块划分 Admin模块 Client模块 Server模块 二.总结 三.最后 声明 原创文章,转载请标注.https: ...

  5. NC15976 小C的周末

    题目链接 题目 题目描述 愉快的周末到了,小C和他的N-1个朋友买了M个游戏,游戏编号从1~M.每个游戏都是多人游戏,他们打算周末一起打游戏. 小C的每个朋友都决定好了要玩哪一款游戏(会有一组人打同一 ...

  6. NC17872 CSL的校园卡

    题目链接 题目 题目描述 今天是阳光明媚,晴空万里的一天,CSL早早就高兴地起床走出寝室到校园里转悠. 但是,等到他回来的时候,发现他的校园卡不见了,于是他需要走遍校园寻找它的校园卡.CSL想要尽快地 ...

  7. NVME(学习笔记三)—PMR

    PMR(Persistent Memory Region)持久性内存区域 NVM Express在2019年完成了NVMe 1.4规范的制定,新的NVMe协议带来了大量的全新特性,尤其在纠错.强化性能 ...

  8. 【Android】使用BluetoothSocket实现跨设备通讯

    1 前言 使用Socket实现跨设备通讯 中介绍了使用 WiFi 通道实现跨设备通讯,本文将介绍使用 Bluetooth 通道实现跨进程通讯. ​ 本文全部代码见→使用BluetoothSocket实 ...

  9. Spring Boot学生信息管理系统项目实战-2.字典管理和模板管理

    1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 3.项目源码 只挑重点讲,详细请看源码. 3.1 字典管理 字典管理这里分为字典的编码和名称和字典数据的增删改查. 前端页面: ...

  10. postgresql常见开发技巧

    1.数据类型 名字 描述 bigint 有符号 8 字节整数 bigserial 自增八字节整数 bit [ (n) ] 定长位串 bit varying [ (n) ] 变长位串 boolean 逻 ...