Java小强个人技术博客站点    手机版
当前位置: 首页 >> 开源 >> 分布式任务调度平台XXL-JOB:调度日志打印时区问题

分布式任务调度平台XXL-JOB:调度日志打印时区问题

4870 开源 | 2022-8-17

Quartz作为开源作业调度中的佼佼者,是作业调度的首选。但是集群环境中Quartz采用API的方式对任务进行管理,从而可以避免上述问题,但是同样存在以下问题:

问题一:调用API的的方式操作任务,不人性化;

问题二:需要持久化业务QuartzJobBean到底层数据表中,系统侵入性相当严重。

问题三:调度逻辑和QuartzJobBean耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;

问题四:quartz底层以“抢占式”获取DB锁并由抢占成功节点负责运行任务,会导致节点负载悬殊非常大;而XXL-JOB通过执行器实现“协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。

XXL-JOB弥补了quartz的上述不足之处。

xxljob3.jpg


由于公司服务器时区使用的是标准时区,不是北京时区,导致很多问题,比如格式化日期对象时,得到的结果,不是北京时间。


首先发现的问题是Task任务程序打印的日志,不是北京时间,看了一下Logback配置,确实是简单的标准配置,没有指定时间:

<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%-4relative:%thread] %-5level %logger{56} - %msg%n" />

修改为:

<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS,CTT} [%-4relative:%thread] %-5level %logger{56} - %msg%n" />

官方说明Chapter 6: Layouts (qos.ch)


CTT代表是上海,那么其他的去哪里查?其实在JDK的java.time.ZoneId文件里面就有:

public static final Map<String, String> SHORT_IDS = Map.ofEntries(
	entry("ACT", "Australia/Darwin"),
	entry("AET", "Australia/Sydney"),
	entry("AGT", "America/Argentina/Buenos_Aires"),
	entry("ART", "Africa/Cairo"),
	entry("AST", "America/Anchorage"),
	entry("BET", "America/Sao_Paulo"),
	entry("BST", "Asia/Dhaka"),
	entry("CAT", "Africa/Harare"),
	entry("CNT", "America/St_Johns"),
	entry("CST", "America/Chicago"),
	entry("CTT", "Asia/Shanghai"),
	entry("EAT", "Africa/Addis_Ababa"),
	entry("ECT", "Europe/Paris"),
	entry("IET", "America/Indiana/Indianapolis"),
	entry("IST", "Asia/Kolkata"),
	entry("JST", "Asia/Tokyo"),
	entry("MIT", "Pacific/Apia"),
	entry("NET", "Asia/Yerevan"),
	entry("NST", "Pacific/Auckland"),
	entry("PLT", "Asia/Karachi"),
	entry("PNT", "America/Phoenix"),
	entry("PRT", "America/Puerto_Rico"),
	entry("PST", "America/Los_Angeles"),
	entry("SST", "Pacific/Guadalcanal"),
	entry("VST", "Asia/Ho_Chi_Minh"),
	entry("EST", "-05:00"),
	entry("MST", "-07:00"),
	entry("HST", "-10:00")
);

其实,CTT是简写,实际上就是代表Asia/Shanghai,注意这里没有北京,上海就是代表北京时区。


看了一下Cron表达式,定制的是

CRON:0 15 0 * * ? *

按设想也就是每天凌晨15分执行,由于服务器使用了标准时间,成了北京时间每天8点才执行,但是查看调度日志,发现打印时间却是凌晨15分,由于已经配置Logback怎么时间还是对不上?

查看源码后发现,XXLJOB的业务日志,是通过XxlJobHelper来记录的,但是他不是通过Logback打印的,而是将每次操作的日志独立成文件,存放到服务器,然后调度中心查看日志时,是拿的节点日志文件内容。

怎么看的,调度中心请求路径是:/xxl-job-admin/joblog/logDetailCat,然后跟源码就可以看到。


然后跟XxlJobHelper的操作即可,看到

