背景:
每次启用都要重启executor,在此之前还要确保该executor下没有任务在执行,很麻烦
方案:
将不常更换的公共包放在executor classpath下,仍然由saturn加载
将常变动的包放在executor类加载器可见范围外,使其不能加载
public class ProxyJdsJavaJob extends AbstractSaturnJavaJob {
private static final Logger LOGGER = LoggerFactory.getLogger(ProxyJdsJavaJob.class);
@Override
public SaturnJobReturn handleJavaJob(final String jobName, final Integer shardItem, final String shardParam, final SaturnJobExecutionContext context) {
LOGGER.info("saturn 传入 java job 文件路径及参数:{}", shardParam);
AbstractJdsJavaJob abstractJdsJavaJob = null;
try {
String [] temp = shardParam.split(";");
String dir = "file:"+temp[0];
LOGGER.info("saturn 传入 java job 文件路径:{}", temp[0]);
URL url = new URL(dir);
URL[] urls2 = {url};
MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls2);
LOGGER.info("saturn 传入 java job 类名:{}", temp[1]);
// Class CA = myUrlClassLoader.loadClass(temp[1]);
Class CA = myUrlClassLoader.findClass(temp[1]);
abstractJdsJavaJob = (AbstractJdsJavaJob)CA.newInstance();
abstractJdsJavaJob.runJdsJob(jobName, shardItem, shardParam);
LOGGER.info("success:{},{},{}", jobName, shardItem, shardParam);
return new SaturnJobReturn(Common.createJobResString("", jobName, shardItem, shardParam, true));
} catch (Exception e) {
LOGGER.info("error:{},{},{}", jobName, shardItem, shardParam);
LOGGER.error(ExceptionUtils.getStackTrace(e));
new SendMailThread(new Date(), e, jobName, shardItem, shardParam).execute();
throw new JdsFastJobException(e);
} catch (Throwable throwable) {
LOGGER.error(throwable.getMessage(), throwable);
throw new JdsFastJobException("类加载器失败");
} finally {
if(abstractJdsJavaJob != null)
abstractJdsJavaJob.release();
}
}
}
public class MyUrlClassLoader extends URLClassLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(MyUrlClassLoader.class);
public MyUrlClassLoader(URL[] urls) {
super(urls, Thread.currentThread().getContextClassLoader());
// 父加载器不一定是系统类加载器,可能是saturn自定义加载器
ClassLoader parent = Thread.currentThread().getContextClassLoader();
LOGGER.info("parent class loader {}", parent);
}
@Override
protected Class> findClass(final String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
1. 如果待热加载jar包(后称为A)在executor类加载器可见范围内,必须使用findclass避开缓存
如果在可见范围外,loadclass与findclass都可以,但请注意:
findclass(A),A中引用B,那么jvm使用loadclass加载B,这就意味着,如果B在父加载器缓存中,则不会热加载
所以如果B也要热加载,则1)将B也放到主程序类加载可见范围外,2)在A中使用findclass避开主类加载器缓存,当然同时也打破双亲委派
2. 这里有个坑,executor的类加载器并不是AppClassLoader,日志显示
(MyUrlClassLoader.java:21) -> parent class loader com.vip.saturn.job.executor.JobClassLoader@5fd764ee
所以,自定义类加载器必须指定parent class loader为saturn的类加载器,而不是使用默认null作为参数,这将导致AppClassLoader作为父加载器,否则
AbstractJdsJavaJob是由saturn类加载器加载
而具体的被热加载的类继承AbstractJdsJavaJob,由自定义类加载器加载,缺省情况下,其parent为系统类加载器,导致自定义加载器和saturn类加载器变为平行关系,saturn加载的类,对我们要热加载的类不可见,从而抛出error
3. 永远要确保finally中的语句是安全的,因为如果它抛异常,将覆盖catch中所有代码
详细见:https://www.cnblogs.com/silyvin/p/9993458.html
4. 由第2点抛出的异常,catch Exception是catch不到的,详细见:https://www.cnblogs.com/silyvin/p/10274898.html
故使用了catch Throwable,成功抓取
[INFO] 2019-01-15 17:26:43 985 [jds.fast.job.ProxyJdsJavaJob] [Saturn-test_ProxyJdsJavaJob-24-thread-1] (ProxyJdsJavaJob.java:30) -> saturn 传入 java job 类名:saturn.DemoJob
[ERROR] 2019-01-15 17:26:43 987 [jds.fast.job.ProxyJdsJavaJob] [Saturn-test_ProxyJdsJavaJob-24-thread-1] (ProxyJdsJavaJob.java:45) -> jds/fast/job/AbstractJdsJavaJob
java.lang.NoClassDefFoundError: jds/fast/job/AbstractJdsJavaJob
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at jds.fast.job.MyUrlClassLoader.findClass(MyUrlClassLoader.java:17)
at jds.fast.job.ProxyJdsJavaJob.handleJavaJob(ProxyJdsJavaJob.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.vip.saturn.job.java.SaturnJavaJob$1.internalCall(SaturnJavaJob.java:218)
at com.vip.saturn.job.basic.AbstractSaturnJob$JobBusinessClassMethodCaller.call(AbstractSaturnJob.java:205)
at com.vip.saturn.job.java.SaturnJavaJob.handleJavaJob(SaturnJavaJob.java:214)
at com.vip.saturn.job.java.SaturnJavaJob.doExecution(SaturnJavaJob.java:204)
at com.vip.saturn.job.basic.JavaShardingItemCallable.call(JavaShardingItemCallable.java:158)
at com.vip.saturn.job.basic.ShardingItemFutureTask.call(ShardingItemFutureTask.java:88)
at com.vip.saturn.job.basic.ShardingItemFutureTask.call(ShardingItemFutureTask.java:17)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: jds.fast.job.AbstractJdsJavaJob
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at jds.fast.job.MyUrlClassLoader.findClass(MyUrlClassLoader.java:17)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 26 common frames omitted
5 7.13补充:
public class MyUrlClassLoader extends URLClassLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(MyUrlClassLoader.class);
public MyUrlClassLoader(URL[] urls) {
super(urls, Thread.currentThread().getContextClassLoader());
// 父加载器不一定是系统类加载器,可能是saturn自定义加载器
ClassLoader parent = Thread.currentThread().getContextClassLoader();
LOGGER.info("parent class loader {}", parent);
ClassLoader grandParent = parent.getParent();
LOGGER.info("grand class loader {}", grandParent);
}
@Override
protected Class> findClass(final String name) throws ClassNotFoundException {
return super.findClass(name);
}
@Override
protected void finalize() throws Throwable {
LOGGER.info("回收MyUrlClassLoader");
super.finalize();
}
}
grandParent 居然是null,这也导致了与以premain方式注入监控的jmx_exporter所在的系统类加载器相互不可见:https://www.cnblogs.com/silyvin/articles/10600530.html
2019.12.26
这篇文章能干啥
1)任务隔离
2)热加载
3)加解密
4)任务状态统一记录