使用Nacos轻松实现分布式单点定时任务方式

 更新时间:2026年06月12日 09:19:21   作者:xianghan收藏册  
这段文章主要介绍了利用Nacos注册中心实现分布式定时任务的方案,通过服务负载均衡确保仅一个实例执行定时任务,同时提供了实现细节和运行机制,适用于使用Dubbo和SpringBoot的项目

项目中需要处理定时任务,我们的应用会发布到多台服务器上运行。为了不会并发的处理导致脏数据,通常我们会引入Elastic-Job或者xxl-Job等分布式调度系统来处理。但是这样需要搭建新系统,如果只是简单实现分布式定时任务,我是这样思考实践的。

项目的分布式组件是Nacos+Dubbo。项目启动后会把Dubbo的provider都注册到Nacos中,正好我们也可以注册自己的定时任务服务到Nacos中。

都注册之后就带来了每个task都会运行的窘境,接下来我们可以通过注册中心负载均衡的思维让每次只有一个服务实例会生效,在该服务下线后其他服务实例的定时任务生效。

1. 初始化Nacos命名服务

Maven依赖

    <dependency>
      <groupId>com.alibaba.nacos</groupId>
      <artifactId>nacos-client</artifactId>
      <version>${nacos.version}</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dubbo-registry-nacos</artifactId>
      <version>${dubbo.version}</version>
    </dependency>

定义分布式任务服务

  1. 获取项目配置
  2. 注册服务到Nacos注册中心
@Component
@Slf4j
public class DistributedTask implements ApplicationContextAware {

  	/**
  	* 应用运行端口
  	*/
    @Value("${dubbo.protocol.port}")
    private Integer serverPort;

    /**
  	* nacos注册中心地址
  	*/
    @Value("${nacos.server-address}")
    private String serverAddress;

    /**
  	* nacos注册中心端口
  	*/
    @Value("${nacos.port}")
    private String nacosPort;

    /**
  	* 分布式task服务名
  	*/
    private static final String SERVICENAME="distributedTask";

    /**
  	* Nacos命名服务,此处为静态对象
  	*/
    public static NamingService naming;

    /**
    * 应用程序上下文,随容器启动
    */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        try {
            if (naming==null) {
                //初始化Nacos命名服务
                naming = NamingFactory.createNamingService(serverAddress+":"+nacosPort);
                //设置服务实例
                Instance instance=new Instance();
                //获取IP,NetUtils是Dubbo源码里获取IP的
                instance.setIp(NetUtils.getLocalHost());
                instance.setPort(serverPort);
                //负载核心思想,记录当前运行时间戳,写入到服务元数据中
                instance.setMetadata(MapUtil.of("timestamp",System.currentTimeMillis()+""));
                //注册实例
                naming.registerInstance(DistributedTask.SERVICENAME,instance);
            }
        } catch (NacosException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
}

接下来我们看看具体定时任务怎么利用Nacos服务的呢?

2. 定义定时任务

定时任务基于SpingBoot的Schedule实现

运行规则

  • 没有运行过则判断是否在当前节点运行
  • 已经运行过则直接运行
@Configuration
@Slf4j
//开启定时任务,通常写在项目启动类上
@EnableScheduling
public class AutoSubmitTask {

    @Autowired
    private DistributedTask distributedTask;

    /**
    * Dubbo远端服务,真实业务逻辑运行服务
    */
    @DubboReference(group = "nx")
    private ILogicService iLogicService;

    //标记当前是否正在运行
    private boolean currentRun=false;

    //调度规则
    @Scheduled(fixedRate = 1000)
    private void configureTasks() {
        //判断当前是否已经在本机运行job
        if(currentRun){
            //有则运行
            run();
        }else{
            //没有则判断是否需要运行,具体实现看集群容错算法章节
            if (distributedTask.isRunInCurrent()) {
                currentRun=true;
                run();
            }
        }
    }

