延迟队列浅学

延迟队列

一、什么是延时队列

延时队列是一种特殊的队列,它允许将消息或任务延迟一段时间后再进行处理。延时队列的作用是在某些需要延迟处理的场景中,提供一种可靠的延迟处理机制。
延时队列的原理可以通过以下步骤来解释:

  1. 将消息或任务存储在队列中:当需要延迟处理的消息或任务到达时,将其放入延时队列中。延时队列可以使用队列数据结构(如数组、链表)或者消息中间件(如RabbitMQ、Kafka)来实现。
  2. 设置延迟时间:每个消息或任务都会被设置一个延迟时间,即需要延迟处理的时间。这个延迟时间可以是固定的,也可以根据业务需求动态设置。
  3. 定时检查:延时队列会定时检查队列中的消息或任务是否到达延迟时间。这个检查可以通过定时任务、时间轮算法等方式来实现。
  4. 处理延迟消息或任务:当延迟时间到达时,延时队列会将消息或任务从队列中取出,并进行相应的处理。处理方式可以是发送消息给消费者、执行任务、更新缓存等。

二、延时队列的应用

技术没有最好的只有最合适的。
延时队列在项目中的应用还是比较多的,尤其像电商类平台:

  1. 12306 下单成功后,在半个小时内没有支付,自动取消订单。
  2. 如果订单一直处于某一个未完结状态时,及时处理关单,并退还库存。
  3. 淘宝新建商户一个月内还没上传商品信息,将冻结商铺等。
  4. 会议预定系统,在预定会议开始前半小时通知所有预定该会议的用户。
  5. 安全工单超过 24 小时未处理,则自动拉企业群提醒相关责任人。
  6. 用户下单外卖以后,距离超时时间还有 10 分钟时提醒外卖小哥即将超时。
  7. 外卖平台发送订餐通知,下单成功后 60s 给用户推送短信。

对于数据量比较少并且时效性要求不那么高的场景,一种比较简单的方式是轮询数据库,定期轮询一下数据库中所有数据,处理所有到期的数据。比如公司内部会议预定系统,因为数据量必然不会很大并且会议开始前提前 30 分钟提醒与提前 29 分钟提醒的差别并不大。

但是数据量比较大实时性要求比较高,尤其电商平台每天的所有新建订单 15 分钟内未支付的自动超时,数量级高达百万甚至千万,这时候轮询数据库则不可取。这种场景下,就需要使用延迟队列提供了一种高效的处理大量需要延迟消费消息的解决方案。

三、实现方式

1.JDk延迟队列

DelayQueue是Java并发包java.util.concurrent中提供的一个支持延时获取元素的无界阻塞队列。它的内部实现是基于优先级队列(PriorityQueue)按照元素的过期时间进行排序(所以元素必须是可比较), 只有过期元素才会出队
PriorityQueue 是带有优先级无界阻塞队列, 每次出队都返回优先级最高或者最低的元素。其内部使用的是平衡二叉树实现的,所以可以很容易的获取到优先级最高或者最低的元素(大顶堆、小顶堆)。所以元素必须是可排序的,这样才能通过比较器comparator 比较各个元素。

1.实现注意事项

队列元素需要实现Delayed接口,

getDelay()方法用于设置延迟时间,

compareTo()方法用于对队列中的元素进行排序。入队:
put()、offer(),线程安全。
出队:
poll(),非阻塞方式获取,没有到期的元素直接返回null。
take(),阻塞方式获取,没有到期的元素线程将会等待。

2.优缺点

优点:
JDK自带的,不用引入其他框架或中间件,使用简单方便。
缺点∶
不支持分布式或持久化,重启后会丢失,如果订单并发量非常大,因为DelayQueve是无界的,订单量越大,队列内的对象就越多,可能造成0OM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。

3.代码演示

流程简介

延迟队列浅学_第1张图片

2.redis过期键实现

原理:

  1. Redis使用一种称为“过期事件(expiry event)”的机制来处理键的过期。当一个键的过期时间到达时,Redis会生成一个事件,通知订阅了该事件的客户端。
  2. Spring Data Redis的KeyExpirationEventMessageListener就是一个订阅了Redis过期事件的客户端。它使用Redis的PSUBSCRIBE命令来订阅一个特殊的频道(channel),通常是__keyevent@0__:expired,其中0表示所有数据库,expired表示过期事件。
  3. 当有Redis键过期时,Redis会将事件消息发送到订阅了__keyevent@0__:expired频道的客户端,其中就包括了KeyExpirationEventMessageListener
准备工作

导入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置redis连接

spring:
  redis:
    host: 150.158.34.233
    port: 6379
    password: lxy123456
    lettuce:
      pool:
        max-active: 15
        min-idle: 0
        max-idle: 8
        max-wait: 100
    timeout: 1000

开启redis过期键服务

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Service;

/**
 * Description: Redis超时监听器
 * User: liw
 * Date: 2023-05-20
 */
@Service
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {


    private static Logger log = LoggerFactory.getLogger(RedisKeyExpirationListener.class);

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        //获取过期的key
        String expireKey = message.toString();
        log.debug("失效+key is:"+ expireKey);
        System.out.println(expireKey);
        //这里还可以根据key的自定义前缀来判断执行哪个条件
        //......
    }
}

缺点:

