Redis消息队列

在程序员这个圈子打拼了太多年,见过太多的程序员使用redis,其中一部分喜欢把redis做缓存(cache)使用,其中最典型的当属存储用户session,除此之外,把redis作为消息队列使用也不在少数,可见redis在互联网中应用是多么的广泛。

redis作为消息队列使用,redis支持的数据结构是可以支撑这类业务,主要是利用了list这种数据结构的特性。Redis的列表相当于编程语言里面的 LinkedList,是一个双向的列表结构,这意味着列表新增和删除元素是非常快的,时间复杂度为O(1),但是查找一个元素的时候需要遍历列表,时间复杂度为O(n)。由于列表的元素操作和消息队列操作类似,所以redis可以适用于消息队列的场景,当然,在适用于的栈的场景下也可以胜任。

需要提醒一下,生产环境中如果对消息的可靠性有十分高的要求(比如订单支付的消费消息),请使用专业的消息队列(例如:rmq,amq等),对消息的丢失有一定容忍度的程序完全可以使用redis,例如我们的日志收集程序

列表这种数据结构的命令为

移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BLPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOP key1 [key2 ] timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOPLPUSH source destination timeout 通过索引获取列表中的元素
LINDEX key index 在列表的元素前或者后插入元素
LINSERT key BEFORE|AFTER pivot value 获取列表长度
LLEN key 移出并获取列表的第一个元素
LPOP key 将一个或多个值插入到列表头部
LPUSH key value1 [value2] 将一个值插入到已存在的列表头部
LPUSHX key value 获取列表指定范围内的元素
LRANGE key start stop 移除列表元素
LREM key count value 通过索引设置列表元素的值
LSET key index value 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
LTRIM key start stop 移除列表的最后一个元素,返回值为移除的元素。
RPOP key 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPOPLPUSH source destination 在列表中添加一个或多个值
RPUSH key value1 [value2] 为已存在的列表添加值
RPUSHX key value

缺陷

消息队列的本质还是消费者和生产者的问题,只要是这样的场景,就会涉及到两端不平衡的情况,具体可表现为:

  1. 生产者生产速度大于消费者消费速度,面临消息不断堆积的问题,随着消息数据的堆积,队列是开启限流措施,还是丢弃某些消息,更或者是把消息数据进行持久化。对于基于redis实现的消息队列,一般为可忍受部分消息丢失的业务,所以很多人选择丢弃消息的方案。另一种方案是基于redis单线程机制,可以增加消费者数量,这也是仅仅针对消息只被消费一次的场景。当然也可以选择持久化方案,但是会对redis的性能产生影响。
  2. 消费者消费速度大于生产者生产速度,有的同学会说,这样挺好啊,是,在某种意义上是比反过来的那个场景要好一些,毕竟可以避免产生消息的堆积问题。但是消费者没有消息消费,会导致消费者进程一直在那里浪费cpu资源,而且还会把redis的QPS拉高。类似于这种死循环的场景,一般而且最常用的解决方案是让线程sleep 一小段时间,既降低了消费端cpu也降低了redis的QPS。 但是sleep会有一个问题,会导致处理消息的延迟,例如sleep了一秒,那消息的延迟处理就有可能会延迟一秒,虽然在大部分场景下这都不是什么问题,但是作为程序员怎么能不追求极致和完美呢?

关于消息延迟的问题,最暴力简单的方式就是增加消费客户端,这样可用多消费端交错的方式来缩小延迟的间隔,当然redis的设计者也考虑了这个问题,所有有了Blpop 命令

Redis Blpop 命令移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

redis 127.0.0.1:6379> BLPOP LIST1 LIST2 .. LISTN TIMEOUT

而且还可以设置超时自动返回,岂不是完美。但是还要顺便一句,redis的连接在空闲一段时间后,服务端可能会主动断开,Blpop命令会抛出异常,所以还要做好了重试或者其他策略为好。

  1. 如果作为专业的消息队列,一个消息被多个不同的业务消费(一个消息被消费多次)是必须要支持的,但是redis是基于自己的list数据结构来实现的伪队列,所以这种业务场景下就不要考虑redis了,或者自己封装一个类似分发器的中间件也可以。
  2. 基于redis的消息队列没有Ack的保证,换句话说,一个消息是否被正常处理redis是不知道的,这在很大程度上限制了它的适用场景。

