专栏首页程序员的SOD蜜每秒生成一千万个【可视有序】分布式ID的简单方案 每秒不重复ID生成数:

每秒生成一千万个【可视有序】分布式ID的简单方案 每秒不重复ID生成数:

去年做了一个产品,会经常导入导出大量的外部数据,这些数据的ID有的是GUID类型,有的是字符串,也有的是自增。GUID类型没有顺序,结果要排序得借助其它业务字段,整体查询效率比较低;字符串ID本来是用来转换GUID的或者数字ID的,结果有些字符串ID不符合规范,常常有特殊数据需要处理;自增主键ID的数据导入合并经常有冲突。

为了避免GUID主键的“索引页分裂”问题,提高查询效率,同时为了解决分布式环境下的数据导入合并问题,强烈需要一种分布式的,有序的ID生成方案。我参考了雪花ID(Twitter-Snowflake,64位自增ID算法)实现方案,设计一个更容易肉眼观察数值连续有序的分布式ID方案。

跟雪花ID方案一样,都是使用时间数据做为生成ID的基础,不同的在于对数据的具体处理方式。另外,为了确保每台机器ID的不同,可以配置指定此ID,在应用程序配置文件中如下配置:

<!--分布式ID标识,3位整数,范围101-999 大小--> 
<add key="SOD_MachineID" value="101"/>

如果不配置分布式ID,默认将根据当前机器IP随机生成3位分布式机器ID。

该算法的实现比雪花算法简单不少,详细的不多说,先直接看代码:

        /// <summary>
        /// 获取一个新的有序GUID整数
        /// </summary>
        /// <param name="dt">当前时间</param>
        /// <param name="haveMs">是否包含毫秒,如果不包含,将使用3位随机数代替</param>
        /// <returns></returns>
        protected internal static long InnerNewSequenceGUID(DateTime dt, bool haveMs)
        {
            //线程安全的自增并且不超过最大值10000
            int countNum = System.Threading.Interlocked.Increment(ref SeqNum);
            if (countNum >= 10000)
            {
                while (Interlocked.Exchange(ref signal, 1) != 0)//加自旋锁
                {
                    //黑魔法
                }
                //进入临界区
                if (SeqNum >= 10000)
                {
                    SeqNum = 0;
                    //达到1万个数后,延迟10毫秒,重新取系统时间,避免重复
                    Thread.Sleep(10);
                    dt = DateTime.Now;
                }
                countNum = System.Threading.Interlocked.Increment(ref SeqNum);
                //离开临界区
                Interlocked.Exchange(ref signal, 0);  //释放锁
            }

            //日期以 2017.3.1日为基准,计算当前日期距离基准日期相差的天数,可以使用20年。
            //日期部分使用4位数字表示
            int days = (int)dt.Subtract(baseDate).TotalDays;
            //时间部分表示一天中所有的秒数,最大为 86400秒,共5位
            //日期时间总位数= 4(日期)+5(时间)+3(毫秒)=12
            int times = dt.Second + dt.Minute * 60 + dt.Hour * 3600;
            //long 类型最大值 9223 3720 3685 4775 807
            //可用随机位数= 19-12=7
            long datePart = ((long)days + 1000) * 1000 * 1000 * 1000 * 100;
            long timePart = (long)times * 1000 * 1000;
            long msPart = 0;
            if (haveMs)
            {
                msPart = (long)dt.Millisecond ;
            }
            else

            {
                msPart = new Random().Next(100, 1000);
            }
            long dateTiePart = (datePart + timePart + msPart*1000) * 10000;

            int mid = MachineID * 10000;
            //得到总数= 4(日期)+5(时间)+3(毫秒)+7(GUID)
            long seq = dateTiePart + mid;
            
            return seq + countNum; ;
        }

注意:上面使用了一个模拟的自旋锁,用来在末尾的顺序号超过1万的时候归零重新计算,并且睡眠10毫秒从而根本上杜绝重复ID。

每秒不重复ID生成数:

从上面的程序代码中,得知 ID总数= 4位(日期)+5位(时间)+3位(毫秒)+7位(GUID)。 其中,7位(GUID)中,除去前3位的分布式机器ID,剩余4位有序数字,可以表示1万个数字。 所以,该方面每毫秒最大可以生成1万个不重复的ID数,每秒最大可以生成1千万个不重复ID。 当然这是理论大小,实际上受到当前机器的计算能力限制。

