分布式唯一ID

一 什么是分布式唯一ID,有些什么需求

在分布式系统中,要求某些ID不能重复,必须唯一。他需要满足以下特点:

第一:全局唯一,不能重复

第二:递增性:下一个ID一定比上一个大

第三:安全性:不得暴露与业务相关的信息,防止用户恶意获取

二  有哪些分布式唯一主键生成方案,各自优缺点

2.1 UUID

优点:简单

缺点:第一,16字节大小,存储成本高,影响写入速度;第二,最关键是的做主键非常不合适,在因为不是趋势递增,那么在主键索引中就不是连续的,那么插入数据的时候就是离散的存储,导致页分裂非常频繁。第三,查询的时候,比较UUID花的时间比数字要长,对查询也不是很友好

2.2 数据库主键自增

通过数据库自带的主键自增功能,也可以实现全局唯一。专门弄一张表,每请求一次,产生一条记录,产生一个主键。

优点:第一,数据库自带功能,比较方便;第二,数字类型,存储成本小;第三,产生的主主键是顺序递增的,符合InnoDB索引的要求

缺点:第一,强依赖数据库,存在单点问题。如果数据库不可用,那么就会导致整个系统不可用;第二,单个MySQL产生ID是可能会有性能瓶颈

2.3 利用Redis生成唯一ID

通过Redis的incr指令实现自增功能,他本身就是一个分布式数据库,所以可以实现全局唯一ID。

优点:第一,数字类型,存储成本低;第二, 产生的主键也是递增的;第三,产生ID的性能高

缺点:第一,如果系统中没有使用Redis,单独引入会增加系统复杂度;第二,单个Redis也存在单点问题

2.4 雪花算法(snowflake)

雪花算法会产生一个64bit的数字作为分布式唯一ID,由实际戳+机器号+序列号组成,第一位是0,表示符号位;第1-40合计41位表示时间戳;第42-51共10位,表示机器ID,意味着最多可以部署在1024台机器上面,第52-63共12位表示这个机器上的自增序列,表示每一毫秒在每台机器上可以产生4096个唯一ID。如图所示:

 

雪花算法实现方式:

 

 

优点:第一,数字类型,而且只有64位,存储成本低;第二,每一秒钟可以产生4096000个ID,性能是很高的;第三,产生的唯一ID也是顺序递增的,

缺点:雪花算法是基于时间计算的,如果存在时钟回拨的问题,那依然有可能产生重复ID

三 雪花算法如何解决时钟回拨问题

我们知道,如果服务器向时间服务器进行时钟校准,有可能会发生时间回拨,那么这样的话就有产生的ID既可能不是递增顺序,也可能会重复。那如何解决这个问题呢?既然受时钟时钟回拨这个问题,那我们想办法在算法中规避时钟回拨。

3.1 抛出异常,人为处理

当比较当前时间和上次产生ID的时间戳时候,如果当前时间小于上次产生ID的时间戳,则认为产生了时间回拨,此时抛出异常。百度的雪花算法实现中,DefaultUidGenerator就是这么干的。

还有一种做法就是,如果发生时钟回拨,则等待一段时间,等待时间进行校正。然后再检查当前时间是不是小于上次产生ID的时间戳,如果还是小于,说明还在受时钟回拨影响,然后再抛出异常;否则可以正常产生ID了。美团的Leaf框架中雪花算法就是这么做的,然后通过Zookeeper的持久化顺序节点,创建workerId,即便每次重启,workerId也不会变。

优点:简单

缺点:实现不优雅,需要人工介入,生产环境不建议这么用

3.2 不使用时钟回拨

通过数据库主键产生一批或者一段数据,然后在内存中进行保存,有请求到来的时候直接从内存取出分配,既避免了时钟回拨问题,又避免了数据库自增ID的性能瓶颈(不用来一个请求,请求一次数据库,然后产生一个ID)。美团Leaf框架中segment模式就是这么干的。

优点:避免了时钟回拨的影响,而且性能还不错

缺点:还是对数据库强依赖,而且需要数据库解决单点问题

3.3 时钟自增,不再和系统时钟进行比较

当初次获取当前时间后,每次上一个ID产生之后需要更新时间戳,此时更新时间戳不再使用系统当前时间,而是在之前的当前时间+1,这样的话,既可以保证产生的ID是有序的,而且还不会受时钟回拨的影响,只要你的ID对时间没有业务上的关联,就没什么影响。而且百度的CachedUidGenerator底层就是这么干的,只不过通过了两个环形数组(RingBuffer)实现了批量产生ID的功能,避免每次请求才进行产生,提升性能。

CachedUidGenerator有两个RingBuffer, 数组中每项叫做slot, 一个是存储ID的,一个是存储ID状态的。存储状态的RingBuffer有两个值0和1,0表示CNAPUT, 1表示CANTAKE,初始状态都是0,表示可以放。当存放ID的RingBuffer对应的slot放一个产生的ID的时候,则会在状态RingBuffer将状态置为1。

 

Tail指针表示已经添加到什么位置,Cursor指针表示已经消费到什么位置,每当消费一个,就把,如果Cursor赶上了Tail说明没有ID消费的,为了避免这种情况,CachedUidGenerator设置了一个阈值,如果RingBuffer中已经消费了50%的ID则起线程产生ID往RingBuffer中放,直到填满为止。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值