前言
本文对initEmbedServer 内容进行介绍。
一、initEmbedServer 作用
initEmbedServer 将执行器的地址项目服务端进行注册,并开启nety 服务,用于接收来自服务端的请求;
二、initEmbedServer 源码内容:
2.1 initEmbedServer 执行器注册
private EmbedServer embedServer = null;
/**
address 服务端地址 通过 xxl.job.executor.address 配置
ip 执行器ip 通过 xxl.job.executor.ip 配置
* port 执行器端口通过 xxl.job.executor.port 配置
* appname 执行器名称通过 xxl.job.executor.appname 配置
* accessToken 与服务端的访问令牌通过 xxl.job.accessToken 配置
**/
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
// fill ip 如果 端口大于0 则取配置的端口,如果小于等于0 则获取可用的端口
port = port>0?port: NetUtil.findAvailablePort(9999);
// 如果没有配置ip 则获取一个ip
ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
// generate address 执行器地址是空的 则使用ip 和port 进行拼接
if (address==null || address.trim().length()==0) {
String ip_port_address = IpUtil.getIpPort(ip, port); // registry-address:default use address to registry , otherwise use ip:port if address is null
address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
}
// accessToken
if (accessToken==null || accessToken.trim().length()==0) {
logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
}
// start 实例化 EmbedServer 对象
embedServer = new EmbedServer();
// start 初始化
embedServer.start(address, port, appname, accessToken);
}
2.2 start 初始化工作:
private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);
// 声明 ExecutorBiz 对象
private ExecutorBiz executorBiz;
// 声明执行器新城
private Thread thread;
public void start(final String address, final int port, final String appname, final String accessToken) {
// 实例化 ExecutorBizImpl
executorBiz = new ExecutorBizImpl();
thread = new Thread(new Runnable() {
@Override
public void run() {
// param netty 服务声明
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-job, 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()
// 注册空闲状态监测IdleStateHandler
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
// 对数据进行编解码
.addLast(new HttpServerCodec())
// 将 HTTP 消息的多个部分合并成一个完整的消息体
.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 启动netty
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
if (e instanceof InterruptedException) {
logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
} else {
logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
}
} finally {
// stop
try {
// 关闭netty 线程
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
thread.start();
}
2.3 startRegistry 执行器的注册:
2.3.1 注册和移除执行器:
public void startRegistry(final String appname, final String address) {
// start registry
ExecutorRegistryThread.getInstance().start(appname, address);
}
ExecutorRegistryThread 类及方法介绍:
public class ExecutorRegistryThread {
// 实例化log 对象
private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
// ExecutorRegistryThread 实例化
private static ExecutorRegistryThread instance = new ExecutorRegistryThread();
public static ExecutorRegistryThread getInstance(){
return instance;
}
// 声明注册线程
private Thread registryThread;
private volatile boolean toStop = false;
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 {
// 执行器对象RegistryParam 封装 ,EXECUTOR 为自动注册
// appname 执行器名称
// address 执行器地址
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) {
// 睡 30 s
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();
}
// 容器停止,则释放资源
public void toStop() {
// 重置标识使得 registryThread 跳出 循环
toStop = true;
// interrupt and wait
if (registryThread != null) {
registryThread.interrupt();
try {
// 等待registryThread 线程任务完成
registryThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
}
2.3.2 注册和移除执行器请求发送:
1) 注册执行器:
@Override
public ReturnT<String> registry(RegistryParam registryParam) {
return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class);
}
2) 移除执行器:
@Override
public ReturnT<String> registryRemove(RegistryParam registryParam) {
return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryParam, String.class);
}
2.4 EmbedHttpServerHandler 任务执行:
2.4.1 channelRead0 接收服务端请求:
@Override
protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
// request parse
//final byte[] requestBytes = ByteBufUtil.getBytes(msg.content()); // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
// 请求信息获取
String requestData = msg.content().toString(CharsetUtil.UTF_8);
String uri = msg.uri();
HttpMethod httpMethod = msg.method();
boolean keepAlive = HttpUtil.isKeepAlive(msg);
// 请求头访问令牌获取
String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
// invoke ThreadPoolExecutor bizThreadPool 线程池进行任务处理
bizThreadPool.execute(new Runnable() {
@Override
public void run() {
// do invoke 处理请求并得到结果
Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
// to json 将结果转为json 字符串
String responseJson = GsonTool.toJson(responseObj);
// write response 通过netty 写回信息
writeResponse(ctx, keepAlive, responseJson);
}
});
}
2.4.2 process 处理请求:
private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
// valid 请求方式检验
if (HttpMethod.POST != httpMethod) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
}
// 请求地址
if (uri==null || uri.trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
// token 校验
if (accessToken!=null
&& accessToken.trim().length()>0
&& !accessToken.equals(accessTokenReq)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
// services mapping
try {
// 如果是服务端向执行器发送心态信息 则返回SUCCESS_CODE = 200;
if ("/beat".equals(uri)) {
return executorBiz.beat();
} else if ("/idleBeat".equals(uri)) {
// 如果是当前任务 的执行器队列是否有任务请求,则返回改任务队列的空闲情况,SUCCESS_CODE = 200;
// 否则返回FAIL_CODE = 500
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
} else if ("/run".equals(uri)) {
// 如果是触发任务执行 则调用run 方法去执行任务
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
} else if ("/kill".equals(uri)) {
// 如果是杀死任务则调用 kill 方法
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
} else if ("/log".equals(uri)) {
// 如果是任务的执行器情况则调用log 请求
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
}
}
2.4.2.1 心跳 beat():
@Override
public ReturnT<String> beat() {
// code = 200; content 为null
return ReturnT.SUCCESS;
}
2.4.2.2 任务队列空闲idleBeat:
@Override
public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam) {
// isRunningOrHasQueue
boolean isRunningOrHasQueue = false;
// 通过任务id 得到改任务执行的线程
JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatParam.getJobId());
// 判断线程是否存在及是否有任务
if (jobThread != null && jobThread.isRunningOrHasQueue()) {
isRunningOrHasQueue = true;
}
if (isRunningOrHasQueue) {
// JobThread 线程队列有任务存在 则返回 code = 500 content 为job thread is running or has trigger queue
return new ReturnT<String>(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.");
}
// 任务空闲 返回code = 200; content 为null
return ReturnT.SUCCESS;
}
2.4.2.3 任务执行run 方法:
由于任务执行的内容较多,放入下面的文章进行介绍:
2.4.2.4 杀掉任务kill 方法:
@Override
public ReturnT<String> kill(KillParam killParam) {
// kill handlerThread, and create new one
// 通过任务id 获取到执行任务的 JobThread 线程
JobThread jobThread = XxlJobExecutor.loadJobThread(killParam.getJobId());
if (jobThread != null) {
// 线程不为空
// 从ConcurrentMap<Integer, JobThread> jobThreadRepository 注册的线程移除
XxlJobExecutor.removeJobThread(killParam.getJobId(), "scheduling center kill job.");
return ReturnT.SUCCESS;
}
return new ReturnT<String>(ReturnT.SUCCESS_CODE, "job thread already killed.");
}
removeJobThread:
public static JobThread removeJobThread(int jobId, String removeOldReason){
JobThread oldJobThread = jobThreadRepository.remove(jobId);
if (oldJobThread != null) {
oldJobThread.toStop(removeOldReason);
oldJobThread.interrupt();
return oldJobThread;
}
return null;
}
2.4.2.5 任务的执行器情况log 方法:
@Override
public ReturnT<LogResult> log(LogParam logParam) {
// log filename: logPath/yyyy-MM-dd/9999.log
// 获取log 文件路径
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logParam.getLogDateTim()), logParam.getLogId());
// 读取log 日志为LogResult 对象
LogResult logResult = XxlJobFileAppender.readLog(logFileName, logParam.getFromLineNum());
// 返回日志结果
return new ReturnT<LogResult>(logResult);
}
三、扩展:
3.1 IdleStateHandler 作用:
IdleStateHandler
是 Netty 中提供的一个用于处理空闲状态的处理器,主要用于在连接空闲时发送特定的事件和处理相应的逻辑。它是 Netty 的一个组件,通常用于网络通信中实现心跳检测、断线重连等功能。
具体来说,IdleStateHandler
的作用包括以下几个方面:
-
空闲状态检测:
IdleStateHandler
可以检测连接的空闲状态,包括读空闲(即在一定时间内未收到数据)、写空闲(即在一定时间内未发送数据)、读写空闲(即在一定时间内未收到或发送数据)等状态。 -
发生空闲状态时触发事件:当连接进入空闲状态时,
IdleStateHandler
会触发特定的事件,可以通过重写相应的方法来处理这些事件,例如发送心跳包、关闭连接等。 -
设置空闲状态检测时间:
IdleStateHandler
可以设置空闲检测的时间间隔,即在多长时间内判断连接是否处于空闲状态,默认为 0 表示不会进行空闲检测。 -
心跳检测机制:
IdleStateHandler
常用于实现心跳检测机制,当连接进入空闲状态时发送心跳包,以保持连接的活跃状态,防止连接被关闭。 -
断线重连:通过在空闲状态下判断连接是否断开,可以触发断线重连逻辑,重新建立与服务器的连接。
总的来说,IdleStateHandler
在 Netty 中的作用是用于处理连接的空闲状态,触发特定事件以执行相应的业务逻辑,如心跳检测、连接维持等功能。使用IdleStateHandler
可以帮助实现网络通信中的稳定性和可靠性,确保连接的正常运行和数据传输的可靠性。
3.2 HttpServerCodec作用:
HttpServerCodec
是 Netty 中提供的一个组合编解码器(codec),用于在 HTTP 通信中处理 HTTP 请求和响应的编解码工作。它是一个组合了HttpRequestDecoder
和HttpResponseEncoder
的编解码器,用于处理 HTTP 协议的编解码。
具体来说,HttpServerCodec
的作用包括以下几个方面:
-
HTTP 请求解码:
HttpServerCodec
负责将从客户端接收到的 HTTP 请求进行解码,将原始的字节数据解析为FullHttpRequest
对象,方便后续业务逻辑处理。 -
HTTP 响应编码:
HttpServerCodec
负责将服务器端的 HTTP 响应信息进行编码,将FullHttpResponse
对象编码为字节数据后发送给客户端。 -
处理 HTTP 消息的完整性:
HttpServerCodec
能够保证接收到的 HTTP 消息的完整性,包括报文头部和消息体的完整性。 -
支持 HTTP 协议的解析:
HttpServerCodec
内部会处理 HTTP 协议相关的头部解析、消息体解析、不同 HTTP 方法的支持(GET、POST等)等。 -
简化 HTTP 服务器的开发:通过使用
HttpServerCodec
,开发人员无需关心具体的 HTTP 请求和响应的编解码细节,能够更加专注于业务逻辑的处理。
总的来说,HttpServerCodec
在 Netty 中的作用是处理 HTTP 请求和响应的编解码工作,简化了开发人员在构建 HTTP 服务器时的编解码处理,同时保证了 HTTP 消息的完整性和正确性。通过使用HttpServerCodec
,可以更轻松地实现基于 HTTP 协议的通信。
3.2 HttpObjectAggregator作用:
HttpObjectAggregator
是 Netty 中提供的一个聚合器(Aggregator),用于将 HTTP 消息的多个部分合并成一个完整的消息体。它通常在处理 HTTP 请求过程中的编解码器链中使用,主要用于处理 HTTP 请求和响应中的分段消息。
具体来说,HttpObjectAggregator
的作用包括以下几个方面:
-
聚合 HTTP 消息:
HttpObjectAggregator
负责将 HTTP 消息的多个部分(如消息头、消息体等)聚合成一个完整的FullHttpMessage
对象,以便后续处理器对整个消息进行处理。 -
处理分块传输编码:当客户端发送的请求使用分块传输编码(Chunked Transfer Encoding)时,
HttpObjectAggregator
能够将多个分块消息合并成一个完整的消息体,方便后续处理器处理。 -
限制消息大小:
HttpObjectAggregator
可以设置最大消息大小限制,防止因为过大的消息导致内存溢出或其他问题。 -
增加性能:通过聚合 HTTP 消息,减少了在处理 HTTP 请求和响应中的编解码过程中的处理次数,提升了性能。
-
简化处理逻辑:使用
HttpObjectAggregator
简化了开发人员对分段消息的处理逻辑,避免了对消息的拆分和合并处理。
总的来说,HttpObjectAggregator
在 Netty 中的作用是将 HTTP 消息的多个部分聚合成一个完整的消息体,方便后续处理器处理。通过使用HttpObjectAggregator
,可以提升 HTTP 请求处理的效率,简化开发过程,并确保消息的完整性和正确性。
总结
本文对执行器注册&任务执行 的源码内容进行介绍。