一、消息队列的三个要求
- 消息有序执行(有序性);
- 由于网络阻塞等原因,消息不会被重复执行(幂等性);
- 宕机后,消息不会丢失(可靠性)。
二、Redis实现消息队列
Redis中可以使用List或者Streams两种数据结构来满足以上消息队列的要求
List
- 有序性:
list本身就是先进先出,天然支持有序性(高并发时需要多个消息队列,每个队列保存同一个服务的消息,对应消费该服务消息的消费者); - 幂等性:
生产者在将消息加入队列时,会给每条消息加上一个id,消费者执行消息时会根据该id判断自己之前是否已经执行过该消息; - 可靠性:
当消费者从队列中取出一条消息时,mq会将该消息存到一个备份List中,当消费者宕机重启后可以从mq的这个备份List中再次读取该消息。
List存在的问题:
- 消息队列无法主动通知消费者队列里面是否有消息,所以消费者为了第一时间进行消费需要不断轮询消息队列中是否存在消息,这会导致消费者CPU资源浪费,Redis采用了BRPOP指令来让消费者获取消息,当队列中没有消息时,消费者的线程将被阻塞挂起(不占CPU资源),直到消息队列里有新加入的消息了才被唤醒进行消费。
- 不支持一个队列对应多个消费者(消费组),可能会导致消费者来不及消费,队列里的消息积压。
Streams
- 可以实现List中的所有功能;
- 特有的功能:
A) 可以实现消费组,负载均衡的让消费者消费队列中的消息,缓解队列阻塞;
B) 使用ACK机制来确保消息不丢失,比List使用备份List的方法更加优雅(也就是消费者收到消息后消息队列不会删除该消息,只有当消费者处理完该消息并返回一个ACK确认信号后,队列里面才会删除该条消息)
三、redis与传统消息队列的对比
- 优点:redis更加轻量级,部署方便而且基于内存速度较快。
- 缺点:Redis作为中间件有丢失数据的风险,redis采取定时持久化的机制就会存在最近一段时间消息丢失的情况(就算是Redis集群也是一个写节点,多个读节点来备份),而Kafka,RabbitMq的话会部署多个写节点,所以一个节点挂了,其他几个节点中会保存有当前的消息,保证消息不会在中间件环节丢失。
参考文章: