话说上次分享了《关于大数据那些事》有朋友私信跟我聊了一下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方法:
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的类:
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 删除。