当前位置: 首页 > news >正文

基于redis实现延迟队列

Redis实现延时队列

延时队列里装的主要是延时任务,用延时队列来维护延时任务的执行时间。

1、延时队列有哪些使用情景?

1、如果请求加锁没加成功

可以将这个请求扔到延时队列里,延后处理。

2、业务中有延时任务的需要

比如说,文章定时发布。

2、基于redis实现延时队列

2.1.使用zset实现

  • 使用redis的zset来实现延时队列

  • 使用zset的添加、查询、删除命令

    • ZADD命令:

    ZADD key score member [score member...]
    • ZRANGEBYSCORE命令:

    ZRANGEBYSCORE key min max
    • ZREM命令:

    ZREM key member [member ...]
  • 1)将延时任务的【到期执行时间作为zset的score】、【延时任务序列化为一个字符串作为zset的member】使用zadd命令装进zset中。

  • 2)zset会为这些延时任务按照到期执行时间排序。

  • 3)设置多线程轮询zset获取到期的任务 | 用@Schedule注解标注定时轮询

  • 4)在zset中删除这些到期任务

  • 5)将这些到期任务放进list中,在list中使用【blpop/brpop】消费任务。

优化1:

对于第3步来说,由于是多线程,所以同一个到期任务可能会被多个进程获取到,然后再使用 zrem 进行争抢。最终一定只有一个进程zrem成功。因此对于那些zrem没成功的进程相当于白取任务。所以我们可以进一步优化:【使用 lua scripting 让 zrangebyscore 和 zrem 一同挪到服务器端进行原子化操作】。

优化2:

使用redis管道操作:通过管道方式将获取到的到期任务push到list中。

优化:redis管道
1)Redis的消息交互

客户端将请求传送给服务器,服务器处理完毕后,再将响应回复给客户端。这要花费一个网络数据包来回的时间。

详细过程是这样的:

1、客户端进程调用 write 将消息写到操作系统内核为套接字分配的发送缓冲 send buffer。

2、客户端操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过「网际路由」送到服务器的网卡。

3、服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲 recv buffer。

4、服务器进程调用 read 从接收缓冲中取出消息进行处理。

5、服务器进程调用 write 将响应消息写到内核为套接字分配的发送缓冲 send buffer。

6、服务器操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过「网际路由」送到客户端的网卡。

7、客户端操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲 recv buffer。

8、客户端进程调用 read 从接收缓冲中取出消息返回给上层业务逻辑进行处理。

9、结束。

由上面的详细过程,可以知道:

【write操作】只负责将数据写到发送缓冲。但是如果发送缓冲满了,那么就需要等待缓冲空出空闲空间。这个就是写操作 IO 操作的真正耗时。

【read操作】只负责将数据从接收缓冲中取出来就完事了。如果缓冲是空的,那么就需要等待数据到来,这个就是读操作 IO 操作的真正耗时。

因此,一条消息的消息交互所花费的时间主要在于:

write 操作几乎没有耗时,直接写到发送缓冲就返回,而 read 就会比较耗时了,因为它要等待消息经过网络路由到目标机器处理后的响应消息,再回送到当前的内核读缓冲才可以返回。这才是一个网络来回的真正开销。

2)连续多条消息指令的消息交互

(1)普通方式:

如果要执行多条消息指令,则需要花费多个网络来回的时间。

(2)使用redis管道:

管道方式是客户端通过将多条命令white到发送缓存中,然后再一次性发送到服务端的缓存中,服务端处理完这些命令后将响应结果写到发送缓存中,最后再发送给接收缓存,客户端第一次read的时候,需要等待一个网络来回,而后续的read操作直接从接收缓存中取就行了,因此总的花费时间只有一个网络来回的时间。

以上两种方式的示例代码:

