什么是消息队列?
消息中间件是指在分布式系统中完成消息的发送和接收的基础软件
消息中间件也可以称消息队列(Message Queue / MQ),用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成
- Broker: 消息处理器,一般有多个Queue,提供核心服务,比如存储,重试等等
- Producer: 生产者,某一方业务作为生产者生产消息到Broker
- Consumes: 消费者,某一方业务进行消费消息然后进行业务处理
消息队列的应用场景(解决的问题)
解耦
- 比如公司有两个系统,系统 1 需要系统 2 来进行后续处理,但是不想两个系统强耦合在一起,最经典的就是订单系统和库存系统 ,中间通过消息队列来传递消息,订单系统完成订单,库存系统负责减少库存
有序性
- 在某些业务场景下对数据的一致性和顺序性有很强的要求,比如说支付相关的业务,需要满足先进先出的顺序,同样这也是队列的特征
消息路由
- 通过消息队列可以将不同的请求发送到不同的服务当中
异步处理
- 比如一个请求,有三个步骤,一个是处理请求得到结果,一个写入日志和缓冲,一个用于数据分析,如果等三个步骤都执行完的话,那么这个请求的时间就会非常长,并且请求的结果强依赖于第一个步骤,第二个第三个步骤不是强依赖,那么就可以使用消息队列进行优化
削峰
- 类似于早高峰、晚高峰的时候使用错峰出行的策略,在某些高峰场景,比如双11减少DB的压力
消息队列需要满足的业务特性
- 消息有序:先来的消息先消费
- 不重复消费:避免同一条消息重复被消费,避免重复的业务逻辑被执行多次
- 消息不丢失,可靠性传输:生产的消息不会出现消息丢失的情况
Redis当中的List数据类型是什么?基本命令是什么?
Redis lists are lists of strings sorted by insertion order.
- Redis列表是按插入顺序排序的字符串列表
Redis 3.2 之前,List 底层实现是 LinkedList 或者 ZipList。 Redis 3.2 之后,引入了 LinkedList 和 ZipList 的结合 QuickList,List 的底层实现变为 QuickList
| 命令 | 描述 |
|---|---|
| LPUSH key value [value ...] | 将一个值或者多个值插入到队列队头,如果有多个则按顺序 |
| BPUSH key value [value ...] | 将一个值或者多个值插入到队列队尾,如果有多个则按顺序 |
| LPOP key | 移除并且返回队列key的队头元素 |
| BLPOP key [key ...] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
| RPOP key | 移除并且返回队列key的队尾元素 |
| BRPOP key [key ...] timeout | 移除并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
| BRPOPLPUSH source destination timeout | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
| RPOPLPUSH source destination | 命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素 |
| LLEN key | 返回列表 key 的长度。如果 key 不存在,则 key 被解释为一个空列表,返回 0 .如果 key 不是列表类型,返回一个错误 |
| LRANGE key start stop | 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定 |
如何使用Redis中List实现消息队列
List本质上是一个线性的有序结构,可以按照推入队列的顺序来存储,它能够保证消费的有序性
入队出队
消息生产,入队操作
LPUSH key value [value ...]
> LPUSH test value1 value2 value3
(integer) 3
消息消费,出队操作
RPOP key
> RPOP test
value1
> RPOP test
value2
> RPOP test
value3
以上的队头队尾也可以翻转
List实现消息队列的潜在问题
消费的重复性问题
- 对生成的消息需要生成一个全局唯一ID
- 消费的时候进行记录,消费的时候先判断再消费,防止重复消费同一条消息
及时性问题
-
List列表并不会在生产者生产消息的时候通知消费者,所以这里存在一个及时性的问题
-
解决方案1:
- 一直循环去BPOP,直到拿到数据
-
解决方案2:
- 一直循环用LLEN命令判断,有数据再去消费,没有数据就暂停消费
-
这两种方案的缺点都是无效的资源浪费
-
解决方案3:
- BPOP命令可以指定超时时间,如果超时时间为0则会变成阻塞式
Rdb.BRPop(0*time.Second, Key).Result()
消息丢失问题
- Redis没有ACK消息确认机制,可能出现消费者消费消息,但是还没有处理完,此时出现重启或者服务器宕机或者程序崩溃,相当于这条消息就丢失了
- 解决方案:
- Redis 提供了
RPOPLPUSH命令,在消费消息的时候同时把消息推到其他一个队列,相当于做一个备份,故障恢复之后从备份队列里面消费,没有故障的话就从备份队列里面删除
- Redis 提供了
request, err := server_context.Rdb.BRPopLPush(Key1, Key2, 1*time.Second).Result()
实战示例
- 业务场景如下:
- 发送视频生成请求或者Bag转换请求,后台会任务去进行生成,由于生成比较慢,但是发送请求比较多,所以有多个协程去处理
Golang代码示例
- Handler
Rdb.LPush(Key, string(request))
- 消费者
func Consumer(ctx *HttpServerContext) {
for {
request, err := server_context.Rdb.BRPopLPush(Key1, Key2, 1*time.Second).Result()
if err != nil{
//log
continue
}
//业务逻辑
}
}