MP批插优化

2025-03-04

java

我虽不富甲天下,却拥有无数个艳阳天和夏日。——梭罗

看到这篇文章

https://mp.weixin.qq.com/s/NkP6kND6wQZqTd_gIuaYAw

MyBatisPlus 高并发场景下的ID生成优化:分布式序列号服务实践

突破分布式ID的性能瓶颈

某电商平台在促销活动期间面临订单创建峰值压力,使用MyBatisPlus默认的雪花算法生成ID时,出现以下问题:

  1. 时间戳精度不足导致ID碰撞率升高(单机QPS超5000时)
  2. 服务器时钟回拨引发的批量插入失败
  3. 长ID对存储空间的额外消耗(18位 vs 传统13位)

压力测试显示,在高并发场景下单节点生成ID的吞吐量上限为1.2万/秒,成为系统瓶颈。通过改造ID生成机制,我们实现了单机5万/秒的ID生成速度,同时将存储空间压缩40%。

混合式ID生成架构设计

技术方案对比

方案类型 吞吐量 碰撞概率 时钟依赖 实现复杂度
原生雪花算法 1.2万/s 0.01% 强依赖
UUIDv4 无限 理论无碰撞 无依赖
数据库序列 800/s
混合分段式 5万+/s 弱依赖

核心实现模块

分布式号段服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
public class SegmentController {
private final Map<String, AtomicLong> segmentPool =
new ConcurrentHashMap<>();

@PostMapping("/segment/apply")
public SegmentResponse applySegment(
@RequestParam String bizTag,
@RequestParam int step) {

long currentMax = segmentPool.compute(bizTag, (k, v) ->
v == null ? new AtomicLong(0) : v
).addAndGet(step);

return new SegmentResponse(
currentMax - step + 1,
currentMax
);
}
}

客户端本地缓冲管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SegmentBuffer implements InitializingBean {
private volatile Segment currentSegment;
private volatile Segment nextSegment;
private final ExecutorService loader =
Executors.newSingleThreadExecutor();

public synchronized Long nextId() {
if (currentSegment.getCurrent() > currentSegment.getMax()) {
if (nextSegment == null || nextSegment.getCurrent() > nextSegment.getMax()) {
loadSegments();
}
currentSegment = nextSegment;
}
return currentSegment.incrementAndGet();
}

private void loadSegments() {
loader.submit(() -> {
nextSegment = fetchNewSegment();
});
}

// 省略容错机制
}

MyBatisPlus 自定义ID生成器

1
2
3
4
5
6
7
8
public class HybridIdGenerator implements IdentifierGenerator {
private final SegmentBuffer buffer;

@Override
public Number nextId(Object entity) {
return buffer.nextId();
}
}

性能对比测试

压力测试结果(单节点)

并发线程数 雪花算法吞吐量 混合方案吞吐量 内存占用对比
100 9,800/s 48,200/s +15%
500 12,400/s 51,300/s +18%
1000 11,900/s 49,800/s +22%

故障模拟场景

  1. 时钟回拨5秒:原生方案产生1300个异常,混合方案零异常
  2. 服务端宕机:客户端缓存支持15分钟正常运作
  3. 网络抖动:自动降级为本地随机数补充模式

工程化实践要点

  1. 双缓冲预热机制:提前加载下一个号段避免等待

  2. 动态步长调整:根据吞吐量自动计算最佳号段长度

    1
    2
    3
    4
    5
    6
    // 动态步长算法示例
    public int calculateStep(int currentQPS) {
    int baseStep = 1000;
    double factor = Math.log10(currentQPS / 1000.0);
    return (int) (baseStep * Math.pow(2, factor));
    }
  3. 异常熔断策略:在服务中心不可用时切换降级模式

  4. ID压缩存储:采用Base62编码缩短长度

    1
    2
    原始长整型:135790246813579 (15位)
    Base62编码:2Cst5WJ (7位)

实施效果

在某物流系统订单模块的应用数据:

  • 日均处理订单量从360万提升至2100万
  • 数据库插入耗时降低58%
  • ID字段存储空间减少41.7%
  • 时钟回拨导致的异常工单减少100%

该方案已在多个金融级系统中验证稳定性,支持春节期间每秒8.4万笔交易记录的创建需求。不同于传统的优化思路,通过将ID生成与数据持久化分离,实现了真正意义上的水平扩展能力。