延迟队列的应用场景也很常见,例如:session的超时过期、自动取消未付款订单等等。redis中有一种数据结构叫做zset,即有序集合。元素类型为String类型,且元素具有唯一性不能重复,每个元素可附带float类型的score即分值。从zset中获取元素的时候可以通过分值进行排序后获取某个分值范围内的元素或所有元素。

public class DelayQueue {

    private String redisHost = "10.5.31.155";
private int redisPort = 6379;
private Jedis redis; private String queueMapKey = "DelayQueueMap";
private String queueSetKey = "DelayQueueSet"; private int delaySecond = 3; @Before
public void before() {
redis = new Jedis(redisHost, redisPort);
} @Test
public void pub() throws InterruptedException {
for (int i = 1; i <= 100000; i++) {
String messageId = UUID.randomUUID().toString().replace("-", "");
String messageBody = "第" + i + "条消息:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
redis.hset(queueMapKey, messageId, messageBody);
redis.zadd(queueSetKey, System.currentTimeMillis() + (delaySecond * 1000), messageId);
Thread.sleep(Math.round(Math.floor(Math.random() * 2000)));
}
} @Test
public void sub() throws InterruptedException {
while (true) {
Set<Tuple> messages = redis.zrangeByScoreWithScores(queueSetKey, System.currentTimeMillis() - (delaySecond * 1000), System.currentTimeMillis());
for (Tuple message : messages) {
Long zrem = redis.zrem(queueSetKey, message.getElement());
if (zrem > 0) {
String messageBody = redis.hget(queueMapKey, message.getElement());
redis.hdel(queueMapKey, message.getElement());
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) + ":" + messageBody);
}
}
Thread.sleep(1000);
}
} @After
public void after() {
redis.close();
} }

输出结果:可以看到pub的消息都是延迟3秒被消费的。

2018-09-28 15:02:58.863:第1条消息:2018-09-28 15:02:52.891
2018-09-28 15:02:58.866:第2条消息:2018-09-28 15:02:54.240
2018-09-28 15:02:59.870:第3条消息:2018-09-28 15:02:56.011
2018-09-28 15:02:59.872:第4条消息:2018-09-28 15:02:56.373
2018-09-28 15:02:59.874:第5条消息:2018-09-28 15:02:56.587

上面的代码模拟了一个简单的延迟队列,思路如下:

  1. 在redis中建立一个hash类型数据,用来存储消息id及消息内容
  2. 在redis中建立一个zset类型数据,用来存储消息id及对应的分数(该消息的过期时间)
  3. pub端推送消息时候,先写hash数据,再写zset数据。
  4. sub端定时按照分数从zset中获取消息id,获取到消息id后逐个删除,删除成功后再处理消息。

缺点:

  1. sub端需要定时轮训,所以会出现不及时消费的情况
  2. 如果pub端的生产能力大于sub端的消费能力,则会导致redis内数据越来越多

需要注意的是:

  1. sub端获取消息、删除消息、处理消息不是一个原子操作,在高并发的情况下,获取到的消息可能被其他服务删除。所以在删除、处理消息的时候,不能一次性删除所有获取到的消息,而是要逐条删除后判断是否删除成功再处理消息。以免消息被重复消费。
  2. 为什么要用消息id呢,不能直接把消息内容放在zset里吗?答案是不可以。消息内容是可能重复的,而zset中的String是不能重复的。
  3. 存在消息id及消息内容的数据不能直接使用redis的String数据结构吗?答案是不建议。因为这样会导致redis的key急剧曾多。

