分布式唯一ID要求
- 唯一性:生成的ID全局唯一,在特定范围内冲突概率极小。
- 有序性:生成的ID按某种规则有序,便于数据库插入及排序递增
- 可用性:可保证高并发下的可用性, 确保任何时候都能正确的生成ID。
- 自主性:分布式环境下不依赖中心认证即可自行生成ID。
- 安全性:不暴露系统和业务的信息, 如:订单数,用户数等。
mysql方案
专门单库单表,用来生成id
优点:超级简单
缺点:单库单表,高并发支持不住,没有高可用,需要定期删除数据,不适合上生产。大数据低并发,用flickr的数据库唯一id生成方案。
UUID方案
优点:无压力
缺点:mysql会页分裂,查询效率低,不适合做分布式唯一id(其它场景的唯一id可以)
snowflake雪花id方案
核心思想,用long的64个bit位,最高位是0,41位放时间(最多可以用69年),10位放机器标识(最多1024台机器),12位放序号(每毫秒,每台机器,可以顺序生成4096个ID)
优点:高性能,高并发,分布式,可伸缩,可以保证局部递增
缺点:有很多生产问题需要自行处理,如时钟回拨问题,需要独立部署维护
Redis自增方案
核心思想:redis单线程,绝对有序自增;内存高性能,集群部署可以支持高并发高可用,根据集群找到机器并计算步长(如5台机器,初始值为1 2 3 4 5,步长自增为5,第一台存的值为1 6 11 16...第二台存的值为2 7 12 17...直到第5台机器是5 10 15 20)
优点:不需要额外开发,可以直接用公司提供的redis集群
缺点:客户端需要写死Redis机器数量,每次获取id都需要找到一台机器,然后根据步长去incrby,返回给系统;伸缩麻烦,抗不住不好改,需要改步长,id需要重新来搞,id方案不好搞
场景:一般不用,分库分表后,一万左右的并发,单机部署,但需要用主从同步+哨兵(异步备份,有id重复的问题)
时间戳+业务id的组合
核心思想:比如打车,时间戳+起点编号+车牌号;电商,时间戳+用户id,一般手点不会重复,还能加下单渠道、第一个商品id等等组合
优点:实现简单,没有额外成本,没有并发之类的扩容问题
缺点:有的场景会有小概率重复,有的场景没法组合成唯一id
场景:部分生产可用,能用尽量用。
flickr数据库mysql唯一id方案
原始方案
需要找一个库建一张表,作为id表
CREATE TABLE `uid_sequence` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`stub` char(1) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM;
REPLACE INTO uid_sequence (stub) VALUES ('test');
SELECT LAST_INSERT_ID();
REPLACE INTO 表里只有一行数据,LAST_INSERT_ID是connection级别,所以select在多个客户端之间不会有问题
支持多业务的优化点,可以把 value 的值作为一个值(业务字段),那么可以select id from table where stub=业务字段,就可以对每个业务有自己的全局自增id
mysql必须双机,主从,高可用方案,两个库的要设置不同的起始位置和步长,保证两个库的id唯一
优点:高可用,表数据量小,多业务
场景:低并发场景
flickr相对高并发变种方案
阿里有一个TDDL中间件的唯一ID生成方案,思想也是号段思想。
mysql获取的只是号段,每个号段有一定范围,如1代表1-1000,2代表1000-2000……
每台服务启动后,先获取mysql的号段,用AtomLong来自增,当超过了最大值,就重新获取号段
缺点:没有自增到最大id,会浪费,需要做号段本地持久化。再特殊高并发场景下,数据库还是扛不住,比如1000为步长,每秒有1000的并发,相当于每秒都需要取数据库获取号段。数据库难以扩容。
snowflake 雪花id方案的优化
示例代码,就是位运算,网上有很多
机器id的获取
通过zk,比如给一个顺序节点,分布式id服务启动后就获取,获取到可以持久化到本地磁盘上。在扩容后就重新获取。
时钟回拨问题解决
当发生时钟回拨时,回拨时间
- 1s以内,在本机内存中存储1秒内每一毫秒生成的所有的id,回到多少秒就根据原来的记录递增
- 1-10s,返回请求其它的状态码,让客户端去请求其它机器
- 10s以上,主动注册中心下线该节点,等恢复了再重新注册
服务宕机解决
异步持久化,是一个方案,但是过于麻烦,可靠性不好,最好的方案是:
通过zk重新生成一个机器码。