Redis中的List类型与消息队列的不同之处。

  • Post author:
  • Post category:其他


作为一个刚接触到消息队列的小白来说,Redis中的List类型超级象理解中的消息队列,并曾经一度认为搞什么消息队列服务,用Redis里的List类型就可以搞定了。

这两天接触了一下真正的消息队列才知道,这根本就是两回事。


  • Redis中的List实现完整的消息队列功能需要作什么呢?

Redis中的List是可以实现消息队列功能的。将消息推进List内,取出同时将消息从List清除,避免其它并发线程再读取该消息,这个是消息队列的基础,但不是全部,以下说一下消息队列的其它需求。


一、容错机制:


要保证处理消息失败后将消息重推回List内,避免消息丢失,这就要求你在读取Redis的List后全程try,并在意外错误后把消息PUSH到队列尾部。


二、消息的延迟性:


有一些消息要作必要的延迟后才能处理的,比如上面容错机制中出错的消息,一般需要都会要求延迟一段时间后再重试操作,避免在一个时间段内出现大量的重复操作。

这要求在消息内接入时间概念,在从Redis POP下来后验证是否有时间以及是否是可使用的时间,如果不是,在PUSH回队列尾部,注意该步骤也需要纳入容错机制内,防止在对比时间与PUSH时出错将消息丢失掉。

Redis的List在实现上面两个功能后基本上就可以达成消息队列的要求了。


  • 消息队列又是怎么处理消息的呢?

消息的进出都差不多就不写了,关键是写一下上面两点的处理方案(以下内容参考与阿里云的消息服务)

消息分四种状态:Active、Inactive、Deleted、Delayed。

  • Active消息允许取出,取出后会变成Inactive状态,在指定时间内没有Deleted的话,Inactive会变回Active允许再次取出。
  • Delayed为定时状态,到时间后Delayed会变成Active允许取出。


一、容错机制:


Active消息取出后变成了Inactive,消息处理成功后需要将消息设置成Deleted,如果发生意外,没运行到Deleted的话,消息会过了指定时间后转回Active状态再次进行处理。


二、消息延迟:


即是上面说的Delayed状态与取出后的Inactive。


  • 总结一下

以上是Redis的List类型与消息队列的一些差异点。使用Reids的List来作完整的消息队列功能非常费劲,而且容错度很差。全程try也不能保证消息不会丢失,而这些东西在消息队列服务来说都是现成的功能,你只需要取出消息处理后删除消息就可以。完全不会担心在删除消息前消息会消失,控制好Inactive转Active的时间差就不会发生并发取消息的困扰,非常简单的事。

写本文章是防止没接触过消息队列的人盲目的看到Redis的List实现队列的文章后就傻傻的把List当消息队列来用,比如象我


  • 2017-12-16补充


    今天偶然翻到一个redis的指令Rpoplpush,是允许从List取数同时将取出的数据放入另一个队列内。


    那么这个指令就更好的完成redis的List容错度了,成功后从备份List中将的消息删除,失败的话,下次先检查备份List是否有消息,有就先处理备份List中的消息即可。或是根据需求将正式的List中的数据处理完了再处理备份List中的数据。

    但要注意一点,这里需要备份列表大概需要两个,当处理正式列表时备份到A列表,处理A列表时备份到B列表,处理B列表时备份到A列表,这样才能真正的安全处理消息。

  • 2022-08-28补充


    回顾时发现上一个补充的设计有点傻,调整了一下,两个List一个Dict即可以实现List容错效果,分别是:

    ListA:正式队列任务列表,

    ListB备份队列,

    Dict:队列执行时间对照表。

    =====================================================================

    代码开始:

    一、检查Dict里有没记录,如果有,根据对应的执行时间,把超过指定时间的任务从ListB推回到ListA后面同时删除对应的Dict的记录。

    二、用Rpoplpush获取ListA第一个数据并将其推给ListB备份,向Dict写入该任务的开始运行时间,开始运行任务。

    三、运行完毕后删除ListB内的备份与Dict的运行时间记录。

    =====================================================================

    因为有时间对照表Dict的存在,这个方案可以让你的队列安心的用多线程处理。实现更高的效率同时不会重复执行队列任务,还不会担心任务的丢失,当然,redis重启导致任务数据丢失,那是另一个问题了,毕竟现在已经有一种实体存储的redis了

    防止发生意外,建议把需要同时运行的操作(如推回ListA后同时删除Dict记录)用lua脚本来处理。

=====================================================================


  • 2023-01-08补充


    一、Dict可以用

    Hash类型

    ,也可以用

    有序集合Zset

    ,其作用只是记录任务的运行时间或结束时间。

    二、Dict里记录的时间可以是

    运行时间

    也可以是

    结束时间

    ,目前我个人倾向于记录

    结束时间

    ,因为这样通用性更强一些,毕竟每个任务的结束时间并不是统一的,如果是结束时间的话,第一步只需要取出结束时间比当前时间小的任务就可以识别为运行失败的任务了。

    三、最终运行的代码里其实还需要加一个

    运行出错次数

    即是从ListB推回ListA的次数,如果出错次数超出一定的次数时就应该视为不可完成的任务,向程序员发出出错警告并在记录下出错信息的情况下将该任务丢弃(即时直接删除ListB的备份和Dict里的记录)不再重试,以避免出现无限次重启根本无法完成的任务的类死循环的情况出现。

    四、运行出错次数最好用一个独立的Dict来记录,这个可以用Hash类型,在第一次超时时开始记录次数,之后逐次累加,超过允许的次数时进入任务清理程序即可。



版权声明:本文为oyiboy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。