上一篇基于redis的list实现了一个简单的消息队列:玩转redis-简单消息队列

源码地址 使用demo

产品经理经常说的一句话,我们不光要有X功能,还要Y功能,这样客户才能更满意。同样的,只有简单消息队列是不够的,还要有延时消息队列才能算是一个完整的消息队列。

看看redis的命令,放眼望去,的有序集合(sorted set)就是一个很好用的命令,完全可以用他做一个延时消息队列

redis有序集合(sorted set)

redis有序集合,每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

简单操作

添加数据

127.0.0.1:6379> ZADD testSet1 5 a
(integer) 1
127.0.0.1:6379> ZADD testSet1 1 b 8 c 7 d
(integer) 3

读取

127.0.0.1:6379> ZRANGEBYSCORE testSet1 0 3
1) "b"
127.0.0.1:6379> ZRANGEBYSCORE testSet1 0 5
1) "b"
2) "a"

也可以把score打出来

127.0.0.1:6379> ZRANGEBYSCORE testSet1 -inf 5 WITHSCORES
1) "b"
2) "1"
3) "a"
4) "5"

查出所有的数据

127.0.0.1:6379> ZRANGEBYSCORE testSet1 -inf inf
1) "b"
2) "a"
3) "d"
4) "c"

删除数据

ZREMRANGEBYSCORE testSet1 0 2

延时队列的实现思路

总体的思路很简单,就是每一个valuescore保存的是时间,也就是说,在添加一个元素时他的score是当前时间+延时的时间。轮循获取数据时,查找小于或等于当前时间的数据项,就是具体的延时消息。

还有一个问题,就是ZRANGEBYSCORElistpop不同,pop是取出元素并且会把元素在list中删除。ZRANGEBYSCORE只会取出数据不会把数据从sorted set中删除。解决方法1,利用redis事务,先ZRANGEBYSCORE取出数据,然后再用ZREMRANGEBYSCORE 把数据删除。

具体实现-code

添加延时消息,参数delay就是我们要延时多久:

func (p *Producer) PublishDelayMsg(topicName string, body []byte, delay time.Duration) error {
if delay <= 0 {
return errors.New("delay need great than zero")
}
tm := time.Now().Add(delay)
msg := NewMessage("", body)
msg.DelayTime = tm.Unix() sendData, _ := json.Marshal(msg)
return p.redisCmd.ZAdd(topicName+zsetSuffix, redis.Z{Score: float64(tm.Unix()), Member: string(sendData)}).Err()
}

使用,比如我们想过1秒再处理

producer.PublishDelayMsg(topicName, body, time.Second)

读取消息并处理

这就比较简单了,就是在一个ticker里循环读取小于或等于当前时间的数据:

func (s *consumer) startGetDelayMessage() {
go func() {
ticker := time.NewTicker(s.options.RateLimitPeriod)
defer func() {
log.Println("stop get delay message.")
ticker.Stop()
}()
topicName := s.topicName + zsetSuffix
for {
currentTime := time.Now().Unix()
select {
case <-s.ctx.Done():
log.Printf("context Done msg: %#v \n", s.ctx.Err())
return
case <-ticker.C:
var valuesCmd *redis.ZSliceCmd
_, err := s.redisCmd.TxPipelined(func(pip redis.Pipeliner) error {
valuesCmd = pip.ZRangeWithScores(topicName, 0, currentTime)
pip.ZRemRangeByScore(topicName, "0", strconv.FormatInt(currentTime, 10))
return nil
})
if err != nil {
log.Printf("zset pip error: %#v \n", err)
continue
}
rev := valuesCmd.Val()
for _, revBody := range rev {
msg := &Message{}
json.Unmarshal([]byte(revBody.Member.(string)), msg)
if s.handler != nil {
s.handler.HandleMessage(msg)
}
}
}
}
}()
}