redis(六)---- 简单延迟队列的更多相关文章

  1. 灵感来袭,基于Redis的分布式延迟队列(续)

    背景 上一篇(灵感来袭,基于Redis的分布式延迟队列)讲述了基于Java DelayQueue和Redis实现了分布式延迟队列,这种方案实现比较简单,应用于延迟小,消息量不大的场景是没问题的,毕竟J ...

  2. PHP基于Redis实现轻量级延迟队列

    延迟队列,顾名思义它是一种带有延迟功能的消息队列. 那么,是在什么场景下我才需要这样的队列呢? 一.背景 先看看一下业务场景: 1.会员过期前3天发送召回通知 2.订单支付成功后,5分钟后检测下游环节 ...

  3. 灵感来袭,基于Redis的分布式延迟队列

    延迟队列 延迟队列,也就是一定时间之后将消息体放入队列,然后消费者才能正常消费.比如1分钟之后发送短信,发送邮件,检测数据状态等. Redisson Delayed Queue 如果你项目中使用了re ...

  4. 你知道Redis可以实现延迟队列吗?

    最近,又重新学习了下Redis,深深被Redis的魅力所折服,我才知道Redis不仅能快还能慢(我想也这么优秀o(╥﹏╥)o),简直是个利器呀. 咳咳咳,大家不要误会,本文很正经的啦! 好了,接下来回 ...

  5. 使用netty HashedWheelTimer构建简单延迟队列

    背景 最近项目中有个业务,需要对用户新增任务到期后进行业务处理.使用定时任务定时扫描过期时间,浪费资源,且不实时.只能使用延时队列处理. DelayQueue 第一想到的是java自带的延时队列del ...

  6. Redis实现简单消息队列

    http://www.jianshu.com/p/9c04890615ba 任务异步化 打开浏览器,输入地址,按下回车,打开了页面.于是一个HTTP请求(request)就由客户端发送到服务器,服务器 ...

  7. redis实现简单延时队列(转)

    继之前用rabbitMQ实现延时队列,Redis由于其自身的Zset数据结构,也同样可以实现延时的操作 Zset本质就是Set结构上加了个排序的功能,除了添加数据value之外,还提供另一属性scor ...

  8. 用redis实现简单的队列

    在工作中,时常会有用到队列的场景,比较常见的用rabbitMQ这些专业的组件,官网地址是:http://www.rabbitmq.com,重要的是官方有.net的客户端,但是如果对rabbitMQ不熟 ...

  9. Redis简单延时队列

    Redis实现简单延队列, 利用zset有序的数据结构, score设置为延时的时间戳. 实现思路: 1.使用命令 [zrangebyscore keyName socreMin socreMax] ...

随机推荐

  1. flutter如何使用配置文件pubspec.yaml(位于项目根目录)来管理第三方依赖包

    官方文档 在软件开发中,很多时候有一些公共的库或SDK可能会被很多项目用到,因此,将这些代码单独抽到一个独立模块,然后哪个项目需要使用时再直接集成这个模块,便可大大提高开发效率.很多编程语言或开发工具 ...

  2. Kubernetes 集群日志管理【转】

    Kubernetes 开发了一个 Elasticsearch 附加组件来实现集群的日志管理.这是一个 Elasticsearch.Fluentd 和 Kibana 的组合.Elasticsearch ...

  3. MySQL导出数据库和导入数据库

    一.导出: 语法:mysqldump --default-character-set=utf8 -u用户名 -p密码 数据库名 -d --add-drop-table > 导出文件名.sql 注 ...

  4. OLTP和OLAP区别详解

    Oracle属于OLTP类型,而gp数据库属于OLAP类型的.具体了解如下: 数据库系统一般分为两种类型,一种是面向前台应用的,应用比较简单,但是重吞吐和高并发的OLTP类型:一种是重计算的,对大数据 ...

  5. 聊一聊Java中的各种运算符(转载)

    计算机之所以叫"计算机",其最基本用途之一就是运算,对应刚刚接触Java的小伙伴而言,熟悉并掌握Java中的各种运算符及其在表达式中的运算优先级是十分必要的. 算术运算 算术运算主 ...

  6. 分享一个简单的C#的通用DbHelper类(支持数据连接池)

    每次新项目的时候,都要从头去找一遍数据库工具类.这里分享一个简单实用的C#的通用DbHelper工具类,支持数据连接池. 连接池配置 <connectionStrings> <add ...

  7. 如何通过 Python 和 OpenCV 实现目标数量监控?

    今天我们将利用python+OpenCV实现对视频中物体数量的监控,达到视频监控的效果,比如洗煤厂的监控水龙头的水柱颜色,当水柱为黑色的超过了一半,那么将说明过滤网发生了故障.当然不仅如此,我们看的是 ...

  8. 一百零四、SAP中ALV事件之十六,让ALV表格能点击修改

    一.上一篇我们写到生产的ALV表格,但是表格内容不支持修改,如果我们需要修改下图的数量,需要怎么操作呢 二.代码如下,设置 ls_fieldcat-edit = 'X'.   "允许编辑. ...

  9. 【转】ASP.NET Core 2.0中的HttpContext

      ASP.NET Core 2.0中的HttpContext相较于ASP.NET Framework有一些变化,这边列出一些之间的区别.   在ASP.NET Framework中的 System. ...

  10. 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring声明式事务管理(基于Annotation注解方式实现)

    在 Spring 中,除了使用基于 XML 的方式可以实现声明式事务管理以外,还可以通过 Annotation 注解的方式实现声明式事务管理. 使用 Annotation 的方式非常简单,只需要在项目 ...