Redis实现简单延队列, 利用zset有序的数据结构, score设置为延时的时间戳.

实现思路:

1、使用命令 [zrangebyscore keyName socreMin socreMax] 会返回已score排序由小到大的一个list

2、list非空则使用[zrem keyName value]  删除第一个元素, 删除成功即代表消费成功, 可以解决多线程并发消费的问题.

使用jedis实现代码:

 package com.nancy.utils;

 import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import redis.clients.jedis.Jedis; import java.lang.reflect.Type;
import java.util.*; /**
* redis实现延时队列,但是对于要求行极高的环境不建议使用,主要原因:
* 1、没有可靠的消息持久机制,消息容易丢失
* 2、ack应答机制确实,没有传统MQ机制的可靠性
*
* @author zhou.guangfeng on 2019/3/9 下午4:31
*/
public class RedisDelayQueue<T> { static class TaskItem<T>{
public String id ;
public T msg ;
} private Jedis jedis ;
private String queueKey ;
private Type taskType = new TypeReference<TaskItem<T>>(){}.getType(); RedisDelayQueue(Jedis jedis, String queueKey){
this.jedis = jedis ;
this.queueKey = queueKey ;
} public void delay(T msg){
TaskItem<T> item = new TaskItem<>() ; item.id = UUID.randomUUID().toString() ;
item.msg = msg ; String content = JSON.toJSONString(item) ;
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
jedis.zadd(queueKey, System.currentTimeMillis() + 5000, content) ;
} public void loop(){
while (!Thread.interrupted()){
Boolean flag = consumer() ;
try {
if(!flag) {
Thread.sleep(500L);
}
}catch (InterruptedException ex){
break;
} }
} /**
*
* 队列消费,利用zrem操作,删除成功即也消费。 并发环境可能出现zrem删除失败情况,从而导致无效的请求。
* @param
* @return
*/
private Boolean consumer(){
// 按照分数即时间, 有序集成员按 score 值递增(从小到大)次序排列。
Set<String> values = jedis.zrangeByScore(queueKey, 0, System.currentTimeMillis()) ;
if (values == null || values.isEmpty()){
return false ;
} String content = values.iterator().next() ;
if(jedis.zrem(queueKey, content) <= 0){
return false ;
} TaskItem<T> item = JSON.parseObject(content, taskType) ;
handleMsg(item.msg) ;
return true ;
} public void handleMsg(T msg){
System.out.println(msg);
} public static void main(String[] args) {
RedisDelayQueue<String> queue = new RedisDelayQueue<>(AbstractDistributedLock.getJedisPool().getResource(), "delay-queue-demo") ; System.out.println("delay queue start, time = " + new Date());
Thread producer = new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
queue.delay("codehole:" + i);
}
}
}; Thread consumer = new Thread(){
@Override
public void run() {
queue.loop();
}
}; producer.start();
consumer.start(); try {
producer.join();
Thread.sleep(6000L); consumer.interrupt();
consumer.join();
}catch (InterruptedException ex){ }finally {
System.out.println("delay queue start, end = " + new Date());
} } }
 public abstract class AbstractDistributedLock implements RedisLock {

     private static JedisPool jedisPool;

     protected static final String LOCK_SUCCESS = "OK";
protected static final Long RELEASE_SUCCESS = 1L;
protected static final String SET_IF_NOT_EXIST = "NX";
protected static final String SET_WITH_EXPIRE_TIME = "EX";
protected static final long DEFAULT_EXPIRE_TIME = 1000 * 10 ;
protected static final long DEFAULT_DELAY_TIME = 2 ; static {
JedisPoolConfig config = new JedisPoolConfig();
// 设置最大连接数
config.setMaxTotal(500);
// 设置最大空闲数
config.setMaxIdle(50);
// 设置最大等待时间
config.setMaxWaitMillis(1000 * 100);
// 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
config.setTestOnBorrow(true);
jedisPool = new JedisPool(config, "127.0.0.1", 6379, 3000);
} public static JedisPool getJedisPool() {
return jedisPool;
} @Override
public String getLockKey(String lockKey){
return "lock:" + lockKey;
} }

AbstractDistributedLock

运行结果:

delay queue start, time = Mon Mar 11 10:21:25 CST 2019
codehole:0
codehole:1
codehole:2
codehole:3
codehole:4
codehole:5
codehole:6
codehole:7
codehole:8
codehole:9
delay queue start, end = Mon Mar 11 10:21:31 CST 2019

以上的代码 jedis.zrangeByScore 和  jedis.zrem 为非原子操作.  如果jedis.zrem一旦失败, 会进入休眠, 造成资源浪费. 因此改造为使用lua脚本执行jedis.zrangeByScore 和  jedis.zrem 保证原子性.

    /**
* 队列消费 使用lua脚本, 保证zrangebyscore 和 zrem操作原子性。
*
* @param
* @return
*/
private Boolean consumerWithLua(){
String script = " local resultDelayMsg = {}; " +
" local arr = redis.call('zrangebyscore', KEYS[1], '0', ARGV[1]) ; " +
" if next(arr) == nil then return resultDelayMsg end ;" +
" if redis.call('zrem', KEYS[1], arr[1]) > 0 then table.insert(resultDelayMsg, arr[1]) return resultDelayMsg end ; " +
" return resultDelayMsg ; ";
Object result = jedis.eval(script, Collections.singletonList(queueKey), Collections.singletonList("" + System.currentTimeMillis()));
List<String> msg = null ;
if (result == null || (msg = (List<String>) result).isEmpty()) {
return false ;
} TaskItem<T> item = JSON.parseObject(msg.get(0), taskType) ;
handleMsg(item.msg) ;
return true ;
}

