Lilishop 技术栈
官方公众号 & 开源不易,如有帮助请点Star
介绍
官网:https://pickmall.cn
Lilishop 是一款Java开发,基于SpringBoot研发的B2B2C多用户商城,前端使用 Vue、uniapp开发 系统全端全部代码开源
本系统用于教大家如何运用系统中的每一个细节,如:支付、第三方登录、日志收集、分布式事务、秒杀场景等各个场景学习方案
git地址 https://gitee.com/beijing_hongye_huicheng/lilishop-spring-learning
本文学习 分布式延时任务
延时任务介绍
即指定一个时间,执行提前约定好的任务,例如:定时取消订单,定时上下架商品,定时开启活动等。
延时任务与定时任务的区别
延时任务适用于个性化的业务场景,比如某订单自动取消,某活动自动开启,某商品自动上下架子。还有一个就是较为精确的,需要实时的事情。
而定时任务适用于全平台的业务,比如计算商品评分统一结算,分销中的可提现金额批量结算,平台统计/店铺统计数据生成等。总的来说就是定时扫描,每天,每小时,每分钟,每个月,不管怎么样都要执行。比如定时上下架,用定时任务也可以,但是要实现精确的任务调度,创建一个每秒任务,是不太理智的。
两个场景需要互补,具体应用什么场景,可以再自己斟酌斟酌。
思路介绍
- 项目启动时启用一个线程,线程用于间隔一定时间去查询redis的待执行任务。其任务id为对象json格式化之后的字符串,值为要执行的时间。
- 查询到执行的任务时,将其从redis的信息中进行删除。(删除成功才执行延时任务,否则不执行,这样可以避免分布式系统延时任务多次执行。)
- 删除redis中的记录之后,启用子线程执行任务。将执行id,也就是json的字符串翻转回要执行的任务信息,这样可以得到用什么执行器去执行任务,参数有哪些。
- 执行延时任务
实际使用
实际场景中,还会设计延时任务修改,删除等,这些场景建议在执行任务创建时,redis标记要执行的任务,如果删除或者修改任务时,修改redis中的标识即可,当然也可以在业务逻辑中做补充的条件判定,都可以。
另外具体执行任务建议使用mq去实现,相当于在执行任务时,线程只是发布一个mq,交给消费者去消费具体的事情。
代码中的进程扫描5秒,也就代表一个延时任务最多延迟5秒去执行,实战场景中可以调整至1秒,或者更低,但是不太建议。另外redis的性能杠杠的,不用太担心redis的连接数导致性能问题。
使用步骤
-
启用redis,可以本地启动,也可以用ELK中docker-compose启动。
-
启动springboot应用。
-
请求springboot 应用 http://127.0.0.1:8080
-
查看控制台输出内容
2021-06-09 12:41:33.168 INFO 40730 — [nio-8888-exec-1] l.t.p.d.AbstractDelayQueueMachineFactory : 增加延时任务, 缓存key test_delay, 等待时间 10
2021-06-09 12:41:33.168 INFO 40730 — [nio-8888-exec-1] c.l.t.p.i.impl.RedisTimerTrigger : 定时执行在【2021-06-09 12:41:43】,消费【test params】
2021-06-09 12:41:44.399 INFO 40730 — [ Thread-5] l.t.p.d.AbstractDelayQueueMachineFactory : 延时任务开始执行任务:[{“score”:1.623213703E9,“value”:"{“triggerTime”:1623213703,“triggerExecutor”:“testTimeTriggerExecutor”,“param”:“test params”}"}]
2021-06-09 12:41:44.403 INFO 40730 — [pool-2-thread-2] c.l.t.p.i.e.TestTimeTriggerExecutor : 执行器具执行任务test params
关键类介绍
缓存操作类 用于延时任务的核型逻辑,间隔查询需要执行的延时任务,考的就是redis的Sorted Set属性来试下排序,执行任务。
/**
* 向Zset里添加成员
*
* @param key key值
* @param score 分数,通常用于排序
* @param value 值
* @return 增加状态
*/
@Override
public boolean zAdd(String key, long score, String value) {
Boolean result = redisTemplate.opsForZSet().add(key, value, score);
return result;
}
/**
* 获取 某key 下 某一分值区间的队列
*
* @param key 缓存key
* @param from 开始时间
* @param to 结束时间
* @return 数据
*/
@Override
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScore(String key, int from, long to) {
Set<ZSetOperations.TypedTuple<Object>> set = redisTemplate.opsForZSet().rangeByScoreWithScores(key, from, to);
return set;
}
/**
* 移除 Zset队列值
*
* @param key key值
* @param value 删除的集合
* @return 删除数量
*/
@Override
public Long zRemove(String key, String... value) {
return redisTemplate.opsForZSet().remove(key, value);
}
延时队列 抽象类,具体延时队列需继承
package cn.lili.trigger.plugin.delay;
import cn.hutool.json.JSONUtil;
import cn.lili.trigger.plugin.cache.Cache;
import cn.lili.trigger.plugin.util.ThreadPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.util