《黑马头条》day05 踩坑以及习后分享

今天并没有踩什么坑,那就主要讲一下延迟任务技术选型的方案对比,再顺带复习一下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的一些注解,感觉这个帖子写的挺好

redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)-阿里云开发者社区

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 高可用架构

  1. 主从复制(Master-Slave)
    读写分离,提高读性能

  2. 哨兵模式(Sentinel)
    自动故障转移,监控主从节点

  3. Redis Cluster
    分布式数据分片,支持大数据量

6. 典型应用场景

  • 缓存(热点数据、页面片段、会话)

  • 分布式锁SET key value NX PX timeout

  • 消息队列(List / Stream)

  • 计数器(点赞、浏览量)

  • 排行榜(ZSet)

  • 延时任务(ZSet + 时间戳 / Stream)

7. 性能优化要点

  • 减少大 key(一次操作过多数据会阻塞)

  • 控制连接数(使用连接池)

  • 批量操作MGET MSET Pipeline)

  • 合理过期时间(避免内存膨胀)

  • 监控INFOslowlogMONITOR

8. 常用调试命令

  • PING:测试连接

  • KEYS pattern:按模式查找(生产慎用)

  • SCAN cursor:安全遍历 key

  • TYPE key:查看数据类型

  • TTL key:查看剩余过期时间

  • MEMORY USAGE key:查看内存占用

具体使用的话我看项目里是抽了很多StringRedisTemplate中的方法

### 黑马头条常见问题及解决方案 #### 关于移动端接口调用中的跨域问题 在黑马头条项目的开发过程中,移动端接口调用可能会遇到跨域问题。通常情况下,前端通过 `axios` 或其他 HTTP 请求库发起请求时会触发浏览器的安全策略限制[^2]。为了处理这一问题,可以采用以下方式: - **后端配置 CORS**:确保服务器端支持跨域资源共享(CORS),允许特定域名访问资源。 - **代理设置**:如果无法修改后端配置,则可以在本地开发环境中使用 Webpack 的代理功能来转发请求。 ```javascript // 配置Webpack devServer代理 devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: {'^/api': ''} } } } ``` 上述代码片段展示了如何利用 Webpack 的 `proxy` 功能实现 API 请求的代理。 --- #### 登录模块可能存在的问题 登录模块作为项目的核心部分之一,在实际开发中容易出现一些典型错误。例如,当用户提交表单数据到服务端时,可能出现如下情况: - **参数校验失败**:如果传递给后端的数据不符合预期格式或者缺少必要字段,可能导致返回错误响应码或提示信息不清晰。 解决方案是在发送之前先验证输入合法性并给出友好反馈;同时也要注意捕获异常以便及时调整逻辑流程。 ```javascript export function validateLoginData(data) { const errors = {}; if (!data.mobile || !/^1\d{10}$/.test(data.mobile)) { errors.mobile = "手机号码格式有误"; } if (!data.code || data.code.length !== 6) { errors.code = "验证码长度应为六位数"; } return Object.keys(errors).length === 0 ? null : errors; } ``` 此函数用于检查用户的手机号和短信验证码是否满足基本条件。 --- #### 数据存储与查询优化 对于涉及数据库操作的部分,尤其是频繁读写的场景下需要注意性能瓶颈以及潜在风险点。比如 Innodb 存储引擎下的事务管理不当会造成死锁现象等问题[^1]。因此推荐遵循最佳实践原则如合理设计索引来加速检索过程减少不必要的锁定时间窗口大小等等措施从而提升整体效率水平。 另外还需关注SQL注入防护机制以免遭受恶意攻击威胁系统安全稳定运行环境。 --- #### 并发控制方面的考量因素 随着应用规模扩大并发量增加则更需重视同步互斥等方面的设计思路防止因竞争条件引起数据丢失更新覆盖等情况发生影响用户体验满意度下降甚至引发业务损失后果严重。 可以通过引入乐观锁悲观锁相结合的方式来应对不同类型的业务需求达到平衡效果既保证一致性又能兼顾吞吐率表现良好状态。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值