共计 2420 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:订阅业务的并发挑战
在 Claude Code 订阅服务上线初期,我们遭遇了典型的突发流量问题。通过监控系统观察到:

- MySQL 集群 QPS 峰值达到 12,000 时出现连接池耗尽(16 核 32G 实例)
- 订单状态不一致率在促销期间高达 0.3%(表现为支付成功但未开通服务)
- 分布式事务超时导致重复扣款投诉日均 5 起
这些问题的本质是传统 CRUD 架构在以下方面的固有缺陷:
- 强一致性锁导致线程阻塞
- 状态变更缺乏可追溯性
- 读写操作耦合影响性能
架构演进:从 CRUD 到事件溯源
传统架构瓶颈
flowchart TD
A[客户端] -->|HTTP 请求 | B[Controller]
B --> C[Service 层事务]
C --> D[DB 行锁竞争]
D --> E[返回响应]
新架构设计
采用事件溯源 +CQRS 模式后:
flowchart LR
A[Command] --> B[CommandHandler]
B --> C[EventStore]
C --> D[MQ]
D --> E[ReadDB Projection]
技术选型依据 :
- 写模型:Spring StateMachine + Axon Framework
- 读模型:Elasticsearch 分片(按用户 ID 哈希)
- 消息中间件:RocketMQ 5.0 事务消息
核心实现细节
事件发布(Spring Cloud Stream)
// 带幂等控制的事件发布器
public class EventPublisher {
@Autowired
private StreamBridge streamBridge;
/**
* @param event 领域事件
* @param idempotentKey 幂等键(用户 ID+ 事件类型 + 日期)*/
public void publish(DomainEvent event, String idempotentKey) {if(redisTemplate.opsForValue().setIfAbsent("event:"+idempotentKey, "1", 24, TimeUnit.HOURS)) {
Message<DomainEvent> message = MessageBuilder
.withPayload(event)
.setHeader("idempotent_key", idempotentKey)
.build();
streamBridge.send("claude-events-out", message);
}
}
}
状态机实现
@Configuration
@EnableStateMachine
public class SubscriptionStateMachineConfig {
// 状态定义
public enum States {INIT, TRIAL, ACTIVE, EXPIRED}
// 事件定义
public enum Events {START_TRIAL, PAYMENT_RECEIVED, EXPIRE}
@Bean
public StateMachine<States, Events> stateMachine() {
StateMachineBuilder.Builder<States, Events> builder =
new StateMachineBuilder.Builder<>();
// 转换规则配置
builder.configureTransitions()
.withExternal()
.source(States.INIT)
.target(States.TRIAL)
.event(Events.START_TRIAL)
.action(checkCreditAction())
.and()
.withExternal()
.source(States.TRIAL)
.target(States.ACTIVE)
.event(Events.PAYMENT_RECEIVED)
.guard(paymentVerifiedGuard());
return builder.build();}
}
验证方案
压测配置(JMeter)
1. 线程组配置:- 起始线程数:500
- 每 30 秒增加 200 线程
- 最大线程数:3000
2. 采样器配置:- HTTP 请求路径:/api/subscribe
- 请求体:模拟不同订阅套餐
测试环境 :
- 8 台 16 核 ECS(阿里云 c6.4xlarge)
- 3 节点 RocketMQ 集群
- Redis Cluster(6 节点)
关键指标对比
| 方案 | QPS 上限 | TP99(ms) | 错误率 |
|---|---|---|---|
| 传统事务 | 8,000 | 420 | 1.2% |
| 本地消息表 | 15,000 | 210 | 0.05% |
| 事务消息 | 28,000 | 85 | 0.01% |
避坑指南
分布式 ID 生成
// 解决时钟回拨
public class SnowflakeIdGenerator {public synchronized long nextId() {long currentMillis = System.currentTimeMillis();
if (currentMillis < lastTimestamp) {
// 时钟回拨时等待两倍差值
long offset = lastTimestamp - currentMillis;
LockSupport.parkNanos(offset * 2 * 1_000_000);
currentMillis = System.currentTimeMillis();}
// ... 正常 ID 生成逻辑
}
}
补偿任务隔离
# Spring Scheduler 配置
spring:
task:
scheduling:
pool:
size: 2 # 专用补偿任务线程池
thread-name-prefix: compensation-
总结
通过事件溯源架构改造,Claude Code 订阅服务在双十一期间成功支撑了峰值 32 万 QPS 的订阅请求,状态不一致率降至 0.001% 以下。关键经验包括:
- 读写分离要配合合理的分片策略
- 领域事件需要保留版本信息
- 补偿机制必须考虑资源隔离
下一步计划探索 Event Sourcing 与 Serverless 的结合,进一步提升弹性伸缩能力。
正文完
