0x01:简介
系统中唯一的ID是我们在开发过程中遇到的一个常见问题,简单地说,生成ID的方法有很多,它们适应不同的性能。
0x02:常见方案
1.数据库自增长序列或字段
这是使用数据库的AUTO_最常见的方法INCREMENT
优点
- 代码简单,方便,性能可接受
- 数字ID有自然排序,对需要分页或排序的结果非常有帮助
缺点
- 当数据库迁移或数据库版本支持时,需要处理不同数据库的语法和实现。
- 只有一个主库可以在单个数据库、读写分离或一主多从的情况下生成,可能会导致单点故障。
- 在性能不符合要求的情况下,很难扩展。
- 分表分库会比较麻烦
优化点
- 对于主库的单点,如果有多个master库,则每个master库设置的起始数字不同,但它们的步长相同,可以是master的数量,如1、4、7、10.master2生成2、5、8、11.master3生成3、6、9、这样,集群中的唯一ID就可以有效地生成,ID生成数据库操作的负载也可以大大降低。
二、UUID
这是最常见的方法,可以使用数据库或程序生成。
优点
- 代码生成简单方便
- 生成ID的性能很好,基本上没有性能问题。
- 在数据迁移、系统数据合并或数据库变更的情况下,世界上唯一的冲突很难产生,也很容易解决。
缺点
- 没有排名,就不能保证趋势的增长
- UUID通常使用字符串存储,查询效率较低
- 存储空间相对较大,一般为16或32位
- 传输数据量大
- 不可读
三、UUID 变种
UUID可用于解决UUID不可读问题 to Int64的方法。以及
/// <summary>/// 唯一的数字序列//////按GUID获得 </summary>public static long Guidtoint64() { byte[] bytes = Guid.NewGuid().ToByteArray(); return BitConverter.ToInt64(bytes, 0);}
NHibernate为解决UUID无序问题提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,用另外6个字节表示GUID生成的时间(DateTime)。
/// <summary> /// Generate a new <see cref="Guid"/> using the comb algorithm. /// </summary> private Guid GenerateComb(){ byte[] guidArray = Guid.NewGuid().ToByteArray(); DateTime baseDate = new DateTime(1900, 1, 1); DateTime now = DateTime.Now; // Get the days and milliseconds which will be used to build //the byte string TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks); TimeSpan msecs = now.TimeOfDay; // Convert to a byte array // Note that SQL Server is accurate to 1/300th of a // millisecond so we pide by 3.333333 byte[] daysArray = BitConverter.GetBytes(days.Days); byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333)); // Reverse the bytes to match SQL Servers ordering Array.Reverse(daysArray); Array.Reverse(msecsArray); // Copy the bytes into the guid Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2); Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4); return new Guid(guidArray);}
使用上述算法进行测试,得到以下结果:作为比较,前三个是使用COMB算法获得的结果,最后12个字符串是时间顺序(统一毫秒生成的3个UUID)。如果在一段时间内再次生成,则12个字符串将大于图中显示的。后三个是直接生成的GUID。
四、Redis 生成 ID
当使用数据库生成ID性能不能满足要求时,可以使用Redis生成ID,这主要取决于Redis是单线程,也可以使用生成全球唯一的ID,可以使用RedisINCR或INCRBY来实现
Redis集群可以用来获得更高的吞吐量。如果一个集群中有5个Redis。每个Redis的值可以初始化为1、2、3、4、5,然后步长为5。每个Redis生成的ID为:
A:1,6,11,16,21B:2,7,12,17,22C:3,8,13,18,23D:4,9,14,19,24E:5,10,15,20,25
这个,任何负载到哪台机器都很难在未来进行修改。但3-5台服务器基本上可以满足,并且可以获得不同的ID。但步长和初始值必须提前需要。使用Redis集群也可以是单点故障问题。
此外,Redis更适合生成每天从0开始的流水号。例如,订单号 = 日期 + 当天的自增长号。可以每天在Redis中生成Key,用INCR累积。
优点
- 不依赖数据库,灵活方便,性能优于数据库
- 数字ID自然排序,对分页或需要排序的结果很好
缺点
- 如果系统中没有Redis,则需要引入Redis,以增加系统组件的复杂性
- 需要编码和配置的工作量相对较大
四、利用 zookeeper 生成唯一 ID
zookeper主要通过znode数据版生成序列号,可生成32位和64位的数据版本号,客户端可以使用此版本号作为唯一的序列号。
zookeper很少用来生成唯一的ID。主要是因为需要依靠zookeper,多步调用API。如果竞争激烈,需要考虑使用分布式锁。因此,在高并发的分布式环境下,性能并不理想。
五、MongoDB 的 ObjectId
MongoDB的Objectid类似于snowflake算法。它被设计成轻量级的,不同的机器可以很容易地用同样的方法生成它。MongoDB 设计从一开始就被用作分布式数据库,处理多个节点是一个核心要求。使其在分片环境中更容易生成。
六、Twittersnowflake算法 法
snowflake是twitter开源的分布式ID生成算法,结果是longID。其核心思想是:使用41bit作为毫秒,10bit作为机器ID(5bit是数据中心,5bit机器ID),12bit作为毫秒内的流量(意味着每个节点可以在每毫秒产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现代码可参考:
https://github.com/twitter/snowflake