选择一个小到可以验证的边界
渐进式迁移可以缩小每次发布的范围,但并非没有风险。从一个输入、输出和负责人都清楚的有限业务流程开始。在切换流量之前,先梳理旧的数据模型、外部副作用以及报表依赖。
记录错误率、响应延迟、队列积压时长,以及已完成订单等业务结果,作为基线。将一部分受控的请求导向新实现,并保留一条清晰的回退路径。除非依赖关系确有要求,否则不要同时迁移存储、框架、支付和用户界面。
让关键写入保持事务性
先校验权限和输入,再在数据库事务中持久化最小的有效状态变更。用唯一约束来保证不变量,例如每个订单只对应一个外部引用。行锁或条件更新在正确使用时,可以保护并发的状态变更。
Redis 锁可以协调工作,但租约过期、进程崩溃或键的范围设置错误,都可能导致执行重叠。它不能替代数据库约束。请记录每个不变量由哪个系统负责,并用并发请求加以测试。
只在数据提交之后再分发任务
工作进程可能在触发它的事务提交之前就开始运行。Laravel 支持在提交后分发任务,这样任务就不会读取到未提交或已回滚的记录。在 Laravel 12 应用中,一个基础模式如下:
DB::transaction(function () use ($validated) {
$order = Order::create($validated);
ProcessOrder::dispatch($order->id)->afterCommit();
});
这个示例刻意不完整:授权、重复请求处理和业务校验应放在它的外围。提交后分发也无法消除数据库提交与交付到外部队列之间的故障窗口。对于需要可靠投递的流程,可以考虑事务性发件箱(outbox)配合会重试的发布器。
按“至少执行一次”来设计任务
假设一个任务可能会执行不止一次。持久化幂等键,记录已完成的操作,并在外部服务商提供时使用其幂等机制。在外部副作用成功之前,不要把任务标记为已完成。对于结果不确定的情况,应进行对账,而不是盲目地重复扣款或发货。
- 根据操作类型设置超时、重试次数上限和退避策略。
- 让工作进程的超时时间短于队列的重试间隔,并为关闭预留余量,以减少重叠的尝试。
- 监控失败任务和队列积压时长;HTTP 202 表示“已接受”,而不是“已完成”。
- 在发布时重启长时间运行的工作进程,确保它们加载的是预期的代码。
演练故障与回滚
测试重复的 Webhook、外部调用之后工作进程崩溃、数据库死锁、队列中断以及部署失败等情况。检查最终产生的记录和副作用,而不仅仅是 HTTP 响应。对于已接受但从未完成的工作,要增加对账机制。
在两个应用同时运行期间,使用向后兼容的表结构变更。在发布前定义回滚条件,并确认旧应用能够读取新应用写入的数据。在明确设计好同步机制之前,每条记录都只保留一个可信的数据来源。
常见问题
什么时候应该保持同步处理?
当调用方需要立即得到确定的结果,并且工作足够短、能满足响应时间预算时。队列适合可以延后的工作,但它会带来自身在投递、可观测性和恢复方面的责任。
提交后分发能保证任务一定进入队列吗?
不能。它可以防止任务在数据库事务提交前被分发,但在提交与交付到外部队列之间仍可能发生故障。如果需要可靠投递,可以评估事务性发件箱,并配合会重试的发布器和对账机制。
如何防止重试产生重复的付款或订单?
使用持久化的幂等键、数据库约束,并在外部服务商提供时使用其幂等机制。记录执行结果,并对不确定的响应进行对账。仅靠一个短期锁,无法覆盖所有崩溃和重试窗口。
怎样让渐进式迁移更容易回滚?
迁移一个有限的流程,保留一条通往旧实现的清晰路由,并使用向后兼容的表结构变更。测试旧应用能否读取新应用创建的记录。在发布前,根据错误和业务结果定义回滚触发条件。
参考资料与延伸阅读
继续阅读
比较不同的渲染策略,或与我们探讨 Laravel 迁移。

Leave a Reply