Easy implementation of distributed single-point timing tasks using Nacos

Posted by evaoparah on Wed, 22 Dec 2021 00:49:29 +0100

background

Timing tasks need to be handled in the project, and our applications will be published and run on multiple servers. In order to avoid concurrent processing leading to dirty data, we usually introduce distributed scheduling systems such as Elastic-Job or xxl-Job to process. But this requires a new system to be built, and if it's just a simple implementation of distributed timing tasks, that's how I think about practice.

The distributed component of the project is Nacos+Dubbo. When the project starts, Dubbo provider s are registered with Nacos, and we can also register our own timer task service with Nacos.

Registering all causes the dilemma that every task will run. Next, we can use the registry load balancing thinking to make only one service instance take effect at a time, and the scheduled tasks of other service instances take effect when the service goes offline.

1. Initialize Nacos Naming Service

Maven Dependency

    <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>

Define distributed task services

  1. Get Project Configuration
  2. Register Service to Nacos Registry
@Component
@Slf4j
public class DistributedTask implements ApplicationContextAware {

  	/**
  	* Application Run Port
  	*/
    @Value("${dubbo.protocol.port}")
    private Integer serverPort;

    /**
  	* nacos Registry Address
  	*/
    @Value("${nacos.server-address}")
    private String serverAddress;

    /**
  	* nacos Registry Port
  	*/
    @Value("${nacos.port}")
    private String nacosPort;

    /**
  	* Distributed task service name
  	*/
    private static final String SERVICENAME="distributedTask";

    /**
  	* Nacos Name the service, where it is a static object
  	*/
    public static NamingService naming;

    /**
    * Application context, launched with container
    */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        try {
            if (naming==null) {
                //Initialize Nacos Naming Service
                naming = NamingFactory.createNamingService(serverAddress+":"+nacosPort);
                //Set up service instances
                Instance instance=new Instance();
                //Get IP, NetUtils gets IP from Dubbo source
                instance.setIp(NetUtils.getLocalHost());
                instance.setPort(serverPort);
                //Load core idea, record current run time stamp, write to service metadata
                instance.setMetadata(MapUtil.of("timestamp",System.currentTimeMillis()+""));
                //Register Instances
                naming.registerInstance(DistributedTask.SERVICENAME,instance);
            }
        } catch (NacosException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
}

Next let's look at how specific timed tasks use the Nacos service.

2. Define timed tasks

Schedule implementation of timed tasks based on SpingBoot

Run Rules

  • If not run then determine whether to run on the current node
  • Run directly if it is already running
@Configuration
@Slf4j
//Open a timed task, usually written on the project startup class
@EnableScheduling
public class AutoSubmitTask {

    @Autowired
    private DistributedTask distributedTask;

    /**
    * Dubbo Remote Service, Real Business Logic Running Service
    */
    @DubboReference(group = "nx")
    private ILogicService iLogicService;

    //Mark if currently running
    private boolean currentRun=false;

    //Scheduling Rules
    @Scheduled(fixedRate = 1000)
    private void configureTasks() {
        //Determine if job is currently running locally
        if(currentRun){
            //Run regularly
            run();
        }else{
            //If not, determine whether you need to run. See the Cluster Fault Tolerant Algorithms chapter for implementation
            if (distributedTask.isRunInCurrent()) {
                currentRun=true;
                run();
            }
        }
    }

    private void run(){
        log.info("implement PaperSubmitTask Timed Tasks: " + LocalDateTime.now());
        iLogicService.autoSubmit();
    }
}

3. Cluster Instance Selection Algorithm

The algorithm is common to many timed tasks in the application, so the DistributedTask implements the method of getting cluster instances.

public class DistributedTask implements ApplicationContextAware {
    /**
    * Whether to run in the current application
    */
		public boolean isRunInCurrent(){
        try {
            //Simple algorithm to get an instance that should run
            Instance instance = findInstance();
            //Current environment for instances that should run
            if (instance!=null && instance.getIp().equals(NetUtils.getLocalHost()) && instance.getPort()==serverPort) {
                return true;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return false;
    }
    
    /**
    * Get the instance of the timed task
    */
    private Instance findInstance(){
        Instance instance=null;
        try {
            //Get all running instances of Timer Task Service through api
            List<Instance> allInstances = naming.getAllInstances(SERVICENAME);
            if (CollectionUtils.isNotEmpty(allInstances)) {
                //Get the first running instance of all instances as the real running instance by running timestamp sorting
                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. Summary

During the run, the first running instance executes a timed task, and if it goes down, it is executed by the later running instance. Make full use of the registry service monitoring offline and offline functions to achieve distributed and uninterrupted timed tasks. Of course, we can also implement task slicing, failover, and so on.

Topics: Dubbo Spring Boot Distribution Microservices Nacos