自定义分布式id生成

405 阅读3分钟

分布式id介绍

为何需要分布式id

在电商项目中,当项目越来越大,就对进行分库分表,此时订单表的自增id无法满足唯一性需求,这时候就需要有一种方式生成全局唯一id,这就是本文的主题:自定义分布式id生成。

分布式ID的特点

  • 全局唯一性
    • 不能出现有重复的id标识,这是基本要求
  • 递增性
    • 确保生成id对于用户或业务是递增的,非递增的id作为索引在插入数据时会引起b+树页分裂,导致插入性能下降。
  • 高可用
    • 确保任何时候都能生成正确的id。
  • 高性能
    • 在高并发的环境下依然表现良好。
  • 安全性
    • 不能像自增id一样被人猜到订单数

常用的分布式id生成方案

  • UUID
    • Java自带的生成一串唯一随机36位字符串(32个字符串+4个“-”)的算法。它可以保证唯一性,且据说够用N亿年,但是其业务可读性差,无法有序递增
  • SnowFlake
    • 雪花算法,它是Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。
  • 数据库自增
    • 因为要做分库分表, 所以给订单表的id去指定数据库自增是不合适的。
    • 所以创建一张单独的表, 只做id自增. 但是高并发情况下对数据库压力较大。

自定义分布式id

借助redis生成分布式id

本文介绍的是使用 32位时间戳 + 32位自增序列号 来作为分布式id

  • 高32位:时间戳,给定一个时间如2024年1月1日, 用下单时间减去该时间, 以秒为单位, 转为时间戳. 共32位. 可使用69年。
  • 低32位:自增序列号,在redis中设置一个自增值。因为有32位, 理论上支持每秒2^32并发量。

redis自增实现分布式id的特点:

  • 时间戳 + 计数器 (序列号), 实现简单, 性能高
  • 每天一个key, 避免无限增长超过2^32. 也方便统计每天, 每月, 每年的订单量.

代码实现

@Component
public class RedisIdWorker {
    /* 2024-1-1 0:0:0对应的时间戳 */
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    /* count左移的位数 (等于32位序列号的位数) */
    private static final int COUNT_BITS = 32;

    private final StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix) {
        /*1. 生成时间戳*/
        LocalDateTime nowDateTime = LocalDateTime.now();
        long nowSeconds = nowDateTime.toInstant(ZoneOffset.UTC).getEpochSecond();
        long timestamp = nowSeconds - BEGIN_TIMESTAMP;

        /*2. 生成序列号*/
        /*key设计为yyyy:MM:dd, 可以很好地统计每天, 每月, 每年的订单量*/
        String todayStr = nowDateTime.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        long count = stringRedisTemplate.boundValueOps("icr:" + keyPrefix + ":" + todayStr).increment();
        

        /*3. 合并返回*/
        /*要把32位时间戳和32位序列号相拼接, 需要将32位时间戳左移32位, 则其右边就空出32个0, 然后与32位序列号做或运算, 则32位序列号就被拼接到32位时间戳的右边*/

        /*相加也能做到同样的效果, 但是或运算效率更高*/
        //(timestamp << COUNT_BITS) + count
        return timestamp << COUNT_BITS | count;
    }
}