从零搭建xxl-job(四):xxljob进行一些性能优化

之前的代码这部分并没有补充完毕,假如调度中心如果判断有定时任务要执行了,该怎么远程通知给执行定时任务的程序呢?当定时任务要把自己的信息发送给调度中心时,是通过一个RegistryParam对象发送的。该对象内部封装了定时任务相关的信息。

public class RegistryParam implements Serializable {
    private static final long serialVersionUID = 42L;

    //定时任务方法的名称
    private String registryKey;
    //定时任务程序部署的服务器的ip地址
    private String registryValue;

    public RegistryParam() {
    }

    public RegistryParam(String registryKey, String registryValue) {

        this.registryKey = registryKey;
        this.registryValue = registryValue;
    }


    public String getRegistryKey() {
        return registryKey;
    }

    public void setRegistryKey(String registryKey) {
        this.registryKey = registryKey;
    }

    public String getRegistryValue() {
        return registryValue;
    }

    public void setRegistryValue(String registryValue) {
        this.registryValue = registryValue;
    }

    @Override
    public String toString() {
        return "RegistryParam{" +
                "registryKey='" + registryKey + '\'' +
                ", registryValue='" + registryValue + '\'' +
                '}';
    }
}

那么,调度中心通知定时任务程序执行的时候,该用什么对象封装什么信息呢?封装的这个信息是我最关心的。什! 么信息可以成为调度中心和定时任务执行程序中判别唯一定时任务的标准呢?其实很简单,就是定时任务方法的名 字。虽然啊一个定时任务程序中可能会定义多个定时任务,但是每个定时任务方法的名称是唯一的,所以,调度中 心只要把要执行的定时任务方法的名称发送给定时任务执行程序即可。并且这个方法名称同样可以封装在一个对象 中,既然是要触发定时任务了,这个对象就可以定义为TriggerParam,意思就是触发参数。请看下面的代码块。

public class TriggerParam implements Serializable{
    private static final long serialVersionUID = 42L;

    // 定时任务方法的名字
    private String executorHandler;

    public String getExecutorHandler() {
        return executorHandler;
    }

    public void setExecutorHandler(String executorHandler) {
        this.executorHandler = executorHandler;
    }
}

这样一来,调度中心只要通过网络把封装着要执行的定时任务名字的TriggerParam对象发送给定时任务执行程序,这样,定时任务程序接收到消息后,就可以从TriggerParam对象中获得要执行的任务名称,然后直接去执行即可。当然,这个过程也很复杂,在后面的章节再给大家细讲。总之,现在我的调度中心终于有了可以向定时任务执行器发送的消息了。所以,我现在就想再次把我的调度中心的核心类重构一下,也就是重构JobScheduleHelper类中的start方法,请看下面的代码块。

@Component
public class JobScheduleHelper {

    // 调度定时任务的线程
    private Thread scheduleThread;

    // 创建当前类的对象
    private static JobScheduleHelper instance = new JobScheduleHelper();

    // 把当前类的对象暴露出去
    public static JobScheduleHelper getInstance(){
        return instance;
    }

    // 启动调度线程工作的方法
    public void start(){
        scheduleThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    // 从数据库中查询所有定时任务信息
                    List<YyJobInfo> yyJobInfoList =  YyJobAdminConfig.getAdminConfig().getYyJobInfoDao().findAll();
                    // 得到当前时间
                    long time = System.currentTimeMillis();
                    // 遍历所有定时任务信息
                    for (YyJobInfo yyJobInfo : yyJobInfoList) {
                        if (time > yyJobInfo.getTriggerNextTime()){
                            // 如果大于就执行定时任务,就调用下面这个方法,开始远程通知定时任务程序
                            // 执行定时任务
                            // 注意,这里引入了一个新的类,JobTriggerPoolHelper
                            JobTriggerPoolHelper.trigger(yyJobInfo);
                            // 计算定时任务下一次的执行时间
                            Date nextTime = null;
                            try {
                                 nextTime = new CronExpression(yyJobInfo.getScheduleConf()).getNextValidTimeAfter(new Date());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            // 下面就是更新数据库中定时任务的操作
                            YyJobInfo job = new YyJobInfo();
                            job.setTriggerNextTime(nextTime.getTime());
                            System.out.println("保存job信息");
                        }
                    }
                }
            }
        });
        scheduleThread.start();
    }

