随着业务容量的不断发展,我们越来越需要对庞大的业务进行解耦、异步操作,这时消息队列的优势就展现出来了,我们会将要处理的业务消息发送到消息队列中,通过消息队列异步操作完成对于业务的处理,从而提高处理业务的能力,那么消息队列是如何保证消费的幂等性呢,本文旨在由浅入深探讨如何保证消息队列的幂等性
以处理用户消费订单业务为例,如果用户在同一时间发起了多次创建订单请求(当然了,这里也可以通过前端来进行判断,这里探讨如何通过后端判断),那么这时多个请求发送过来,很简单,我们可以先判断当前用户订单状态是否已经更新,如果已经更新了那么我们就没必要再进行更新了,但是这里会有一个问题,如果订单处理业务要耗费很长时间,在并发场景下多个请求同时打到服务器上,就有可能出现其实当前正在更新状态,但是实际上判断并不生效的结果,因此这种最简朴的方法在业务繁琐的场景下并不奏效
这时我们想到,既然先判断后更新不行,我们加一把锁不就好了,直接在第一个请求来的时候,就给它处理业务的代码加一把锁,这样就能保证即使其他请求发送过来,也不会造成当前业务重复执行了,毫无疑问,这样一定能够保证同一个业务即使被发送多次也只会执行一次,但是在高并发的场景下,这样做会大大降低总体业务执行的一个效率。
就拿锁的两个分支乐观锁与悲观锁举例,如果我们使用悲观锁,在第一个请求来的时候就直接锁住整个业务,那么当其他请求过来时,就必须要等待,这样降低了其他服务执行的性能,得不偿失,而如果我们使用乐观锁,在第一个请求过来时,每次都执行,为了保证业务只能执行一次,我们必须要加一些版本号的字段等保证这个业务不会被重复执行,这样做加大了代码的复杂度,很显然,使用锁在高并发场景下也并不是最佳方案
其实要实现这个业务代码只执行一次很简单,我们只需要在业务请求第一次发送过来时,设置一个状态字段表示当前业务代码正在执行中,这样即使后续请求发送过来也能够保证当前请求只执行一次,但是如果这个请求在执行过程中失败了,那么我们又该如何保证这个请求能够成功地执行呢?
这里我们可以用数据库中的事务+插入消息表解决,我们可以额外建立一张数据库表为消息表,当我们执行业务代码时,按照如下流程执行请求:
这里我们以请求执行成功与失败两种情况进行分析:
执行成功:如果当前请求执行成功,那么会插入一条包含请求执行成功的消息,那么当相同的请求被再次发送到服务器上执行时,会在插入消息这里就失败,这时服务器可以认为当前请求已经执行成功了,因此可以直接返回,保证了请求只成功地执行一次
执行失败:如果请求在执行过程中出现失败,那么由于事务的特性发生回滚再次执行,直到执行成功为止,这样就保证了请求的幂等性
因此通过这种方案就保证了请求能够被幂等性地执行
但是,本篇文章的主角是如何通过消费队列保证幂等性,使用数据库的事务操作肯定可以满足,可是如果操作的不是MySQL这种关系型数据库,而是Redis这种没有事务机制的非关系型数据库,又或者我们要跨数据库执行请求,那么我们又该如何保证幂等性呢
为了能够更广泛地满足幂等性,我们可以使用消息队列结合前面提到的策略实现一套方案保证消费的幂等性:
在这个方案中,我们依然沿用了上面提到的设置状态、插入消息表等方案,不同的是在这里我们添加了一个延迟消费模块,在消息队列中也可以被称为延信队列,延信队列可以使在队列中的请求每隔一段时间就重新请求一次,这样就保证了即使请求执行失败,也可以再次执行直到成功为止,这样就保证了消费的幂等性
但是上面这种方案就是完美的了吗?试想一种极端情况,如果请求执行的过程中发生了宕机,请求一直执行不成功,或者业务代码本身就有问题,即使执行多次也不成功,那么这样岂不是要死循环了?
解决方法很简单,我们可以添加重试策略来规避这种情况,比如在消费开始时,就设置一个请求执行最大时间,若超时就返回错误:
又或者添加重试次数(RocketMQ默认重试16次),这样就使得即使业务代码执行出错也不会陷入到死循环中,也就解决了消费的幂等性
在中间插入状态表、执行业务插入消息表中,我们不一定必须使用数据库来完成插入操作,可以使用其他存储介质例如Redis等来完成插入操作,也能提高一部分性能
好了,这就是关于使用消息队列保证消费的幂等性的全部内容了,希望能对你有所帮助,祝好!!!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。