一、项目启动核心源码
上一篇文章说了项目真正启动之前,所做的准备工作,大体就是 将配置信息进行解析成对象,然后按照注册中心 进行持久化,
本篇介绍一下,这个项目到底是使用什么启动的?答案借助于netty
1.new ShardingSphereProxy().start(port, addresses); 使用了netty作为服务器
public static void main(final String[] args) throws IOException, SQLException {
BootstrapArguments bootstrapArgs = new BootstrapArguments(args);
YamlProxyConfiguration yamlConfig = ProxyConfigurationLoader.load(bootstrapArgs.getConfigurationPath());
int port = bootstrapArgs.getPort().orElseGet(() -> new ConfigurationProperties(yamlConfig.getServerConfiguration().getProps()).getValue(ConfigurationPropertyKey.PROXY_DEFAULT_PORT));
List<String> addresses = bootstrapArgs.getAddresses();
new BootstrapInitializer().init(yamlConfig, port, bootstrapArgs.getForce());
boolean cdcEnabled = null != yamlConfig.getServerConfiguration().getCdc() && yamlConfig.getServerConfiguration().getCdc().isEnabled();
if (cdcEnabled) {
new CDCServer(addresses, yamlConfig.getServerConfiguration().getCdc().getPort()).start();
}
// 类似netty启动,里面注册了很多消息处理器,规定了客户端发送消息服务端到底如何接收
new ShardingSphereProxy().start(port, addresses);
}
1.核心方法 start
其中核心方法startInternal(),表示开启内部,也就是netty的配置
accept(),主要是阻塞主线程,也就是main线程暂时不会执行到finally里面,nettyserver也持续运行,如果监听到关闭事件,可以优雅的关闭通道和nettyserver。
public void start(final int port, final List<String> addresses) {
try {
List<ChannelFuture> futures = startInternal(port, addresses);
accept(futures);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
BackendExecutorContext.getInstance().getExecutorEngine().close();
}
}
=》startInternal(port, addresses)
再使用netty当服务器时,会有一下几步,几乎是一个公式化
- createEventLoopGroup() : 创建boss线程组和worker线程组.
boss线程组指的是用于监听客户端的连接请求,将连接请求发送给 workerGroup 进行处理;
worker线程组指的是用于处理客户端连接的数据读写 - 创建 ServerBootstrap 对象,用于启动 Netty 服务器
- initServerBootstrap来初始化netty服务器的配置
- bind 绑定端口,开始接收客户端请求
private List<ChannelFuture> startInternal(final int port, final List<String> addresses) throws InterruptedException {
createEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
initServerBootstrap(bootstrap);
List<ChannelFuture> futures = new ArrayList<>();
for (String address : addresses) {
futures.add(bootstrap.bind(address, port).sync());
}
return futures;
}
}
=》initServerBootstrap(port, addresses)
接下来,使用ServerBootstrap 配置一下之前设置的线程组,option 设置写缓冲区水位线,防止待发送数据内存占用无限制的增长从而导致 OOM;childOption 设置子通道的内存分配器,handler 注册了一个日志处理器,按照info级别,来打印netty日志;childHandler 通道初始化回调函数,在启动的时候可以自动调用,这个是最核心的,项目启动之后,发过来请求包,到底先经过什么,就在里面定义。
private void initServerBootstrap(final ServerBootstrap bootstrap) {
Integer backLog = ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().getProps().<Integer>getValue(ConfigurationPropertyKey.PROXY_NETTY_BACKLOG);
bootstrap.group(bossGroup, workerGroup)
.channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(8 * 1024 * 1024, 16 * 1024 * 1024))
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_BACKLOG, backLog)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.TCP_NODELAY, true)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ServerHandlerInitializer(FrontDatabaseProtocolTypeFactory.getDatabaseType()));
}
=》ServerHandlerInitializer()
在这里又使用了spi机制DatabaseProtocolFrontendEngine,目前来说我们实现了兼容达梦、人大金仓、oceanbase、tidb,
下面的消息处理器依次会执行,总的来说就是
channelActive,当客户端上线的时候会触发这个方法;
channelRead,当 Channel 中有来自客户端的数据时就会触发这个方法
exceptionCaught,当有异常时触发这个方法
其中PacketCodec编解码引擎、FrontendChannelInboundHandler核心处理逻辑,这两个是比较重要的,使用SPI机制时已经获取到了关于这个数据库编解码引擎,具体核心的处理包在下图所示。FrontendChannelInboundHandler下面介绍
protected void initChannel(final SocketChannel socketChannel) {
DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine = TypedSPIRegistry.getRegisteredService(DatabaseProtocolFrontendEngine.class, databaseType.getType(), new Properties());
databaseProtocolFrontendEngine.initChannel(socketChannel);
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new ChannelAttrInitializer());
pipeline.addLast(new PacketCodec(databaseProtocolFrontendEngine.getCodecEngine()));
pipeline.addLast(new FrontendChannelLimitationInboundHandler(databaseProtocolFrontendEngine));
pipeline.addLast(new FrontendChannelInboundHandler(databaseProtocolFrontendEngine, socketChannel));
}
=》FrontendChannelInboundHandler()
当 Channel 中有来自客户端的数据时就会触发这个方法,从而首先做的是认证,验证用户名密码是否正确,后面会详细展开介绍,而ProxyStateContext.execute() 的下一步回到核心处理流程中 也就是 CommandExecutorTask.run()方法
public void channelRead(final ChannelHandlerContext context, final Object message) {
if (!authenticated) {
authenticated = authenticate(context, (ByteBuf) message);
return;
}
ProxyStateContext.execute(context, message, databaseProtocolFrontendEngine, connectionSession);
}
二、总结
本篇主要介绍了,该项目是如何启动的,借助于netty当做服务器,是如何配置的,以及当启动完成后,进行连接的时候到底从那个地方开始接收报文,方便后续debug。