今天并没有踩什么坑,那就主要讲一下延迟任务技术选型的方案对比,再顺带复习一下redis吧
延迟任务
概述
什么是延迟任务
-
定时任务:有固定周期的,有明确的触发时间
-
延迟队列:没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事件,任务可以立即执行,也可以延迟
相对于苍穹外卖中的springtask实现定时任务,也就每几分钟或是每天扫描一下订单状态,这次头条项目中是实现文章的定时发布的延迟任务
技术对比
DelayQueue
JDK自带DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素
DelayQueue属于排序队列,它的特殊之处在于队列的元素必须实现Delayed接口,该接口需要实现compareTo和getDelay方法
getDelay方法:获取元素在队列中的剩余时间,只有当剩余时间为0时元素才可以出队列。
compareTo方法:用于排序,确定元素出队列的顺序。
简要实现思路:
class DelayedTask implements Delayed {
private long startTime;
public long getDelay(TimeUnit unit) { return unit.convert(startTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }
public int compareTo(Delayed o) { return Long.compare(this.startTime, ((DelayedTask) o).startTime); }
}
DelayQueue<DelayedTask> queue = new DelayQueue<>();
queue.put(new DelayedTask(System.currentTimeMillis() + 5000));
queue.take(); // 会阻塞直到任务到期
RabbitMQ
-
TTL:Time To Live (消息存活时间)
-
死信队列:Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以重新发送另一个交换机(死信交换机)
-
简要实现思路
MessageProperties props = new MessageProperties();
props.setDelay(delayMs);
rabbitTemplate.convertAndSend("delayed.exchange", "routing.key", message, m -> {
m.getMessageProperties().setDelay(delayMs);
return m;
});
redis
zset数据类型的去重有序(分数排序)特点进行延迟。例如:时间戳作为score进行排序
简要实现思路:
// 添加任务
redisTemplate.opsForZSet().add("delayQueue", taskId, System.currentTimeMillis() + delayMs);
// 消费任务(定时轮询)
Set<String> tasks = redisTemplate.opsForZSet().rangeByScore("delayQueue", 0, System.currentTimeMillis());
该项目中采用的是redis实现,也就是在文章提交审核的时候添加任务,然后通过定时轮询来消费任务,
本项目的延迟精度是秒级(每秒查询一次)
下面给出三种技术选型的
综合对比
对比维度 | Redis (ZSet/Redisson) | RabbitMQ (延迟插件/DLX) | Java DelayQueue |
---|---|---|---|
延迟精度 | 秒级或亚秒级(取决于轮询间隔) | 毫秒级,触发精度高 | 毫秒级 |
可靠性 | 中等:依赖 Redis 持久化(AOF/RDB),宕机可能丢任务 | 高:开启持久化后几乎不丢 | 低:进程内内存队列,JVM 重启就没了 |
性能 | 高(内存操作,O(logN) 查询) | 中(磁盘 I/O + 网络传输) | 高(纯内存,无网络 I/O) |
任务量 | 支持大规模任务(取决于 Redis 内存) | 支持大规模任务(取决于磁盘与 RabbitMQ 配置) | 受 JVM 内存限制,适合小规模任务 |
分布式支持 | 原生支持(Redis 集群) | 原生支持(MQ 集群) | 无(需要额外分布式协调) |
部署复杂度 | 低(已有 Redis 时) | 中高(RabbitMQ 集群 + 插件) | 低(无需额外组件) |
使用复杂度 | 中(需自己实现轮询/调度) | 低(消息自动触发,消费端只关心业务) | 低(直接使用 BlockingQueue API) |
适用场景 | 轻量延迟任务、秒杀库存释放、缓存过期处理 | 高可靠延迟任务、订单超时取消、精确延迟推送 | 单机定时任务、测试环境延迟模拟 |
典型缺点 | 延迟精度受限、需要额外调度线程 | 部署复杂、插件依赖 | 无法跨进程、任务丢失风险高 |
感觉现在还是用rabbitmq的比较多,考虑后续能不能把这里进行一个升级
Redis基础知识复习
因为我只有才苍穹外卖里面才接触到了Redis,然后也只是用了一点springcache的知识,Redis这里懂得不多,就让GPT生成了个大概的,混个眼熟吧,更详细的还请自行查找网络资源
先附上文档吧
redis/redis - Redis 中文文档
关于在java中的使用我目前就只是了解阶段,主要还是要用项目中封装的方法,苍穹外卖更是只用了Springcache的一些注解,感觉这个帖子写的挺好
1.基础概念
-
类型:内存型键值数据库(Key-Value Store)
-
特点:
-
数据存储在内存中,读写速度极快
-
支持多种数据结构(不仅是字符串)
-
单线程事件驱动(命令执行是原子性的)
-
可持久化(RDB、AOF)
-
支持主从复制、哨兵、集群
-
2.常用数据结构
类型 | 存储内容 | 常用命令 | 应用场景 |
---|---|---|---|
String | 二进制安全的字符串 | SET GET INCR DECR MSET | 缓存、计数器、分布式锁 |
List | 有序列表(双向链表) | LPUSH RPUSH LPOP RPOP LRANGE | 消息队列、时间轴 |
Hash | 键值对集合(类似 Map) | HSET HGET HGETALL HDEL | 存储对象信息 |
Set | 无序且不重复的集合 | SADD SMEMBERS SISMEMBER SREM | 标签、唯一值集合 |
Sorted Set (ZSet) | 有序集合(分数排序) | ZADD ZRANGE ZREVRANGE ZREM | 排行榜、延时队列 |
本项目目前所采用的就是zset,将文章发布时间作为分数进行排序实现,当下任务用list
后面的我基本就一点没见过了
3.持久化机制
RDB(Redis Database)
-
定期将内存快照保存到磁盘
-
优点:体积小,恢复速度快
-
缺点:可能丢失最后一次快照之后的数据
AOF(Append Only File)
-
记录每一次写命令
-
优点:更安全,可配置
appendfsync
策略(always / everysec / no) -
缺点:文件体积大,恢复慢
实际生产常用 RDB + AOF 混合持久化(Redis 4.0+)
4.过期与淘汰策略
4.1过期策略
-
定时删除:到期立即删除(CPU 占用高)
-
惰性删除:访问时发现过期再删除(可能占用内存)
-
定期删除:周期性抽样检查过期键
4.2内存淘汰策略(maxmemory-policy
)
-
noeviction
:不淘汰,直接报错 -
allkeys-lru
/volatile-lru
:最近最少使用淘汰 -
allkeys-random
/volatile-random
:随机淘汰 -
volatile-ttl
:优先淘汰剩余时间短的
5.Redis 高可用架构
-
主从复制(Master-Slave)
读写分离,提高读性能 -
哨兵模式(Sentinel)
自动故障转移,监控主从节点 -
Redis Cluster
分布式数据分片,支持大数据量
6. 典型应用场景
-
缓存(热点数据、页面片段、会话)
-
分布式锁(
SET key value NX PX timeout
) -
消息队列(List / Stream)
-
计数器(点赞、浏览量)
-
排行榜(ZSet)
-
延时任务(ZSet + 时间戳 / Stream)
7. 性能优化要点
-
减少大 key(一次操作过多数据会阻塞)
-
控制连接数(使用连接池)
-
批量操作(
MGET
MSET
Pipeline) -
合理过期时间(避免内存膨胀)
-
监控(
INFO
、slowlog
、MONITOR
)
8. 常用调试命令
-
PING
:测试连接 -
KEYS pattern
:按模式查找(生产慎用) -
SCAN cursor
:安全遍历 key -
TYPE key
:查看数据类型 -
TTL key
:查看剩余过期时间 -
MEMORY USAGE key
:查看内存占用
具体使用的话我看项目里是抽了很多StringRedisTemplate中的方法