定时任务是我们在开发系统时非常常见的一种功能,比如数据库的定时备份,日志系统的定时备份等都是我们常见的定时任务需求。今天我们就用一个常见的定时提醒大家喝水的场景来聊一聊使用
SpringTask、Quartz实现定时任务的功能。
定时任务
利用SpringTask、Quartz实现定时提醒发送,不过小程序的消息模板推送已经不支持随意给用户发消息了,需要用户自行去触发。
延迟队列
用户手动触发一次喝水动作,这时候我们可以使用延迟队列来实现,推荐使用开源的第三方工具包redisson,同时保证redis支持lua特性。
配置application.properties:
# 堆代码 duidaima.com
# redisson lock
redisson.address=redis://127.0.0.1:6379
redisson.password=123456
参数配置类RedissonProperties :
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
private int timeout = 3000;
private String address;
private String password;
private int connectionPoolSize = 64;
private int connectionMinimumIdleSize=10;
private int slaveConnectionPoolSize = 250;
private int masterConnectionPoolSize = 250;
private String[] sentinelAddresses;
private String masterName;
}
自动装配 RedissonAutoConfiguration:
@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
@Autowired
private RedissonProperties redssionProperties;
/**
* 单机模式自动装配
* @return
*/
@Bean
RedissonClient redissonSingle() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(redssionProperties.getAddress)
.setTimeout(redssionProperties.getTimeout())
.setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
serverConfig.setPassword(redssionProperties.getPassword());
}
return Redisson.create(config);
}
/**
* 装配locker类,并将实例注入到RedissLockUtil中
* @return
*/
@Bean
RedissLockUtil redissLockUtil(RedissonClient redissonClient) {
RedissLockUtil redissLockUtil = new RedissLockUtil();
redissLockUtil.setRedissonClient(redissonClient);
return redissLockUtil;
}
}
工具类RedissLockUtil:
/**
* redis分布式锁帮助类
* 堆代码 duidaima.com
*/
public class RedissLockUtil {
private static RedissonClient redissonClient;
public void setRedissonClient(RedissonClient locker) {
redissonClient = locker;
}
/**
* 初始红包数量
* @param key
* @param count
*/
public void initCount(String key,int count) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
mapCache.putIfAbsent(key,count,3,TimeUnit.DAYS);
}
/**
* 递增
* @param key
* @param delta 要增加几(大于0)
* @return
*/
public int incr(String key, int delta) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return mapCache.addAndGet(key, 1);//加1并获取计算后的值
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public int decr(String key, int delta) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill");
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return mapCache.addAndGet(key, -delta);//加1并获取计算后的值
}
public static RedissonClient getRedissonClient() {
return redissonClient;
}
}
入队列,伪代码:
RedissonClient redissonClient = RedissLockUtil.getRedissonClient();
/**
* 目标队列
*/
RBlockingQueue<RemindDelay> blockingRemindQueue
= redissonClient.getBlockingQueue("remindDelayQueue");
/**
* 定时任务将到期的元素转移到目标队列
*/
RDelayedQueue<RemindDelay> delayedRemindQueue
= redissonClient.getDelayedQueue(blockingRemindQueue);
/**
* 延时信息入队列
*/
delayedRemindQueue.offer(new RemindDelay(remind.getId()), sec, TimeUnit.SECONDS);
队列消息实体标识:
public class RemindDelay implements Serializable {
/**
* 主键ID
*/
private long id;
/**
* 创建时间戳
*/
private long timestamp;
public RemindDelay() {
}
public RemindDelay(long id) {
this.id = id;
this.timestamp = System.currentTimeMillis();
}
public long getId() {
return id;
}
public long getTimestamp() {
return timestamp;
}
}
消费提醒队列:
/**
* 消费提醒队列
*/
@Component
public class RemindRunner implements ApplicationRunner {
private final static Logger LOGGER = LoggerFactory.getLogger(RemindRunner.class);
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
private static ThreadPoolExecutor executor =
new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100));
@Autowired
private RemindService remindService;
@Autowired
private WxMaService wxService;
@Override
public void run(ApplicationArguments args) throws Exception {
/**
* 出队列
*/
RedissonClient redissonClient = RedissLockUtil.getRedissonClient();
RBlockingQueue<RemindDelay> delayedRemindQueue
= redissonClient.getBlockingQueue("remindDelayQueue");
new Thread(() -> {
LOGGER.info("提醒队列启动成功");
while (true){
try {
RemindDelay delay = delayedRemindQueue.take();
push(delay);
LOGGER.info("提醒ID:{}",delay.getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public void push(RemindDelay delay){
/**
* 推送
*/
Runnable task = () -> {
//发送消息通知逻辑
};
executor.execute(task);
}
}
适用场景:
淘宝订单到期,下单成功后60s之后给用户发送短信通知,限时支付、缓存系统、红包过期、开奖提醒等等。
小结
以上方案单机模式,生产环境可根据实际业务需求选择使用。