Redis学习笔记之延时队列
一、业务场景
所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解
1.1 实践场景
- 订单支付失败,每隔一段时间提醒用户
- 用户并发量的情况,可以延时2分钟给用户发短信
- ...
1.2 实现方式
这些情况都可以使用延时队列来做,实现延时队列比较场景的有使用消息队列MQ来实现,比如RocketMQ等等,也可以使用Redis来实现,本博客主要介绍一下Redis实现延时队列
二、Redis延时队列
2.1 Redis列表实现
Redis实现延时队列可以通过其数据结构列表(list)来实现,顺便复习一下Redis的列表,实现列表,Redis可以通过队列和栈来实现:
/* 队列:First in first out */
//加两个value
>rpush keynames key1 key2
2
//计算
>llen keynames
2
>lpop keynames
key1
>lpop keynames
key2
//rpush会自动过期的
>rpop keynames
NULL
/* 栈:First in last out */
//同样,加两个元素
>rpush keynames key1 key2
2
>rpop keynames
key2
>rpop keynames
key1
对于Redis的基本数据结构,可以参考我之前的博客:https://blog.csdn.net/u014427391/article/details/82860694
然后怎么实现延时?Thread睡眠或者线程join?这种方法是可以实现,不过假如用户一多?10个请求就要延时10N了,这种情况系统性能不好的话就会出现线程阻塞了的情况。
队列空了的情况?就会出现pop 的死循环,这种情况很可怕,很吃系统CPU,虽然可以通过线程睡眠方法来缓解,但不是最好的方法
这时候就要介绍一下Redis的blpop/brpop来替换lpop/rpop,blpop/brpop阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻醒过来。消息的延迟几乎为零
2.2 Redis集合实现
Redis的有序集合(zset)也可以用于实现延时队列,消息作为value,时间作为score,这里顺便复习一下Redis的有序集合
//9.0是score也就是权重
>zadd keyname 9.0 math
1
>zadd keyname 9.2 history
1
//顺序
>zrange keyname 0 -1
1) history
2) math
//逆序
>zrevrange keyname 0 -1
1) math
2) history
//相当于count()
>zcard keyname
2
获取指定key的score
>zscore keyname math
9
然后多个线程的环境怎么保证任务不被多个线程抢了?这里可以使用Redis的zrem命令来实现
Redis Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。
当 key 存在但不是有序集类型时,返回一个错误。
注意: 在 Redis 2.4 版本以前, ZREM 每次只能删除一个元素。
下面给出来自《Redis 深度历险:核心原理与应用实践》小册的例子:例子就是用有序集合和zrem来实现的
import java.lang.reflect.Type;
import java.util.Set;
import java.util.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import redis.clients.jedis.Jedis;
public class RedisDelayingQueue<T> {
static class TaskItem<T> {
public String id;
public T msg;
}
// fastjson 序列化对象中存在 generic 类型时,需要使用 TypeReference
private Type TaskType = new TypeReference<TaskItem<T>>() {
}.getType();
private Jedis jedis;
private String queueKey;
public RedisDelayingQueue(Jedis jedis, String queueKey) {
this.jedis = jedis;
this.queueKey = queueKey;
}
public void delay(T msg) {
TaskItem<T> task = new TaskItem<T>();
task.id = UUID.randomUUID().toString(); // 分配唯一的 uuid
task.msg = msg;
String s = JSON.toJSONString(task); // fastjson 序列化
jedis.zadd(queueKey, System.currentTimeMillis() + 5000, s); // 塞入延时队列 ,5s 后再试
}
public void loop() {
while (!Thread.interrupted()) {
// 只取一条
Set<String> values = jedis.zrangeByScore(queueKey, 0, System.currentTimeMillis(), 0, 1);
if (values.isEmpty()) {
try {
Thread.sleep(500); // 歇会继续
} catch (InterruptedException e) {
break;
}
continue;
}
String s = values.iterator().next();
if (jedis.zrem(queueKey, s) > 0) { // 抢到了
TaskItem<T> task = JSON.parseObject(s, TaskType); // fastjson 反序列化
this.handleMsg(task.msg);
}
}
}
public void handleMsg(T msg) {
System.out.println(msg);
}
public static void main(String[] args) {
Jedis jedis = new Jedis();
RedisDelayingQueue<String> queue = new RedisDelayingQueue<>(jedis, "q-demo");
Thread producer = new Thread() {
public void run() {
for (int i = 0; i < 10; i++) {
queue.delay("codehole" + i);
}
}
};
Thread consumer = new Thread() {
public void run() {
queue.loop();
}
};
producer.start();
consumer.start();
try {
producer.join();
Thread.sleep(6000);
consumer.interrupt();
consumer.join();
} catch (InterruptedException e) {
}
}
}
不过在多线程环境,是很难做控制的,上面例子也有缺陷,下面引用小册的说法:
上面的算法中同一个任务可能会被多个进程取到之后再使用 zrem 进行争抢,那些没抢到的进程都是白取了一次任务,这是浪费。可以考虑使用 lua scripting 来优化一下这个逻辑,将 zrangebyscore 和 zrem 一同挪到服务器端进行原子化操作,这样多个进程之间争抢任务时就不会出现这种浪费了。
Redis学习笔记之延时队列的更多相关文章
- Redis学习笔记02-消息队列与延时队列
写在前面:Redis的消息队列并不是专业的消息队列,没有ACK保证,没有特别多的高级特性,如果对消息的可靠性有很高的要求,就放弃它吧. 1.Redis消息队列 Redis通过内部的list数据结构来实 ...
- Redis学习笔记~实现消息队列比MSMQ更方便
什么是队列:简单的说就是数据存储到一个空间里(可以是内存,也可以是物理文件),先存储的数据对象,先被取出来,这与堆栈正好相反,消息队列也是这样,将可能出现高并发的数据进行队列存储,并按着入队的顺序依次 ...
- redis 学习笔记三(队列功能)
Redis队列功能介绍 List 常用命令: Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用 Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用 Brpoplpus ...
- Redis学习笔记~目录
回到占占推荐博客索引 百度百科 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合). ...
- Redis学习笔记4-Redis配置详解
在Redis中直接启动redis-server服务时, 采用的是默认的配置文件.采用redis-server xxx.conf 这样的方式可以按照指定的配置文件来运行Redis服务.按照本Redi ...
- Redis学习笔记(二) Redis 数据类型
Redis 支持五种数据类型:string(字符串).list(列表).hash(哈希).set(集合)和 zset(有序集合),接下来我们讲解分别讲解一下这五种类型的的使用. String(字符串) ...
- Redis学习笔记4-Redis配置具体解释
在Redis中直接启动redis-server服务时, 採用的是默认的配置文件.採用redis-server xxx.conf 这种方式能够依照指定的配置文件来执行Redis服务. 依照本Redi ...
- Redis学习笔记八:集群模式
作者:Grey 原文地址:Redis学习笔记八:集群模式 前面提到的Redis学习笔记七:主从复制和哨兵只能解决Redis的单点压力大和单点故障问题,接下来要讲的Redis Cluster模式,主要是 ...
- redis学习笔记(详细)——高级篇
redis学习笔记(详细)--初级篇 redis学习笔记(详细)--高级篇 redis配置文件介绍 linux环境下配置大于编程 redis 的配置文件位于 Redis 安装目录下,文件名为 redi ...
随机推荐
- iserver频繁崩溃、内存溢出事故解决小记
一.事故分析 在生产项目中,频繁遇到iserver隔一段时间就会出现崩溃的情况. 将iserver错误日志发给技术客服后,说是内存溢出的问题. 查看服务器的配置是32g内存,按理说不该出现此类问题. ...
- 软工作业1—java实现wc.exe
github项目地址 https://github.com/liyizhu/wc.exe WC 项目要求 基本功能列表: wc.exe -c file.c //返回文件 file.c 的字符数 ...
- 洛谷1027 Car的旅行路线
原题链接 将每个城市拆成四个点,即四个机场来看,那么这题就是求最短路. 不过建图有些麻烦,先要找出第四个机场的坐标. 设另外三个机场的坐标为\((x_1, y_1), (x_2, y_2), (x_3 ...
- vue中部分api解释 ($nextTick)
1:this.$nextTick(function(){ }) 传如的参数是一个函数 这个API主要是获取dom元素 为什么需要这个api,在vue框架开发中,更新dom是一个异步操作,如果更新完do ...
- 对js原型及构造函数的相关理解
一.js中的原型创建(声明)一个函数,浏览器在内存中会创建一个对象.每个函数都默认会有一个属性prototype指向了这个对象,就是说prototype的属性的值就是这个对象.此对象就是该函数的原型对 ...
- Nexus 3 搭建 npm 私服 (windows)
Nexus 3 搭建 npm 私服备忘 下载与安装 在官网下载Nexus Repository Manager OSS 3.x, 解压至任意位置. 管理员运行 powershell, 切换到 nexu ...
- 批处理-Java JDK环境变量配置
setx /M JAVA_HOME "C:\Program Files\Java\jdk1.8.0_131" setx /M CLASSPATH ".;%%JAVA_HO ...
- monkey测试样例
我们通过在CMD窗口中执行: adb shell monkey {+命令参数}来进行Monkey测试了.首先,我们准备了一个有bug的项目CityWeather:通过测试这个项目(源码在附件文件夹中) ...
- java多线程中注入Spring对象问题
web应用中java多线程并发处理业务时,容易抛出NullPointerException. 原因: 线程中的Spring Bean没有被注入.web容器在启动时,没有提前将线程中的bean注入,在线 ...
- LINUX 系统下部署 NFS服务
NFS服务 NFS,是Network File System的简写,即网络文件系统.也被称为NFS: NFS允许一个系统在网络上与他人共享目录和文件. NFS通常运行于2049端口. 部署NFS 前提 ...