我们知道,分布式系统,不同的服务部署在不同的机器上,当多个系统同时操作数据库的时候,可能存在id是冲突的情况,此时就产生了一个问题,分布式系统下,如何保证生成的id是唯一的。
分布式系统生成全局唯一id一般有四种策略,个人建议使用后面两种:
- uuid
我们知道uuid基本是不会重复的(一般单台机器部署UUID生成器,基本不会重复),所以分布式系统下我们可以使用uuid作为全局唯一id,但是uuid是字符串,且不能排序,如果我们要生成订单编号且要进行排序,此时uuid就不能满足使用场景了。 - 使用mysql数据库时设置步长和初始值
我们知道mysql数据库的主键是唯一的且自增的,不会重复的,我们可以使用主键作为全局唯一id,但是如果数据库做了集群,就会出现在不同的数据库中出现相同的主键,此时全局Id就不是唯一的了,但是我们可以采用设置数据库主键的初始值和步长,所谓初始值就是主键从哪个值开始,数据库默认是1,所谓步长,就是每次自增的值,数据库默认是1,现在假设我们mysql数据库部署了三台:
可以设置数据库1的主键初始值为1,步长为3,则数据库1的主键值依次为1,4,7…
可以设置数据库2的主键初始值为2,步长为3,则数据库2的主键值依次为2,5,8…
可以设置数据库3的主键初始值为3,步长为3,则数据库3的主键值依次为3,6,9…
这样主键就不会重复了,可以作为全局唯一id,但是如果我们后期要扩展数据库,比如现在要增加2台,那么我们的初始值和步长就需要重新设置了,而且数据迁移也是一个问题。 - 基于redis生成全局唯一id
我们知道redis是单线程的,因此我们可以借助redis的API来生成唯一主键,API为redisTemplate.opsForValue().increment(key, 1),这个是个自增命令,就算在并发编程下,redis也可以保证线程的安全性。
例子:分布式系统下生成订单编号
/**
* 生成订单
*/
private Long orderId(String prefix) {
// 作为redis的key值
String key = "ORDER_ID" + prefix;
String orderId = null;
Long increment = redisTemplate.opsForValue().increment(key, 1);
//往前补6位
orderId = prefix + String.format("%1$03d", increment);
return Long.valueOf(orderId);
}
以上生成的订单编号就是 prefix+“ORDER_ID”+increment 的值,而increment 的值是一直自增的,所以不会重复。
- 雪花算法生成全局唯一id
基于雪花算法生成全局唯一id是采用时间戳+部署的机器id+序列号来生成的一串数字,这个可以网上有很多相关的代码,这块我就不演示了,因为部署的机器id不一样,生成的序列号也不一样,因此,雪花算法生成的id都是不会重复的,而且还可以排序,因为都是按照时间戳递增的。