【Java笔记】实现延时队列2:Redis

文章目录

  • 过期监听
    • 准备工作
      • 稍微复习下Jedis与JedisPool
    • 模拟延时队列
    • 优缺点
      • **优点**:
      • **缺点**:
  • `ZSet ` 实现延时队列
    • 引入依赖
    • 模拟延时队列
    • 优缺点
      • **优点**(跟过期监听一样):
      • **缺点**:
  • Reference

Redis实现延时队列主要有两种方式:

  • 通过SETEX 与发布订阅机制过期监听
  • 通过 ZSET 实现

过期监听

Redis通过set key and expire + RedisExpirationListener 过期监听实现延时队列,主要还是基于Redis的发布/订阅模式实现。

准备工作

找到redis安装目录的redis.windows.conf与redis.windows-server.conf中的“notify-keyspace-events”,取消注释

notify-keyspace-events Ex

然后重启下Redis,windows中会直接使用redis-server.exe,不会加载redis.windows.conf这个配置文件,需要用进入安装目录,命令行启动:

redis-server.exe redis.windows.conf

如果这时命令行报错:

nvalid argument during startup: unknown conf file parameter :

那么要注意了,Redis配置里要顶格!要顶格!要顶格!

稍微复习下Jedis与JedisPool

首先明确一点,Jedis实例不是线程安全的,所以不可以多个线程共用一个Jedis实例

那是不是要开很多个Jedis实例呢?当然也不行,因为更多的Jedis实例意味着要建立更多的socket连接

所以就需要JedisPool,就是一个线程安全的网络连接池,类似ThreadPool可以复用Thread线程实例,JedisPool可以创建一些可靠的Jedis实例,并且在后续复用,能够提高性能,并且不需要更多的Socket连接

// 开一个连接池
JedisPool jedisPool = new JedisPool("127.0.0.1", 6379); // HOST, PORT
// 获取一个Jedis连接
Jedis jedis = jedisPool.getResource();

模拟延时队列

