分布式ID

1. 是什么?

分布式 ID 就是在分布式项目中我们给数据库记录用的 ID。和单机版项目有啥不同呢?单机版的我们可以用 数据库自增等方式来生成 ID,但是分布式项目中,项目部署在好几台机器上,数据库自增也是有可能会出现重复的情况。所以就需要一种算法来生成适用于分布式系统的 ID。

2. 生成分布式 ID 的算法要求:

  • 全局唯一:生成的 ID 必须全局唯一;
  • 趋势递增:我们应该尽量选择有序的主键来保证索引的性能;
  • 单调递增:尽量保证下一个的 ID 大于上一个;
  • 信息安全:如果是连续的 ID,攻击者很容易就猜出下一条记录的 ID,所以有些情况下尽量让 ID 无规则;
  • 含时间戳:含时间戳便于追踪。

3. 生成分布式 ID 系统的可用性要求:

我们用一个系统来生成分布式 ID,那么这个系统必须符合以下条件:

  • 高可用:要保证在99.999%的情况下能够生成一个唯一的分布式 ID;
  • 低延迟:生成分布式 ID 的速度一定要快;
  • 高QPS:假如一下子来10w个生成分布式 ID 的请求,服务器要能扛得住并且能够一下子生成10w个。

4. 分布式 ID 的生成方案:

  • UUID:包含32个16进制的数字,以连字符分割成五段,格式是8-4-4-4-12。优点是性能好,本地生成,没有网络消耗。缺点是它无序,不能生成递增的 ID,而且很长,入库性能差,因为 MySQL的 是 B+ 树索引,每插入一条新数据,都会对索引进行改造,因为 UUID 的无序,每次插入数据时 B+ 树的改造就会很大,也就是导致索引分裂。

  • 数据库自增:我们可以专门搞个表,利用 MySQL 的replace into 语句来生成 ID。比如创建一个表:

create table t_test(
    id bigint(20) unsigned not null auto_increment primary key,
    col char(1) not null default '',
    unique key col (col)
)

然后我们可以执行语句:

replace into t_test (col) values('a');

每执行一次,t_test 表的 id 字段值就会自增,我们就可以用这个 id 来做分布式 ID。这个方案对于并发量不高的系统足够了,但是并发量高的话还是不行的。首先用来生成 ID 的这个 MySQL 扛不住高并发,一秒钟生成10w个肯定是做不到的。

  • 使用 redis 生成分布式 ID:因为 redis 的命令是原子操作的,所以可以使用 incr 和 incrby 来生成分布式 ID。但是这个方案也不是很完美,因为 redis 是集群的话,我们同样需要设置不同的自增步长,同时 key 要设置过期时间。集群有多少台,步长这么设置这些都是要考虑的。而且为了一个 ID 要部署和维护一套 redis 的集群,成本偏高。

所以以上三种方案都存在一定的缺点,现在比较流行的是用雪花算法。

5. 雪花算法:

雪花算法是推特开源的一套用于生成分布式 ID 的算法。它可以生成一个 64bit 大小的整数,类型是 Long,转成字符串后最长是19位。

(1). 雪花算法的组成:

0         0000000000 0000000000 0000000000 0000000000 0         0000000000      0000000000 00
1bit符号位                这 41 bit 是时间戳                  10 bit 工作进程位     12bit 序列号位

1 + 41 + 10 + 12 的结构,总共 64bit,所以刚好可以对应 java 的 long 类型,所以雪花算法生成的 id 就用 long 类型存储。

  • 符号位永远是0,0表示整,1表示负,我们生成的 id 肯定不希望是负的;
  • 时间戳是41位,假如全都是1,那就是2的41次方减1,该值是毫秒,换算成年就是69.73年,所以说雪花算法可以用大约69年,从1970开始,可以用到2039年。
  • 工作进程位是10bit,并且这10bit有5bit是机房号,5bit是机器编号,2的5次方是32,所以就支持32个机房,每个机房32台机器,总共就支持1024个节点。
  • 序列号位是12bit,用来记录同毫秒内产生的不同 id。2的12次方减1是4095,表示同一机器在同一毫秒内可以生成4095个 ID。

(2). 怎么用?

hutool 工具包已经给我们封装好了,只需要在项目中引入:

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-captcha -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.6.6</version>
</dependency>

然后通过如下方式即可获取到雪花算法生成的 ID:

public class SnowFlakeUtil {

    public static long snowFlakeId(long workerId, long datacenterId){
        Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId);
        return snowflake.nextId();
    }

    public static void main(String[] args){
        ExecutorService service = Executors.newFixedThreadPool(5);
        // 机器的workerId
        long workerId = 1;
        // 机房ID
        long datacenterId = 1;
        for (int i = 0; i < 20; i++) {
            service.submit(() ->{
                System.out.println(snowFlakeId(workerId, datacenterId));
            });
        }
        service.shutdown();
    }
}

(3). 雪花算法优缺点:

优点是简单易用,有序递增,带时间戳,也满足信息安全。缺点也有,就是依赖机器时钟,可能会有时钟回拨问题。如果两台服务器的时间不同步,可能会导致生成重复的 ID。

(4). 雪花算法的优化:

百度开源的 UidGenerator 和美团开源的 Leaf 就解决了时钟回拨问题。

推荐阅读更多精彩内容