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

背景

项目中需要处理定时任务,我们的应用会发布到多台服务器上运行。为了不会并发的处理导致脏数据,通常我们会引入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. 总结

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xianghan收藏册

极简精品作,一分也是一份鼓励哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值