SpringBoot Nacos 轻松实现分布式单点定时任务

Posted 地表最强菜鸡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot Nacos 轻松实现分布式单点定时任务相关的知识,希望对你有一定的参考价值。

一、背景

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

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

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

二、初始化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服务的呢?

三、 定义定时任务

        定时任务基于SpringBoot的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();
    

四、集群实例选择算法

        算法对于应用中多个定时任务都是通用的,所以在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;
    

五、总结

        运行过程中,第一个运行的实例会执行定时任务,如果宕机了则由后面运行的实例执行。充分利用到了注册中心服务监听上下线功能,做到分布式不间断定时任务。当然我们基于此也可以实现任务切片,故障转移等。

以上是关于SpringBoot Nacos 轻松实现分布式单点定时任务的主要内容,如果未能解决你的问题,请参考以下文章

springboot2.x+dubbo2.x+seata1.x AT+nacos+zk实现分布式事务

springboot2.x+dubbo2.x+seata1.x AT+nacos+zk实现分布式事务

SpringBoot使用Nacos配置中心

SpringBoot整合Nacos集群报错,以及解决方案

Springboot+Dubbo+Nacos实现RPC调用

Nacos学习笔记 Nacos整合SpringBoot流程