Skip to content

事件驱动与异步任务 ​

事件记录已经发生的业务事实,任务表达希望执行的动作。例如 VideoUploaded 表示视频已经上传完成,TranscodeVideo 表示需要转码。一个事件可以产生多个任务,也可以在判断条件后不产生任务。

异步执行把后续处理交给独立的 Worker。它减少请求等待时间,但请求成功通常只意味着业务与任务已被接受,不意味着转码、通知等动作已经完成。

一次业务变化如何产生任务 ​

以视频上传为例:

阶段需要确认的事实
完成上传文件已经可读,业务记录指向确定的对象版本
保存事件事件 ID、视频 ID 和版本已持久化
订阅事件转码服务接收 VideoUploaded
创建任务保存目标格式、源文件版本和业务键
执行转码Worker 检查对象仍存在并生成结果
发布结果记录产物版本,使正确结果对用户可见

对象存储与业务数据库可能分别提交。上表以“文件已完成上传并校验”为起点;上传后未登记的孤立对象,需要另行清理或核对。

数据库提交与事件发布 ​

先提交业务再发布事件,存在“业务已保存,进程却在发布前崩溃”的窗口。先发布事件再提交业务,则可能让消费者处理最终回滚的变化。

事务发件箱(Transactional Outbox)把业务记录与待发事件写入同一个数据库事务,独立的投递器再发布事件。下面是依据 AWS Outbox 模式说明整理的流程:

数据库提交与事件落表可以原子完成,投递器发布与标记已发布仍是两个动作。投递器可能重复发送,因此消费者仍要去重。

确认发生在哪一层 ​

发布确认说明消息代理按自身配置接受了消息;消费确认说明消费者声明这一投递已经处理。RabbitMQ 官方文档说明这两类确认彼此独立,见发布确认与消费确认。

消费者先确认再提交业务,崩溃后可能缺失处理;先提交业务再确认,崩溃后可能收到重复消息。对于需要恢复的业务,通常采用后者,并把业务更新与去重记录放进同一事务。

如果消费者只是创建一个持久化任务,消费确认可以放在任务提交之后。后续任务是否完成,由任务系统记录,不能在这里混为一个确认。

重复、乱序与旧事件 ​

  • 事件 ID 用来识别同一条事实的重复投递。
  • 业务键用来识别同一个动作,例如某视频某版本的某种输出格式。
  • 业务版本用来识别已经被新状态取代的旧事件。

这些字段承担不同职责。仅靠随机任务 ID,无法判断两个不同事件是否要求同一个业务动作。

库存提醒还需要明确是“每次库存变化都通知”,还是“从正常区间进入低库存区间时通知一次”。处理器可以依据当前状态和告警状态决定是否发送;旧事件不能无条件覆盖新状态。

兜底与修复 ​

Outbox 投递器要记录最老待发事件的年龄,消费者要记录积压和失败。兜底任务可以核对“已上传但没有对应版本转码结果的视频”,补建缺失动作。

修复时使用与正常流程相同的业务键和处理器,避免另一套绕过幂等与条件校验的写入路径。

参考资料 ​