    private void run(){
        log.info("执行PaperSubmitTask定时任务: " + LocalDateTime.now());
        iLogicService.autoSubmit();
    }
}

3. 集群实例选择算法

算法对于应用中多个定时任务都是通用的,所以在DistributedTask实现获取集群实例的方法。

public class DistributedTask implements ApplicationContextAware {
    /**
    * 是否在当前应用中运行
    */
		public boolean isRunInCurrent(){
        try {
            //通过简单的算法得出应该运行的实例
            Instance instance = findInstance();
            //当前环境对于应该运行的实例
            if (instance!=null && instance.getIp().equals(NetUtils.getLocalHost()) && instance.getPort()==serverPort) {
                return true;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return false;
    }
    
    /**
    * 获取定时任务所在实例
    */
    private Instance findInstance(){
        Instance instance=null;
        try {
            //通过api获取定时任务服务的所有运行实例
            List<Instance> allInstances = naming.getAllInstances(SERVICENAME);
            if (CollectionUtils.isNotEmpty(allInstances)) {
                //通过运行时间戳排序,获取所有实例中最先运行的实例为真实运行实例
                allInstances.sort(Comparator.comparing((Instance a) -> a.getMetadata().get("timestamp")));
                instance=allInstances.get(0);
                log.debug("distributed task run in host:{},port:{}",instance.getIp(),instance.getPort());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }
}

4. 总结

运行过程中,第一个运行的实例会执行定时任务,如果宕机了则由后面运行的实例执行。

充分利用到了注册中心服务监听上下线功能,做到分布式不间断定时任务。当然我们基于此也可以实现任务切片,故障转移等。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java中File类的使用方法

    java中File类的使用方法

    本篇文章介绍了,在java中File类的使用方法。需要的朋友参考下
    2013-04-04
  • SpringBoot整合SpringTask实现定时任务的流程

    SpringBoot整合SpringTask实现定时任务的流程

    这篇文章主要介绍了SpringBoot整合SpringTask实现定时任务的流程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • java使用randomaccessfile在文件任意位置写入数据

    java使用randomaccessfile在文件任意位置写入数据

    Java在文件任意位置写入数据可以使用RandomAccessFile方法来完成,下面看一个简单的示例就明白了
    2014-01-01
  • Springboot利用Aop捕捉注解实现业务异步执行

    Springboot利用Aop捕捉注解实现业务异步执行

    在开发过程中,尽量会将比较耗时且并不会影响请求的响应结果的业务放在异步线程池中进行处理,那么到时什么任务在执行的时候会创建单独的线程进行处理呢?这篇文章主要介绍了Springboot利用Aop捕捉注解实现业务异步执行
    2023-04-04
  • spring注解在自定义jar包中无法被扫描的解决方案

    spring注解在自定义jar包中无法被扫描的解决方案

    这篇文章主要介绍了spring注解在自定义jar包中无法被扫描的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • java中this关键字的详细使用介绍

    java中this关键字的详细使用介绍

    大家好,本篇文章主要讲的是java中this关键字的详细使用介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2022-01-01
  • 深度解析Java常量池中的Integer缓冲池和String常量池

    深度解析Java常量池中的Integer缓冲池和String常量池

    为了减少对象重复创建、提升运行时效率,Java 内部提供了两种重要的优化机制Integer 缓冲池(IntegerCache)和 String 常量池(String Pool),本文将深入剖析两大常量池的底层实现、工作流程、适用范围,并通过流程图和代码示例帮助你彻底掌握
    2026-05-05
  • SpringCloud如何解决服务之间的通信问题

    SpringCloud如何解决服务之间的通信问题

    本文主要介绍了SpringCloud如何解决服务之间的通信问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • Springboot整合quartz产生错误及解决方案

    Springboot整合quartz产生错误及解决方案

    这篇文章主要介绍了Springboot整合quartz产生错误及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Java模拟UDP通信示例代码

    Java模拟UDP通信示例代码

    这篇文章主要介绍了Java模拟UDP通信,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
    2020-06-06

最新评论