One minute
RabbitMQ
- RabbitMQ是一个消息队列,消息队列是分布式系统中重要的组件。消息队列可以比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。
使用场景
消息队列常见的使用场景最核心的有三个:解耦,异步,削峰
解耦
- 消息发送者(生产者)发送消息到消息队列中,消息接收者从消息队列中获取消息。两者之间没有直接耦合。
- 对新增业务,只要对消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
异步和削峰
在不使用消息队列服务器的时候,用户请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。
在使用消息队列之后,用户的请求数据发送给消息队列之后立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。消息队列处理速度快于数据库,且消息队列也比数据库具有更好的伸缩性,所以响应速度会得到大幅度改善。
消息队列具有很好的削峰功能的作用,通过异步处理,将短时间的高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。
需要特别注意的是,用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验,写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程。
使用消息队列的缺点
- 系统可用性降低:系统引入的外部依赖越多,越容易怪掉。在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等情况。但是引入MQ之后你就需要去考虑了。
- 系统复杂性提高:加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等问题。
- 一致性问题:虽然消息队列可以实现异步,消息队列带来的异步确实可以提高系统的响应速度。但是万一消息的真正消费者并没有正确的消费消息,这样就会导致数据不一致的情况了。
如何保证消息不被重复消费(幂等性)
- 拿到数据要写库,就先根据数据的主键查一下,如果数据库中存在这些数据,就不要插入了,Update一下。
- 可以基于数据库的唯一键来保证重复数据不会插入多条,因为有唯一键约束了,重复插入数据指挥报错,不会导致数据库中出现脏数据。
- 如果是写Redis就没啥问题,因为set天然幂等性。
如何保证消息队列的高可用
- RabbitMQ是基于主从来做高可用的。
- RabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式
- 单机模式很少使用,普通集群模式只是提高了系统吞吐量,让集群中多个节点来服务某个队列的读写操作。
- 镜像集群模式是真正实现RabbitMQ高可用的模式。镜像集群模式中创建的队列,无论元数据,还是队列中的消息,都会存在于多个实例上,然后你每次写消息到队列的时候,都会自动和多个实例队列进行消息同步。
- 好处:任何一台机器宕掉,不会影响其他机器的使用。
- 坏处:性能开销太大,消息同步所有的机器会导致网络带宽压力和消耗比较重。
如何保证消息的可靠性传输(消息丢失的处理)
RabbitMQ中有三个角色:
- 消息生产者:向队列中发布消息的角色。
- 消息代理者:RabbitMQ自己,它不生产消息,也不对消息进行消费。就是起一个消息容器,对消息进行分发的作用。
- 消息消费者:从队列中获取消息进行消费的角色。
对于消息生产者
可以选择RabbitMQ提供的事务功能,就是生产者再发送数据前开启RabbitMQ的事务,然后再发送消息。如果消息没有被成功接收,生产者就会接收到异常报错,此时可以回滚事务,重新发送。如果消息被接收到了,就可以提交事务。缺点就是:太耗费性能,会降低吞吐量。
可以开启confirm模式,在生产者那里开启后,你每次写的消息都会被分配一个全局唯一ID。如果消息写入到了队列中,那么RabbitMQ会回传一个ACK的消息。如果MQ没能处理这个消息,会回调一个你的NACK接口,告诉你消息没收到,然后你可以重新发送。
事务机制和confirm机制最大的区别在于事务机制是同步的,你提交一个事务之后会阻塞在那儿,但confirm机制是异步的,你发送完消息就可以接着发下一个,如果消息被接受了之后,它会异步回调接口通知你。
对于消息代理者
开启RabbitMQ的持久化,讲就是将消息写入之后会持久化到磁盘,这样就算是自己挂掉了,也会在恢复之后自动读取之前存储的数据。
设置消息持久化有两步:
- 创建队列时将其设置为持久化。(队列持久化,只会把队列中的元数据持久化,不会把队列中的消息持久化的磁盘上)
- 发送消息时,将消息也设置为持久化,这样RabbitMQ就会将消息持久化到磁盘上。
持久化可以和生产者的confirm机制配合起来,只有消息被持久化到磁盘上了,才会通知生产者ACK。这样就算是在持久化之前RabbitMQ挂掉了,数据丢了,但生产者没收到ACK,这样你自己也可以重发消息。
对于消息消费者
关闭RabbitMQ的自动ACK机制,改成你自己手动的。就是每次你自己代码里确保处理完的时候,再在程序里ACK一下。这样,如果你没有处理完,就没有ACK。那么RabbitMQ就认为你没有处理完,这时RabbitMQ会把这个消息分配给其它的消费者去处理,消息是不会丢的。
如何保证消息的顺序性
- 拆分多个queue,每个queue一个consumer。
- 就一个queue对应一个consumer,然后consumer内部用内存队列做排队,然后分发给底层不同的worker来处理。