在此之前,我们曾经讨论过如何使用Redis实现一个延时队列,我们可以简单地利用Redis的队列数据结构,就可以轻松地实现一个队列的功能,然而这并不是一种非常完美的解决方案,主要是用Redis队列实现的MQ功能并不能实现多播。
什么叫多播呢?举一个简单的例子,在电商系统中,但用户完成一次成单操作后,可能需要通知非常多的系统,例如通知仓库可以去发货了,通知商家已经接收到新的订单,通知广告系统看看是否需要结算,在阿里,每一笔订单成交后,有上百个系统需要进行感知并处理相关的业务,所以我们每成一个新的订单,产生的一条消息,要被多个其他系统消费,这便是多播。
为了解决多播的问题,Redis的作者在Redis5.0中,放出一个新的数据结构,Stream。Redis Stream 的内部,其实也是一个队列,每一个不同的key,对应的是不同的队列,每个队列的元素,也就是消息,都有一个msgid,并且需要保证msgid是严格递增的。在Stream当中,消息是默认持久化的,即便是Redis重启,也能够读取到消息。那么,stream是如何做到多播的呢?其实非常的简单,与其他队列系统相似,Redis对不同的消费者,也有消费者Group这样的概念,不同的消费组,可以消费同一个消息,对于不同的消费组,都维护一个Idx下标,表示这一个消费群组消费到了哪里,每次进行消费,都会更新一下这个下标,往后面一位进行偏移。
在消息系统中,要保证消息被每个消费群组至少消费一次,Redis Stream是如何实现的呢?不如保证在消费的过程中不会丢失消息呢?原来,在Redis Stream的每次消费过程中,对于每一个客户端,都会维护一个PendingList,用来表示已分配给客户端,待客户端进行消费确认,客户端如果消费完成后,会向Redis服务器发起一次ack,让服务器从PedingList中将这条消息出去。否则,在客户端下一次重连的时候,服务器会重复下发对应的消息,让客户端进行消费。
如上图所示,一个RedisStream可以同时被两个不同的消费组所消费,每个消费组都可以有多台机器,但id分配给对应的机器然后还未得到消费确认的时候,就会放到pdl中。