public class RedisPipelineTestDemo {public static void main(String[] args) {//连接redisJedis jedis = new Jedis("10.101.17.180", 6379);//jedis逐一给每个set新增一个valueString zSetKey = "Pipeline-test-set";int size = 100000;//普通方式long begin = System.currentTimeMillis();for (int i = 0; i < size; i++) {jedis.sadd(zSetKey + i, "aaa");}log.info("Jedis逐一给每个set新增一个value耗时:{}ms", (System.currentTimeMillis() - begin));//管道方式      Pipeline Pipeline = jedis.Pipelined();begin = System.currentTimeMillis();for (int i = 0; i < size; i++) {             Pipeline.sadd(zSetKey + i, "bbb");}         Pipeline.sync();log.info("Jedis Pipeline模式耗时:{}ms", (System.currentTimeMillis() - begin));}
}
​
普通方式:162655ms
管道方式:504ms

2.2.使用redis键空间通知

redis的PubSub,发布者订阅者模型。

摘自javaguide:

在 pub/sub 模式下,生产者需要指定消息发送到哪个 channel 中,而消费者则订阅对应的 channel 以获取消息。

Redis 中有很多默认的 channel,这些 channel 是由 Redis 本身向它们发送消息的,而不是我们自己编写的代码。其中,__keyevent@0__:expired 就是一个默认的 channel,负责监听 key 的过期事件。也就是说,当一个 key 过期之后,Redis 会发布一个 key 过期的事件到__keyevent@<db>__:expired这个 channel 中。

我们只需要监听这个 channel,就可以拿到过期的 key 的消息,进而实现了延时任务功能。

这个功能Redis官方称为keyspace notifications,字面意思就是键空间通知。

代码实现:

Spring已经实现了监听__keyevent@*__:expired这个channel这个功能,__keyevent@*__:expired中的*代表通配符的意思,监听所有的数据库。

在配置类中:

@Configuration
public class RedisConfiguration {
​@Beanpublic RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();redisMessageListenerContainer.setConnectionFactory(connectionFactory);return redisMessageListenerContainer;}
​@Beanpublic KeyExpirationEventMessageListener redisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {return new KeyExpirationEventMessageListener(redisMessageListenerContainer);}/**KeyExpirationEventMessageListener实现了对__keyevent@*__:expiredchannel的监听当KeyExpirationEventMessageListener收到Redis发布的过期Key的消息的时候,会发布RedisKeyExpiredEvent事件**/
​
}

所以我们只需要监听RedisKeyExpiredEvent事件就可以拿到过期消息的Key,也就是延迟消息。对RedisKeyExpiredEvent事件的监听实现MyRedisKeyExpiredEventListener。

@Component
public class MyRedisKeyExpiredEventListener implements ApplicationListener<RedisKeyExpiredEvent> {
​@Overridepublic void onApplicationEvent(RedisKeyExpiredEvent event) {byte[] body = event.getSource();System.out.println("获取到延迟消息:" + new String(body));}
​
}

缺点:

1、能否及时的监听到过期键过期,取决于【redis的过期键删除策略】。由于可能出现redis过期键已经过期,但是redis还未将其删除从而导致无法监听到过期键过期。使得最后消息延迟。

补充:redis过期键删除策略
  • 定时删除

  • 惰性删除

1)定时删除

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

1、从过期字典中随机 20 个 key;

2、删除这 20 个 key 中已经过期的 key;

3、如果过期的 key 比率超过 1/4,那就重复步骤 1;

同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过【 25ms】。

大量key同时过期存在的问题:

但是如果一个Redis 实例中所有的 key 在同一时间过期,即使有25ms的扫描时间上限,如果此时有101 个客户端同时将请求发过来,25ms后才能处理一个客户端的请求,然后25ms后再处理一个客户端的请求,那么第101个客户端需要等待2500ms后才能被处理请求。

大量key同时过期解决方法:

给过期时间设置一个随机范围,而不能全部在同一时间过期。

2)惰性删除

在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。

参考:

《Redis深度历险》

