当前位置: 首页 > 后端技术 > Java

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

时间:2023-04-01 17:38:49 Java

单点定时任务JDK原生从JDK1.5开始提供ScheduledExecutorService代替TimerTask执行定时任务,可靠性好。公共类somescheduledexecutorService{publicstaticvoidmain(string[]args){//创建创建,共10个线程//执行任务:1秒后开始执行,每30秒执行一次scheduledExecutorService.scheduleAtFixedRate(()->{System.out.println("executiontask:"+newDate());},10,30,TimeUnit.SECONDS);}}SpringTaskSpringFramework自带的定时任务,提供了一个cron表达式,用于实现丰富的定时任务配置。新手推荐使用https://cron.qqe2.com/来匹配你的cron表达式。@Configuration@EnableSchedulingpublicclassSomeJob{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(SomeJob.class);/***每分钟执行一次(例如:18:01:00、18:02:00)*secondsminuteshoursdaysMonthWeekYear*/@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(JOB0,KEY,nowSecond);for(Stringtask:strings){//开始消费任务LOGGER.info("executiontask:{}",task);}}//删除已经执行的任务stringRedisTemplate.opsForZSet().remove(JOB_KEY,0,nowSecond);}}适用场景如下:下单15分钟后,如果用户没有付款,系统需要自动取消订单红包24小时没有被查看,需要延迟退款服务;指定某个活动在某个时间段内生效&过期;优点是:省略了MySQL的查询操作,改用性能更高的Redis;无因停机等原因,错过要执行的任务;键空间通知方式我们可以通过Redis键空间通知来实现定时任务。它的实现思路是为所有的定时任务设置一个过期时间。过期后,我们通过订阅过期消息,就可以感知到需要执行定时任务了。这时候我们就可以执行定时任务了。默认情况下,Redis是不开启keyspace通知的,我们需要通过命令configsetnotify-keyspace-eventsEx手动开启。启动后指定时间任务的代码如下:自定义监听器/***自定义监听器。*/publicclassKeyExpiredListenerextendsKeyExpirationEventMessageListener{publicKeyExpiredListener(RedisMessageListenerContainerlistenerContainer){super(listenerContainer);}@OverridepublicvoidonMessage(Messagemessage,byte[]pattern){//channelStringchannel=newString(message.getChannel(),StandardCharsets.UTF_8);//过期键Stringkey=newString(message.getBody(),FStandard_8);UT/8)。/todo你的处理}}设置这个监听器/***描述:定时任务是通过订阅Redis过期通知实现的。
**@authormxy*/@ConfigurationpublicclassRedisExJob{@AutowiredprivateRedisConnectionFactoryredisConnectionFactory;@BeanpublicRedisMessageListenerContainerredisMessageListenerContainer(){RedisMessageListenerContainerredisMessageListenerContainer=newRedisMessageListenerContainer();redisMessageListenerContainer.setConnectionFactory(redis连接工厂);返回redisMessageListenerContainer;}@BeanpublicKeyExpiredListenerkeyExpiredListener(){返回newKeyExpiredListener(this.redisMessageListenerContainer());}}Spring会监听符以下格式的Redis消息privatestaticfinalTopicTOPIC_ALL_KEYEVENTS=newPatternTopic("__keyevent@*");基于redis的定时任务应用场景有限,实现起来比较简单,但是对幂等函数有很大的要求。从使用场景来看,应该叫延迟任务。场景示例:下单15分钟后,如果用户没有付款,系统需要自动取消订单。红包24小时未验收,需延迟退款服务;优缺点是:被动触发,服务资源消耗少;RedisPub/Sub不可靠,没有ACK机制等,但一般都可以容忍;keyspacenotification函数会消耗一些CPU。在分布式定时任务组件或中间件中引入分布式定时任务,将定时任务作为单独的服务来处理,抑制了重复消费,独立的服务也有利于扩展和维护。Quartz依赖于MySQL。使用起来比较简单,可以部署在多个节点上。它通过竞争数据库锁确保只有一个节点执行任务。没有图形化的管理页面,使用起来比较麻烦。elastic-job-lite依赖于Zookeeper,通过zookeeper的注册和发现,可以动态添加服务器。多种作业模式、故障转移、运行状态收集、多线程处理、数据幂等、容错处理、支持spring命名空间、图形化管理页面、LTS依赖Zookeeper、集群部署、可动态添加服务器。您可以手动添加计划任务、启动和暂停任务。业务日志记录器SPI扩展支持故障转移节点监控多样化任务执行结果支持FailStore容错动态扩展对spring比较友好具有监控管理图形化界面任务,支持横向扩展。您可以手动添加计划任务、启动和暂停任务。弹性扩展Shard广播故障转移滚动实时日志GLUE(支持在线代码编辑,免发布)任务进度监控任务依赖数据加密Email告警运行报告优雅宕机国际化(中文友好)总结微服务下,推荐使用xxl-jobthis一类组件服务合理有效地管理定时任务。单点定时任务有其局限性,适用于规模较小、对未来扩展要求不高的业务。相对来说,基于springtask的定时任务最简单、速度最快,而xxl-job的难点主要体现在集成和调试上。不管是哪种定时任务,都需要保证任务不会因为集群部署而被多次执行。任务出现异常,有效处理。任务处理太慢,导致本应在预期时间点执行的任务大量积压。中间件可以解耦服务,但增加了复杂度。