前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >雪花ID应用分享

雪花ID应用分享

原创
作者头像
谭广健
修改2022-03-22 15:17:55
1.5K0
修改2022-03-22 15:17:55
举报
文章被收录于专栏:谭广健的专栏谭广健的专栏

话说上次分享了《关于大数据那些事》有朋友私信跟我聊了一下ID增加的事情,他不是很明白为什么不自增或GUID,因为这样就十分简单并且可取,而采用所谓的雪花ID,好像很复杂。。我在这里分享一下吧,

1、首先自增ID,自增ID都是INT即是数字,1、2、3、4这样数据库一直累加。这样就会很容易给人去猜测。例如:显示公告内容index?id=3这样就很容易被人篡改为index?id=2.就可以调到第二条的内容。当然你说加权限什么的都可以,但麻烦并且一下循环就全部出来了,所以安全自己衡量吧,当然也是有方法就是将原来显示的10位进制转为64位进制;但这样做就要写多一个转换的函数。好吧!自增ID可以用但对程序要求不高能用就可以。

2、GUID,GUID就没有上面的安全问题,并且也很容易就一句Guid.NewGuid().ToString()即可,既不用担心重复又不用担心破译。但这样会导致一个问题就是无法回溯,即是说这个ID不具有什么的信息,就是随机数而已。可能这里又会问这就是我要的效果,我可以在记录里面增加日期时间自动,这样不就解决了。。当然可以you like,这样你就不用往下看了。。

既然你能在数据库中建立GUID的字段为什么不善用数据库字段充份用好,这个时候就是雪花ID上场的时候,首先雪花ID不存在像自增ID这样容易被调用因为他是18位数字,你去猜把18位猜一整天也未必能猜到,因为是有算法的。除了算法外还可以进行ID回溯,通过ID回溯就能获取相关的信息例如上一个ID、时间戳又能引出时间更能获得当天第几条记录。所以比GUID强很多,好吧。说了这么就让我们来看看怎么做,上代码。

这个是引用雪花ID方法:

代码语言:javascript
复制
var snowflakeId = new SnowFlakeId(2, 5);//2 dataCenterId 数据中心ID,5 workerId 机器ID
var id = snowflakeId.GenerateId(); //生成18位 293656184756371525
var xid= snowflakeId.AnalyzeId(293656184756371525);//解析ID 获得里面的元素

通过AnalyzeId解出来的ID将有7个元素,分别是id、Timestamp、UnixTimestamp、UtcDateTime、Sequence、DataCenterId、WorkerId。

下面这个就是雪花ID的类:

代码语言:javascript
复制
using System;
using System.Runtime.InteropServices;

namespace TanGuangjian_Qcloud
{
    public class SnowFlakeId
    {

        /// <summary>
        /// 数据中心Id长度
        /// </summary>
        public int DataCenterIdBitLength { get; private set; }

        /// <summary>
        /// 服务器Id长度
        /// </summary>
        public int WorkerIdBitLength { get; private set; }

        /// <summary>
        /// 自增长序号长度
        /// </summary>
        public int SequenceBitLength { get; private set; }

        /// <summary>
        /// 时间戳长度
        /// </summary>
        public int TimestampBitLength { get; private set; }

        /// <summary>
        /// 数据中心Id最大值
        /// </summary>
        public long MaxDataCenterId { get; private set; }

        /// <summary>
        /// 服务器Id最大值
        /// </summary>
        public long MaxWorkerId { get; private set; }

        /// <summary>
        /// 自增序号最大值
        /// </summary>
        public long MaxSequence { get; private set; }

        /// <summary>
        /// 时间戳最大值
        /// </summary>
        public long MaxTimestamp { get; private set; }

        public DateTime MaxUtcTime { get { return Jan1st1970.AddMilliseconds(MaxTimestamp + StartTimestamp); } }

        /// <summary>
        /// 上次生成ID
        /// </summary>
        public SnowIdClass LastSnowId { get; private set; }

        /// <summary>
        /// 数据中心Id
        /// </summary>
        public long DataCenterId { get; private set; }

        /// <summary>
        /// 服务器Id
        /// </summary>
        public long WorkerId { get; private set; }


        public SnowFlakeId() : this(16, 0, 0, 0, 0)
        {

        }

