C# 分布式自增ID算法snowflake(雪花算法)

news2025/1/18 13:51:40

文章目录

  • 1. 概述
  • 2. 结构
  • 3. 代码
    • 3.1 IdWorker.cs
    • 3.2 IdWorkerTest.cs (测试)

1. 概述

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

该项目地址为:https://github.com/twitter/snowflake 是用 Scala实现的

参考:

  • C# 分布式自增ID算法snowflake(雪花算法) - 五维思考 - 博客园 (cnblogs.com)

  • C#雪花Id_c# 雪花id-CSDN博客

2. 结构

第1位第2位第3位第4位第5位
位数时间戳(ms)数据中心ID(DatacenterId )工作节点ID (MachineId )自增序列号
0000000000000000000000000000000000000000000
  • 第1位:未使用
  • 第2位:接下来的41位为毫秒级时间(41位的长度可以使用69年),用毫秒级的时间戳来表示自1970年1月1日 00:00:00 GMT以来的时间。
  • 第3-4位:用来区分不同的数据中心 datacenterIdmachineId,可根据实际情况分配,最多可容纳1024个数据中心(2^10=10位的长度最多支持部署1024个节点),也可以设置成5位,最大节点是32个。
  • 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

一共加起来刚好64位,为一个Long型。(转换成字符串长度为18)

snowflake生成的ID整体上按照时间自增排序,并且 整个分布式 系统内不会产生ID碰撞(由datacentermachineId作区分),并且效率较高。据说:snowflake每秒能够产生26万个ID

注意:

  • 在实际使用中,需要根据不同的分布式环境配置合适的数据中心ID和工作节点ID,以保证生成的雪花Id的唯一性和顺序性。
  • 其中 dataCenterIdworkerId 分别是数据中心和工作节点的标识,该生成器依赖于数据中心ID和工作节点ID两个参数进行初始化。具体的生成过程是根据当前时间戳、数据中心ID、工作节点ID和自增序列号,通过位运算组合生成一个64位的唯一标识。

3. 代码

3.1 IdWorker.cs

using System;
/// <summary>
/// Twitter的分布式自增ID雪花算法
/// </summary>
public class IdWorker
{
    //起始的时间戳
    private static long START_STMP = 1480166465631L;

    //每一部分占用的位数
    private static int SEQUENCE_BIT = 12; //序列号占用的位数
    private static int MACHINE_BIT = 5;   //机器标识占用的位数
    private static int DATACENTER_BIT = 5;//数据中心占用的位数

    //每一部分的最大值
    private static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    //每一部分向左的位移
    private static int MACHINE_LEFT = SEQUENCE_BIT;
    private static int DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private static int TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId = 1;  //数据中心
    private long machineId = 1;     //机器标识
    private long sequence = 0L; //序列号
    private long lastStmp = -1L;//上一次时间戳

    #region 单例:完全懒汉
    private static readonly Lazy<IdWorker> lazy = new Lazy<IdWorker>(() => new IdWorker());
    public static IdWorker Singleton { get { return lazy.Value; } }
    private IdWorker() { }
    #endregion

    public IdWorker(long cid, long mid)
    {
        if (cid > MAX_DATACENTER_NUM || cid < 0) throw new Exception($"中心Id应在(0,{MAX_DATACENTER_NUM})之间");
        if (mid > MAX_MACHINE_NUM || mid < 0) throw new Exception($"机器Id应在(0,{MAX_MACHINE_NUM})之间");
        datacenterId = cid;
        machineId = mid;
    }

    /// <summary>
    /// 产生下一个ID
    /// </summary>
    /// <returns></returns>
    public long nextId()
    {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) throw new Exception("时钟倒退,Id生成失败!");