Redis常见面试题总结(上) | JavaGuide

相关文章:

基于redis实现延迟队列

Redis实现延时队列 延时队列里装的主要是延时任务&#xff0c;用延时队列来维护延时任务的执行时间。 1、延时队列有哪些使用情景&#xff1f; 1、如果请求加锁没加成功 可以将这个请求扔到延时队列里&#xff0c;延后处理。 2、业务中有延时任务的需要 比如说&#xff0…...

PHP微信小程序共享充电桩系统设计与实现计算机毕业设计源代码作品和开题报告

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育、辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…...

【网络面试篇】TCP与UDP类

目录 一、综述 1. TCP与UDP的概念 2. 特点 3. 区别 4. 对应的使用场景 二、补充 1. 基础概念 &#xff08;1&#xff09;面向连接 &#xff08;2&#xff09;可靠的 &#xff08;3&#xff09;字节流 2. 相关问题 &#xff08;1&#xff09;TCP 和 UDP 可以同时绑定…...

Windows转Mac过渡指南

最近由于工作原因开始使用mac电脑&#xff0c;说实话刚拿到手的时候&#xff0c;window党表示真的用不惯。坚持用一下午之后&#xff0c;发现真的yyds&#xff0c;这篇文章说说mac电脑的基本入门指南。 1. 不会使用mac的触摸板&#xff0c;接上鼠标发现滚轮和windows是反的。 …...

LeetCode100之盛最多水的容器(11)--Java

1.问题描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量 注意 你不能倾斜容器 示例1 输入&…...

【VMware】使用笔记

一、安装 win11支持16.2以上版本&#xff0c;其他版本不兼容 安装参考&#xff1a; 二、设置 1、蓝屏设置 参考&#xff1a;win11打开VMware虚拟机蓝屏解决_win11vmware蓝屏-CSDN博客 2、VMwareTool配置 第一步&#xff1a;移除“open-vm-tools” sudo apt-get autoremo…...

<项目代码>YOLOv8 猫狗识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…...

存储数据库的传输效率提升-ETLCloud结合HBASE

一、大数据存储数据库–HBASE HBase&#xff0c;作为一个开源的分布式列存储数据库&#xff0c;基于Google的Bigtable设计而成&#xff0c;专为处理大规模结构化数据而优化。使用HBase打造大数据解决方案的好处主要包括&#xff1a;高可扩展性&#xff0c;能够处理PB级的数据&…...

HO-XGBoost河马算法优化极限梯度提升树多变量回归预测(Matlab)

HO-XGBoost河马算法优化极限梯度提升树多变量回归预测&#xff08;Matlab&#xff09; 目录 HO-XGBoost河马算法优化极限梯度提升树多变量回归预测&#xff08;Matlab&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现HO-XGBoost多变量回归预测&…...

【Hive sql面试题】找出连续活跃3天及以上的用户

表数据如下&#xff1a; 要求&#xff1a;求出连续活跃三天及以上的用户 建表语句和插入数据如下&#xff1a; create table t_useractive(uid string,dt string );insert into t_useractive values(A,2023-10-01 10:10:20),(A,2023-10-02 10:10:20),(A,2023-10-03 10:16…...

Linux curl命令下载显示时间/速度/大小

命令&#xff1a; curl -# -O --compressed -w "大小: %{size_download} bytes\n时间: %{time_total} seconds\n速度: %{speed_download} B/s\n" 下载URL链接。 例子&#xff1a; curl -# -O --compressed -w "大小: %{size_download} bytes\n时间: %{time_to…...

sklearn|机器学习:决策树(一)

文章目录 sklearn&#xff5c;机器学习&#xff1a;决策树&#xff08;一&#xff09;&#xff08;一&#xff09;概述&#xff08;二&#xff09;实战1. 环境配置2. sklearn 中的决策树&#xff08;1&#xff09;模块 sklearn.tree&#xff08;2&#xff09;sklearn 基本建模流…...