在上面的代码块中,我在远程通知执行定时任务的程序的操作处做了一点变动,引入了一个新的名为JobTriggerPoolHelper的新类,这个类中的trigger方法,就是用来远程通知定时任务执行器执行定时任务的。所以现在请大家看一看JobTriggerPoolHelper这个类内部的构造。

public class JobTriggerPoolHelper {

    private static final Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);

    private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();

    public static void trigger(YyJobInfo yyJobInfo){
        // helper其实就是该类的单例对象
        helper.addTrigger(yyJobInfo);
    }

    public void addTrigger(YyJobInfo yyJobInfo){
        // 这是我引入的新的类
        YyJobTrigger.trigger(yyJobInfo);
    }
}

上面的代码就目前来说很简单,因为我的调度中心要去触发定时任务,让定时任务执行了,所有我就又搞了一个触发任务的类,这个类就负责把定时任务的信息向程序内部继续传递下去。在JobScheduleHelper类中调用了JobTriggerPoolHelper.trigger(yyJobInfo)方法后,程序就会来到JobTriggerPoolHelper类中,调用该类的trigger方法,接着又会调用该类的addTrigger方法,在该方法中,程序就来到了我又引入新的YyJobTrigger类中,这个类就是用来真正触发定时任务远程调用的。这个类就是用来真正触发定时任务远程调用的。请大家看下面的代码块。

public class YyJobTrigger {

    private static Logger logger = LoggerFactory.getLogger(YyJobTrigger.class);

    public static void trigger(YyJobInfo jobInfo) {
        processTrigger(jobInfo);
    }

    private static void processTrigger(YyJobInfo jobInfo) {
        // 初始化触发器参数,这里的这个出发参数,是要在远程调用的另一端,也就是定时任务执行程序的那一端使用的
        TriggerParam triggerParam = new TriggerParam();
        // 设置执行器要执行的任务的方法名称
        triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
        // 选择具体的定时任务执行器地址,这里默认使用集合汇总的第一个。
        String address = jobInfo.getRegistryList().get(0);
        // 在这里执行远程调用,也就是要把执行的定时任务的执行信息发送给定时任务
        // 定时任务程序执行完毕后,返回一个执行结果信息,封装在ReturnT对象中
        ReturnT<String> triggerResult = runExecutor(triggerParam, address);
        // 输出一下状态码,根据返回的状态码判断任务是否执行成功
        logger.info("返回的状态码" + triggerResult.getCode());
    }

    public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address) {
        // 在这个方法中把消息发送给定时任务执行程序
        HttpURLConnection connection = null;
        BufferedReader bufferedReader = null;

        try {
            // 创建连接
            URL realUrl = new URL(address);
            // 得到连接
            connection = (HttpURLConnection) realUrl.openConnection();
            // 设置连接属性
            // post请求
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setReadTimeout(3 * 1000);
            connection.setConnectTimeout(3 * 1000);
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
            // 进行连接
            connection.connect();
            // 判断请求题是否为null
            if (triggerParam != null) {
                // 序列化请求体,也就是要发送的触发参数
                String requestBody = GsonTool.toJson(triggerParam);
                // 下面就开始正式发送消息了
                DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
                dataOutputStream.write(requestBody.getBytes(StandardCharsets.UTF_8));
                // 刷新缓冲区
                dataOutputStream.flush();
                // 释放资源
                dataOutputStream.close();
            }
            // 获取响应码
            int statusCode = connection.getResponseCode();
            if (statusCode != 200) {
                // 设置失败结果
                return new ReturnT<String>(ReturnT.FAIL_CODE, "yy-job remoting fail, StatusCode(" + statusCode + ") invalid. for url:" + address);
            }
            // 下面就开始接受返回的结果了
            bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            StringBuilder result = new StringBuilder();
            String line;
            // 接受返回消息
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
            // 转换为字符串
            String resultJson = result.toString();
            try {
                // 转换为ReturnT对象,返回给用户
                ReturnT returnT = GsonTool.fromJson(resultJson, ReturnT.class, String.class);
                return returnT;
            } catch (Exception e) {
                logger.error("yy-job remoting (url=" + address + ") response content invalid(" + resultJson + ").", e);
                return new ReturnT<String>(ReturnT.FAIL_CODE, "yy-job remoting error(" + e.getMessage() + "),for url : " + address);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ReturnT<String>(ReturnT.FAIL_CODE, "yy-job remoting error(" + e.getMessage() + "),for url:" + address);
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e2) {
                logger.error(e2.getMessage(), e2);
            }
        }
    }

