生成全局唯一ID

148 阅读2分钟

为什么要生成全局唯一ID?

当我们做项目碰到一些业务比如生成订单时,要求订单id唯一,我们首先想到的可能就是使用数据库的主键生成自增的订单id,但是如果生成的订单过多,需要分表时,要确定多个表的订单id唯一,而数据库的主键自增只能确保一个表中订单id唯一并且自增,那么此时我们就需要通过其他方法来实现多个表的订单id唯一并且自增了。


基于Redis的字符串自增的思路

我们想要实现全局唯一ID并且实现自增,那么是不是需要定义一个变量,比如订单数量,每多一个订单该字段加一,然后将其保存到数据库中? 如果只是简单定义一个变量,会存在一些问题: 1、在分布式系统中,变量不被多个服务器共享。 解决方案:使用Redis的string类型存储 2、如果订单直接显示简单的序列号,那么订单号可能就会暴露一些信息,比如一天的订单 解决方案:可以拼接其他值,例如时间戳

思路实现

我们可以使用Long类型数据来作为订单id,Long类型是8个字节即64位,第一位代表符号,第二位到第三十二位可以存储时间戳,用来拼接保证订单id的安全性,第三十三位到六十四位可以存储序列号,序列号存储在redis的string类型中,一共可以生成2^32个订单号,为了防止订单过多存储不下并且可以统计每一天的订单,我们可以存储每一天的订单序列号在redis中。

代码实现

@Component
public class RedisWorker {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
//         表示2024年1月1日凌晨的时间戳
    private static final long BEGIN_STAMPS = 1704067200;

    private static final int BIT_COUNT = 32;

    public long getGlobalId(String prefixKey) {
//        生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long end = now.toEpochSecond(ZoneOffset.UTC);
//      记录时间间隔大小,使得三十一位能够存储更久
        long timeStamps = end - BEGIN_STAMPS;
//        生成序列号
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        //记录每一天的订单数量
        String key = "icr:" + prefixKey + ":" + date;
        Long inc = stringRedisTemplate.opsForValue().increment(key);
        //拼接时间戳和序列号
        return timeStamps << BIT_COUNT | inc;
    }
}