1、使用背景
在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识
在美团点评的金融、支付、餐饮、酒店和猫眼电影等产品系统中数据日渐增长,对数据库分库分表后需要唯一ID来标识一条数据或消息;
像订单、优惠券、电影票等都需要有唯一的ID作标识。
此时就需要一个能够生成全局唯一ID的系统是非常有必要的
。
2、ID生成规则要求
全局唯一
- 趋势递增
– 在MySQL的InnoDB引擎中使用的是聚集索引。由于多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上面应该尽量使用有序的主键保证写入性能。 - 单调递增
– 保证下一个ID大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。 - 信息安全
– 如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按顺序访问指定URL即可;如果是订单号就更危险了,竞争对手通过某些订单ID可以直接知道我们一天的单量。所以在一些应用场景下,需要ID无规则不规则。 - 含时间戳
– 在开发中快速了解这个分布式id的生成时间。
3、ID号生成系统的可用性要求
高可用
– 发一个获取分布式id的请求,该服务器就要保证99.9999%的情况下给我们创建一个唯一分布式id。
低延迟
– 发一个获取分布式id的请求,服务器回复要快(极速)
高QPS
– 假如并发一口气10万个创建分布式id的请求打过来,服务器要顶得住且能够一下子成功创建10万个分布式id。
4、ID生成的一般通用规则
(1)UUID
- UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符。示例:5f26d8c0-6e09-43cf-9300-6fda1d11985c
- 优点:性能非常高,本地生成,没有网络消耗;实现了唯一性。
- 缺点:
– 无序,无法预测它的生成顺序,不能生成递增有序数字。
– 主键,ID作为主键时在特定环境中会存在一些问题。例如在做DB主键的场景下,UUID就非常不适用MySQL官方有明确的建议主键要尽量越短越好。
– 索引,B+树索引的分裂。分布式id作为主键,而主键包含索引,mysql的索引是通过B+树实现的,每一次新的UUID数据的插入,为了查询优化,都会对索引底层的B+树进行修改,又因为UUID是无序的,所以每一次UUID数据的插入会对主键底层的B+树进行很大的修改,这样会导致一些中间节点断裂,也会创造出很多不饱和的节点,这样大大降低了数据库的插入操作。
(2) 数据库自增主键
- 在分布式里面,数据库的自增ID机制主要原理是:数据库自增ID和mysql数据库的replace into实现的。
- 这里replace into跟insert功能类似,不同点在于:replace into首先尝试插入数据列表中,如果发现表中已经存在此行数据(根据主键或唯一索引判断)则先删除,再插入;否则则可以直接插入新数据。
replace into的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据
。 - 优点:实现了唯一性、趋势递增。
- 缺点(集群情况):
– 系统水平拓展比较困难,比如定义好了步长和机器台数之后,如果需要新添加机器怎么做?假设现在 只有一台机器发号是1,2,3,4,5(步长=1),这时候需要扩容一台新机器:将第二台机器的初值设置的比第一台多很多。当扩容机器达到100台呢?就会出现初值设置会非常大。
– 数据库压力还是很大,每次获取id都得读写一次数据库,非常影响性能,不符合分布式ID系统低延迟、高QPS的硬性要求。
(3)基于Redis生成全局策略主键
- 因为Redis是单线程的,天生就能够保证原子性,可以使用原子操作INCR和INCRBY来实现。
- Redis集群可以获取更高的吞吐量。假如一个集群有台Redis,可以初始化每台Redis的值分别为1,2,3,4,5,然后步长都是5: – 各个Redis生成的ID为:
• A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
- 缺点:需要额外配置和维护Redis集群等工作才能获得id,比较麻烦。
5、雪花算法
Twitter的分布式自增ID算法snowflake
- ID按照时间有序生成。
- 是一个64bit大小的整数,为一个Long型(转化为字符串长度最多为19位)。
- 分布式系统内不会产生ID碰撞(由datacenter和workId作区分),效率高。
雪花算法的几个核心组成部分:
- 1bit 符号位:不用,因为二进制中最高位是符号位(1表示负数,0表示正数)。生成的id一般都是用正数,所以最高位固定位0。
- 41bit时间戳位:可以表示2^41 - 1个毫秒值。
- 可以部署2^10=1024个节点,10bit工作进程位=5位datacenterId+5位workerId。
- 12bit序列号位:用来记录同
毫秒内
产生的不同的id。同一机器同一时间戳(毫秒)内可以产生2^12=4095个ID序号。