共计 2354 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
在现代应用中,技能创建服务(Create Skill Service)已成为语音助手、自动化流程等场景的核心组件。例如,用户可能同时创建多个语音控制技能,或者企业需要批量部署自动化工作流。这类服务面临两个主要挑战:

- 高并发请求处理:当大量用户同时创建技能时,传统的数据库写入容易成为瓶颈
- 技能状态一致性:创建过程中的异步操作(如权限校验、资源分配)可能导致最终状态不一致
常见的症状包括创建请求超时、重复技能记录,甚至数据损坏。这些问题在传统 CRUD 架构下尤其明显,因为:
- 直接更新数据库记录会导致锁竞争
- 业务逻辑与数据存储强耦合,难以追溯状态变更历史
架构设计
模式选择
对比两种主流架构风格:
- 传统 CRUD:
- 优点:实现简单,适合低复杂度业务
-
缺点:难以应对高频修改场景,历史追溯能力弱
-
事件溯源(Event Sourcing)+ CQRS:
- 优点:通过事件日志实现无锁并发,天然支持审计追溯
- 缺点:学习曲线较陡,需要处理事件版本迁移
我们选择后者,具体架构如下图所示:
flowchart LR
Client-->|Command|API
API-->CommandHandler
CommandHandler-->|Event|EventStore[(MongoDB 分片)]
EventStore-->|Publish|RabbitMQ
RabbitMQ-->ReadModelBuilder
ReadModelBuilder-->|Projection|ReadDB[(ReadDB)]
ReadDB-->API
关键决策
- 事件存储:采用 MongoDB 分片集群,利用其:
- 自动水平扩展能力
- 灵活的模式设计(适合存储异构事件)
-
内置的文档版本控制(_v 字段)
-
消息中间件:使用 RabbitMQ 实现:
- 事件广播(Fanout Exchange)
- 死信队列处理失败事件
- 消息 TTL 控制重试间隔
核心实现
幂等控制
通过自定义注解保证重复请求安全:
@Idempotent(key = "#request.clientId +'-'+ #request.skillName",
ttl = 5, timeUnit = TimeUnit.MINUTES)
@PostMapping("/skills")
public Mono<SkillResponse> createSkill(@RequestBody CreateSkillRequest request) {return commandService.handle(request);
}
实现原理:
- 使用 SpEL 表达式生成唯一键
- 通过 Redis 原子操作实现锁机制
- TTL 自动清理旧记录
事件冲突检测
在聚合根(Aggregate Root)中维护版本号:
public class SkillAggregate {
@Version
private Long version;
public void apply(SkillCreatedEvent event) {if(event.getExpectedVersion() != this.version) {throw new ConcurrentModificationException("版本冲突");
}
this.version++;
}
}
背压处理
使用 Project Reactor 控制流量:
Flux.fromIterable(events)
.onBackpressureBuffer(1000) // 缓冲队列大小
.delayElements(Duration.ofMillis(10)) // 控制消费速率
.subscribe(event -> {// 事件处理逻辑});
性能优化
压测对比
使用 JMeter 模拟 1 万并发用户,结果如下:
| 存储方案 | TPS | 平均延迟 | 错误率 |
|---|---|---|---|
| MySQL 单节点 | 1,200 | 450ms | 12% |
| MongoDB 副本集 | 3,800 | 120ms | 0.5% |
| MongoDB 分片(3 分片) | 8,500 | 35ms | 0% |
冷启动优化
- 事件快照:每 100 个事件生成一次聚合状态快照
- 预热策略:
@PostConstruct
public void warmUp() {executorService.submit(() -> {
// 预加载热点数据
cacheManager.preload("popular_skills");
});
}
避坑指南
事件风暴实践
- 使用不同颜色便签区分:
- 蓝色:领域事件(Domain Event)
- 黄色:命令(Command)
- 红色:异常场景
- 按时间轴排列事件流
- 重点关注「事件风暴事件风暴(Event Storming)」中的:
- 时间边界(Bounded Context)划分
- 外部系统集成点
生产监控
必须配置的指标:
- 事件存储延迟:
event_store_lag_seconds - 重放队列深度:
replay_queue_size - 命令处理耗时:
command_process_duration
使用 Prometheus+Grafana 监控看板示例:
rate(event_store_append_total[1m]) > 1000
灰度发布
采用双事件存储方案:
- 新版本写入新存储同时兼容旧格式
- 通过特征开关(Feature Flag)控制流量
- 使用适配器模式处理版本差异
延伸思考
开放性问题
- 如何设计技能间的依赖关系?例如技能 B 必须在技能 A 之后创建
- 能否实现事件时间旅行(Time Travel)调试?
建议尝试:
- 在事件模型中增加
dependsOn字段 - 开发事件回放调试器(Event Replayer)
总结
通过事件溯源架构,我们实现了:
- 线性扩展能力(实测支持 10K+ TPS)
- 精确到毫秒的业务追溯
- 自然解耦的微服务边界
后续可探索方向:
- 事件流分析(如技能创建模式挖掘)
- 基于 Wasmer 的沙箱化技能运行时
完整的示例代码已开源在 GitHub(伪 URL):
https://github.com/example/create-skill-service
正文完
