谈谈跟事务相关的业务逻辑

近日,在业务方面有些许领悟,特写下。暂记事务、缓存(redis)、消息中间件(rabbitmq)在业务中的使用。

事务定义与特性,这里不多说些。可不得不说,有关DML(数据操作语言)跟事务分不开,只要是一个方法中设计DML,就不得不在方法头加一个@Transcational注解(但如果方法有且仅有一条DML,可以不加,因为这条语句本身就会开启一个事务,当然加上也不会有错)。现在需要在一个方法做如下事情:1.针对数据库多于余条DML的执行,2.把记录发送至rabbitmq队列等待消费(第一次,redis保存这条记录)。

现在说说事情的顺序。毫无疑问,发送mq消息【可使用附录中send方法解决】,必须在事务提交后进行,嗯,这个可以使用TransactionSynchronizationManager.registerSynchronization来完成。

到这里,这种写法,可以解决事务失败,但是mq消息发送出去进而被消费的情况A。先说说,这个情况A带来的影响,之前做一个项目,需要调用第三方API,操作第三方的数据库,发送第三方请求在事务里面,当一切正常的时候,没问题,可天有不测风云,1.本地宕机、网络延迟,数据库死锁,总之本地事务回滚了,第三方操作完美执行了,这时,本地数据库数据依然正常,可在第三方的数据改变了,数据一致性没了,2.本地事务提交了,第三方失败了,嗯,依然是数据不一致了。当时是这样解决的,针对1,本地写一圈验证,判断在第三方是否有记录,没有就提示需要怎样怎样,总之把在第三方把数据弄上去,针对2,发送mq的时候,把记录保存至DB/Redis/MongoDB(总之保存起来),然后写一个Quartz定时任务,每X分钟遍历保存的记录,重新发送mq,保证第三方最后一定得成功。(可以保证最终一致性,有点延迟罢了)。

现在,事务提交后才发mq消息,已经解决这个坏情况,可能你已经看到附录send()方法里面redis的保存并不在事务提交后执行,为什么不放进去呢?如果事务回滚,但redis保存了,redis保存没有意义啊,更糟的情况是如果有个定时任务遍历redis,将记录消费了,就将出现不测风云1的情况。嗯,将redis保存也写在事务提交以后(写法二),多好。真的好吗?嗯,你可能忘记了项目会自动重启(k8s)或者宕机等等,假设项目在事务提交后,事务提交后其他动作(保存redis,发送mq)执行前,项目自动重新,这个时候,就会出现比不测风云2更严重的情况,你根本无法采用先前说道的补偿措施,因为根本没有保存到redis,没法使用定时任务来补偿,会导致消息丢失,致命bug。

既然如此,现在说说,附录写法的设计思路:当项目重启时,a.事务提交时,redis保存了,就算mq没有发出去也没关系,写一个定时任务遍历redis,重发(或写一个定时任务提示运维,redis里面有多少mq消息记录,让运维决定是否手动发送mq消息,这里采用这个,后面说原因);b.刚保存redis完重启,事务没有提交,这是一个弊端,这是redis是脏数据。你可能已经想到了,前面为什么让运维决定是否手动重发mq,不错,运维可以对比数据库,手动清除redis脏数据,耗时些罢了。c.还没到redis,重启,光依赖事务就很不错。

 

附录(java):

绑定事务,在事务提交后才执行

public void send(){

     //....something

 

//第一次的队列保存在redis

 

redisTemplate.opsForValue()
.set(QueueConstants.MQ_REDIS_NAMESPACE + msgId, msgData, 7L, TimeUnit.DAYS);

// 再发消息

 

TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
rabbitTemplate.convertAndSend(exName, routingKey, msgData, correlationId);
}
});
}
  
posted on 2019-12-24 18:52  CarlCN  阅读(330)  评论(0编辑  收藏  举报