        public SnowFlakeId(long dataCenterId, long workerId) : this(12, 5, 5, dataCenterId, workerId)
        {

        }

        public SnowFlakeId(int sequenceBitLength, int datacenterIdBitLength, int workerIdBitLength, long dataCenterId, long workerId)
        {
            this.SequenceBitLength = sequenceBitLength;
            this.DataCenterIdBitLength = datacenterIdBitLength;
            this.WorkerIdBitLength = workerIdBitLength;
            this.TimestampBitLength = Marshal.SizeOf<long>() - (SequenceBitLength + DataCenterIdBitLength + WorkerIdBitLength);

            this.MaxSequence = GetBitLengthMaxValue(this.SequenceBitLength);
            this.MaxDataCenterId = GetBitLengthMaxValue(this.DataCenterIdBitLength);
            this.MaxWorkerId = GetBitLengthMaxValue(this.WorkerIdBitLength);
            this.MaxTimestamp = GetBitLengthMaxValue(this.TimestampBitLength);

            if (dataCenterId > MaxDataCenterId) throw new OverflowException(nameof(dataCenterId));
            if (workerId > MaxWorkerId) throw new OverflowException(nameof(workerId));

            this.DataCenterId = dataCenterId;
            this.WorkerId = workerId;

            this.LastSnowId = new SnowIdClass()
            {
                Timestamp = 0L,
                Sequence = 0L,
                DataCenterId = this.DataCenterId,
                WorkerId = this.WorkerId,
            };

        }

        /// <summary>
        /// 生成ID
        /// </summary>
        /// <returns></returns>
        public long GenerateId()
        {
            lock (LastSnowId)
            {
                long timestamp = GetCurrentTimestamp();
                if (timestamp > LastSnowId.Timestamp) //时间戳改变,毫秒内序列重置
                {
                    LastSnowId.Sequence = 0L;
                }
                else if (timestamp == LastSnowId.Timestamp) //如果是同一时间生成的,则进行毫秒内序列
                {
                    LastSnowId.Sequence += 1L;
                    if (LastSnowId.Sequence > MaxSequence) //毫秒内序列溢出
                    {
                        LastSnowId.Sequence = 0L;
                        timestamp = GetNextTimestamp(LastSnowId.Timestamp); //阻塞到下一个毫秒,获得新的时间戳
                    }
                }
                else   //当前时间小于上一次ID生成的时间戳,证明系统时钟被回拨,此时需要做回拨处理
                {
                    LastSnowId.Sequence += 1L;
                    timestamp = LastSnowId.Timestamp; //停留在最后一次时间戳上,等待系统时间追上后即完全度过了时钟回拨问题。
                    if (LastSnowId.Sequence > MaxSequence)  //毫秒内序列溢出
                    {
                        LastSnowId.Sequence = 0L;
                        timestamp = LastSnowId.Timestamp + 1L;   //直接进位到下一个毫秒                          
                    }
                    //throw new Exception(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp));
                }
                LastSnowId.Timestamp = timestamp;       //上次生成ID的时间戳
                LastSnowId.Id = GenerateId(LastSnowId);
                return LastSnowId.Id;
            }
        }

        /// <summary>
        /// 生成ID
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <param name="sequence">序号</param>
        /// <returns></returns>
        public long GenerateId(SnowIdClass snowId)
        {
            //移位并通过或运算拼到一起组成64位的ID
            var id = (snowId.Timestamp << (SequenceBitLength + DataCenterIdBitLength + WorkerIdBitLength))
                    | (snowId.Sequence << (DataCenterIdBitLength + WorkerIdBitLength))
                    | (snowId.DataCenterId << WorkerIdBitLength)
                    | (snowId.WorkerId);

            return id;
        }
        
