Redis简单延时队列
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简单延时队列的更多相关文章
- redis实现简单延时队列(转)
继之前用rabbitMQ实现延时队列,Redis由于其自身的Zset数据结构,也同样可以实现延时的操作 Zset本质就是Set结构上加了个排序的功能,除了添加数据value之外,还提供另一属性scor ...
- 基于Redis实现延时队列服务
背景 在业务发展过程中,会出现一些需要延时处理的场景,比如: a.订单下单之后超过30分钟用户未支付,需要取消订单 b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论 c.点我达 ...
- 【转】基于Redis实现延时队列服务
背景 在业务发展过程中,会出现一些需要延时处理的场景,比如: a.订单下单之后超过30分钟用户未支付,需要取消订单b.订单一些评论,如果48h用户未对商家评论,系统会自动产生一条默认评论c.点我达订单 ...
- laravel+Redis简单实现队列通过压力测试的高并发处理
秒杀活动 在一般的网络商城中我们会经常接触到一些高并发的业务状况,例如我们常见的秒杀抢购等活动, 在这些业务中我们经常需要处理一些关于请求信息过滤以及商品库存的问题. 在请求中比较常见的状况是同一用户 ...
- redis简单消息队列
<?php $redis = new Redis(); $redis->connect('127.0.0.1',6379); $redis->flushall(); $redis-& ...
- Redis学习笔记之延时队列
目录 一.业务场景 二.Redis延时队列 一.业务场景 所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解 1.1 实践场景 订单支付失败,每隔一段时间提醒用户 用户并发量的情况,可以 ...
- Redis学习笔记02-消息队列与延时队列
写在前面:Redis的消息队列并不是专业的消息队列,没有ACK保证,没有特别多的高级特性,如果对消息的可靠性有很高的要求,就放弃它吧. 1.Redis消息队列 Redis通过内部的list数据结构来实 ...
- Redis的批量操作是什么?怎么实现的延时队列?以及订阅模式、LRU。
前言 这次的内容是我自己为了总结Redis知识而扩充的,上一篇其实已经总结了几点知识了,但是Redis的强大,以及适用范围之广可不是单单一篇博文就能总结清的.所以这次准备继续总结,因为第一个问题,Re ...
- 了解一下Redis队列【缓兵之计-延时队列】
https://www.cnblogs.com/wt645631686/p/8454021.html 我们平时习惯于使用 Rabbitmq 和 Kafka 作为消息队列中间件,来给应用程序之间增加 异 ...
随机推荐
- 使用命令行登陆数据库配置文件修改 解决ora12528
下面是问题解决: ORA-12528: TNS:listener: all appropriate instances are blocking new connections 1:修改listene ...
- 读SRE Google运维解密有感(二)
前言 这是读“SRE Google运维解密”有感第二篇,第一篇参见 这本书最近又读了几章,结合自己的经历,有些地方真的能感同身受,有些地方也惊叹SRE充满辩证的思想,总之SRE是好一本好书,会给你很大 ...
- Android Studio 超级简单的打包生成apk
为什么要打包: apk文件就是一个包,打包就是要生成apk文件,有了apk别人才能安装使用.打包分debug版和release包,通常所说的打包指生成release版的apk,release版的apk ...
- Laravel 返回数据库中的随机一行数据
最佳方式 $total = Book::count() - 1; $skip = mt_rand(0, $total); $item = Book::select('name', 'author_id ...
- django----过滤器和自定义标签
模板语法之过滤器 1.default:如果一个变量是false或者为空,使用给定的默认值.否则,使用变量的值.例如: <p>default过滤器:{{ li|default:"如 ...
- python 全栈开发,Day120(路由系统, 实例化Flask的参数, 蓝图(BluePrint), before_request after_request)
昨日内容回顾 1.Flask: from flask import Flask app = Flask(__name__) # 从源码中可以看出,Flask集成的run方法是由werkzeug中的ru ...
- C#的委托(delegate、Action、Func、predicate)
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递.事件是一种特殊的委托. 1.委托的声明 delegate我们常用到的一种声明 delegate至少0个参数,至多32个参 ...
- [转] 深入理解React 组件状态(State)
React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据. 一. 如何定义State 定义一个合适 ...
- POJ 3050 Hopscotch【DFS带回溯】
POJ 3050 题意: 1.5*5的方阵中,随意挑一格,记住这个格子的数字 2.可以上下左右走,走5次,每走一次记录下所走格子的数字 3.经过以上步骤,把所得6个数字连起来,形成一串数字.求共可以形 ...
- 【noip模拟赛4】汽艇 模拟
描述 一天sxc,zsx,wl到gly坐汽艇,本来和其他的人约好了一起去,结果被放了鸽子,3人便只有一人负担x元去坐汽艇(很贵哦).坐了才发现如果汽艇上人多了位置就不宽敞,就不好玩了.而3个人貌似是最 ...