redis实现延时队列,但是对于要求行极高的环境不建议使用,主要原因:

1、没有可靠的消息持久机制,消息容易丢失. 需要自己实现

2、ack应答机制缺失,没有传统MQ机制的可靠性

因此, 如果对数据一致性有严格的要求, 还是建议使用传统MQ.

Redis简单延时队列的更多相关文章

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

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

  2. 基于Redis实现延时队列服务

    背景 在业务发展过程中,会出现一些需要延时处理的场景,比如: a.订单下单之后超过30分钟用户未支付,需要取消订单 b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论 c.点我达 ...

  3. 【转】基于Redis实现延时队列服务

    背景 在业务发展过程中,会出现一些需要延时处理的场景,比如: a.订单下单之后超过30分钟用户未支付,需要取消订单b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论c.点我达订单 ...

  4. laravel+Redis简单实现队列通过压力测试的高并发处理

    秒杀活动 在一般的网络商城中我们会经常接触到一些高并发的业务状况,例如我们常见的秒杀抢购等活动, 在这些业务中我们经常需要处理一些关于请求信息过滤以及商品库存的问题. 在请求中比较常见的状况是同一用户 ...

  5. redis简单消息队列

    <?php $redis = new Redis(); $redis->connect('127.0.0.1',6379); $redis->flushall(); $redis-& ...

  6. Redis学习笔记之延时队列

    目录 一.业务场景 二.Redis延时队列 一.业务场景 所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解 1.1 实践场景 订单支付失败,每隔一段时间提醒用户 用户并发量的情况,可以 ...

  7. Redis学习笔记02-消息队列与延时队列

    写在前面:Redis的消息队列并不是专业的消息队列,没有ACK保证,没有特别多的高级特性,如果对消息的可靠性有很高的要求,就放弃它吧. 1.Redis消息队列 Redis通过内部的list数据结构来实 ...

  8. Redis的批量操作是什么?怎么实现的延时队列?以及订阅模式、LRU。

    前言 这次的内容是我自己为了总结Redis知识而扩充的,上一篇其实已经总结了几点知识了,但是Redis的强大,以及适用范围之广可不是单单一篇博文就能总结清的.所以这次准备继续总结,因为第一个问题,Re ...

  9. 了解一下Redis队列【缓兵之计-延时队列】

    https://www.cnblogs.com/wt645631686/p/8454021.html 我们平时习惯于使用 Rabbitmq 和 Kafka 作为消息队列中间件,来给应用程序之间增加 异 ...

随机推荐

  1. igmpproxy启动时错误:There must be at least 2 Vif's where one is upstream.

    openwrt 下启动igmpproxy时报错 # /etc/init.d/igmpproxy start Not starting instance igmpproxy::instance1, an ...

  2. python for dl

    算是python的简明教程吧,总结的不错: https://zhuanlan.zhihu.com/p/24162430 python for opencv: https://zhuanlan.zhih ...

  3. WAP网页中点击链接直接拨打电话的方法

    主要方法有两种: 第一种: 这种也是最简单的. 示例代码: <a href="tel:+18790853577">点击拨打</a> 第二种: <a h ...

  4. 读取web.config和app.config配置文件

    app.config:       <add key="Password" value="123456"/> C#:   string TQpwd ...

  5. JavaScript脚本的两种放置方式

    JavaScript脚本的两种放置方式 1在body里用 script标签引用 2 直接写在<script></script>标签之中

  6. poj 1679 判断MST是不是唯一的 (次小生成树)

    判断MST是不是唯一的 如果是唯一的 就输出最小的权值和 如果不是唯一的 就输出Not Unique! 次小生成树就是第二小生成树  如果次小生成树的权值和MST相等  那么MST就不是唯一的 法一: ...

  7. poj 2485 求最小生成树中 最长的一条边

    Sample Input 1 //T 3 //n0 990 692 //邻接矩阵990 0 179692 179 0Sample Output 692 prim # include <iostr ...

  8. [ZJOI2012]数列

    超级水的题还wa了一次 首先很容易发现其实就只有两个值并存 然后 要注意把数组初始化啊...可能后面有多余的元素(对拍的时候由于从小到大就没跑出错) #include <bits/stdc++. ...

  9. Windows 7下载安装 Visual C++ 6.0(VC6) 全程图解

    说实话我也一直没有试过,所以也想当然的认为Win7下就不能安装VC6,压根就100%不兼容?一直使用高版本的VS(如VS2008和现在用的VS2010)的我今天亲身在Win7下安装一次试试. 注:文中 ...

  10. Linux 之 Makefile 报错

    1. 在make命令后出现这种错误提示,是提示第4行没有分隔符. 例如: 1 target:prerequisites ...... 4 command …… 改为: 1 target:prerequ ...