系统唯一id是我们在设计阶段常常遇到的问题。在复杂的分布式系统中,几乎都需要对大量的数据和消息进行唯一标识。在设计初期,我们需要考虑日后数据量的级别,如果可能会对数据进行分库分表,那么就需要有一个全局唯一id来标识一条数据或记录。生成唯一id的策略有多种,但是每种策略都有它的适用场景、优点以及局限性。
id一般是数据库的主键,数据库上会建立聚集索引,即在物理存储上以这个字段排序。这个记录标识上的查询往往又有分页或者排序的业务需求,如果再增加一个time字段以其建立普通索引则访问效率低(因为普通索引存储的是实际记录的指针)。如果ID在生成时就能基本保证时间有序,则可以省去这个字段。
故这个ID一般具有以下特点:
这个不用多说,使用数据库自带的字段自增功能
优点
缺点
冗余主库,避免写入单点数据水平切分,保证各主库生成的ID不重复(由1个写库变成3个写库,每个写库设置不同的 auto_increment 初始值,以及相同的增长步长)。
改进后的架构保证了可用性但数据库的写压力依然很大,每次生成ID都要访问数据库,而且丧失了ID生成的“绝对递增性”:先访问DB 01生成0,3,再访问DB 02生成1,可能导致在非常短的时间内,ID生成不是绝对递增的(这个问题不大,目标是趋势递增,不是绝对递增)
uuid是一种常见的本地生成ID的方法,利用程序生成,减少耗时。(不管是通过数据库,还是通过服务来生成ID,业务方Application都需要进行一次远程调用,比较耗时)
UUID () 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。在这样的情况下,就不需考虑数据库建立时的名称重复问题。
UUID的标准形式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID。
例如 Java中:
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
运行结果如下:
优点
缺点
为了解决UUID不可读, 可以使用UUID to Int64的方法 。
为了解决UUID无序的问题, NHibernate在其主键生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,用另6个字节表示GUID生成的时间(DateTime)。
雪花ID生成的是一个64位的二进制正整数,然后转换成10进制的数。64位二进制数由如下部分组成:
成员部分 | 说明 |
---|---|
1位标识符 | 始终是0,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。 |
41位时间戳 | 41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截 )得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的。 |
10位机器标识码 | 可以部署在1024个节点,如果机器分机房(IDC)部署,这10位可以由 5位机房ID + 5位机器ID 组成。 |
12位序列 | 毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号 |
优点
缺点