写在最后

我还是建议不要用redis做专业的MQ使用,毕竟MQ这种场景不是redis的设计初衷,但是太多人把redis做MQ使用,于是redis的作者基于redis的核心代码实现了一个消息队列:disque,也许未来会作为redis的核心组件,地址为 https://github.com/antirez/disque

除了disque,Redis Stream也是一个把redis作为MQ的比较好的解决方案,有兴趣的同学可以研究一下。

千万不要把任何一个业务场景想象的太简单

程序员过关斩将--redis做消息队列,香吗?的更多相关文章

  1. Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流

    1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...

  2. Redis 做消息队列

    一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现.定义: 生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...

  3. Redis做消息队列

    1.连接从Redis中获取日志文件并存储到ES中 [root@Logstash ~]# vim /usr/local/logstash/config/redis.conf input {     be ...

  4. 使用Redis做消息队列

    基于内存的单线程数据库,使Redis的线程安全性极高.而Redis的双向链表数据类型(List)天生就可作为消息队列存储消息. 在这里就不说消息队列的等等一些优点.但是补充一下Redis的List类型 ...

  5. RabbitMQ跟Redis做消息队列的区别

    区别 https://www.zhihu.com/question/20795043 https://blog.csdn.net/dd18709200301/article/details/79077 ...

  6. Redis做消息队列文章两篇

    介绍:http://www.cnblogs.com/lhfcws/p/3732535.html 具体做法:http://shift-alt-ctrl.iteye.com/blog/1867454 另外 ...

  7. 使用Redis Stream来做消息队列和在Asp.Net Core中的实现

    写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消息队列,而不想引入新的服务,kafk ...

  8. PHP使用Redis实现消息队列

    消息队列可以使用MySQL来实现,可以参考博客PHP使用MySQL实现消息队列,虽然用MySQL可以实现,但是一般不这么用,因为MySQL的数据都存在硬盘中,而从硬盘中对MySQL的操作,I/O花费的 ...

  9. Redis作为消息队列服务场景应用案例

    NoSQL初探之人人都爱Redis:(3)使用Redis作为消息队列服务场景应用案例   一.消息队列场景简介 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...

随机推荐

  1. mybatis两种方式

    // 1.传统的使用 statementId方式 //获取详情 sysApiDocumentMode = template.selectOne("oaApiDocument.getProje ...

  2. 0182 JavaScript执行机制:单线程,同步任务和异步任务,执行栈,消息队列,事件循环

    以下代码执行的结果是什么? [结果是1 2 3 ] console.log(1); setTimeout(function () { console.log(3); }, 1000); console ...

  3. Java小白集合源码的学习系列:LinkedList

    目录 LinkedList 源码学习 LinkedList继承体系 LinkedList核心源码 Deque相关操作 总结 LinkedList 源码学习 前文传送门:Java小白集合源码的学习系列: ...

  4. 倍增笔记ST表

    https://noip-1253948194.cos.ap-beijing.myqcloud.com/%E5%80%8D%E5%A2%9E-ST%E7%AE%97%E6%B3%95.mp4 1123 ...

  5. java intellij 工具的简单用法

    一.目录结构 1.新建项目(Empty Project) ->  新建module(可以有多个) => 出来src文件夹 -> 在src文件夹中新建package -> 在pa ...

  6. Qt Installer Framework翻译(3-5)

    指定设置选项 设置页面使用户可以指定代理设置或安装附加组件. 用户在简介页面上点击"设置"来指定设置选项. 指定代理设置 默认情况下,安装程序使用系统代理设置.用户可以选择不使用或 ...

  7. java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSession

    java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSessio ...

  8. python实现数据结构-栈

    注:本文档主要是学习<Python核心编程(第二版)>时的练习题. 栈是一种"后进先出"的数据结构(LIFO),是一种操作受限的线性结构,数据只能从栈顶进入和栈顶出去. ...

  9. 学习Python中遇到的各种错误

    错误列表 TypeError : 'moudle' object is not callable 错误:TypeError : 'moudle' object is not callable 代码: ...

  10. 靶机-BTRSys 2.1 Walkthrough

    BTRSys 2.1 https://www.vulnhub.com/entry/btrsys-v21,196/ 参考:https://www.jianshu.com/p/9813095ce04d 提 ...