事件驱动与异步任务
事件记录已经发生的业务事实,任务表达希望执行的动作。例如 VideoUploaded 表示视频已经上传完成,TranscodeVideo 表示需要转码。一个事件可以产生多个任务,也可以在判断条件后不产生任务。
异步执行把后续处理交给独立的 Worker。它减少请求等待时间,但请求成功通常只意味着业务与任务已被接受,不意味着转码、通知等动作已经完成。
一次业务变化如何产生任务
以视频上传为例:
| 阶段 | 需要确认的事实 |
|---|---|
| 完成上传 | 文件已经可读,业务记录指向确定的对象版本 |
| 保存事件 | 事件 ID、视频 ID 和版本已持久化 |
| 订阅事件 | 转码服务接收 VideoUploaded |
| 创建任务 | 保存目标格式、源文件版本和业务键 |
| 执行转码 | Worker 检查对象仍存在并生成结果 |
| 发布结果 | 记录产物版本,使正确结果对用户可见 |
对象存储与业务数据库可能分别提交。上表以“文件已完成上传并校验”为起点;上传后未登记的孤立对象,需要另行清理或核对。
数据库提交与事件发布
先提交业务再发布事件,存在“业务已保存,进程却在发布前崩溃”的窗口。先发布事件再提交业务,则可能让消费者处理最终回滚的变化。
事务发件箱(Transactional Outbox)把业务记录与待发事件写入同一个数据库事务,独立的投递器再发布事件。下面是依据 AWS Outbox 模式说明整理的流程:
数据库提交与事件落表可以原子完成,投递器发布与标记已发布仍是两个动作。投递器可能重复发送,因此消费者仍要去重。
确认发生在哪一层
发布确认说明消息代理按自身配置接受了消息;消费确认说明消费者声明这一投递已经处理。RabbitMQ 官方文档说明这两类确认彼此独立,见发布确认与消费确认。
消费者先确认再提交业务,崩溃后可能缺失处理;先提交业务再确认,崩溃后可能收到重复消息。对于需要恢复的业务,通常采用后者,并把业务更新与去重记录放进同一事务。
如果消费者只是创建一个持久化任务,消费确认可以放在任务提交之后。后续任务是否完成,由任务系统记录,不能在这里混为一个确认。
重复、乱序与旧事件
- 事件 ID 用来识别同一条事实的重复投递。
- 业务键用来识别同一个动作,例如某视频某版本的某种输出格式。
- 业务版本用来识别已经被新状态取代的旧事件。
这些字段承担不同职责。仅靠随机任务 ID,无法判断两个不同事件是否要求同一个业务动作。
库存提醒还需要明确是“每次库存变化都通知”,还是“从正常区间进入低库存区间时通知一次”。处理器可以依据当前状态和告警状态决定是否发送;旧事件不能无条件覆盖新状态。
兜底与修复
Outbox 投递器要记录最老待发事件的年龄,消费者要记录积压和失败。兜底任务可以核对“已上传但没有对应版本转码结果的视频”,补建缺失动作。
修复时使用与正常流程相同的业务键和处理器,避免另一套绕过幂等与条件校验的写入路径。
参考资料
- AWS Prescriptive Guidance:Transactional outbox pattern
- RabbitMQ:Consumer Acknowledgements and Publisher Confirms