private static boolean logDetail(StackTraceElement callInfo, String appendLog) {
	XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
	if (xxlJobContext == null) {
		return false;
	}

	/*// "yyyy-MM-dd HH:mm:ss [ClassName]-[MethodName]-[LineNumber]-[ThreadName] log";
	StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
	StackTraceElement callInfo = stackTraceElements[1];*/

	StringBuffer stringBuffer = new StringBuffer();
	stringBuffer.append(DateUtil.formatDateTime(new Date())).append(" ")
			.append("["+ callInfo.getClassName() + "#" + callInfo.getMethodName() +"]").append("-")
			.append("["+ callInfo.getLineNumber() +"]").append("-")
			.append("["+ Thread.currentThread().getName() +"]").append(" ")
			.append(appendLog!=null?appendLog:"");
	String formatAppendLog = stringBuffer.toString();

	// appendlog
	String logFileName = xxlJobContext.getJobLogFileName();

	if (logFileName!=null && logFileName.trim().length()>0) {
		XxlJobFileAppender.appendLog(logFileName, formatAppendLog);
		return true;
	} else {
		logger.info(">>>>>>>>>>> {}", formatAppendLog);
		return false;
	}
}

刚才已经说了,如果你的服务器时区不是北京时间的话,使用Date时会有问题,再看其源码DateUtil中内容:

private static final ThreadLocal<Map<String, DateFormat>> dateFormatThreadLocal = new ThreadLocal<Map<String, DateFormat>>();
private static DateFormat getDateFormat(String pattern) {
	if (pattern==null || pattern.trim().length()==0) {
		throw new IllegalArgumentException("pattern cannot be empty.");
	}

	Map<String, DateFormat> dateFormatMap = dateFormatThreadLocal.get();
	if(dateFormatMap!=null && dateFormatMap.containsKey(pattern)){
		return dateFormatMap.get(pattern);
	}

	synchronized (dateFormatThreadLocal) {
		if (dateFormatMap == null) {
			dateFormatMap = new HashMap<String, DateFormat>();
		}
		dateFormatMap.put(pattern, new SimpleDateFormat(pattern));
		dateFormatThreadLocal.set(dateFormatMap);
	}

	return dateFormatMap.get(pattern);
}

为了效率使用了ThreadLocal来存储时间格式化对象,但是其没有配置时区,这样我们业务在北京,但是服务器使用标准时区,就会造成时间差异。

因此修改为:

private static final ThreadLocal<Map<String, DateFormat>> dateFormatThreadLocal = new ThreadLocal<Map<String, DateFormat>>();
private static DateFormat getDateFormat(String pattern) {
	if (pattern==null || pattern.trim().length()==0) {
		throw new IllegalArgumentException("pattern cannot be empty.");
	}

	Map<String, DateFormat> dateFormatMap = dateFormatThreadLocal.get();
	if(dateFormatMap!=null && dateFormatMap.containsKey(pattern)){
		return dateFormatMap.get(pattern);
	}

	synchronized (dateFormatThreadLocal) {
		if (dateFormatMap == null) {
			dateFormatMap = new HashMap<String, DateFormat>();
		}
		SimpleDateFormat bjSdf = new SimpleDateFormat(pattern); // 北京
		bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); // 设置北京时区
		dateFormatMap.put(pattern, bjSdf);
		dateFormatThreadLocal.set(dateFormatMap);
	}

	return dateFormatMap.get(pattern);


这样,无论Logback打印,还是实际的业务日志,都成了北京时间,更加方便我们调试日志了。

但是这里也就出现一个问题,就是Cron表达式要调整,比如要凌晨执行,就要改成凌晨8点执行了,当然刚才说了,所有日志的时间,都是按北京来的。


END

推荐您阅读更多有关于“ 日志 分布式 任务 XXLJOB Quartz 定时任务 时区 ”的文章

上一篇:SpringBoot中application.yml引入多个YML文件 下一篇:分布式任务调度平台XXL-JOB:调度报告生成报错

猜你喜欢

发表评论: