相信大家都用过消息MQ。可以很好的对系统进行解耦,降低系统的复杂度,还可以进行调峰,增加系统的高并发稳定性。那么使用MQ有哪些注意事项呢?MQ万无一失?MQ消息从生成到消费有没有可能失败?可能在哪些环节失效,如何处理?1、消息生产失败一般来说,从生产者到通过网络调用MQ中间件,都可能出现网络调用失败的情况。以下原因可能会导致MQ生产失败,比如网络波动。虽然生产者和MQ服务器是内网调用,但不代表网络调用的成功率是100%,内网调用也会遇到网络波动,导致调用超时或失败。再比如被调用的MQ机器瞬间崩溃,可能会导致调用失败。面对生产者调用MQ失败的情况,我们很容易处理,也比较容易处理。我们只需要简单地重试。如果重试2-3次都失败,很有可能是出了大问题。此时再试意义不大,需要引起警惕和处理。2、MQ处理存储失败消息到达消息中间件后,通常进行存储。只有写入磁盘时,消息才真正存储起来,不会丢失。但是大部分MQ中间件并不会立即将消息写入磁盘,而是因为磁盘的写入速度比内存慢很多,所以像Kafka这样的消息系统会将消息写入磁盘。消息写入缓冲区并异步写入磁盘。如果机器在中途突然断电,就有可能丢失消息。为了解决这个问题,大部分MQ都是分布式部署的。消息会被成功写入多台机器上的缓存,然后返回给业务方。由于多台机器同时断电的可能性很小,我们可以认为这是一种成本相对较低且可靠的方案。3、消费者处理失败。一般的MQ都有MQ重试机制。如果处理失败,会反复尝试消费MQ。这样带来的问题是MQ可能消费成功了,但是通知MQ中间件的时候失败了。这时候的结果就是重复消费消息。同样,生产者重试时,也会遇到消息重复消费的问题。这时候就要求我们把接口设计的尽量幂等。这时候,即使是重复消费,也不用担心出现什么问题。基本上做好这三点,就可以大大提高我们系统的可用性!这里需要重点关注几个关键点:幂等性不仅仅是一个(或多个)请求对资源没有副作用(比如查询数据库操作,没有增删改查,所以对资源没有影响数据库)。幂等性还包括在发出第一个请求时对资源产生副作用,但后续请求将不再对资源产生副作用。幂等性关注的是后续多次请求是否对资源产生副作用,而不是结果。幂等性是系统服务对外的一种承诺(而不是实现)。它承诺只要调用接口成功,多次外部调用对系统的影响是一致的。声明为幂等的服务将假设外部调用失败是正常的,并且失败后必须重试。什么情况下需要幂等?在业务开发中,经常会遇到重复提交,无论是因为网络问题收不到请求结果重新发起请求,还是前端运行抖动导致重复提交。在交易系统中,支付系统重复提交带来的问题尤为明显。比如用户在APP上多次点击提交订单,后台只生成一个订单;当向支付系统发起支付请求时,由于网络问题或系统BUG重发,支付系统只扣款一次。显然,声明幂等性的服务相信外部调用者会进行多次调用。为了防止多个外部调用多次改变系统数据状态,服务被设计成幂等的。幂等性VS防重复上面例子中遇到的问题只是重复提交的情况,与服务幂等性的初衷不同。重复提交是指在第一次请求成功后,人为地执行多次操作,导致不满足幂等性要求的服务多次改变状态。但是幂等性更多的用在第一次请求不知道结果(比如超时)或者失败,发起多次请求的时候。目的是为了多次确认第一次请求成功,而不是因为多次请求。有多个状态变化。什么情况下需要保证幂等性?以SQL为例,有以下三种场景。只有第三种场景需要开发者使用其他策略来保证幂等性:SELECTcol1FROMtab1WHERcol2=2,无论执行多少次都会改变状态,自然是幂等的。UPDATEtab1SETcol1=1WHEREcol2=2,无论执行多少次,状态都是一致的,所以也是幂等操作。UPDATEtab1SETcol1=col1+1WHEREcol2=2,每次执行的结果都会改变,这个不是幂等的。为什么要设计幂等服务幂等性可以让客户端逻辑处理更简单,但代价是服务逻辑复杂。要满足幂等服务的需求,逻辑上至少有两点:第一,查询上次执行状态。如果没有,则认为是第一次请求。在服务改变状态的业务逻辑之前,是幂等的,保证防重复提交的逻辑幂等性。幂等性不足是简化了客户端的逻辑处理,却增加了服务提供者的逻辑和成本。是否需要需要根据具体场景具体分析。因此,除了特殊的业务需求,尽量不要提供幂等接口。增加了控制幂等性的业务逻辑,使业务功能复杂化;将并行执行函数改为串行执行会降低执行效率。保证幂等策略的幂等性需要通过唯一的业务票号来保证。也就是说,相同的业务订单号被视为相同的业务。利用这个唯一的业务订单号,保证以后多次处理同一个业务订单号的处理逻辑和执行效果是一致的。我们以支付为例。不考虑并发实现幂等性很简单:①首先检查订单是否已经支付,②如果已经支付,则返回支付成功;如果不是,请转到付款流程并修改订单状态为“已付款”。防重复提交策略上述幂等保证方案分为两步。第二步依赖于第一步的查询结果,不能保证原子性。在高并发下,会出现如下情况:当第一个请求的第2步中的订单状态还没有变为'已付款状态'时,第二个请求到达。既然得出了这个结论,剩下的问题就简单了:锁定查询和状态改变操作,将并行操作改为串行操作。如果乐观锁只是更新已有的数据,就没有必要对业务加锁。在设计表结构时使用了乐观锁。一般用version做乐观锁,既能保证执行效率,又能保证幂等性。例如:UPDATEtab1SETcol1=1,version=version+1WHEREversion=#version#但是如果乐观锁失败,是ABA常见的问题,但是如果version版本一直自增,则不会出现ABA案件。防重表使用序号orderNo作为去重表的唯一索引,每次请求根据序号向去重表插入一条数据。第一个请求是查询订单的支付状态。当然,订单还没有支付,进行支付操作。不管成功与否,执行后更新订单状态为成功或失败,并删除去重表中的数据。后续订单由于表中唯一索引插入失败,返回操作失败,直到第一个请求完成(成功或失败)。可见,反重表的作用就是加锁的作用。分布式锁这里使用的反重表可以用分布式锁代替,比如Redis。当订单发起支付请求时,支付系统会去Redis缓存中查询订单号的Key是否存在。如果不存在,则将该Key作为订单号添加到Redis中。查询订单支付是否已经支付,如果没有支付,支付完成后删除订单号的Key。分布式锁是通过Redis实现的。只有这次的订单支付请求完成了,下一个请求才能进来。相对于去重表,把并发放到缓存中效率更高。思路是一样的,同一时间只能完成一个支付请求。token代币这样分为两个阶段:申请代币阶段和支付阶段。第一阶段,在进入订单提交页面之前,订单系统需要根据用户信息向支付系统发起token请求,支付系统将token保存在Redis缓存中,供第二阶段支付使用。第二阶段,订单系统使用申请的token发起支付请求,支付系统会检查Redis中是否存在该token。如果存在,则表示第一次发起支付请求,删除缓存中的token后开始支付逻辑处理;如果缓存中不存在,则说明是非法请求。其实这里的token就是token,支付系统根据token确认你是你妈的孩子。缺点是需要两个系统之间的交互,过程比上述方法复杂。支付缓冲快速跟随订单的支付请求,一个用于快速接受订单的缓冲流水线。后续使用异步任务处理管道中的数据,过滤掉重复的待支付订单。优点是同步转异步,吞吐量高。缺点是不能及时返回支付结果,需要跟进支付结果的异步返回。
