为何使用?其实就是在实际业务中,有个具体的场景,若是不使用MQ,可能会有不少麻烦,用了MQ以后带给咱们不少好处。场景其实有不少,常见的有三个:1.解耦、2.异步、3.削峰数据库
A系统要发送一条数据到BCD三个系统,接口调用发送,若是新增个E系统,也须要这条数据呢?若是C系统如今不须要这条数据了呢?若是A系统又要发送第二种数据了呢?并且A系统要时刻关注BCD系统的状态,BCD挂了怎么办?要不要重发?要不要把数据存起来?api
若是系统中存在相似状况,你能够考虑这个调用是否是必需要同步调用?若是用MQ来解耦,会省去不少麻烦。缓存
A系统接收一个请求,须要在ABCD四个数据库进行写库操做,A本地写库须要30ms,B库须要100ms,C库须要200ms,C库须要200ms,则一共须要30+100+200+200ms。若是用MQ来进行异步操做,则只须要30ms后便可返回,BCD异步写入便可,A不用考虑。网络
天天0~11点,A系统风平浪静,每秒并发100,12点时,并发暴增到10000,系统每秒只能处理1000个请求,怎么办?这时能够用MQ进行流量削峰。并发
优势已经说了,解耦,异步,削峰,接下来讲消息队列的缺点:异步
原本只须要考虑系统自己可用性,如今引入了MQ,若是MQ挂了怎么办?性能
MQ加进来了,消息丢失怎么办?重复投递怎么办?重复消费怎么办?消息的顺序性怎么保证?日志
A处理完返回成功了,调用者觉得请求成功了,但是BC成功,D失败了怎么办,数据就不一致了。orm
因此,消息队列其实结构很是复杂,引入它会带来不少好处,可是同时须要规避不少问题。blog
消息的丢失可能会出如今三个地方:
(1)生产者弄丢数据
生产者将数据发送到RabbitMQ的时候,可能数据就在半路给搞丢了,由于网络啥的问题,都有可能。怎么解决?
①事务:生产者发送数据以前开启RabbitMQ事务(channel.txSelect),而后发送消息,若是消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就能够回滚事务(channel.txRollback),而后重试发送消息;若是收到了消息,能够提交事务(channel.txCommit)。可是问题是,RabbitMQ事务机制一搞,基本上吞吐量会下来,由于太耗性能。
②confirm模式:在生产者那里设置开启confirm模式以后,你每次写的消息都会分配一个惟一的id,而后若是写入了RabbitMQ中,RabbitMQ会给你回传一个ack消息,告诉你说这个消息ok了。若是RabbitMQ没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你能够重试。并且你能够结合这个机制本身在内存里维护每一个消息id的状态,若是超过必定时间还没接收到这个消息的回调,那么你能够重发。
因此通常在生产者这块避免数据丢失,都是用confirm机制的。
(2)Mq弄丢数据
就是RabbitMQ本身弄丢了数据,这个你必须开启RabbitMQ的持久化,就是消息写入以后会持久化到磁盘,哪怕是RabbitMQ本身挂了,恢复以后会自动读取以前存储的数据,通常数据不会丢。
设置持久化有两个步骤:
①第一个是建立queue和交换器的时候将其设置为持久化,这样就能够保证RabbitMQ持久化相关的元数据,可是不会持久化queue里的数据;
②第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去
必需要同时设置这两个持久化才行
持久化能够和生产者的confirm结合,当持久化成功后,再ack生产者。若是持久化以前RabbitMQ挂了,生产者没收到ack,会重发。
(3)消费者弄丢数据
RabbitMQ若是丢失了数据,主要是由于你消费的时候,刚消费到,还没处理,结果进程挂了,好比重启了,那么就尴尬了,RabbitMQ认为你都消费了,这数据就丢了。
这个时候得用RabbitMQ提供的ack机制,简单来讲,就是你关闭RabbitMQ自动ack,能够经过一个api来调用就行,而后每次你本身代码里确保处理完的时候,再程序里ack一把。这样的话,若是你还没处理完,不就没有ack?那RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。
从根本上说,异步消息是不该该有顺序依赖的。在MQ上估计是无法解决。要实现严格的顺序消息,简单且可行的办法就是:保证生产者- MQServer -消费者是一对一对一的关系。
若是有顺序依赖的消息,要保证消息有一个hashKey,相似于数据库表分区的的分区key列。保证对同一个key的消息发送到相同的队列。A用户产生的消息(包括建立消息和删除消息)都按A的hashKey分发到同一个队列。只须要把强相关的两条消息基于相同的路由就好了,也就是说通过m1和m2的在路由表里的路由是同样的,那天然m1会优先于m2去投递。并且一个queue只对应一个consumer
分为两大类状况:一、生产者消息重复发送; 2.MQ向消费者投递时重复投递
终极解决办法:幂等性
1. MVCC:
多版本并发控制,乐观锁的一种实现,在生产者发送消息时进行数据更新时须要带上数据的版本号,消费者去更新时须要去比较持有数据的版本号,版本号不一致的操做没法成功。例如博客点赞次数自动+1的接口: public boolean addCount(Long id, Long version); update blogTable set count= count+1,version=version+1 where id=321 and version=123
每个version只有一次执行成功的机会,一旦失败了生产者必须从新获取数据的最新版本号再次发起更新。
2. 去重表:
利用数据库表单的特性来实现幂等,经常使用的一个思路是在表上构建惟一性索引,保证某一类数据一旦执行完毕,后续一样的请求再也不重复处理了(利用一张日志表来记录已经处理成功的消息的ID,若是新到的消息ID已经在日志表中,那么就再也不处理这条消息。)
以电商平台为例子,电商平台上的订单id就是最适合的token。当用户下单时,会经历多个环节,好比生成订单,减库存,减优惠券等等。每个环节执行时都先检测一下该订单id是否已经执行过这一步骤,对未执行的请求,执行操做并缓存结果,而对已经执行过的id,则直接返回以前的执行结果,不作任何操做。这样能够在最大程度上避免操做的重复执行问题,缓存起来的执行结果也能用于事务的控制等。