• JAVA有哪些框架工具可以实现定时任务功能?
  • 发布于 2个月前
  • 432 热度
    0 评论
定时任务是我们在开发系统时非常常见的一种功能,比如数据库的定时备份,日志系统的定时备份等都是我们常见的定时任务需求。今天我们就用一个常见的定时提醒大家喝水的场景来聊一聊使用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之后给用户发送短信通知,限时支付、缓存系统、红包过期、开奖提醒等等。

小结
以上方案单机模式,生产环境可根据实际业务需求选择使用。
用户评论