Past recommendation
[RabbitMQ analysis] 01 SimpleMessageListenerContainer principle analysis
[sharding-sphere] - 01 SQL Routing
[Nacos source code analysis] - 02 Obtaining the configuration process
[Java Concurrent Programming] - 03 MESI, memory barrier
[Spring source code]- 11 Programmatic transactions of Spring AOP
Actuator example
The following are xxl-job
example codes for integrating executors in different ways:
The most commonly used springboot
method is of course the integration method. Let's use this example to study xxl-job
the startup process of the client executor.
start process
The client executor startup process entry is in XxlJobSpringExecutor
the class, by implementing the spring extension SmartInitializingSingleton
, when the IOC singleton bean is loaded, the method is called afterSingletonsInstantiated()
:
@Override
public void afterSingletonsInstantiated() {
// init JobHandler Repository (for method)
initJobHandlerMethodRepository(applicationContext);
// refresh GlueFactory
GlueFactory.refreshInstance(1);
// super start
try {
super.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Bean type task analysis
First, let's analyze initJobHandlerMethodRepository(applicationContext)
the method, which
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
// init job handler from method
String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = applicationContext.getBean(beanDefinitionName);
Map<Method, XxlJob> annotatedMethods = null; // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
try {
annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
new MethodIntrospector.MetadataLookup<XxlJob>() {
@Override
public XxlJob inspect(Method method) {
return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
}
});
} catch (Throwable ex) {
logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
}
if (annotatedMethods==null || annotatedMethods.isEmpty()) {
continue;
}
......
}
}
The above logic is to traverse IoC
the container Bean
, obtain and analyze @XxlJob
the method with annotations, and finally return Map<Method, XxlJob>
the result type, key
which is @XxlJob
annotated Method
, which is the annotation information value
parsed on the method .@XxlJob
After parsing and obtaining the information with @XxlJob
annotations Method
, let's see how to proceed further:
// 遍历
for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
Method method = methodXxlJobEntry.getKey();
XxlJob xxlJob = methodXxlJobEntry.getValue();
if (xxlJob == null) {
continue;
}
// 获取@XxlJob注解value配置
String name = xxlJob.value();
if (name.trim().length() == 0) {
throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
}
// 判断是否同名的已经加载,有则抛异常
if (loadJobHandler(name) != null) {
throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
}
// @XxlJob注解方法参数校验:必须只有一个参数,且参数类型是String,否则抛出异常
if (!(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {
throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " + "The correct method format like \" public ReturnT<String> execute(String param) \" .");
}
// @XxlJob注解方法返回值校验,必须返回ReturnT类型
if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {
throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " + "The correct method format like \" public ReturnT<String> execute(String param) \" .");
}
method.setAccessible(true);
// init and destory
Method initMethod = null;
Method destroyMethod = null;
// 解析@XxlJob注解init配置
if (xxlJob.init().trim().length() > 0) {
try {
initMethod = bean.getClass().getDeclaredMethod(xxlJob.init());
initMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
}
}
// 解析@XxlJob注解destroy配置
if (xxlJob.destroy().trim().length() > 0) {
try {
destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy());
destroyMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
}
}
// 将@XxlJob注解的Method、initMethod和destroyMethod封装成MethodJobHandler,并放入到Map中完成注册,[email protected]
registJobHandler(name, new MethodJobHandler(bean, method, initMethod, destroyMethod));
}
GlueFactory initialization
GlueFactory
It mainly deals with GLUE(Java)
type jobs, GLUE(Java)
compiles type job source code, creates instances and calls, and can support spring dependency injection, such as support in source code @Autowired
, , @Resource
, @Qualifier
etc.
Executor startup process
super.start()
This sentence will actually enter the executor startup process XxlJobExecutor#start
:
public void start() throws Exception {
// 初始化日志路径
XxlJobFileAppender.initLogPath(logPath);
// 初始化AdminBizClient,用于和admin远程交互
initAdminBizList(adminAddresses, accessToken);
// 初始化日志清理线程,用于清理日志目录下过期日志文件
JobLogFileCleanThread.getInstance().start(logRetentionDays);
// 初始化回调线程,triggerCallbackThread
TriggerCallbackThread.getInstance().start();
// init executor-server
initEmbedServer(address, ip, port, appname, accessToken);
}
The main thing here is the latter two statements, TriggerCallbackThread.getInstance().start();
which are mainly used to call back after the job execution is completed and pass the result to the admin module. For details, see the analysis of the job execution process of the client executor in the next section.initEmbedServer(address, ip, port, appname, accessToken);
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
// fill ip port
port = port>0?port: NetUtil.findAvailablePort(9999);
ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
// generate address
if (address==null || address.trim().length()==0) {
String ip_port_address = IpUtil.getIpPort(ip, port);
address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
}
// start
embedServer = new EmbedServer();
embedServer.start(address, port, appname, accessToken);
}
The logic above is for processing ip:port
and parsing. The key is the following two sentences, EmbedServer#start
which create a thread Thread internally and start it:
public void start(final String address, final int port, final String appname, final String accessToken) {
executorBiz = new ExecutorBizImpl();
thread = new Thread(new Runnable() {
@Override
public void run() {
......
}
});
thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
thread.start();
}
Let's take a look at what this thread does internally:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
0,
200,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
}
});
try {
// start server
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
.addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
}})
.childOption(ChannelOption.SO_KEEPALIVE, true);
// bind
ChannelFuture future = bootstrap.bind(port).sync();
logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);
// start registry
startRegistry(appname, address);
// wait util stop
future.channel().closeFuture().sync();
}
There are a lot of codes above, but the logic is actually very simple. It mainly does two things:
netty
One is started using initializationhttp server
, which is mainly used to receiveadmin模块
and send instructions to the executor, such as issuing execution job instructions and kill job instructions, and the main processing logic is encapsulated inEmbedHttpServerHandler
;startRegistry(appname, address)
Start the client executor to regularlyadmin模块
register threads, the logic code is inExecutorRegistryThread#start
the method, which is relatively simple;
xxl-job
The client executor registration process is roughly as follows:
1. The client uses adminBiz.registry(registryParam)
the timing cycle to admin模块
send registration information to the client;
2. admin模块
After receiving the client registration information, insert|update the field time value xxl_job_registry
of the table update_time
;
3. admin模块
Start JobRegistryMonitorHelper
the thread to scan xxl_job_registry
the table regularly, remove the timeout, and splice the online instance collection together and update it to the executor address_list
field information whose executor address is automatically registered.
Summarize
xxl-job
The startup process of the client executor is relatively simple, and there are two core points:
The application starts
netty
ahttp server
container andIP:PORT
brings the application registration information toadmin
the module, soadmin
that the executor can issue commands such as running jobs and killing jobs;The executor
admin
registers once at regular intervals (30 seconds by default). Whenadmin
the executor registration information is not received for more than 90 seconds, the executor is deemed to have timed out and will be removed offline;
Long press the QR code to identify attention