当前业务流程是在进入 mq 队列之前持久化一个模型数据,传入 id 后在 work 中进行查询然后进一步处理,现在的情况是 create 后进入 work 中根据 id 查询不到相应的数据,出现频率大概为 3/10,导致后续流程无法跑通,因为内部约定不能使用 sleep,一直 while 进行循环获取又有点傻,请问还有其他办法吗。
这应该要确保数据库保存后才入队。入队之前数据库事务 commit 了没?
以下两种情况会导致你所说的问题。
你推送 id 进入 RabbitMQ 的逻辑用的是 after_save 而不是 after_commit.
after_save
after_commit
通常 DB 有四个级别,serializable, repeatable read, read commit, read uncommitted. MySQL 默认隔离级别是 repeatable read,不同 transaction 内部未提交数据,对于其他 transaction 不可见,避免了 dirty read, phantom read 的问题。after_save 时,数据库的事务还没提交,其他事务是看不到你创建的记录的。
repeatable read
数据库的隔离级别是一个很有意思的话题,是很多面试必考题目,值得学习。
解决方法:使用 after_commit 就可以避免类似 bug。
primary 和 replica 之间通常有 0.5-10 秒的延迟。在延迟很高的情况下,你的写入成功了,但是从库却没有数据。
解决思路有三种
发送消息时,延迟 10 秒。比如像 sidekiq 的 MyJob.perform_in(10.minutes)
你可以强制这个任务读写都使用 primary database。
使用计算和存储分离的数据库,比如 Aurora,数据库节点之间 0 延迟。
大概率就是 2 楼说的原因了,你没 commit 就提交了异步任务,然后 save 的时候校验没过回滚了,自然执行异步用 id 找不到数据
能拿到 ID 应该是持久化成功了,save 时的 validation 应该是通过了,感觉 replica 同步延迟的可能性大一些
要异步的对象 save 成功 但是业务流后面的逻辑或者其他校验失败了导致事务回滚
拿到 ID 不代表已经 commit
排查下 job 是不是放到事务中了
不一定是校验没过导致回滚了,很大可能是还没持久化,就根据 id 查数据