玩转redis-延时消息队列的更多相关文章

  1. Redis 做消息队列

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

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

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

  3. redis resque消息队列

    Resque 目前正在学习使用resque .resque-scheduler来发布异步任务和定时任务,为了方便以后查阅,所以记录一下. resque和resque-scheduler其优点在于功能比 ...

  4. 【springboot】【redis】springboot+redis实现发布订阅功能,实现redis的消息队列的功能

    springboot+redis实现发布订阅功能,实现redis的消息队列的功能 参考:https://www.cnblogs.com/cx987514451/p/9529611.html 思考一个问 ...

  5. 【Redis】php+redis实现消息队列

    在项目中使用消息队列一般是有如下几个原因: 把瞬间服务器的请求处理换成异步处理,缓解服务器的压力 实现数据顺序排列获取 redis实现消息队列步骤如下: 1).redis函数rpush,lpop 2) ...

  6. spring boot Rabbitmq集成,延时消息队列实现

    本篇主要记录Spring boot 集成Rabbitmq,分为两部分, 第一部分为创建普通消息队列, 第二部分为延时消息队列实现: spring boot提供对mq消息队列支持amqp相关包,引入即可 ...

  7. Lumen开发:结合Redis实现消息队列(1)

    1.简介 Lumen队列服务为各种不同的后台队列提供了统一的API.队列允许你推迟耗时任务(例如发送邮件)的执行,从而大幅提高web请求速度. 1.1 配置 .env文件的QUEUE_DRIVER选项 ...

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

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

  9. sping+redis实现消息队列的乱码问题

    使用spring支持redis实现消息队列,参考官方样例:https://spring.io/guides/gs/messaging-redis/ 实现后在运行过程中发现消费者在接收消息时会出现乱码的 ...

随机推荐

  1. MySQL记录操作(单表查询)

    单表查询的语法及关键字执行的优先级 单表查询语法 SELECT DISTINCT 字段1,字段2... FROM 表名 WHERE 条件 GROUP BY field HAVING 筛选 ORDER ...

  2. iview Checkbox 多选框 单个的时候 如果需要change 以后进行赋值 就要用value 不要用v-modal 然后用updateModel 方法

    noSuchSituationSetFalse () { this.noSuchSituationOne = false this.$refs.noSuchSituationRef.updateMod ...

  3. java-打印流(新手)

    实例:1.直接输出到文件里面. import java.io.*; //打印流->保持数据原样性并且打印.(各种数据类型.) public class Dyl { public static v ...

  4. python中可变长度参数详解

    1. *args用法:python会将所有位置的参数收集到一个元组中 2. **args用法:python会将关键字参数传递给一个新的字典.**允许将关键字参数转换为字典 用法见如下代码: def f ...

  5. VS2019 C++动态链接库的创建使用(1) - 创建使用dll

    静态库:函数和数据被编译进一个二进制文件,通常扩展名为.lib,在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件. ...

  6. 一个简单的方法去掉angular application中URLs的hashtag

    本文转载自:Pretty URLs in AngularJS: Removing the # By default, AngularJS will route URLs with a hashtag. ...

  7. 关于git 远程仓库账户密码错误的问题

    主要是电脑凭证把第一次输入的账户密码记录了下来,在控制面板->用户账户->凭据管理器里, 选择windows凭证, 你会找到git:凭据,直接删掉或者更改都可以! 对应的Git的凭证,删除 ...

  8. 运行docker大致流程

    平时部署测试环境使用jenkins将代码打包成docker镜像部署在rancher中,闲下来研究了一下docker的大致流程,自己画了一个流程图

  9. 强化学习之三点五:上下文赌博机(Contextual Bandits)

    本文是对Arthur Juliani在Medium平台发布的强化学习系列教程的个人中文翻译,该翻译是基于个人分享知识的目的进行的,欢迎交流!(This article is my personal t ...

  10. 微信内置浏览器对于html5的支持

    微信内置浏览器对于html5的支持 来源: 作者: 热度:102 日期:14-06-10, 09:10 AM 我在做针对微信的HTML5应用, 目前遇到的几个问题是 一. 安卓版微信直接调用系统浏览器 ...