这里开两个线程:

  • 一个用于订阅监听事件:因为subscrib()会阻塞等待,需要异步初始化:
  • Jedis.subscribe(JedisPubSub实现类, channel)订阅频道需要两个参数:
    • channel参数:"keyevent@0:expired"是发布删除过期key信息的channel
    • JedisPubSub实现类:简单来说就是实现``JedisPubSub`接口的一些方法,当有key过期删除前/后/时执行一些逻辑
  • 一个用于生产数据:set(key, seconds, value)
    • key:这个订单集合的名字
    • seconds:过期时间(s)
    • value:当前订单的名字(编号)
public class RedisKeyExpireTest {private static JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);public static void main(String[] args) throws InterruptedException {// subscribe方法会阻塞等待,用异步去初始化订阅监听事件new Thread(() -> {jedisPool.getResource().subscribe(new RedisSub(), "__keyevent@0__:expired");}).start();// 添加几个带过期时间的keynew Thread(() -> {try {for (int i = 0; i < 5; i++) {String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));jedisPool.getResource().setex("orderNo100" + i, i + 1, "orderNo100" + i);System.out.println(time + ":生成订单,订单号:orderNo100" + i + ",有效期:" + (i + 1) + "秒");Thread.sleep(1000);}} catch (Exception e) {e.printStackTrace();}}).start();}
}class RedisSub extends JedisPubSub {@Overridepublic void onMessage(String channel, String message) {String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));System.out.println(time + ":订单号:" + message + "已到期");}
}

输出:

2024-03-30 22:04:21:生成订单,订单号:orderNo1000,有效期:1秒
2024-03-30 22:04:22:生成订单,订单号:orderNo1001,有效期:2秒
2024-03-30 22:04:22:订单号:orderNo1000已到期
2024-03-30 22:04:23:生成订单,订单号:orderNo1002,有效期:3秒
2024-03-30 22:04:24:生成订单,订单号:orderNo1003,有效期:4秒
2024-03-30 22:04:24:订单号:orderNo1001已到期
2024-03-30 22:04:25:生成订单,订单号:orderNo1004,有效期:5秒
2024-03-30 22:04:26:订单号:orderNo1002已到期
2024-03-30 22:04:28:订单号:orderNo1003已到期
2024-03-30 22:04:30:订单号:orderNo1004已到期

优缺点

优点

  • 实现简单,redis内存操作,速度快,性能高,集群扩展方便
  • 可以通过AOF和RDB实现消息队列的持久化,适合对延迟精度要求不高的业务场景

缺点

  • redis的key过期有惰性清除和定时清除两种策略,可能会存在延迟时间不精确的问题

    惰性清除:不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key

    • 优点:对 CPU 时间最友好。因为每次访问时,才会检查 key 是否过期,所以此策略只会使用很少的系统资源。
    • 缺点:对内存不友好。如果一个 key 已经过期,而这个 key 又仍然保留在数据库中,那么只要这个过期 key 一直没有被访问,它所占用的内存就不会释放,造成了一定的内存空间浪费。

    定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key,如果当前抽取的key中过期的超过25%,就再抽一次,循环至过期比例在25%以内。

    • 优点:可以减少内存压力
    • 缺点:难以确定删除操作执行的时长和频率。太频繁对CPU不友好;频率太低,过期key得不到及时释放,对内存不友好。

    而且,很明显,定期查询是一个循环,为了保证不会循环过度导致线程卡死,存在一个定期删除循环流程的时间上限,默认不超过25ms

  • 极端情况下不可靠:如果客户端故障或重启期间有key过期则过期通知事件的数据就丢失了(订单无法过期)

    • 可以用定时任务去做轮询补偿

ZSet 实现延时队列

Redis 可以使用有序集合(ZSet)的方式来实现延迟消息队列

Set 有一个 Score 属性可以用来存储延迟执行的时间

  • 使用zadd score1 value1 生产消息
  • 利用zrangebyscore 查询符合条件的任务,通过循环执行队列任务

引入依赖

<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version>
</dependency>

模拟延时队列

下面模拟一下这个过程,需要开两个线程:

  • 一个生产者,往redis数据库里生产数据,主要方法:
    • zadd(key, value, score)
      • key:这个订单集合的名字
      • value:当前订单的名字(编号)
      • score:当前订单的过期时刻
  • 一个消费者,不停轮询这个key的Sorted Set,主要方法:
    • zrangeWithScores(key, start, end) :返回key中在startend间的value和score的Tuple集合,比如
      • zrangeWithScores(key, 0, 0):获取score最小的一个tuple
      • zrangeWithScores(key, 0, 1):获取score最小的两个tuple
    • 也可以用zrangeWithScores(key, start, end),这是startend需要是准确时间。
public class CancelOrderRedisTest {private static JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);public static void main(String[] args) {// 开一个线程,放几个订单元素到zset中new Thread(() -> {try {for (int i = 0; i < 5; i++) {String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));redisClient().zadd("cancel:order:list", System.currentTimeMillis() + (i + 1) * 1000, "orderNo100" + i);System.out.println(time + ":生成订单,订单号:orderNo100" + i + ",有效期:" + (i + 1) + "秒");Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}}).start();// 再开一个线程轮询这个有序集合new Thread(() -> {Jedis jedis = redisClient();while (true){// 取这个有序集合的第一个,也就是score最小的元素Set<Tuple> items = jedis.zrangeWithScores("cancel:order:list", 0, 1);if (items == null || items.isEmpty()){// 避免空指针异常,因为是两个线程,一个线程生产数据(往redis里放订单),一个(现在这个)线程消费数据(清理过期数据)// 可能目前redis里的数据都消费完了,所以要sleep一会,等待另一个线程生产try {Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}} else {// 轮询成功拿到redis的数据,下面就判断一下是否到期Tuple tuple = (Tuple) items.toArray()[0];long score = (long) tuple.getScore();// 如果现在的时间大于当前数据的score(过期时间),进行清理if (System.currentTimeMillis() >= score){Long num = jedis.zrem("cancel:order:list", tuple.getElement());if (num != null && num > 0){String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));System.out.println(time +  ":订单号:" + tuple.getElement() + "已到期");}}}}}).start();}/*** 获取Redis连接* @return*/private static Jedis redisClient() {return jedisPool.getResource();}
}

输出:

2024-03-30 20:32:58:生成订单,订单号:orderNo1000,有效期:1秒
2024-03-30 20:32:59:订单号:orderNo1000已到期
2024-03-30 20:32:59:生成订单,订单号:orderNo1001,有效期:2秒
2024-03-30 20:33:00:生成订单,订单号:orderNo1002,有效期:3秒
2024-03-30 20:33:01:订单号:orderNo1001已到期
2024-03-30 20:33:01:生成订单,订单号:orderNo1003,有效期:4秒
2024-03-30 20:33:02:生成订单,订单号:orderNo1004,有效期:5秒
2024-03-30 20:33:03:订单号:orderNo1002已到期
2024-03-30 20:33:05:订单号:orderNo1003已到期
2024-03-30 20:33:07:订单号:orderNo1004已到期

优缺点

优点(跟过期监听一样):

  • 实现简单,redis内存操作,速度快,性能高,集群扩展方便
  • 可以通过AOF和RDB实现消息队列的持久化,适合对延迟精度要求不高的业务场景

缺点

  • 第一个就是轮询带来的问题:
    • 轮询线程如果不休眠或休眠时间过短,可能导致过多的空轮询,CPU飙高
    • 如果带休眠时间过长,因为现在过期的数据得等到下一轮轮询才能处理,延时队列的延时也就不准确了
    • 另外,【没有队列的纯粹轮询】还有个问题,就是数据量太大时,可能一个轮询周期检查不完,这里只需要轮询队头的一个或几个数据,所以不太会有这个问题
  • 然后是大部分中间件做延时队列都会有的问题:
    • 极端条件下,会丢失数据,不可靠,比如:
      • Redis过期通知时,应用正好重启,可能丢失事件(导致订单一直无法关闭)。
      • 先删数据在处理订单还是先处理订单再删除数据,处理异常时可能会导致数据丢失。
      • 可以用定时任务去做轮询补偿
    • 存储维护成本高:需要监听的数据较大时会占用中间件大量的存储空间,增加维护成本

Reference

订单超时自动取消的技术方案解析及代码实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/784552.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024.2.6力扣每日一题——魔塔游戏

2024.2.6 题目来源我的题解方法一 贪心优先队列 题目来源 力扣每日一题&#xff1b;题序&#xff1a;LCP 30 我的题解 方法一 贪心优先队列 思路&#xff1a;使用贪心的思想&#xff0c;从左到右遍历&#xff0c;若遇到加上当前房间的生命值后小于等于0&#xff0c;由于需要…

论文阅读: Visual Attention Network

Motivation 自注意力机制在2D自然图像领域面临3个挑战&#xff1a; 视二维图像为一维序列。对于高分辨率图像&#xff0c;二次复杂度消耗太大。只捕捉空间适应性&#xff0c;忽略通道适应性。 Contribution 设计了 Large Kernel attention(LKA)&#xff0c;包含卷积和自注意…

第三题:分数

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 1/11/21/41/8⋯ 每项是前一项的一半&#xff0c;如果一共有 20 项,求这个和是多少&#xff0c;结果用分数表示出来。 类似&#xff1a;3/2​&#xff0c;当然&…

虚拟机Linux(centos)安装python3.8(超详细)

一、Python下载 下载地址&#xff1a;https://www.python.org/downloads/source/ 输入下面网址即可直接下载&#xff1a; python3.8&#xff1a;https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz python3.6&#xff1a;https://www.python.org/ftp/python/3.6.5/…

复习中心极限定理

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 中心极限定理的核心概念&#xff1a; 中心极限定理&#xff08;Central Limit Theorem&#xff09;是统计学中的核心理论&#xff0c;指出当独立随机变量的样本量足够多时&#xff0c;它们的平均值将逐…

【现代企业管理】企业组织结构和组织文化的理论与实践——以华为为例

一、前言 管理是科学和艺术的统一体&#xff0c;它是企业成长的保证。企业管理中&#xff0c;管理者面对的往往不是一个完整的系统&#xff0c;而是各种不具有整体规律性的零碎信息的总和&#xff0c;因此进行信息的整合和研究是管理的重点和关键。 组织管理作为管理的四大职…

SpringBoot常见注解有哪些

Spring Boot的核心注解是SpringBootApplication , 他由几个注解组成 : ● SpringBootConfiguration&#xff1a; 组合了- Configuration注解&#xff0c;实现配置文件的功能&#xff1b; ● EnableAutoConfiguration&#xff1a;打开自动配置的功能&#xff0c;也可以关闭某个自…

【计算机网络】四层负载均衡和七层负载均衡

前言 1、分层方式 首先我们知道&#xff0c;在计算机网络中&#xff0c;常用的协议分层方式&#xff1a;OSI和TCP/IP&#xff0c;以及实际生产中使用的协议划分方式。 在OSI中&#xff0c;各层的职责如下&#xff1a; 应用层&#xff1a;对软件提供接口以使程序能使用网络服…

03-MySQl数据库的-用户管理

一、创建新用户 mysql> create user xjzw10.0.0.% identified by 1; Query OK, 0 rows affected (0.01 sec) 二、查看当前数据库正在登录的用户 mysql> select user(); ---------------- | user() | ---------------- | rootlocalhost | ---------------- 1 row …

新闻管理系统(源码+文档)

新闻管理系统&#xff08;小程序、ios、安卓都可部署&#xff09; 文件包含内容程序简要说明含有功能项目截图客户端新闻详情新闻首页分类退出登录个人中心拨打客服热线注册界面个人资料新闻评论成功 管理端用户管理分类管理新闻管理 文件包含内容 1、搭建视频 2、流程图 3、开…

完成一个程序,谈谈Rust写多线程并行算法的体会

退休了&#xff0c;重操旧业&#xff0c;我计划重写《极限切割》这款排料软件&#xff0c;重中之重就是重写排料算法。因为计划把算法做成云服务形式&#xff0c;所以开发工具就选择 Rust 了。先说结论&#xff0c;Rust 写后台服务程序的确好用&#xff0c;免去很多可能的Bug&a…

v3-admin-vite 改造自动路由,view页面自解释Meta

需求 v3-admin-vite是一款不错的后端管理模板&#xff0c;主要是pany一直都在维护&#xff0c;最近将后台管理也进行了升级&#xff0c;顺便完成一直没时间解决的小痛痒&#xff1a; 在不使用后端动态管理的情况下。我不希望单独维护一份路由定义&#xff0c;我希望页面是自解…

计算机网络—VLAN 间路由配置

目录 1.拓扑图 2.实验环境准备 3.为 R3 配置 IP 地址 4.创建 VLAN 5.配置 R2 上的子接口实现 VLAN 间路由 6.配置文件 1.拓扑图 2.实验环境准备 配置R1、R3和S1的设备名称&#xff0c;并按照拓扑图配置R1的G0/0/1接口的IP地址。 [Huawei]sysname R1 [R1]interface Giga…

JVM面试题(三)

1. 举几个可能发生内存泄漏的情况&#xff1f; 内存泄漏可能发生在多种情况下&#xff0c;以下是一些常见的例子&#xff1a; 类的构造函数和析构函数不匹配&#xff1a;当创建对象时&#xff0c;通过new动态分配了内存&#xff0c;但在对象销毁时&#xff0c;却没有通过dele…

【Java EE】多线程(一)

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

Mac OS上使用matplotlib库显示中文字体

文章目录 问题描述解决步骤参考文章 问题描述 如果我们想要使用matplotlib画图的话&#xff0c;可能会出现下面的这种warning: UserWarning: Glyph 24212 (\N{CJK UNIFIED IDEOGRAPH-5E94}) missing from current font.解决步骤 解决这个问题&#xff0c;可以按照下面的做法…

Excel求解二元一次方程

背景&#xff1a;如果想求解二元一次方程&#xff0c;常规方法就是联立方程求出一个未知数&#xff0c;然后带入任意一个等式。那么在excel里面应该怎么解决呢&#xff1f; 总所周知&#xff0c;大学里面会学矩阵行列式&#xff0c;二元一次方程其实就是一个简单的矩阵行列式。…

【云开发笔记No.19】关于中台架构(1)

在云开发领域&#xff0c;中台架构是一种至关重要的组织架构&#xff0c;它为企业提供了一种灵活且高效的方式来应对市场的快速变化。下面将详细阐述中台架构的定义、起源、定位和价值。 中台架构的定义 中台架构是指在企业信息系统中&#xff0c;将业务流程、数据和应用系统…

2024.3.31力扣(1200-1400)刷题记录

一、1523. 在区间范围内统计奇数数目 1.模拟 class Solution:def countOdds(self, low: int, high: int) -> int:# 模拟return len(range(low,high1,2)) if low & 1 else len(range(low1,high1,2)) 2.数学 总结规律。首为偶数就向下取整&#xff1b;奇数就向上取整。…

洛谷P1083 借教室(二分,差分)

题目描述 在大学期间&#xff0c;经常需要租借教室。大到院系举办活动&#xff0c;小到学习小组自习讨论&#xff0c;都需要向学校申请借教室。教室的大小功能不同&#xff0c;借教室人的身份不同&#xff0c;借教室的手续也不一样。 面对海量租借教室的信息&#xff0c;我们…