Sentinel通信模块解析
概览
在Sentinel中有两个模块需要进行远程通信,分别是Sentinel Client与Dashboard的通信以及Sentinel Client与Token Server的通信,分别位于Sentinel项目中的sentinel-transport模块与sentinel-cluster模块。其中Sentinel Client与Dashboard之间相互通信采用的是传统HTTP方式,Client开放HTTP接口接收来自Dashboard的控制指令,Dashboard开放HTTP接口接收来自Client的数据上报。而Sentinel Client与Token Server的通信采用的TCP长链接的方式。
Sentinel-Transport
sentinel-transport项目是用于Sentinel Client接收控制命令的,coomon子模块定义了一些通用的基础抽象类,例如请求体、请求响应体、命令处理器等等。实际的通信功能是由其余的子模块来提供的,目前提供了由netty、nio以及spring-mvc实现的三种不同HTTP端点。通常情况下我们希望sentinel的控制端口能够与业务的web端口分离,sentinel端口不对外暴露,所以一般采用的transport都是netty-http或是simple-http,这里我们着重来看一下netty实现的http transport。
netty server相关功能是由HttpServer类负责实现的,而启动是由NettyHttpCommandCenter负责的。NettyHttpCommandCenter实现了CommandCenter接口,这个接口定义了beforeStart、start、stop三个函数,从定义上来看CommandCenter更像是Lifecycle Listener,并没有定义命令处理的相关接口。那么CommandCenter的beforeStart、start是由谁负责调用的呢?在Sentinel中InitExecutor负责初始化所有实现了InitFunc的接口的实现类,其中查找所有实现InitFunc接口的实现类并不是采用我们常见的Bean查找的方式,而是利用自实现的SpiLoader在META-INF/services/目录下的文件定义来加载所有的实现类。
@InitOrder(-1)
public class CommandCenterInitFunc implements InitFunc {
@Override
public void init() throws Exception {
CommandCenter commandCenter = CommandCenterProvider.getCommandCenter();
if (commandCenter == null) {
RecordLog.warn("[CommandCenterInitFunc] Cannot resolve CommandCenter");
return;
}
commandCenter.beforeStart();
commandCenter.start();
RecordLog.info("[CommandCenterInit] Starting command center: "
+ commandCenter.getClass().getCanonicalName());
}
}
在NettyHttpCommandCenter中首先初始化一个HttpServer对象,这里比较特别的是NettyHttpCommandCenter并没有通过直接创建一个线程的方式来启动Netty server,而是使用了一个单线程的线程池,将启动任务提交至线程池中,这么做的好处是方便了线程的管理。
@Override
public void start() throws Exception {
pool.submit(new Runnable() {
@Override
public void run() {
try {
server.start();
} catch (Exception ex) {
RecordLog.warn("[NettyHttpCommandCenter] Failed to start Netty transport server", ex);
ex.printStackTrace();
}
}
});
}
接着来看下Sentinel对于Netty的使用,从HttpServer的start函数中我们可以很直观的看到与Seata的Netty Server不同的是,Sentinel并没有做过多的设置,只是完成了简单的HTTP功能的初始化,其中HttpServerHandler则是处理Http请求的关键类。
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline p = socketChannel.pipeline();
p.addLast(new HttpRequestDecoder());
p.addLast(new HttpObjectAggregator(1024 * 1024));
p.addLast(new HttpResponseEncoder());
p.addLast(new HttpServerHandler());
}
}
HttpServerHandler继承自SimpleChannelInboundHandler,整体处理的逻辑也比较直观,将Http请求解析成CommandRequest对象,根据请求的Request Target找到命令处理器CommandHandler,处理完请求后返回CommandResponse对象。
private void handleRequest(CommandRequest request, ChannelHandlerContext ctx, boolean keepAlive)
throws Exception {
String commandName = HttpCommandUtils.getTarget(request);
// Find the matching command handler.
CommandHandler<?> commandHandler = getHandler(commandName);
if (commandHandler != null) {
CommandResponse<?> response = commandHandler.handle(request)