分布式id

Author:闫玉良

当产品使用人数达到一定量级,一般会采用分库分表等优化操作,但是分布式 id 如何保证全局唯一呢?

更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

1.UUID

UUID 全称是 Universally Unique Identifier ,翻译为通用唯一识别码。此码由网卡 MAC 地址、时间戳、时序、随机数等等一系列元素组合而成,从而保证唯一。碰撞几率几乎为零,大可不必考虑重复问题,放心使用即可。其被广泛应用于各种场景,如图片验证码编号,分布式 id 等。

UUID 是由128位二进制组成,一般转换为十六进制,然后使用 String 表示。

1.1 优点

1) 通过本地生成,没有经过网络 I/O,性能较优

2) 无序,无法预测其生成顺序

但是无序也变成了它的缺点之一

1.2 缺点

1) 128位二进制一般转换成36位的十六进制,因为过于长只能使用 String 存储,空间占用较多

2) 不能生成递增有序的数字。这就导致作为数据库表主键之后效率远不及自增主键

由于不规则,每向数据库插入一条数据就需要重新排列,因此效率不及自增主键

2.数据库表自身主键

在使用唯一标识符时,下意识会考虑到主键自增,因为经常使用,似乎并没有什么问题。但是不要忘记,一般公司产品并未达到分库分表的使用场景,所以不会有任何问题,但并不代表其一直有效。那么它就无法使用了吗?当然不是,可以使用特殊方式:

1) 单独维护一张表,用来生成 id 供分库分表之后共同使用,保证唯一。但是由于所有数据库依赖这张表,其一旦发生意外将导致服务直接崩掉无法使用,所以需要三思而后行。

2) 使用主键的另外一种方式,设置起始值与步长。比如分了两张表,第一张表的主键从1开始,步长为2,表现为:1,3,5 … 第二张表的主键从2开始,步长为2,表现为:2,4,6 …

2.1 优点

1) 简单方便

2) 有序递增

3) 方便排序和分页

2.2 缺点

1) 分库分表会带来问题,需要进行特殊处理

2) 并发性能不高,受限于数据库的性能

3) 简单递增容易被其他人猜测利用。比如你有一个用户服务的递增,那么其他人可以根据分析注册的用户 id 来得到当天你的服务有多少人注册,从而描绘出此服务当前整体状况。

4) 数据库宕机后服务不可用

3.Redis

不要认为 redis 只可以用来做缓存,它的使用场景超多。此处利用命令 lncr 即可生成分布式 id

lncr 命令是将 key 中存储的数字值进行加一操作。如果 key 不存在,那么其值会被初始化为0.

注意:因为 Redis 是单线程的,所以可以保证原子性。就不要再担忧并发数据出错问题了 ~

3.1 优点

1) 性能比数据库高得多

2) 能满足有序递增的要求

3.2 缺点

1) redis 是基于内存的键值数据库,虽然有 AOFRDB 等持久化操作,但是依然会存在数据丢失问题,从而导致 id 不唯一。

有人会问,数据丢失和不唯一有什么关联?比如数据库生成的主键到111113了,但是发生宕机,有一秒的数据丢失,恢复后从111111开始计算,因此重复了两个值111112和111113。

2) 依赖于 redis ,如果其不稳定,那么也会影响 id 的生成。

4.雪花算法

雪花算法由 Twitter 提出,英文名为 Snowflake ,它的目的是生成一个 64bit 的整数(1bit符号位 + 41bit时间戳 + 10bit工作机器id + 12bit序列号 )。由于其保持增长有序并能通过处理保证唯一而被各大厂商广泛应用于分布式,比如头条。

1) 1bit 是符号位,不做任何处理

2) 41bit 用来记录时间戳,这里可以记录69年,如果设置好起始时间,比如今年是2020年,那么可以用到2089年。有人会问,到时候怎么办?放心吧,一般产品的生命周期不到69年,即使它活过了69年,此系统一定重构过好多次,因此不是问题

3) 10bit 用来记录机器的 id ,总共可以记录1024台。一般前5位代表数据中心,后面5位是某个数据中心的机器 id

4) 12bit 是循环位,用来解决同一个毫秒之内产生不同的 id 。12位最多可以记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下一毫秒

上述规范适用于 64bit 划分标准,其他情况可以根据实际场景划分。比如:服务目前 QPS 10万,预计几年之内会发展到百万;当前机器三地部署(上海、北京和贵州);机器共有10台左右,预计未来增加到百台以上。

这个时候我们根据上面的场景可以再次合理划分 62bitQPS 几年之内到百万,那么每毫秒就是千级请求,目前10台机器每台承担百万级的请求,为了保证扩展,后面的循环位可以限制到1024,也就是2的10次方,循环位10位足矣。机器三地部署,我们可以用 3bit 总共8来表示机房位置,当前的机器10台,为了保证扩展到百台,可以使用 7bit 也就是128来表示,时间位依然是 41bit ,那么还剩下 64-10-3-7-41-1,也就是 2bit。剩下的 2bit 可以用来进行扩展。

QPS 是每秒查询率

4.1 优点

1) 由于时间戳是不断增大的,再加上循环位不断增加,其他位置相对固定,所以可以保证生成有序的 id,提高数据库的性能。

4.2 缺点

1) 时间回拨问题可能导致重复 id

雪花算法强依赖时间,而我们的机器可能因为各种原因发生时间回拨(与时间服务器校准,发现机器时间快了,往回调一下),这就导致有可能生成重复 id

解决方案一:用当前时间和上一次时间进行判断,如果发生回拨,算法抛出错误,保证不重复(用户看到提示界面,再次进行操作时,肯定已经过了几秒,所以我们毫秒级的处理丝毫不慌,用户体验也并不会降低)。

解决方案二:关闭机器与 ntp 同步

总结

四种方案各有优缺点,可以根据产品体量自行选择。不过推荐雪花算法。你懂得 ~

更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • 页面访问量: 独立访客访问数:
  • 更多精彩文章请关注微信公众号『全栈技术精选』,id 为『Pythonnote』

请我喝杯咖啡吧~

支付宝
微信