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
- Get Project Configuration
- 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.