前言MQ最近越来越火了。许多公司和个人都在使用它。它的重要性不言而喻。但是如果我让你回答下面的问题:我们为什么要用mq?引入mq会有什么问题?如何解决这些问题?你心里有答案吗?本文将一一为您解答,这些看似平凡却又有意义的问题。1传统模式的痛点是什么?1.1痛点1对于一些复杂的业务系统,一个用户请求可能会同时调用N个系统接口,需要等待所有接口返回才能得到执行结果。这种同步接口调用的方式总是耗时较长,非常影响用户体验,尤其是在网络不稳定的情况下,非常容易出现接口超时的问题。1.2痛点2很多复杂的业务系统一般都会拆分成多个子系统。这里我们以用户的订单为例。请求会先通过订单系统,然后分别调用:支付系统、库存系统、积分系统和物流系统。系统之间的耦合度太高。如果调用的任何一个子系统出现异常,整个请求都会出现异常,这对系统的稳定性是非常不利的。1.3痛点3有时候为了吸引用户,我们会搞一些活动,比如秒杀。如果用户较少,也不会影响系统的稳定性。但是,如果突然增加用户,所有的请求都会一下子跑到数据库,可能会导致数据库承受不了这么大的压力,响应变慢或者直接挂掉。对于这种突然的请求高峰,系统的稳定性无法得到保证。2为什么要用mq?对于上面传统模式中的三类问题,我们可以使用mq轻松解决。2.1异步针对痛点一:同步接口调用导致响应时间长。使用mq后,将同步调用改为异步调用,可以明显减少系统响应时间。A系统作为消息的生产者,可以在完成自己的工作后直接返回结果。不再等待消息消费者的归来,而是最终独立完成所有的业务功能。这样可以避免总时间比较长,影响用户体验的问题。2.2解耦针对痛点2:子系统之间耦合度过高的问题,使用mq后,我们只需要依赖mq,避免了子系统之间强依赖的问题。订单系统作为消息生产者,只需要保证自己没有异常,不会受到支付系统等业务子系统异常的影响,各个消费者业务子系统之间也不会相互影响。这样,将之前复杂的业务子系统依赖转化为只依赖mq的简单依赖,从而显着降低了系统间的耦合度。2.3消峰针对痛点3:突发的请求高峰导致系统不稳定。使用mq后,可以起到消峰的作用。订单系统收到用户的请求后,直接将请求发送给mq,然后订单消费者从mq中消费消息,进行写数据库操作。如果出现请求高峰,由于消费者的消费能力有限,他们会按照自己的节奏消费消息。很多请求不会被处理,会保留在mq队列中,不会影响系统的稳定性。3mq的引入有哪些问题?引入mq后,降低了我们子系统之间的耦合度。异步处理机制减少了系统的响应时间,同时可以有效处理峰值请求问题,提高系统的稳定性。但是引入mq也会带来一些问题。3.1重复消息问题重复消费的问题可以说是MQ中的通病,无论使用哪种MQ,都无法避免。什么情况下会出现重复消息?消息生产者生成重复的消息。回调kafka和rocketmq的offset。消息消费者确认失败。消息消费者确认消息消费者超时。处理会对业务产生很大影响,产生重复数据,或造成数据异常,如会员系统开通一个月多会员。3.2数据一致性问题很多时候,如果mq消费者业务处理出现异常,就会出现数据一致性问题。例如:一个完整??的业务流程是下单成功后发送100积分。订单写入数据库,但是消息消费者发送点数失败,会造成数据不一致,即业务流程的数据一部分写入数据库,另一部分没有写入数据库.如果下单和发送点在同一个事务中,要么同时成功,要么同时失败,就不会有数据一致性问题。但是由于跨系统调用,出于性能的考虑,一般不采用强一致性方案,而是实现最终一致性。3.3消息丢失的问题同样消息丢失的问题也是mq中的一个普遍问题,无论你使用哪个mq,都无法避免。什么情况下会出现消息丢失问题?当消息生产者产生消息时,由于网络原因,MQ失败。mq服务器持久化时,磁盘异常。回调kafka和rocketmq的offset时,会跳过很多消息。消息消费者刚读完消息,已经ack确认了,但是还没有处理完业务就重启了服务。导致消息丢失问题的原因有很多。生产者、mq服务器、消费者都可能有问题。我不会在这里列出它们。最终的结果将是消费者无法正确处理消息,导致数据不一致。3.4消息序列问题一些业务数据是有状态的。例如,一个订单有下单、付款、完成和退货等状态。如果使用订单数据作为消息体,就会涉及到顺序问题。如果一个消费者收到同一个订单的两条消息,第一条消息的状态是已下单,第二条消息的状态是已付款,这样就可以了。但是如果第一条消息的状态是支付,第二条消息的状态是订单,就会出现问题。不下单就先付款?消息顺序的问题是一个非常难的问题,比如:Kafka在同一个partition的介质中可以保证顺序,但是对于不同的partition就不能保证顺序。rabbitmq同一个队列可以保证顺序,但是如果多个消费者使用同一个队列,就会出现顺序问题。如果消费者使用多线程消费消息,则无法保证顺序。如果在消费消息时,同一个订单的多条消息中有一条出现异常,就会打乱顺序。另外,如果生产者发送给mq的路由规则和消费者发送的路由规则不一样,则不能保证顺序。3.5消息积累如果消息消费者读取消息的速度能够跟上消息生产者的节奏,那么整个MQ机制就可以发挥最大的作用。但是很多时候,由于一些批处理或者其他原因,导致消息消费的速度比生产的速度要慢。这会直接导致消息堆积的问题,进而影响业务功能。下面是一个通过下订单开通会员的例子。如果消息堆积如山,会导致用户下单后很久才成为会员。这种情况肯定会引起大量的用户投诉。3.6系统复杂度的提高这里所说的系统复杂度和系统耦合度是不一样的。比如原来只有A系统、B系统、C系统三个系统,现在引入mq后,需要关注前三个系统,另外,还需要关注mq服务。需要注意的点越多,系统的复杂度就越高。mq的机制需要:producer,mqserver,consumer。有一定的学习成本,需要额外部署一个MQ服务器,还有一些MQ,比如:rocketmq,功能很强大,但是使用起来有点复杂。如果使用不当,就会出现很多问题。有些问题不像接口调用那样容易排查,增加了系统的复杂度。4如何解决这些问题?MQ是一种趋势。一般来说,我们的系统优势大于劣势。是不是因为它会带来一些问题,所以我们不用它?那么我们如何解决这些问题呢?4.1RepeatMessage问题无论是生产者产生的重复消息,还是消费者造成的重复消息,我们都会在消费者中出现这个问题。这就要求消费者在做业务处理的时候要做幂等的设计。如果有不懂设计的朋友,可以参考《高并发下如何保证接口的幂等性?》了解详情。这里我推荐增加一个消费消息表来解决mq的此类问题。在消费消息表中,使用messageId作为唯一索引。在处理业务逻辑之前,根据messageId查看消息是否已经处理完毕。如果已经处理,则直接返回成功。未办理的,继续办理业务。4.2数据一致性问题我们都知道数据一致性分为:强一致性弱一致性最终一致性MQ出于性能考虑使用最终一致性,所以数据不一致是不可避免的。这种问题很可能是消费者读取消息后业务逻辑处理失败导致的。这时候可以加入重试机制。重试分为:同步重试和异步重试。在一些消息量比较小的业务场景下,可以使用同步重试??。如果消费消息时处理失败,立即重试3-5次。如果仍然失败,则将其写入记录表。但是如果消息量比较大,不推荐使用这种方式,因为如果出现网络异常,可能会不断重试大量消息,影响消息读取速度,造成消息堆积。对于消息量比较大的业务场景,推荐使用异步重试。消费者处理失败后,会立即写入重试表,有一个job专门负责定时重试。另一种方法是,如果消费失败,你可以自己给同一个topic发送一条消息,稍后某个时间点,你会再次消费这条消息,起到重试的作用。这种方法可以用于消息顺序不高的场景。4.3消息丢失的问题不管你承认不承认,有时候消息真的丢失了,即使概率很小,也会对业务造成影响。生产者、mq服务器、消费者都有可能导致消息丢失的问题。为了解决这个问题,我们可以添加一个消息发送表。生产者发送完消息后,会往表中写入一条数据,状态会被标记为等待确认。消费者阅读消息后,调用生产者的api将消息状态更新为已确认。有一个工作,每隔一段时间检查一次消息发送表。如果5分钟后(这个时间可以根据实际情况确定)还有一条消息的状态需要确认,则认为该消息已经丢失,重新发送一条消息。这样无论是生产者、mq服务器还是消费者造成的消息丢失问题,作业都会重新发送消息。4.4消息顺序问题消息顺序问题是我们很常见的问题。下面以kafka消费订单消息为例。订单包括:下单、付款、完成、退货等状态。这些状态是有序的。订单错了,业务就会异常。在解决这类问题之前,先确认一下消费者是否真的需要知道中间状态,而只知道最终状态?其实很多时候,我真正需要知道的是最终状态。这时候可以优化流程:这种方式可以解决大部分的消息顺序问题。但是如果真的需要保证消息的顺序。订单号被路由到不同的分区,相同订单号的消息每次都发送到同一个分区。4.5消息堆积如果消费者消费消息的速率低于生产者生产消息的速率,就会出现消息堆积问题。其实造成这种问题的原因有很多。想了解更多可以看我的另一篇文章《我用kafka两年踩过的一些非比寻常的坑》。那么如何解决消息堆积的问题呢?这个要看是否需要保证消息的顺序。如果不需要保证顺序,可以在读取消息后使用多线程处理业务逻辑。这样可以提高业务逻辑处理的速度,解决消息堆积的问题。但是需要合理配置核心线程数和线程池最大线程数,否则可能会浪费系统资源。如果需要保证顺序,可以读取消息,将消息按照一定的规则分发到多个队列中,然后在队列中用单线程进行处理。好了,今天就分享到这里吧,我们下期再见。我只是在这里抛砖引玉。其实mq相关的内容还有很多,比如:定时发送、延迟发送、私有消息队列、事务问题等等。
