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. tomcat jsp页面乱码解决

    浏览器接收服务器响应的中文参数: JSP页面中告诉浏览器使用什么编码: <%@ page language="java" contentType="text/htm ...

  2. FreeSWITCH与PSTN对接

    FreeSWITCH与PSTN电话系统对接设定 一.环境介绍 我们目前使用模拟的话机连接到电信运营商,购买他们配套的电话总机,勉强实现了公司的基本通话.但是客户电话总机系统以及升级添加新的应用不但费用 ...

  3. position:absolute在IE8浏览器下无法显示正确位置

      在网页head下添加 <meta http-equiv="x-ua-compatible" content="ie=8" />

  4. OCM_第十七天课程:Section7 —》GI 及 ASM 安装配置 _管理和配置 GRID /实施 ASM 故障组 /创建 ACFS 文件系统

    注:本文为原著(其内容来自 腾科教育培训课堂).阅读本文注意事项如下: 1:所有文章的转载请标注本文出处. 2:本文非本人不得用于商业用途.违者将承当相应法律责任. 3:该系列文章目录列表: 一:&l ...

  5. MACE(2)-----模型编译

    作者:十岁的小男孩 QQ:929994365 无用 本文仅用于学习研究,非商业用途,欢迎大家指出错误一起学习,文章内容翻译自 MACE 官方手册,记录本人阅读与开发过程,力求不失原意,但推荐阅读原文. ...

  6. python接口自动化测试一:http协议

    1. http简介:http(超文本传输协议)是一个基于请求与响应模式的.无状态的.应用层的协议 2. url详解:百度搜索的一个url地址:https://www.baidu.com/s?wd=%E ...

  7. mysql操作查询结果case when then用法举例

    举例1: 使用该查询,得出iFavoriteID,iFavUserType ,cUser,iArticleID,dFavoriteTime五个字段的值: SELECT iFavoriteID,CASE ...

  8. ORACLE分页查询SQL语法——高效的分页

    --1:无ORDER BY排序的写法.(效率最高)--(经过测试,此方法成本最低,只嵌套一层,速度最快!即使查询的数据量再大,也几乎不受影响,速度依然!) SELECT * FROM (SELECT  ...

  9. linux命令之grep用法

    grep是linux中很常用的一个命令,主要功能就是进行字符串数据的对比,能使用正则表达式搜索文本,并将符合用户需求的字符串打印出来.grep全称是Global Regular Expression ...

  10. 行为型模式之Command模式

    命令模式将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化, 并且可以对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能. 概念描述 把命令的调用者与执行者分开,使双方不必关心对方是 ...