前言
本文将介绍 MyBatis-Plus 如何使用雪花算法(Snowflake)生成分布式全局唯一 ID,并解决 ID 冲突问题。
版本说明
雪花算法的介绍
概述
Snowflake 算法来源于 Twitter,使用 Scala 语言实现,利用 Thrift 框架实现 RPC 接口调用,最初的项目起因是数据库从 MySQL 迁移到 Cassandra,而 Cassandra 没有现成可用的 ID 生成机制,就催生了该算法。Snowflake 的特性如下:
- Snowflake 能够按照时间有序生成 ID
- 经测试 Snowflake 每秒能够产生 26 万个自增可排序 ID
- 分布式系统内不会产生 ID 碰撞(由
dataCenterId 和 workerId 做区分),并且生成效率较高 - Snowflake 算法生成 ID 的结果是一个 64 bit 大小的整数,刚好为一个 Long 型,转换成字符串后长度最多是 19
结构
Snowflake 算法的特性是有序、全局唯一、高性能、低延迟(响应时间在 2ms 以内),可在分布式环境(多集群,跨机房)下使用,因此使用 Snowflake 算法得到的 ID 是分段组成的:
- 第 1 位不使用,因为二进制里第一个位如果是 1,那么都是负数,但往往生成的 ID 都是正数,所以第一个位统一都是 0
- 与指定日期(
1970-01-01 00:00:00)的时间差(毫秒级),41 位,够用 69 年 - 数据中心 ID + 工作机器 ID,一共 10 位,包括 5 位
dataCenterId 和 5 位 workerId,最多支持 32 个数据中心(机房),每个数据中心 32 台机器,一共是 1024 台机器 - 序列号,12 位,每台机器每毫秒内最多产生 4096 个序列号
![snowflake-1]()
1bit:符号位,固定是 0,表示全部 ID 都是正整数41bit:时间戳(毫秒数时间差),从指定的日期算起,够用 69 年,用 Long 类型表示的时间戳是从 1970-01-01 00:00:00 开始算起的10bit:数据中心 ID + 工作机器 ID,有异地部署、多数据中心(机房)的也可以配置,需要线下规划好各地数据中心(机房),各机器实例 ID 的编号12bit:序列号,一共 12 位,前面所有位都相同的话,每台机器每毫秒内最多产生 4096 个序列号
优缺点
优点:
- 可以根据自身业务特性分配
bit 位,非常灵活 - 毫秒数在高位,自增序列在低位,整个 ID 都是趋势递增的
- 不依赖数据库等三方系统,以服务的方式部署,稳定性更高,生成 ID 的效率也是非常高,低延迟
缺点:
- 强依赖机器时钟,如果机器的时钟回拨了,会导致生成重复的 ID
- 若生成环境中使用了容器化技术(比如 Docker、K8s),实例的个数随时有变化,那么 Snowflake 需要一定的改造才能更好地应用到生产环境中
- 在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步(如时钟回拨),有时候可能会出现不是全局递增的情况(此缺点可认为无所谓,一般分布式 ID 只是要求趋势递增,并不会严格要求递增,90% 的业务需求都只需要趋势递增)
适用场景:
雪花算法的使用
- 当使用
@TableId(type = IdType.ASSIGN_ID) 时,MyBatis-Plus 会自动使用其内置的雪花算法(com.baomidou.mybatisplus.core.toolkit.Sequence)生成主键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @TableName("t_order") @Data public class Order {
@TableId(type = IdType.ASSIGN_ID) private Long id;
private String orderNo;
private Long userId;
private BigDecimal amount;
}
|
- 在 MyBatis-Plus 中,Snowflake 算法生成的 ID 是一个 64 位长整型(
| 1 bit 符号位 | 41 bit 时间戳 | 5 bit 数据中心 ID | 5 bit 工作机器 ID | 12 bit 序列号 |),其默认的 Snowflake 参数为:
| 参数 | 默认值 | 说明 |
|---|
dataCenterId | 1 | 数据中心 ID(0 ~ 31) |
workerId | 1 | 工作机器 ID(0 ~ 31) |
ID 冲突问题
- 在 MyBatis-Plus 中,Snowflake 参数的默认值在分布式部署时可能会出现 ID 冲突,因此通常需要自定义 Snowflake 参数(包括
dataCenterId 和 workerId),避免 ID 冲突的发生。
ID 冲突解决方案
在 MyBatis-Plus 中,Snowflake 参数的默认值在分布式部署时可能会出现 ID 冲突,因此通常需要自定义 Snowflake 参数(包括 dataCenterId 和 workerId),避免 ID 冲突的发生。
硬编码 Snowflake 参数
- 通过定义一个 Bean 来覆盖 MyBatis-Plus 默认的
IdentifierGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.toolkit.Sequence; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class SnowflakeConfig {
@Bean public IdentifierGenerator idGenerator() { return new IdentifierGenerator() {
private final Sequence sequence = new Sequence(2, 3);
@Override public Number nextId(Object entity) { return sequence.nextId(); }
}; } }
|
通过配置文件 Snowflake 参数
- 在
application.yml 配置文件中,添加以下配置内容
1 2 3
| snowflake: workerId: 2 dataCenterId: 3
|
- 通过定义一个 Bean 来覆盖 MyBatis-Plus 默认的
IdentifierGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.toolkit.Sequence; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Data @Configuration @ConfigurationProperties(prefix = "snowflake") public class SnowflakeConfig {
private long workerId; private long dataCenterId;
@Bean public IdentifierGenerator idGenerator() { return new IdentifierGenerator() {
private final Sequence sequence = new Sequence(workerId, dataCenterId);
@Override public Number nextId(Object entity) { return sequence.nextId(); }
}; }
}
|
动态计算得到 Snowflake 参数
- 通过定义一个 Bean 来覆盖 MyBatis-Plus 默认的
IdentifierGenerator,Snowflake 参数都是动态计算得到的,适用于 Docker / Kubernetes 环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.toolkit.Sequence; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import java.net.InetAddress; import java.net.NetworkInterface;
@Configuration public class SnowflakeConfig {
@Bean public IdentifierGenerator identifierGenerator() { return new IdentifierGenerator() {
private final Sequence sequence = new Sequence(getWorkerId(), getDatacenterId());
@Override public Number nextId(Object entity) { return sequence.nextId(); }
}; }
private long getWorkerId() { try { String hostAddress = InetAddress.getLocalHost().getHostAddress(); return (hostAddress.hashCode() & 31); } catch (Exception e) { return 0; } }
private long getDatacenterId() { try { InetAddress inetAddress = InetAddress.getLocalHost(); NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress);
if (ni == null) { return 0; }
byte[] mac = ni.getHardwareAddress(); if (mac == null || mac.length == 0) { return 0; }
return (mac[mac.length - 1] & 31); } catch (Exception e) { return 0; } }
}
|
从 Redis 获取 Snowflake 参数
- 从 Redis 或者数据库中获取 Snowflake 参数(大公司常用),确保在分布式环境(Docker、Kubernetes、集群)下数据中心 ID(
dataCenterId)和工作机器 ID(workerId)都不会重复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.toolkit.Sequence; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration public class SnowflakeConfig {
private final StringRedisTemplate redisTemplate;
public SnowflakeConfig(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }
@Bean public IdentifierGenerator identifierGenerator() { return new IdentifierGenerator() {
private final Sequence sequence = new Sequence(getWorkerIdFromRedis(), getDatacenterIdFromRedis());
@Override public Number nextId(Object entity) { return sequence.nextId(); }
}; }
private long getWorkerIdFromRedis() { try { Long value = redisTemplate.opsForValue().increment("snowflake:workerId"); if (value == null) { return 0; } return value % 32; } catch (Exception e) { return 0; } }
private long getDatacenterIdFromRedis() { try { Long value = redisTemplate.opsForValue().increment("snowflake:dataCenterId"); if (value == null) { return 0; } return value % 32; } catch (Exception e) { return 0; } }
}
|
参考资料