下面的代码块就是ReturnT类的具体内容,可以看到,定时任务的执行结果就封装在里面

public class ReturnT<T> implements Serializable {
	public static final long serialVersionUID = 42L;

	public static final int SUCCESS_CODE = 200;
	public static final int FAIL_CODE = 500;

	public static final ReturnT<String> SUCCESS = new ReturnT<String>(null);
	public static final ReturnT<String> FAIL = new ReturnT<String>(FAIL_CODE, null);


	private int code;
	private String msg;
	private T content;

	public ReturnT(){}
	public ReturnT(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	public ReturnT(T content) {
		this.code = SUCCESS_CODE;
		this.content = content;
	}
	
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	public T getContent() {
		return content;
	}
	public void setContent(T content) {
		this.content = content;
	}

	@Override
	public String toString() {
		return "ReturnT [code=" + code + ", msg=" + msg + ", content=" + content + "]";
	}

}

根据上述代码,请大家仔细想一想,现在的程序主要是谁在干活?干的什么活?因为说到底,设计一个程序不能只考虑程序的运行,还要尽可能考虑程序的性能。当运行没有问题的时候,提高性能就成为最主要的问题了。那我目前的调度中心,性能如何,性能的瓶颈又在哪里呢?其实已经很明显了,目前的调度中心,按照我现在的编码流程,所有的活实际上都是JobScheduleHelper类中的scheduleThread线程在干。请大家再次回顾一下JobScheduleHelper类的内容。

public class JobScheduleHelper {

    // 调度定时任务的线程
    private Thread scheduleThread;

    // 创建当前类的对象
    private static JobScheduleHelper instance = new JobScheduleHelper();

    // 把当前类的对象暴露出去
    public static JobScheduleHelper getInstance(){
        return instance;
    }

    // 启动调度线程工作的方法
    public void start(){
        scheduleThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    // 从数据库中查询所有定时任务信息
                    List<YyJobInfo> yyJobInfoList =  YyJobAdminConfig.getAdminConfig().getYyJobInfoDao().findAll();
                    // 得到当前时间
                    long time = System.currentTimeMillis();
                    // 遍历所有定时任务信息
                    for (YyJobInfo yyJobInfo : yyJobInfoList) {
                        if (time > yyJobInfo.getTriggerNextTime()){
                            // 如果大于就执行定时任务,就调用下面这个方法,开始远程通知定时任务程序
                            // 执行定时任务
                            // 注意,这里引入了一个新的类,JobTriggerPoolHelper
                            JobTriggerPoolHelper.trigger(yyJobInfo);
                            // 计算定时任务下一次的执行时间
                            Date nextTime = null;
                            try {
                                 nextTime = new CronExpression(yyJobInfo.getScheduleConf()).getNextValidTimeAfter(new Date());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            // 下面就是更新数据库中定时任务的操作
                            YyJobInfo job = new YyJobInfo();
                            job.setTriggerNextTime(nextTime.getTime());
                            System.out.println("保存job信息");
                        }
                    }
                }
            }
        });
        scheduleThread.start();
    }

可以看到,在JobScheduleHelper中,一旦start方法被调用了,schedulueThread线程就会启动,然后在一个循环中不停得扫描数据库,调度任务去执行。并且,调度任务去执行时,最终会一路调用YyJobTrigger类中的runExecutor方法,在该方法中,会通过http协议,把封装好的定时任务信息的对象发送给定时任务程序,并且——注意里这是是必须要强调清楚的——scheduleThread会等待定时任务程序执行完定时任务后把执行的结果回复过来。如果定时任务执行的时按

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值