使用Redis的key过期事件来处理延迟任务是一种常见的方法,但它也有一些缺点和限制:

  1. 精度问题: Redis的过期事件是以秒为单位的,这意味着你不能精确地指定毫秒级的延迟。如果你需要更精确的延迟,例如毫秒级的延迟,Redis的过期事件机制可能不够用,因为它无法提供毫秒级的精度。
  2. 不适合大规模任务: 当需要管理大量延迟任务时,Redis的key过期事件可能会导致性能问题。因为Redis是单线程的,当有大量的key过期事件发生时,Redis可能会阻塞其他操作,导致性能下降。
  3. 无法处理任务失败和重试: Redis的过期事件机制不提供自动重试失败的任务的功能。如果一个任务在执行时失败,你需要自己实现重试逻辑。
  4. 无法查看延迟任务队列: 使用Redis的key过期事件,你无法轻松查看当前所有的延迟任务队列,因为Redis不提供直接的命令来列出所有即将过期的key。
  5. 无法修改延迟时间: 一旦你将key设置为过期,你不能轻松地修改它的过期时间。如果需要更改任务的延迟时间,你需要执行复杂的操作,例如将任务重新添加到队列并删除旧的key。
  6. 缺乏监控和管理工具: Redis本身并没有提供用于监控和管理延迟任务的工具。你需要自行开发或使用第三方工具来监视和管理任务。
  7. 可靠性问题: Redis的过期事件机制是基于内存的,如果Redis服务器发生故障或重启,已经设置的过期事件可能会丢失,导致任务无法正常执行。

虽然Redis的过期事件可以用于一些简单的延迟任务场景,但对于更复杂、大规模、高可用性和高可靠性的延迟任务处理,通常需要考虑使用专门的延迟队列或任务调度系统。这些系统提供了更强大的功能,例如精确的延迟控制、重试机制、监控和管理工具,以及高可用性的保证。选择适合你需求的解决方案是根据具体情况而定。

3.使用sorted Set实现延迟队列

1.sorted Set介绍

Zset,即有序集合(Sorted Set),是 Redis 提供的一种复杂数据类型。Zset 是 set 的升级版,它在 set 的基础上增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列。

在 Zset 中,集合元素的添加、删除和查找的时间复杂度都是 O(1)。这得益于 Redis 使用的是一种叫做跳跃列表(skiplist)的数据结构来实现 Zset。

Zset 的主要特性包括:

  1. 唯一性:和 set 类型一样,Zset 中的元素也是唯一的,也就是说,同一个元素在同一个 Zset 中只能出现一次。
  2. 排序:Zset 中的元素是有序的,它们按照 score 的值从小到大排列。如果多个元素有相同的 score,那么它们会按照字典序进行排序。
  3. 自动更新排序:当你修改 Zset 中的元素的 score 值时,元素的位置会自动按新的 score 值进行调整。
ZSet 常用命令
2.1、添加操作

在 Redis 中,ZADD 命令用于向有序集合(Zset)中添加一个或多个成员,或者更新已存在成员的分数。它的基本语法如下:

ZADD key score member [score member ...]
1

其中,key 是有序集合的名称,score 是成员的分数,member 是成员的值。你可以一次添加一个或多个成员。

例如,你可以使用以下命令向名为 myzset 的有序集合中添加一个成员 one,其分数为 1

ZADD myzset 1 one
1

如果你想要一次添加多个成员,可以在命令后面依次列出它们的分数和值,例如:

ZADD myzset 1 one 2 two 3 three
1

这个命令会向 myzset 集合中添加三个成员,它们的分数分别为 123

如果添加的成员在有序集合中已经存在,那么它的分数会被更新为新的值,同时该成员在集合中的位置也会相应地发生变化。

2.2、返回指定成员分数

在 Redis 中,ZSCORE 命令用于返回有序集合(Zset)中,指定成员的分数。它的基本语法如下:

ZSCORE key member
1

其中,key 是有序集合的名称,member 是要查询分数的成员。

例如,你可以使用以下命令查询名为 myzset 的有序集合中,成员 one 的分数:

ZSCORE myzset one
1

如果指定的成员存在于有序集合中,那么命令会返回该成员的分数。如果指定的成员不存在于有序集合中,那么命令会返回 nil

需要注意的是,ZSCORE 命令返回的分数是字符串形式的浮点数。

2.3、返回指定成员排名

在 Redis 中,ZRANK 命令用于返回有序集合(Zset)中指定成员的排名,其中分数值从低到高排序。它的基本语法如下:

ZRANK key member
1

其中,key 是有序集合的名称,member 是要查询排名的成员。

例如,你可以使用以下命令查询名为 myzset 的有序集合中,成员 one 的排名

ZRANK myzset one
1

如果指定的成员存在于有序集合中,那么命令会返回该成员的排名。排名以 0 为底,也就是说,分数最低的成员排名为 0。

如果指定的成员不存在于有序集合中,那么命令会返回 nil

需要注意的是,ZRANK 命令返回的排名是字符串形式的整数。

  1. ZREVRANK key member:返回有序集合中指定成员的索引,分数值从高到低排序。

  2. ZRANGE key start stop [WITHSCORES]:返回有序集中,指定区间内的成员。

  3. ZREVRANGE key start stop [WITHSCORES]:返回有序集中,指定区间内的成员,通过索引,分数值从高到低。

  4. ZREM key member [member …]:移除有序集合中的一个或多个成员。

  5. ZCARD key:获取有序集合的成员数。

  6. ZCOUNT key min max:计算在有序集合中指定区间分数的成员数。

  7. ZINCRBY key increment member:为有序集合中的成员添加增量。

    ZRANGEBYSCORE:返回分值介于min和max的之间的成员.
    p [WITHSCORES]:返回有序集中,指定区间内的成员,通过索引,分数值从高到低。

  8. ZREM key member [member …]:移除有序集合中的一个或多个成员。

  9. ZCARD key:获取有序集合的成员数。

  10. ZCOUNT key min max:计算在有序集合中指定区间分数的成员数。

  11. ZINCRBY key increment member:为有序集合中的成员添加增量。

    ZRANGEBYSCORE:返回分值介于min和max的之间的成员.

你可能感兴趣的:(java,java)