什么是消息队列
一听到消息队列,第一时间反应的当然是异步(请求链路长响应慢,则分开,并且有些动作可化同步为异步返回 ),限流,削峰(比如大量秒杀请求过来,先入队,后端自行按自己的节奏消费处理),解耦(比如上游订单服务处理完入队,下游有需要的自行订阅这个队列去处理)。本来想搞得正式一点的,查了下百科,一堆说明看得头晕,而且并不是很直观易懂。还是沿用一贯喜欢的招式,通俗一点讲吧。“消息队列”是在消息的传输过程中保存消息的容器。这句话真的非常精辟,可谓高度概括了这个词语的精髓。也就是说它其实是一个容器,至于容器是队列还是啥类型,它是不管的,能装就行。于是,我们来围观一下消息队列的两种模式或者说是消息通信模式:点对点模式和发布订阅模式。
点对点模式有的时候我们也叫做生产者消费者模式。生产者发送一条消息到queue,只有一个消费者能收到。也就是我们所说的1对1模式。
发布订阅模式我们有的时候也叫做广播。因为发布者发送到topic(主题有时也叫做频道channel,不同MQ取名不一样而已)的消息,只有订阅了topic的订阅者才会收到消息。也就是我们所说的1对多模式。
Redis实现原理
redis是如何实现上面两种模式的呢?还记得redis有一个数据类型叫List吗,我们通常直接称它为队列。因为它就是满足数据结构中队列的实现。于是,我们就用它来当容器实现消息队列。
实现点对点模式
我们在生产者客户端使用LPUSH命令将值依次插入某个KEY的列表当中,在消费者客户端使用LPOP命令移出并获取列表的第一个元素。这样就实现了一个简单的消息队列(请参照上面的说明图)。有人要问了,那我消费者如果去触发消费呢?嗯,是个好问题。简单的我们可以在消费者客户端直接使用while (true){consumerMessage();}这样子来轮询消费就可以了。
细心的你看肯定发现了,那这不是死循环吗?消费者不停地调用LPOP去查看List中是否有待处理消息,而这每一次都会发现一次连接,造成不必要的资源浪费。小机灵鬼的同学灵机一动,我可以采用休眠获取或者使用定时器隔段时间去获取呢。没错,这的确是个不错的方法,但是这个是存在问题的。
1.如果生产者速度大于消费者消费速度,消息队列长度会一直增大,时间久了会占用大量内存空间;
2.如果间隔时间过长,这样不能处理一些时效性的消息,睡眠时间过短,同样会在连接上造成比较大的资源开销。
那么有没有办法在生产者有数据的时候,我消费者就立马消费,如果没有数据则消费者一直等待,等到有数据了再立马消费呢?答应自然是肯定的。
我们来看Redis的阻塞获取列表系列的命令。先来看BLPOP命令,该命令移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。命令格式如:BLPOP key1 [key2 ] timeout 。啥意思呢,就是现在我要获取一个或多个队列中的一个数据出来,如果没有我就一直阻塞,直到超时就返回nil。
所以,我们消费者使用BLPOP命令就可以在LIST没有数据的时候直接阻塞,大大减少不断连接的开销。有的同学说,我还是不过瘾,这里非要设置一个超时时间,因为不设置就会报错,但是我阻塞超时完如果LIST还是没有数据我还是得连接。别急,你把阻塞时间设置为0试试,是不是就是一直阻塞啦。于是就算“死循环”也不会怕了。
下面我们就用例子来大概体验一下吧。
生产者
消费者
我们注意到消费者客户端BLPOP runoobkey 0这个命令,获取test3值之前是在一直等待生产者加入数据,当生产者一旦写入数据,立马就获取到值,下面显示了等待时间是262.35s。
实现发布订阅模式
点对点模式讲完了,来聊聊发布订阅模式。教程如是说:Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。注意这里的频道概念,有些MQ也称为主题,但意思差不多。
可以理解为,驾驶人员(客户端)先都订阅了交通广播电台(频道),然后其中有一个客户端(电台DJ)向频道发送了一个消息(发布交通管制信息),所有驾驶人员都收听到了这条消息。哈哈,是不是很好理解。
那Redis的命令是怎么实现的呢?教程里的这块例子非常清晰了,笔者就直接借用了。
啊,大功告成。你认为这样就完了吗?不,还是太年轻了。
Redis消息队列有什么弊端
相比于市场上的主流MQ,Redis消息队列似乎被用得比较少,其实是有原因的,我们还是用通俗的语言聊一下缺陷。
点对点模式:
1.做消费确认ACK比较麻烦,就是不能保证消费者在读取之后,未处理后的宕机问题。导致消息意外丢失。通常需要自己维护一个Pending列表,保证消息的处理确认。也就是取出之后没有应答处理完毕了没,就当作消费成功了。没有可靠性。
2.不能重复消费,一旦消费就会被删除。这一点仁者见仁吧。
3.不支持分组消费,需要自己在业务逻辑层解决。
发布订阅模式:
1.消息一旦发布,不接收消息就没了。换句话就是发布时若客户端不在线,则消息丢失,不能寻回。也就是发布出去就不管对方有没接收到了,因为没有接收应答。
2.不能保证每个消费者接收的时间是一致的。这点仁者见仁吧。
3.若消费者客户端出现消息积压,到一定程度,会被强制断开,导致消息意外丢失。通常发生在消息的生产远大于消费速度时。似乎这个缺陷比较严重,可靠性大打折扣。
所以对可靠性要求不高的话可以考虑Redis简单搞搞,但一般现在都上主流MQ了,毕竟能用上MQ的项目,体量和项目可靠性要求一般也不会低。元芳,你怎么看?