该方法进行了再次封装,用于在不同情况下分别使用:

      /// <summary>
       /// 生成一个新的在秒级别有序的长整形“GUID”,在一秒内,数据比较随机,线程安全,
       /// 但不如NewUniqueSequenceGUID 方法结果更有序(不包含毫秒部分)
       /// </summary>
       /// <returns></returns>
       public static long NewSequenceGUID()
       {
           return UniqueSequenceGUID.InnerNewSequenceGUID(DateTime.Now,false);
       }

       /// <summary>
       /// 生成一个唯一的更加有序的GUID形式的长整数,在一秒内,一千万个不重复ID,线程安全。可用于严格有序增长的ID
       /// </summary>
       /// <returns></returns>
       public static long NewUniqueSequenceGUID()
       {
           return UniqueId.NewID();
       }

        /// <summary>
        /// 当前机器ID,可以作为分布式ID,如果需要指定此ID,请在应用程序配置文件配置 SOD_MachineID 的值,范围大于100,小于1000.
        /// </summary>
        /// <returns></returns>
        public static int CurrentMachineID()
        {
            return UniqueSequenceGUID.GetCurrentMachineID();
        }

最后,像下面这样使用即可:

        Console.WriteLine("当前机器的分布式ID:{0}",CommonUtil.CurrentMachineID());
            Console.WriteLine("测试分布式ID:秒级有序");
            for (int i= 0; i < 50; i++)
            {
                Console.Write(CommonUtil.NewSequenceGUID());
                Console.Write(",");
            }
            Console.WriteLine();
            Console.WriteLine("测试分布式ID:唯一且有序");
            for (int i = 0; i < 50; i++)
            {
                Console.Write(CommonUtil.NewUniqueSequenceGUID());
                Console.Write(",");
            }
            Console.WriteLine();

下面是生成的ID数字示例:

当前机器的分布式ID:832

注:本文生成ID的方法已经在产品中大量使用,运行情况良好。

要使用本程序,你可以Nuget 下载SOD的程序包(支持.NET 2.0项目),然后像本文示例这样使用即可:

Install-Package PDF.NET.SOD.Core

获取SOD的源码,请Fork我们的Github:

https://github.com/znlgis/sod

源码位置在 https://github.com/znlgis/sod/tree/master/src/SOD 目录下。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • PDF.NET数据开发框架实体类操作实例(for PostgreSQL,并且解决自增问题) PDF.NET数据开发框架实体类操作实例(MySQL)

    本篇是 PDF.NET数据开发框架实体类操作实例(MySQL) 的姊妹篇,两者使用了同一个测试程序,不同的只是使用的类库和数据库不同,下面说说具体的使用过程。 ...

    用户1177503
  • .net异步性能测试(包括ASP.NET MVC WebAPI异步方法)

    很久没有写博客了,今年做的产品公司这两天刚刚开了发布会,稍微清闲下来,想想我们做的产品还有没有性能优化空间,于是想到了.Net的异步可以优化性能,但到底能够提升...

    用户1177503
  • 使用“消息服务框架”(MSF)实现分布式事务的三阶段提交协议(电商创建订单的示例)

    1,示例解决方案介绍 在上一篇 《消息服务框架(MSF)应用实例之分布式事务三阶段提交协议的实现》中,我们分析了分布式事务的三阶段提交协议的原理,现在我们来看看...

    用户1177503
  • mysql left( right ) join使用on 与where 筛选的差异

    有这样的一个问题mysql查询使用mysql中left(right)join筛选条件在on与where查询出的数据是否有差异。 可能只看着两个关键字看不出任...

    java达人
  • Google Analytics里的各种ID

    默认情况下,Google Analytics 会为每台设备分配一个唯一的 Client ID,并在报告中将每个 Client ID 视为一个唯一身份用户。Cli...

    GA小站
  • SAP CRM产品主数据里的七种ID

    3实际上就是PRODUCT_ID了。除了4之外,其他6种ID都能作为alternative ID被查找到。

    Jerry Wang
  • 关于基因ID的二三事

    对于一个基因而言,我们经常使用的,同时在文章里面能看到的还是基因名。例如: TP53, RNF180。这样的名字,是这个基因功能+编号的简写。例如TP53就是T...

    医学数据库百科
  • 漫画:什么是SnowFlake算法?

    UUID是通用唯一识别码 (Universally Unique Identifier),在其他语言中也叫GUID,可以生成一个长度32位的全局唯一识别码。

    用户5927304
  • Google Analytics里面的几个用户标识

    默认情况下,Google Analytics 会为每台设备分配一个唯一的 Client ID,并在报告中将每个 Client ID 视为一个唯一身份用户。Cli...

    GA小站
  • 细聊分布式ID生成方法

    一、需求缘起 几乎所有的业务系统,都有生成一个记录标识的需求,例如: (1)消息标识:message-id (2)订单标识:order-id (3)帖子标识:t...

    架构师之路

扫码关注云+社区

领取腾讯云代金券