当前位置: 首页 > 科技观察

几种主流的分布式定时任务,你知道哪些?

时间:2023-03-20 12:10:29 科技观察

单点定时任务JDK原生从JDK1.5开始提供ScheduledExecutorService代替TimerTask执行定时任务,可靠性好。publicclassSomeScheduledExecutorService{publicstaticvoidmain(String[]args){//创建一个总共有10个线程的任务队列ScheduledExecutorServicescheduledExecutorService=Executors.newScheduledThreadPool(10);//执行任务:1秒后开始,每30秒执行一次秒);}}SpringTaskSpringFramework自带的定时任务,提供了一个cron表达式,用于实现丰富的定时任务配置。新手推荐使用https://cron.qqe2.com/来匹配你的cron表达式。@Configuration@EnableSchedulingpublicclassSomeJob{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(SomeJob.class);/***每分钟执行一次(eg:18:01:00,18:02:00)*第二分钟小时日月周年*/@Scheduled(cron="00/1***?*")publicvoidsomeTask(){//...}}单点定时服务在当前微服务环境下应用的场景越来越有限,我们来尝试一下分布式定时任务。基于Redis的实现与前面两种方式相比,这种基于Redis的实现可以通过多点增加定时任务,多点消费。但要做好防止双重消费的准备。通过ZSet将定时任务存储在ZSet集合中,并将过期时间存储在ZSet的Score字段中,然后通过循环判断当前时间内是否有需要执行的定时任务,如果有则执行他们。具体实现代码如下:/***描述:基于Redis的ZSet定时任务。
**@authormxy*/@Configuration@EnableSchedulingpublicclassRedisJob{publicstaticfinalStringJOB_KEY="redis.job.task";privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(RedisJob.class);@AutowiredprivateStringRedisTemplatestringRedisTemplate;/***添加任务。**@paramtask*/publicvoidaddTask(Stringtask,Instantinstant){stringRedisTemplate.opsForZSet().add(JOB_KEY,task,instant.getEpochSecond());}/***定时任务队列消费*每分钟消费一次(可以缩短间隔到1s)*/@Scheduled(cron="00/1***?*")publicvoiddoDelayQueue(){longnowSecond=Instant.now().getEpochSecond();//查询当前时间的所有任务Setstrings=stringRedisTemplate.opsForZSet().range(JOB_KEY,0,nowSecond);for(Stringtask:strings){//开始消费任务LOGGER.info("executetask:{}",task);}//删除已执行的任务服务字符串RedisTemplate.opsForZSet().remove(JOB_KEY,0,nowSecond);}}适用场景如下:如果用户下单15分钟后仍未付款,系统需要自动取消订单。红包24小时未验收,需延迟办理退款业务;指定活动在特定时间段内生效和过期;优点是:省略了MySQL的查询操作,改用性能更高的Redis;不会因为宕机等原因错过Task;键空间通知方式我们可以通过Redis键空间通知来实现定时任务。它的实现思路是为所有的定时任务设置一个过期时间。过期后,我们可以通过订阅过期消息来感知定时需要执行的任务。这时候我们就可以执行定时任务了。默认情况下,Redis是不开启keyspace通知的,我们需要通过命令configsetnotify-keyspace-eventsEx手动开启。打开后的定时任务代码如下:CustomlistenerSetthelistener/***Customlistener。*/publicclassKeyExpiredListenerextendsKeyExpirationEventMessageListener{publicKeyExpiredListener(RedisMessageListenerContainerlistenerContainer){super(listenerContainer);}@OverridepublicvoidonMessage(Messagemessage,byte[]pattern){//channelStringchannel=newString(message.getChannel(),StandardCharsets.UTF_8);//过期键Stringkey=newString(message.getBody(),StandardCharsets.UTF_8);//todoyourprocessing}}Setthislistener/***描述:通过订阅Redis过期通知实现定时任务。
**@authormxy*/@ConfigurationpublicclassRedisExJob{@AutowiredprivateRedisConnectionFactoryredisConnectionFactory;@BeanpublicRedisMessageListenerContainerredisMessageListenerContainer(){RedisMessageListenerContainerredisMessageListenerContainer=newRedisMessageListenerContainer();redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);返回redisMessageListenerContainer;}@BeanpublicKeyExpiredListenerkeyExpiredListener(){返回新的KeyExpiredListener(this.redisMessageListenerContainer());}}Spring会监听符合如下格式的Redis消息privatestaticfinalTopicTOPIC_ALL_KEYEVENT__keySevent=new*");基于Redis的定时任务可以应用的场景有限,但是实现起来比较简单,但是对对接有很大的要求功能幂等,从使用场景来看,应该叫延时任务,场景示例:下单后15分钟,如果用户不付款,系统需要自动取消订单,红包还没有到24小时查询,需要延迟退款服务;优点和缺点是:被动触发,服务资源消耗少;RedisPub/Sub不可靠,没有ACK机制等,但一般可以被容忍;keyspacenotification功能会消耗一些CPU,分布式定时任务引入分布式定时任务组件或中间件,将定时任务作为独立的te服务,抑制重复消费,独立服务也有利于扩展和维护。Quartz依赖于MySQL。使用起来比较简单,可以部署在多个节点上。它通过竞争数据库锁确保只有一个节点执行任务。没有图形化的管理页面,使用起来比较麻烦。elastic-job-lite依赖于Zookeeper,通过zookeeper的注册和发现,可以动态添加服务器。多种作业模式、故障转移、运行状态收集、多线程数据处理、幂等性、容错处理、支持spring命名空间、图形化管理页面、LTS依赖Zookeeper、集群部署、可动态添加服务器。您可以手动添加计划任务、启动和暂停任务。业务日志记录器SPI扩展支持故障转移节点监控多样化任务执行结果支持FailStore容错动态扩展对spring比较友好具有监控管理图形化界面任务,支持横向扩展。您可以手动添加计划任务、启动和暂停任务。弹性扩展Shard广播故障转移滚动实时日志GLUE(支持在线代码编辑,免发布)任务进度监控任务依赖数据加密Email告警运行报告优雅宕机国际化(中文友好)总结微服务下,推荐使用xxl-jobthis一类组件服务合理有效地管理定时任务。单点定时任务有其局限性,适用于规模较小、对未来扩展要求不高的业务。相对来说,基于springtask的定时任务最简单、速度最快,而xxl-job的难点主要体现在集成和调试上。不管是哪种定时任务,都需要保证:任务不会因为集群部署而被多次执行。任务异常得到有效处理。任务处理太慢,导致大量积压。任务应该在预期的时间点执行。中间件可以解耦服务,但增加了复杂度