分布式组件-分布式ID

分布式ID是什么?

就像我们每个人都有一个生份证来标识我是我,每条数据也需要一个唯一标识方便后续查找。在单库单表应用中用自增的id来标识数据,但是,这种方式在数据过大进行分库分表之后就会有id冲突的问题。所以,我们需要引入分布式的id(即为每条数据生成全局唯一的id)来解决冲突的问题。

分布式ID满足什么条件?

  • 全局唯一(基本条件)
  • 高性能
  • 高可用
  • 趋势递增(方便建立索引,提高查找效率)

分布式ID有哪些生成方式?

  1. UUID

    uuid是一个方案,但是uuid它不仅是太长还是字符串,太长导致存储空间大,字符串的话就会导致查询耗时(主要是这个缺点)

  2. 基于数据库自增ID

    可以单独一个mysql来通过自增的方式生成ID,当我们需要一个ID时就往表插入一条记录,并返回主键ID。但是,这种方案缺点也比较致命,当访问量上来的时候数据库成为了系统的瓶颈,不太适合

  3. 基于数据库集群自增ID

    前边说了单点数据库方式不可取,那对上边的方式做一些高可用优化,换成主从模式集群。A数据库设置起始的ID为1,每次加2;B数据的起始ID为2,每次加2;这样两个MySQL实例的自增ID分别就是:

    1,3,5,7,9
    2,4,6,8,10
    

    这样也提高了性能,但是,如果数据量继续增加,两台机器不够,后续扩容比较麻烦。所以,也不推荐这种方式

  4. 基于数据库的号段模式

    号段模式是当下分布式ID生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,100] 代表100个ID,具体的业务服务将本号段,生成1~100的自增ID并加载到内存。表结构如下:

    CREATE TABLE test (
      biz_tag	int(20) NOT NULL COMMENT '业务类型',
      max_id bigint(20) NOT NULL COMMENT '当前最大id',
      step int(20) NOT NULL COMMENT '号段的步长,即每次多生成几个id',
      version int(20) NOT NULL COMMENT '版本号,乐观锁',
      PRIMARY KEY (`id`)
    ) 
    
    biz_tagmax_idstepversion
    Order200020001

    第一次生成2000个id加载到内存,用完之后在申请2000个,这样不像上面频繁操作数据库,对数据库压力小很多

    这个方式最大的问题在于生成的号码是连续的,类似订单id的业务,容易被扫库或者测算出订单量

  5. 基于redis模式

    redis模式其实类似mysql自增id的方式,原理就是利用redisincr命令实现ID的原子性自增。但是存在一个问题,redis持久化问题。redis持久化分为两种,RDBAOP

    RDB会定时打一个快照进行持久化,这种模式有可能Redis挂掉之后,没有及时保存ID,下次重启的时候ID会重复

    AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但是AOF持久化会损耗性能,并且在宕机重启后可能由于文件过大导致恢复数据时间过长

  6. 基于雪花算法(SnowFlake)模式

    雪花算法是twitter开源的分布式ID生成算法,是另一种当下主流生成方式,其核心思想是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,保持自增性且不重复

    结构图:

    雪花算法

    值得注意的是,中间10bit(记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器,用不了这么多),一般用5bit表示机房ID,5bit表示机器id。

    当然,雪花算法也有问题,就是时钟回拨的问题,会导致ID重复(由于使用的容器配置写错,我们就碰到ID冲突的问题)

总结

基本上,市面上主流的解决方案就是以上几种方式或者基于之上的。像百度的uid-generator是基于雪花算法,并解决时钟回拨的问题;滴滴的Tinyid则是基于数据库号段模式;而美团的leaf,则两种模式都兼容。