Redis消息队列与主流的消息队列中间件对比

【前言】
与主流的消息队列组件,如Kafka,RabbitMQ等相比,Redis可以轻松实现一个轻量级的消息队列。基本上现在的系统都会涉及到缓存,如果不是单体应用,一般主流会选择Redis作为一个跨服务的缓存组件。所以如果对于实时性没有特别苛刻的要求,Redis是可以胜任作为一个消息队列来使用的。
【Redis作为消息队列使用的基本思路-list】
之前说过,Redis的list结构中,如果lpush和rpop结合或者rpush与lpop结合,就是一个简易的消息队列了,可以支持多消费者与生产者并发收发消息。
【优化一】
如果消费端轮训使用pop来获取消息,当队列长时间为空时,这样的轮训其实就增加了服务器和Redis的负担。就像我们的BS结构服务,如果页面上展示内容需要轮训从后端获取,那么网络消耗、后端查询数据库的压力、服务器资源的消耗是必须要考虑的,如果页面多了,我们可能倾向于选择由服务器主动通知页面而不是页面来轮训获取消息。这里也一样,我们可以采用blpop、brpop来处理,b表示blocking即阻塞读,队列没有消息时,会进入休眠状态,一旦有消息会立刻醒过来。
【优化二】
如果客户端按照上述处理方案采用阻塞读,那么当消息队列长时间没有消息时,服务器一般会主动断开链接,减少闲置资源占用,此时blpop与brpop会抛出异常。在处理客户端时,这里需要合理地处理这个异常。
【加锁失败处理】
setnx key value可以被用作分布式锁,这里介绍了几个比较合理的策略来处理加锁失败的异常处理:
1.抛异常,通知用户稍后操作--省心
2.sleep一会儿,重试--这里要根据场景来看,sleep多久,多少次,会不会造成超长的响应,发生的频率多高...总之要考虑清楚
3.请求转到延时队列,过会儿再试--相较于2,这里能保证稍后处理,同时也能快速响应请求,异步处理了请求,但是延时队列也是有维护成本的

【Redis使用zset实现延时队列】
消息序列作为zset的value,消息的到期处理时间为score,而后用多线程轮训处理zset中到期的任务。多个线程要考虑并发争抢任务,确保消息不会重复执行。
一种有效的思路是使用zrem来确定是否成功得到该任务,返回1时执行,否则不执行,确保该任务的属主唯一。
另外,具体的任务执行处理中,异常需要管理好,避免异常抛出导致线程退出。
【优化一】
在上述过程中,多线程去争抢任务的过程中,会zrangebyscore找到此刻可以执行的任务,然后再用zrem去争夺这个消息的执行权。可以考虑使用lua scripting将这两个步骤合二为一,保证服务器端执行这个脚本的原子性,那么多个线程就不会因为zrem失败而白取一次任务。

【为什么Redis不能保证100%的可靠性】
对于Redis来说,一个list,一个zset,都无法做到消息的重复消费,当客户端成功拿到了这个消息之后,Redis的结构中就会把这个元素剔除,其它客户端是拿不到这条消息的,很多场景就无法满足需求;如果拿到消息的客户端崩溃了,如断电,并且还没有处理成功,那么这条消息就丢失了。所以我们不能指望Redis替我们保留已经消费过的历史消息。
针对重复消费的问题,Redis采用<发布/订阅>的队列模型进行处理。当多个客户端订阅某个队列,生产者发出消息时,多组消费者解除阻塞,获取到这条消息。但在这个过程中,如果发生Redis宕机、消费者下线(某个消费者下线,那么这个消息会丢失;如果所有消费者都下线了,那么生产者的消息也会被丢弃)、消息堆积(缓冲区堆积消息,达到预设的上限体积时,Redis会强制剔除消费速度慢的消费者)都会造成消息丢失。因为这个过程中,消息是没有存储的,只有实时转发,不具备持久化的能力。

【与主流的队列对比】
在Redis5.0后,作者引入了Stream这种数据类型,它具备消息持久化的能力,已经趋近于专业的消息队列。
但与真正的消息队列相比,在生产者发送消息、消费者接收消息的处理思路可以保证消息不丢,但是对于队列本身,有两个问题:
1.AOF持久化配置为每秒写盘,即写盘是异步的,如果这期间宕机,那么这一秒内的消息就会丢失;
2.主从复制也是异步,主从切换时,如果从库还没有同步完成主库发来的数据就被提成主库,那么也会发生数据的丢失。
所以Redis无法严格保证数据的完整性。
另外,对于消息堆积的处理,Redis虽然可以用AOF写入磁盘,但是它的数据还是存在内存中,始终会有OOM的风险。所以Redis的Stream提供了可以指定队列最大长度的功能。与主流队列真正结合磁盘来管理消息相比,成本更高。

【总结】
如果业务场景简单,对于数据的丢失不敏感,且消息积压的概率很小,这种情况下可以使用Redis作为队列。否则就要考虑上专业的消息中间件来处理业务。但前者在部署、运维等方面还是很轻量,在普遍被当做缓存使用的时候,将其作为一个简易的消息队列使用也是值得考虑的。

原文地址:https://www.cnblogs.com/bruceChan0018/p/15642207.html