        /// <summary>
        /// 生成ID,传时间戳
        /// </summary>
        /// <returns>
        ///  long Timestamp;
        ///  var TID = snowflakeId.GIdTimestamp(out Timestamp);
        ///</returns>
        public long GIdTimestamp(out long Timestamp)
        {
            lock (LastSnowId)
            {
                long timestamp = GetCurrentTimestamp();
                if (timestamp > LastSnowId.Timestamp) //时间戳改变,毫秒内序列重置
                {
                    LastSnowId.Sequence = 0L;
                }
                else if (timestamp == LastSnowId.Timestamp) //如果是同一时间生成的,则进行毫秒内序列
                {
                    LastSnowId.Sequence += 1L;
                    if (LastSnowId.Sequence > MaxSequence) //毫秒内序列溢出
                    {
                        LastSnowId.Sequence = 0L;
                        timestamp = GetNextTimestamp(LastSnowId.Timestamp); //阻塞到下一个毫秒,获得新的时间戳
                    }
                }
                else   //当前时间小于上一次ID生成的时间戳,证明系统时钟被回拨,此时需要做回拨处理
                {
                    LastSnowId.Sequence += 1L;
                    timestamp = LastSnowId.Timestamp; //停留在最后一次时间戳上,等待系统时间追上后即完全度过了时钟回拨问题。
                    if (LastSnowId.Sequence > MaxSequence)  //毫秒内序列溢出
                    {
                        LastSnowId.Sequence = 0L;
                        timestamp = LastSnowId.Timestamp + 1L;   //直接进位到下一个毫秒                          
                    }
                    //throw new Exception(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp));
                }
                LastSnowId.Timestamp = timestamp;       //上次生成ID的时间戳
                LastSnowId.Id = GenerateId(LastSnowId);
                Timestamp = timestamp;
                return LastSnowId.Id;
            }
        }

        /// <summary>
        /// 解析雪花ID
        /// </summary>
        /// <returns></returns>
        public SnowIdClass AnalyzeId(long Id)
        {
            var snowIdClass = new SnowIdClass()
            {
                Id = Id,
                Timestamp = Id >> (SequenceBitLength + DataCenterIdBitLength + WorkerIdBitLength),
                Sequence = Id >> (DataCenterIdBitLength + WorkerIdBitLength) & MaxSequence,
                DataCenterId = Id >> WorkerIdBitLength & MaxDataCenterId,
                WorkerId = Id & MaxWorkerId
            };
            return snowIdClass;
        }

        private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        // 开始时间戳 
        public static readonly long StartTimestamp = (long)(new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc) - Jan1st1970).TotalMilliseconds;

        /// <summary>
        /// 阻塞到下一个毫秒,直到获得新的时间戳
        /// </summary>
        /// <param name="lastTimestamp">上次生成ID的时间戳</param>
        /// <returns>当前时间戳</returns>
        private static long GetNextTimestamp(long lastTimestamp)
        {
            long timestamp = GetCurrentTimestamp();
            while (timestamp <= lastTimestamp)
            {
                timestamp = GetCurrentTimestamp();
            }
            return timestamp;
        }

        /// <summary>
        /// 获取当前时间戳
        /// </summary>
        /// <returns></returns>
        private static long GetCurrentTimestamp()
        {
            return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds - StartTimestamp;
        }

        private static long GetBitLengthMaxValue(int length)
        {
            return -1L ^ (-1L << length);
        }


        /// <summary>
        /// 雪花ID结构
        /// </summary>
        public class SnowIdClass
        {
            /// <summary>
            /// ID
            /// </summary>
            public long Id { get; set; }
            /// <summary>
            /// 时间戳
            /// </summary>
            public long Timestamp { get; set; }

            /// <summary>
            /// Unix时间戳
            /// </summary>
            public long UnixTimestamp { get { return Timestamp + StartTimestamp; } }

            /// <summary>
            /// 生成时间
            /// </summary>
            public DateTime UtcDateTime { get { return Jan1st1970.AddMilliseconds(UnixTimestamp); } }

            /// <summary>
            /// 自增长序号
            /// </summary>
            public long Sequence { get; set; }

            /// <summary>
            /// 数据中心Id
            /// </summary>
            public long DataCenterId { get; set; }

            /// <summary>
            /// 服务器Id
            /// </summary>
            public long WorkerId { get; set; }

        }

    }
}

最后就除了上面的雪花ID方法外其实还有一种比较便捷的就是时间戳+随机数,这样也能生成一个唯一ID并且也很方便查询;但这样不好回溯并且进行数据分布就麻烦了。各花入各眼,但别再用老掉牙的自增ID及GUID了。。最后记得设置索引和主键。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档