        if (currStmp == lastStmp)
        {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) currStmp = getNextMill();
        }
        else
        {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT       //时间戳部分
                      | datacenterId << DATACENTER_LEFT       //数据中心部分
                      | machineId << MACHINE_LEFT             //机器标识部分
                      | sequence;                             //序列号部分
    }

    private long getNextMill()
    {
        long mill = getNewstmp();
        while (mill <= lastStmp)
        {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp()
    {
        return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
    }
}



3.2 IdWorkerTest.cs (测试)

使用

IdWorker idworker = IdWorker.Singleton;
Console.WriteLine(idworker.nextId());

测试

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Test.Simple
{
    public static class IdWorkerTest
    {
        public static void Test()
        {
            /***
             * 
             *  两种测试方法,均为500并发,生成5000个Id:
             *  Machine1() 模拟1台主机,单例模式获取实例
             *  Machine5() 模拟5台主机,创建5个实例
             */
            
            Machine1();

            Machine2();

            Machine5();
        }

        public static void Machine1()
        {

            int cid = 1;
            int mid = 15;
            Console.WriteLine("雪花ID -- IdWorkerTest -- 模拟1台主机( 数据中心{0} - 机器节点{1}): ", cid, mid);
            IdWorker idworker = new IdWorker(cid, mid);
            Console.WriteLine(idworker.nextId());

            cid = 2;
            mid = 10;
            Console.WriteLine("雪花ID -- IdWorkerTest -- 模拟1台主机( 数据中心{0} - 机器节点{1}): ", cid, mid);
            idworker = new IdWorker(cid, mid);
            Console.WriteLine(idworker.nextId());
        }

        public static void Machine2()
        {
            Console.WriteLine("雪花ID -- IdWorkerTest -- 模拟1台主机 : ");
            for (int j = 0; j < 500; j++)
            {
                Task.Run(() =>
                {
                    IdWorker idworker = IdWorker.Singleton;
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine(idworker.nextId());
                    }
                });
            }
        }

        public static void Machine5()
        {
            Console.WriteLine("雪花ID -- IdWorkerTest -- 模拟5台主机 : ");
            List<IdWorker> workers = new List<IdWorker>();
            Random random = new Random();
            for (int i = 0; i < 5; i++)
            {
                workers.Add(new IdWorker(1, i + 1));
            }
            for (int j = 0; j < 500; j++)
            {
                Task.Run(() =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        int mid = random.Next(0, 5);
                        Console.WriteLine(workers[mid].nextId());
                    }
                });
            }
        }
    }
}


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1572192.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

画图理解JVM相关内容

文章目录 1. JVM视角下&#xff0c;内存划分2. 类内存分布硬核详解1. 获取堆内存参数2. 扫描堆内存&#xff0c;定位实例3. 查看实例所在地址的数据4. 找到实例所指向的类信息的地址5. 查看class信息6. 结论 3. Java的对象创建流程4. 垃圾判别算法4.1 引用计数法4.2 可达性分析…

DRF:认证(单视图或全局设置认证方案和源码分析、设置多个认证方案、如何设置不允许匿名访问)

概念&#xff1a;request.user、request.auth、认证方案authentication_classes 官网原文&#xff1a; 验证始终在视图的最开始进行&#xff0c;在执行权限和限制检查之前以及允许任何其他代码继续执行之前。 request.user 属性通常被设置为contrib.auth 包中 User 类的一个…

数学知识--(欧拉函数,快速幂,扩展欧几里得算法)

本文用于记录个人算法竞赛学习&#xff0c;仅供参考 目录 一.欧拉函数 二.欧拉函数模板 三.用筛法求每个数的欧拉函数 四.快速幂 五.扩展欧几里得算法 六.用扩展欧几里得算法求线性同余方程 一.欧拉函数 即有一个数n&#xff0c; n通过质因数分解得到 通过欧拉函数有 证明&…

Java 程式 main 方法传参数

Java 程式运行时如果需要传递参数时&#xff0c;常用的方法有两种&#xff1a; 使用 Program Arguments 来传递值使用 VM Arguments 来传递值 1、使用 Program Arguments 来传递值 使用 Program Arguments 来传递值时&#xff0c;main 方法的写法如下&#xff1a; public st…

Linux 常用命令(持续更新中...)

1. ls 查看文件列表命令 语法&#xff1a; ls [-a -l -h] [Linux路径] -a -l -h 是可选的选项 &#xff08;-h需配合-l命令一起使用&#xff09;Linux路径是此命令可选的参数 ls #查看当前目录所有非隐藏文件(平铺方式显示) ls -a #查看当前目录下所有文件 …

Web 后台项目,权限如何定义、设置、使用:菜单权限、按钮权限 ts element-ui-Plus

Web 后台项目&#xff0c;权限如何定义、设置、使用&#xff1a;菜单权限、按钮权限 ts element-ui-Plus 做一个后台管理项目&#xff0c;里面需要用到权限管理。这里说一下权限定义的大概&#xff0c;代码不多&#xff0c;主要讲原理和如何实现它。 一、权限管理的原理 权限…

Polardb MySQL 产品架构及特性

一、产品概述; 1、产品族 参考&#xff1a;https://edu.aliyun.com/course/3121700/lesson/341900000?spma2cwt.28120015.3121700.6.166d71c1wwp2px 2、polardb mysql架构优势 1&#xff09;大容量高弹性&#xff1a;最大支持存储100T&#xff0c;最高超1000核CPU&#xff0…

55、美国德克萨斯大学奥斯汀分校、钱德拉家族电气与计算机工程系:通过迁移学习解决BCI个体差异性[不得不说,看技术还得是老美]

