XXL-JOB之Client端启动流程分析

本文详细介绍了XXL-JOB客户端启动过程,包括在bean中搜索@XxlJob方法并封装成JobHandler的缓存机制,日志文件夹初始化,服务端调用器的封装,日志清理策略,客户端执行完毕后的服务端回调处理,以及客户端Netty服务器的构建和注册过程,用于接收服务端的调用执行任务。
摘要由CSDN通过智能技术生成

概要

        本次主要梳理一下客户端启动的流程。

整体架构流程

         以上是客户端启动过程中,所执行的逻辑。下面会进行详细分析。

技术细节

1、在bean中搜索含有@XxlJob的方法,并封装成JobHandler: 

    

         最终,是将构建好的JobHandler放入了Map中进行缓存,key是JobHandler的名称,也就是@XxlJob中value定义的名称。这里为什么要大费周章缓存这么个东西呢?想一想,如果服务端获取到任务之后,(任务中有个属性是JobHandler的名字),怎么才能快速找到一个执行器来执行业务逻辑?如果缓存了这个东西,请求到达客户端之后,就可以快速从里面找到执行器进行执行,因为JobHandler中封装了bean的类型,方法,init方法,destroy方法,这样直接通过反射就可以执行了。所以,从这里可以看出,所谓的执行器,其实就是我们在业务中通过@XxlJob注解标记的一个方法,而且这个执行器的名字要是唯一的,否则会报错。

2、日志文件夹初始化:

        如果没有配置客户端日志保存的文件路径,默认是/data/applogs/xxl-job/jobhandler。

 3、封装服务端调用器:

 

         首先,我们看AdminBiz这个接口,它其实是定义了3个给客服端操作服务端的方法:执行回调、注册、和摘除客户端的方法。从上面可以知道,具体实现类是AdminBizClient:

         这里就是通过http的方式调用的服务端的能力。那我们得需要知道服务端的具体地址,所以,这里进行了封装。避免单点故障,服务端节点有可能会配置多个。客户端这边配置多个服务端地址时以逗号隔开。

4、对日志文件进行清理,防止日志文件过多的占用磁盘。这里需要注意的是,如果配置的logRetentionDays的值小于3时,这个功能是不开启的。默认是30,也就是默认清理超过30天的日志文件。

5、客服端执行器执行完之后,对服务端回调的处理:

  

         通过生产者-消费者模型方式处理的。最终是通过http的方式,调用服务端的能力,对服务端进行回调处理。如果服务端节点配置了多个,这里会不会对服务端进行重复调用?不会。这里的处理比较讨巧。通过一个for循环,如果调用成功,直接跳出来。如果没有成功,就catch住,然后调用下一个节点,直到成功。为啥会有没成功的情况?要么网络抖动,要么出现问题了,下线了。所以,不会出现重复执行的情况。这么做的好处是,不需要额外去维护服务端节点上下线的逻辑。坏处就是,客户端无法自动感知服务器节点的扩容和缩容。服务器节点数量发生变化,只能在客户端这边进行重新配置服务器的地址,然后重启才能生效。

        之前在分析XXL-MQ的时候,它采用的是注册中心的方式。通过一个自研的注册中心,来维护服务端和客户端节点变化,直接在注册中心中获取就是最新的地址,可以自动感知变化,不需要重启。

        两种方式都可行。根据业务场景来就可以了。如果服务端不是经常变化的话,直接提前指定好,完全是可行的。

6、构建客服端的netty服务器:

         这里的参数address、ip和port是指客服端的。默认是没有配置的。所以,这里要做的是,找到当前这个客户端节点的ip,并且从9999这个端口为界,先向上找到一个小于65535的没有使用的端口,如果没有,就向下找,直到找到为止,和ip拼接成一个客户端的地址。寻找的方式,就是通过socket去监听端口号,成功了,就说明没有使用。

        找到合适的客户端address之后,就构建了一个netty的服务器:

         这里为啥要构建一个客户端的服务器呢?从构建的细节来看,是个http服务器。既然是服务器,那是给谁来调用的呢?显然是给服务端来调用。分析《XXL-JOB之服务端启动流程分析》的时候,提到过服务端会预拉取一批需要执行的任务,放到时间轮中等待调度。真正执行的逻辑是在客户端的。所以,客户端这边需要提供能力,给服务器端来调用,这就需要具有web的能力。

        既然要实现http的请求,那肯定得知道客户端的地址。所以,客户端找到合适的address之后,还需要把这个address保存到服务端去,也就是上面的注册到调度中心。具体逻辑在这个方法startRegistry(appname, address):

   public void start(final String appname, final String address){
        // valid
        if (appname==null || appname.trim().length()==0) {
            logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, appname is null.");
            return;
        }
        if (XxlJobExecutor.getAdminBizList() == null) {
            logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, adminAddresses is null.");
            return;
        }
        registryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                // registry
                while (!toStop) {
                    try {
                        //todo 心跳维持。每个30秒向调度中心注册一次,以此来维持心跳
                        RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
                        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                            try {
                                ReturnT<String> registryResult = adminBiz.registry(registryParam);
                                if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                    registryResult = ReturnT.SUCCESS;
                                    logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                    break;
                                } else {
                                    logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                }
                            } catch (Exception e) {
                                logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);
                            }
                        }
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                    try {
                        if (!toStop) {
                            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                        }
                    } catch (InterruptedException e) {
                        if (!toStop) {
                            logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());
                        }
                    }
                }
                // registry remove
                try {
                    RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
                    for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                        try {
                            ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
                            if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                registryResult = ReturnT.SUCCESS;
                                logger.info(">>>>>>>>>>> xxl-job registry-remove success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                break;
                            } else {
                                logger.info(">>>>>>>>>>> xxl-job registry-remove fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                            }
                        } catch (Exception e) {
                            if (!toStop) {
                                logger.info(">>>>>>>>>>> xxl-job registry-remove error, registryParam:{}", registryParam, e);
                            }
                        }
                    }
                } catch (Exception e) {
                    if (!toStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, executor registry thread destroy.");
            }
        });
        registryThread.setDaemon(true);
        registryThread.setName("xxl-job, executor ExecutorRegistryThread");
        registryThread.start();
    }

        ①调用服务端的能力,将自己的信息注册到了服务端的表xxl-job-registry中;

        ②自己下线的时候,会调用服务端的api,将自己的信息从服务端的表xxl-job-registry中移除;

        ③30秒更新一下服务端表xxl-job-registry中的时间,维持心跳。 

        ④服务端那边之前分析过,它会不停扫描xxl-job-registry表,将活跃的客户端的注册信息进行归档,存到xxl-job-group表中。

        再看一下客户端提供的api,在EmbedHttpServerHandler中:  

         和普通的web服务一样,直接调用就可以了。

小结

1、大量使用了异步,生产者-消费者模型,提高了吞吐量;

2、netty的使用场景,平时可能不怎么会用到,用到的时候,可以参考这个;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值