seata改进型雪花分布式ID算法-java实现
seata改进型雪花算法分布式ID-java实现
1,简介
在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。通俗的讲就是,多台机器支撑一个服务,但是他们生成的id是不重复的,且最好单调递增(降低mysql B+聚簇索引的页分裂的出现)。
当前现有的实现方式有:
实现方式 | 描述 | 优缺点 |
---|---|---|
mysql自增id | 直接使用mysql自带的自增id功能 |
优点: ①实现简单 缺点: ①并发性能差 ②依赖于mysql数据库 |
redis自增键值 | 直接使用redis自带的incr key功能实现 | 优点: ①实现简单 缺点: ①并发性能差 ②依赖于redis缓存 |
uuid | 直接使用代码生成的uuid | 优点: ①实现简单 ②并发性能高 缺点: ①id生成不是单调递增 |
雪花算法及其衍生改进型 | 使用标记位+时间戳+节点id+序列号的方式组成 | 优点: ①实现简单 缺点: ①时钟回溯问题 ②标准版每个时刻只有4096个并发量 |
Seata改进型雪花算法 | 使用标记位+节点id+时间戳+序列号的方式组成 | 优点: ①实现简单 ②并发性能可达409.6W/s ③解决部分时钟回拨问题 缺点: ①不是全局单调递增,只是分机器单调递增 |
美团leaf分布式id生成框架 | 直接调用leaf的分布式id生成服务 | 优点: ①并发性能高 ②解决时钟回溯问题 缺点: ①需要额外依赖其他服务 |
2,优化策略:
- 时间戳与节点ID互换位置
由原版的标记位(1位)+时间戳(41位)+节点ID(10位)+序列号(12位)
更改为 标记位(1位)+节点ID(10位)+时间戳(41位)+序列号(12位)
- id生成只依赖于初始化时缓存的时间戳,不再实时追随最新时间
3,核心解决问题:
1,解决原有雪花算法一个ms内4096/ms的性能限制。由于标准版雪花算法是实时追随系统时间的,所以1台机器1个ms内最多只能生成4096个唯一id;但是改进型只是在系统初始化时缓存一次时间戳,之后是在这个时间戳+序列号的组合基础上进行单调递增,即便序列号4095继续向上递增,也只会超前消费时间戳里面的位数,不会出现违反唯一性的问题;
2,线程安全(使用CAS原子类保证每一个节点ID内安全单调递增);
3,弱依赖于系统时间。只会在系统启动的时候缓存当前时间戳,之后就不依赖时间戳,即便时钟小幅度回拨也是不受影响;除非人为的大幅度回拨,那么会有影响;
4,其他问题:
1,理论上会有并发高的时候序列号消耗完,超前消费时间戳导致的数据重复可能。但是,前提是生成器的QPS稳定在4096/ms以上,也就是409.6W/s以上,但是我这边测试了一下在8C16G的机器上的QPS性能只有26.7W/s,所以说现在的瓶颈已经不是分布式id生成器,这个超前消费的问题现在不用担心
2,id不随机问题,这个是美团那边的leaf分布式id生成器的一个需求,他们怕被竞争对手窃取数据,直接通过一天id的起始值和终止值分析出业务量是多少!这个问题当前确实存在。
3,不是全局单调递增,只是分机器单调递增。这个可以查看博文【关于新版雪花算法的答疑】给出的解析,分段单调递增也是可以减少插入数据的也分裂问题,只不过是分段的段尾进行递增!
5,java代码实现
package site.activeclub.acCore.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
/**
* seata中优化的分布式雪花算法生成分段自增id
*/
@Slf4j
@Component
public class SnowflakeIdUtil implements InitializingBean{
/**
* 机器码移位53
*/
private final long MACHINE_BIT = 53;
/**
* 时间戳移位12
*/
private final long TIMESTAMP_BIT = 12;
/**
*
* business meaning: machine ID (0 ~ 1023)【每个机器码下对应的id是分段单调递增】
* actual layout in memory:
* highest 1 bit: 0
* next 10 bit: workerId【机器码】
* middle 41 bit: timestamp【时间戳】
* lowest 12 bit: sequence【这个时间戳下的自增id,严格单调递增】
*/
private AtomicLong idSequence;
@Value("${snowflake.worker.id:1}")
private long workerId;
/**
* 将机器码移位到高53位
*/
@Override
public void afterPropertiesSet() {
// 机器码左移至高位
workerId <<= MACHINE_BIT;
// 跟先前保存好的高11位进行一个或的位运算
long startId = workerId | (System.currentTimeMillis()<<TIMESTAMP_BIT);
idSequence = new AtomicLong(startId);
}
public long nextId() {
return idSequence.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
SnowflakeIdUtil snowflakeIdUtil = new SnowflakeIdUtil();
// 机器码左移至高位
snowflakeIdUtil.workerId <<= snowflakeIdUtil.MACHINE_BIT;
// 跟先前保存好的高11位进行一个或的位运算
long startId = snowflakeIdUtil.workerId | (System.currentTimeMillis()<<snowflakeIdUtil.TIMESTAMP_BIT);
snowflakeIdUtil.idSequence = new AtomicLong(startId);
//计时开始时间
long start = System.currentTimeMillis();
//让100个线程同时进行
final CountDownLatch latch = new CountDownLatch(100);
//判断生成的20万条记录是否有重复记录
final Map<Long, Integer> map = new ConcurrentHashMap();
for (int i = 0; i < 100; i++) {
//创建100个线程
new Thread(() -> {
for (int s = 0; s < 20000; s++) {
long snowID =snowflakeIdUtil.nextId();
log.info("生成雪花ID={}",snowID);
Integer put = map.put(snowID, 1);
if (put != null) {
throw new RuntimeException("主键重复");
}
}
latch.countDown();
}).start();
}
//让上面100个线程执行结束后,在走下面输出信息
latch.await();
log.info("生成20万条雪花ID总用时={}", System.currentTimeMillis() - start);
}
}
输出结果:
nowflakeIdUtil - 生成雪花ID=6753605615453437
00:11:48.165 [Thread-30] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615452978
...
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463735
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463736
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463737
00:11:48.201 [Thread-81] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463729
00:11:48.201 [Thread-81] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463739
00:11:48.201 [Thread-74] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463733
00:11:48.201 [Thread-74] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463740
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463738
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463742
00:11:48.201 [Thread-74] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463741
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463743
00:11:48.201 [Thread-63] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成雪花ID=6753605615463744
00:11:48.201 [main] INFO site.activeclub.acCore.utils.SnowflakeIdUtil - 生成20万条雪花ID总用时=748