RabbitMQ,MQ博弈
程序员文章站
2024-01-24 13:06:34
想想为什么要使用MQ? 1.解耦,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦! 2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度 3.削峰,并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常 使用了消息队列会有... ......
想想为什么要使用mq? 1.解耦,系统a在代码中直接调用系统b和系统c的代码,如果将来d系统接入,系统a还需要修改代码,过于麻烦! 2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度 3.削峰,并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常 使用了消息队列会有什么缺点? 1.系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低 2.系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。 如何保证消息队列是高可用的? 使用集群的方式维持mq的可高用性。 如何保证消息不被重复消费? 保证消息不被重复消费的关键是保证消息队列的幂等性,这个问题针对业务场景来答分以下几点: 1.比如,你拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。 2.再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。 3.如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以k-v形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。 如何解决丢数据的问题? 1.生产者丢数据 生产者的消息没有投递到mq中怎么办?从生产者弄丢数据这个角度来看,rabbitmq提供transaction和confirm模式来确保生产者不丢消息。 transaction机制就是说,发送消息前,开启事物(channel.txselect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txrollback()),如果发送成功则提交事物(channel.txcommit())。 然而缺点就是吞吐量下降了。因此,按照博主的经验,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的id(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitmq就会发送一个ack给生产者(包含消息的唯一id),这就使得生产者知道消息已经正确到达目的队列了.如果rabiitmq没能处理该消息,则会发送一个nack消息给你,你可以进行重试操作。 2.消息队列丢数据 处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个ack信号。这样,如果消息持久化磁盘之前,rabbitmq阵亡了,那么生产者收不到ack信号,生产者会自动重发。 那么如何持久化呢,这里顺便说一下吧,其实也很容易,就下面两步 ①、将queue的持久化标识durable设置为true,则代表是一个持久的队列 ②、发送消息的时候将deliverymode=2 这样设置以后,rabbitmq就算挂了,重启后也能恢复数据。在消息还没有持久化到硬盘时,可能服务已经死掉,这种情况可以通过引入mirrored-queue即镜像队列,但也不能保证消息百分百不丢失(整个集群都挂掉) 3.消费者丢数据 启用手动确认模式可以解决这个问题 ①自动确认模式,消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理成功。不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试。 ②手动确认模式,如果消费者来不及处理就死掉时,没有响应ack时会重复发送一条信息给其他消费者;如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常;如果对异常进行了捕获,但是没有在finally里ack,也会一直重复发送消息(重试机制)。 ③不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端异常还是断开,只要发送完就移除,不会重发。 如何保证消息的顺序性? 针对这个问题,通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中。然后只用一个消费者去消费该队列。同一个queue里的消息一定是顺序消息的。我的观点是保证入队有序就行,出队以后的顺序交给消费者自己去保证,没有固定套路。例如b消息的业务应该保证在a消息后业务后执行,那么我们保证a消息先进queuea,b消息后进queueb就可以了。