2024年2月5日跨被试最新文章&#xff1a; 德州州立大学奥斯汀分校研究团队最近的一项研究成果&#xff0c;通过非侵入式的脑机接口&#xff0c;可以让被试不需要任何校准就可以使用脑机接口设备&#xff0c;这意味着脑机接口具备了大规模被使用的潜力。 一般来说&#xff0c;…

哈希-字母异位词分组

字母异位词&#xff0c;词频一样&#xff0c;但是顺序不一样&#xff0c;可以进行排序&#xff0c;获取一个key&#xff0c;放在map中即可。 class Solution {public List<List<String>> groupAnagrams(String[] strs) {Map<String, List<String>> ma…

彩虹聚合DNS管理系统v1.0全新发布

聚合DNS管理系统&#xff08;https://github.com/netcccyun/dnsmgr&#xff09;可以实现在一个网站内管理多个平台的域名解析&#xff0c;目前已支持的域名平台有&#xff1a;阿里云、腾讯云、华为云、西部数码、CloudFlare。本系统支持多用户&#xff0c;每个用户可分配不同的…

STM32CubeIDE基础学习-舵机控制实验

STM32CubeIDE基础学习-舵机控制实验 文章目录 STM32CubeIDE基础学习-舵机控制实验前言第1章 硬件介绍第2章 工程配置2.1 基础工程配置部分2.2 生成工程代码部分 第3章 代码编写第4章 实验现象总结 前言 SG90、MG996舵机在机器人领域用得非常多&#xff0c;因为舵机有内置控制电…

利用nginx-http-flv-module实现三种直播

目录 一、说明 二、目标 三、实现 四、直播地址 一、说明 此文在《流媒体服务器的搭建(支持hls)》《搭建nginx-http-flv-module直播系统》之后编写,很多详细内容需要参考它。 流媒体服务器的搭建(支持hls)

【面经】interrupt()、interrupted()和isInterrupted()的区别与使用

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 interrupt方法 如果打断线程正在sleep&#xff0c;wait&#xff0c;join会导致被打断的线程抛出InterruptedException&#xff0c;并清除打断标记。如…

商标“五分法”,如何起名显著性更强通过率更高!

1976年在Abercrombie一案美国判例中提出的商标五分法&#xff0c; 基本上在全球范围内得到认可和共识&#xff0c;普推知产老杨平常检索时&#xff0c;我国一些专家相关的论文及专著和判例中也会经常涉及到。 商标五分法主要是把商标分成个五种类型&#xff0c; 通用的&#xf…

Linux:数据链路层

文章目录 路由表数据链路层分片mac帧报头ARP协议ARP的周边话题 路由表 当主机a想要发送消息到主机b&#xff0c;这一整个过程中&#xff0c;数据报文在进行传输的过程实际上是一跳一跳的过去的&#xff0c;而报文可能会经过公网进行传递&#xff0c;本质上这些网络都是靠对应的…

【Arthas案例】某应用依赖两个GAV-classifier不同的snakeyaml.jar,引起NoSuchMethodError

多个不同的GAV-classifier依赖冲突&#xff0c;引起NoSuchMethodError Maven依赖的三坐标体系GAV(G-groupId&#xff0c;A-artifactId&#xff0c;V-version) classifier通常用于区分从同一POM构建的具有不同内容的构件物&#xff08;artifact&#xff09;。它是可选的&#xf…

美食分享|基于Springboot和vue的地方美食分享网站系统设计与实现(源码+数据库+文档)

地方美食分享网站系统 目录 基于Springboot和vue的地方美食分享网站系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、前台&#xff1a; 2、后台 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介…

四信AI智能视频边缘分析盒+传感云平台,开启食品安全智慧监管新模式

方案背景 民以食为天&#xff0c;食品是人类生存必备的物质之一&#xff0c;食品生产安全关乎每个人的生命健康与社会可持续发展。在食品生产过程中&#xff0c;如何实现安全、健康生产是监管机构首要考虑因素&#xff0c;也是当今社会必须共同关注与努力的方向。 监管机构必…

数据库性能优化入门:数据库分片初探

数据库分片是一种用于提升数据库性能的架构模式&#xff0c;选择正确的分片策略和实施方式对于提高数据库性能和应对大规模数据挑战至关重要。 本文介绍了数据库分片的定义、原理和实施方法。文章解释了数据库分片是如何通过将数据切分、分散存储在多个服务器上来提升性能&…

【Kotlin】委托模式

1 委托模式简介 委托模式的类图结构如下。 对应的 Kotlin 代码如下。 fun main() {var baseImpl BaseImpl()var baseWrapper BaseWrapper(baseImpl)baseWrapper.myFun1() // 打印: BaseImpl, myFun1baseWrapper.myFun2() // 打印: BaseImpl, myFun2 }interface Base {fun my…