我们在开发过程中,单库下一般使用Mysql自增ID, 但是分库分表后,会造成不同分片上的数据表主键的重复
现在有个需求:表ID需要性能强劲且全局唯一并且能防止恶意用户根据id的规则来获取数据
下面来看看业界常用的ID解决方案:
一、数据库自增ID
利用自增id, 设置不同的自增步长,auto_increment_offset、auto-increment-increment
DB1: 单数
//从1开始、每次加2
DB2: 偶数
//从2开始,每次加2
缺点:
依靠数据库系统的功能实现,但是未来扩容麻烦
主从切换时的不一致可能会导致重复发号
性能瓶颈存在单台sql上
二、UUID
优点:性能非常高,没有网络消耗
缺点:
无序的字符串,不具备趋势自增特性
UUID太长,不易于存储,浪费存储空间,很多场景不适用
三、Redis发号器
利用Redis的INCR和INCRBY来实现,原子操作,线程安全,性能比Mysql强劲
缺点:需要占用网络资源,增加系统复杂度
四、Snowflake雪花算法
优点:
twitter 开源的分布式 ID 生成算法,代码实现简单、不占用宽带、数据迁移不受影响
生成的 id 中包含有时间戳,所以生成的 id 按照时间递增
部署了多台服务器,需要保证系统时间一样,机器编号不一样
缺点
依赖系统时钟(多台服务器时间一定要一样)
具体的选择,根据业务情况和公司服务器情况来定,这里重点介绍shardingjdbc中使用雪花算法,以及坑的解决
什么是雪花算法呢?
twitter用scala语言编写的高效生成唯一ID的算法,生成的ID不重复,算法性能高,基于时间戳,基本保证有序递增
雪花算法生成的数字,long类,所以就是8个byte,64bit
表示的值 -9223372036854775808(-2的63次方) ~ 9223372036854775807(2的63次方-1)
生成的唯一值用于数据库主键,不能是负数,所以值为0~9223372036854775807(2的63次方-1)
一般来说,分布式ID的生成需求是 性能强劲,全局唯一不能重复,防止恶意用户根据id的规则来获取数据
其中,全局唯一不能重复-请注意 有坑
坑一:分布式部署就需要分配不同的workId, 如果workId相同,可能会导致生成的id相同
坑二:分布式情况下,需要保证各个系统时间一致,如果服务器的时钟回拨,就会导致生成的 id 重复
使用方式一:
订单id使用MybatisPlus的配置,TrafficDO类配置
@TableId(value = "id", type = IdType.ASSIGN_ID)
默认实现类为DefaultIdentifierGenerator雪花算法
使用方式二:
使用Sharding-Jdbc配置文件,注释DO类里面的id分配策略
#id生成策略
key-generator:
column: id
props:
worker:
id: 0
#id生成策略
type: SNOWFLAKE
一般使用第二种,但是需要动态指定sharding jdbc 的雪花算法中的属性work.id属性
坑一的解决方式:
动态指定方式是启动程序的时候,通过JVM参数去控制,覆盖变量
@Configuration
public class SnowFlakeWordIdConfig {
/**
* 动态指定sharding jdbc 的雪花算法中的属性work.id属性
* 通过调用System.setProperty()的方式实现,可用容器的 id 或者机器标识位
* workId最大值 1L << 100,就是1024,即 0<= workId < 1024
* {@link SnowflakeShardingKeyGenerator#getWorkerId()}
*
*/
static {
try {
InetAddress ip4 = Inet4Address.getLocalHost();
String addressIp = ip4.getHostAddress();
System.setProperty("workerId", (Math.abs(addressIp.hashCode())%1024)+"");
} catch (UnknownHostException e) {
throw new BizException(BizCodeEnum.OPS_NETWORK_ADDRESS_ERROR);
}
}
}
再更改下上面的配置,动态获取workerId
#id生成策略
key-generator:
column: id
props:
worker:
id: ${workerId}
#id生成策略
type: SNOWFLAKE
坑二的解决方式:
shardingjdbc-4.1版本中做了相关操作,看源码:
当判断到时间有差距,且在容忍范围内,就等待时间同步到最后一次id生成的时间再进行。
超过容忍度,则直接抛出异常clock is moving backwards,last time is ...