Rust中三种方式使用环境变量

环境变量是存储在操作系统中的一组键值对。它们用于存储系统和其他应用程序所需的配置信息。本文我们将探索如何在Rust中使用标准库以及dotenv crate来处理环境变量。 环境变量 环境变量提供了一种灵活的方式来配置应用程序&#xff0c;而无需直接在源代码中硬编码配置值。这…...

搭建支持国密GmSSL的Nginx环境

准备 1、服务器准备&#xff1a;本文搭建使用的服务器是CentOS 7.6 2、安装包准备&#xff1a;需要GmSSL、国密Nginx&#xff0c;可通过互联网下载或者从 https://download.csdn.net/download/m0_46665077/89936158 下载国密GmSSL安装包和国密Nginx安装包。 服务器安装依赖包…...

Docker部署Portainer CE结合内网穿透实现容器的可视化管理与远程访问

文章目录 前言1. 本地安装Docker2. 本地部署Portainer CE3. 公网远程访问本地Portainer-CE3.1 内网穿透工具安装3.2 创建远程连接公网地址4. 固定Portainer CE公网地址前言 本篇文章介绍如何在Ubuntu中使用docker本地部署Portainer CE可视化管理工具,并结合cpolar实现公网远程…...

不适合的学习方法

文章目录 不适合的学习方法1. 纯粹死记硬背2. 过度依赖单一资料3. 线性学习4. 被动学习5. 一次性学习6. 忽视实践7. 缺乏目标导向8. 过度依赖技术9. 忽视个人学习风格10. 过于频繁的切换 结论 以下是关于不适合的学习方法的更详细描述&#xff0c;包括额外的内容和相关公式&…...

在子类中调用父类的构造函数

在Java中调用父类构造函数 使用super()关键字&#xff1a;在子类的构造函数中&#xff0c;可以使用super()来调用父类的构造函数。如果父类有默认构造函数&#xff08;即没有参数的构造函数&#xff09;&#xff0c;并且子类的构造函数没有显式调用super()&#xff0c;Java编译…...

【K8S系列】Kubernetes 中 Service 的流量不均匀问题【已解决】

在 Kubernetes 中&#xff0c;Service 是一种抽象&#xff0c;用于定义一组 Pod 的访问策略。当某些 Pod 接收的流量过多&#xff0c;而其他 Pod 的流量较少时&#xff0c;可能会导致负载不均衡。这种情况不仅影响性能&#xff0c;还可能导致某些 Pod 过载&#xff0c;影响应用…...

C-小H学生物

题意&#xff1a;一棵树节点编号为1具有n种不同物种的演化树上。物种i将遗传信息向下传递到物种j会产生dij的遍历。dij是一个长为l的01串。变异程度duv为u到v简单路径上的所有编译信息的异或和。基因多样性定义为 分析&#xff1a;计算Di的遗传信息&#xff0c;用dfs将遗传信息…...

什么是软件设计模式, 它们⽤于解决什么问题, 它们为什么有效

什么是设计模式 软件设计模式是指在软件设计过程中&#xff0c;经过验证的、可复⽤的、对特定 场景下常⻅问题的解决⽅案的⼀种描述或模板。这些模式并不是具体的 代码&#xff0c;⽽是⽤于指导如何组织代码、类和对象&#xff0c;以便更好地解决问题和 满⾜需求。 ⽤于解决的…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

DAY 45 超大力王爱学Python

来自超大力王的友情提示&#xff1a;在用tensordoard的时候一定一定要用绝对位置&#xff0c;例如&#xff1a;tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾&#xff1a; tensorboard的发展历史和原理tens…...

Copilot for Xcode (iOS的 AI辅助编程)

Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot&#xff0c;它能根据上下文补全代码&#xff0c;快速生成常用…...

Git 命令全流程总结

以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结&#xff0c;按操作场景分类整理&#xff1a; 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...