Dubbo源码学习笔记 之 服务端Server启动&请求Handler
开始,先放一张Dubbo官网的整体设计图,
一、Server 启动
依靠统一的URL 传递配置数据,扩展动态加载机制, 上下层的代码关联非常少。 Protocol 与Exchange 两层之间,就非常明显。
以默认的dubbo 协议为例 ( 每个协议,处理方式不一样,例如httpProtocol 启动server就简单些 )。
1. Netty4 Server端启动,仅是一句代码: server = Exchangers.bind(url, requestHandler);
启动Server ,仅仅调用一个静态方法,传入DubboProtocol 类的实例化的 ExchangeHandlerAdapter 实例requestHandler 。
requestHandler仅有一个reply(ExchangeChannel channel, Object message)方法,message 是已经被反序列化的Invocation对象。
requestHandler 执行2个重要逻辑:
1. 根据channel里面的url数据,获取Invoker (Invoker 为 封装好的FilterChain 过滤链) ,
2. 执行invoker 调用,获取接口服务处理结果,因为Invoker为FilterChain,会优先执行Fitler 里面的逻辑,最后一个调用才执行 最终的 接口实现逻辑调用。
2. Exchanges.bind()
Exchanges.bind() 只有1行关键代码, getExchanger(url).bind(url, handler);
getExchanger(url) 会调用: ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type) , type就是默认的 header ,最终获取到 HeaderExchanger 扩展( 这里没有经过adaptive扩展)。
在HeaderExchanger的bind 方法里面,会进行handler的再次包装,nettyserver 监听本机端口。
将代码展开(方法里面内嵌调用,变成前置变量,方便阅读,理解)后,代码如下:
@Override public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { ChannelHandler h = new DecodeHandler(new HeaderExchangeHandler(handler)); //1. handler 会再次经过2层包装,增加功能 Server s = Transporters.bind(url,h); //2. Transports 操作会启动netty 监听端口,配置序列化实现, // 3.返回 会对 上步netty建立的server ,对此再次进行包装,主要增加 channel 空闲的检测,检测到超过一定时间的空闲的channel,会关闭 return new HeaderExchangeServer( s); }
DecodeHandler : 该handler逻辑比较简单,对消息和消息内容进行识别,提取真实请求数据,给后续handler处理。
HeaderExchangeHandler : 处理channel状态事件,会对channel 写入读写时间属性,对接收请求进行过滤。 目前只看了server端代码,单根据逻辑,该handler 应该是在netty客户端也会用到,会对response进行处理。
Transporters.bind() 方法,启动nettyServer端。 后面的逻辑较多,下个章节单独讲。
HeaderExchangeServer: 会对nettyServer 进行包装, 主要增加2个功能:
a. 对channel进行 空闲时间检测,超过则关闭连接,节省资源。
b. 如果server关闭,则发送消息给client端,不再发送请求到该server。
二、 nettyServer 启动
Transporters.bind(url,handler) ,因为采用默认缺省配置,最终会到org.apache.dubbo.remoting.transport.netty4.NettyTransporter , 直接 new NettyServer(url, listener) 。
url 是之前的url,listener 就是上面经过包装的 handler, 跟着代码进入NettyServer 。
public NettyServer(URL url, ChannelHandler handler) throws RemotingException { // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants. // the handler will be warped: MultiMessageHandler->HeartbeatHandler->handler super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))); //super方法必须放在第一行,这里无法拆分临时变量 }
1. handler包装
NettyServer在调用super方法的时候,调用 ChannelHandlers.wrap 方法,对传入的handler 再次进行了包装。代码如下:
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) { return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class) .getAdaptiveExtension().dispatch(handler, url))); //Dispatch.dispatch()方法实际是对handler的再次装饰,根据配置动态的添加新功能 }
channelhandler.wrap 里面会调用Dispatcher 的扩展,进行dispatch操作,实际是对handler 的包装动态化。
根据配置不同,调用Dispatch扩展包装后的handler 是不一样的。 默认采用AllDispatcher扩展,用 AllChannelHandler 包装1次。
采用默认配置,经过包装之后,传入super方法的handler 包装链如下:
MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExchangeHandler -> DubboProtocol.requestHandler
MultiMessageHandler : 检查消息是否为MutiMessage ,如果 是,分开单条调用后续handler
HeartbeatHandler :1.在每个channel动作,对channel标记时间属性, 2. 检查是否心跳请求,是则直接返回心跳,不继续后续请求。
AllChannelHandler : 1. 将后续handler 包装成 ChannelEventRunnable,捕获后续执行的异常,记录日志 。 2. 包装的runnable 放到独立线程池运行, 达到全流程异步化效果。
2. codec2扩展注入
用idea 生成NettyServer 类关系图,如下:
NettyServer 间接继承AbstractEndPoint , 在AbstractEndPoint构造方法里面,会调用ExtensionLoader,获取 Codec2 扩展。
Codec2 默认扩展名为dobbo ,获取到的是:DubboCountCodec ,在DoubboCountCodec内部,实例化了 DubboCodec,最终的解编码操作还是DubboCodec。
DubboCountCodec 与DubboCodec 涉及到 dubbo的通讯协议,这个后续再仔细学习。
继承的AbstractServer的构造方法里,会调用抽象的doOpen()方法,实际调用NettyServer的doOpen() 打开端口监听。
三、 doOpen 启动NettyServer端口监听
doOpen方法代码较少,主要就是NettyServer 设置&启动。
因为netty的封装,一个简单的nettyserver启动,主要关注: 1. 消息处理 handler ,2. 消息解码&编码
如下:
@Override protected void doOpen() throws Throwable { bootstrap = new ServerBootstrap(); bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true)); workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS), new DefaultThreadFactory("NettyServerWorker", true)); // 1. nettyServerHandler 构造方法需要传入 经过链式包装的handler final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this); channels = nettyServerHandler.getChannels(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE) .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childHandler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { // FIXME: should we use getTimeout()? int idleTimeout = UrlUtils.getIdleTimeout(getUrl()); // 2. encode & decode 编码 adapter ,需要获取父类注入的 codec2对象, NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug .addLast("decoder", adapter.getDecoder()) //3. 收取消息的解码器 .addLast("encoder", adapter.getEncoder()) //4.发送消息的编码器 .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS)) .addLast("handler", nettyServerHandler); //5. 收发消息的handler } }); // bind ChannelFuture channelFuture = bootstrap.bind(getBindAddress()); channelFuture.syncUninterruptibly(); channel = channelFuture.channel(); }
注释1、5 ,就是 netty 的消息handler 实例化与设置。
注释2、3、4 ,为netty消息 解码、编码器的实现与设置。 在注释2,NettyCodecAdapter 内部,会 借由传入的Codec2 对象实现解编码工作。
1. Channel 转换&处理
NettyServerHandler 是netty的消息处理实现,在方法channelRead、write等里面,会从入参ChannelHandlerContext里面获取实际的netty 数据channel,转换成Dubbo定义的NettyChannel类, 执行相应后续业务动作。
相关类图如下:
Channel、ChannelHandler 是dubbo定义的抽象接口。
NettyChannel是一个 包装类,持有一个netty的实际数据channel通道,同时实现了Dubbo的Channel、ChannelHandler 的抽象接口, channel handler里面的 需要实际读取、写入数据时,会由nettychannel里面的 channel 属性实现。
NettyChannel是一个中间类,类似的应该还有MinaChannel,功能主要是封装底层传输实现, 包装成框架的逻辑接口 。 是一个隔离中间层。
在上面注释1.中,nettyserverhandler初始化的时候 new NettyServerHandler(getUrl(), this) ,传入了 nettyserver对象。 因为nettyserver是 继承 AbstractPeer ,算是对channelhandler的再次封装。
所以当netty收到消息时,NettyServerHandler 的channelRead方法会被调用:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); try { handler.received(channel, msg); // 1. channel 为NettyChannel,里面有netty的实际数据传输channel , } finally { NettyChannel.removeChannelIfDisconnected(ctx.channel()); } }
channelRead方法里面,会从netty的ctx里面获取数据channel,然后封装成功nettychannel, 在调用nettychannel的sent方法时,会将对象写入真实的channel,发送!
方法里面的handler 对象,调用包装链如下:
NettyServer -> MultiMessageHandler -> HeartbeatHandler -> AllChannelHandler -> DecodeHandler -> HeaderExchangeHandler -> DubboProtocol.requestHandler
HeaderExchangeHandler 是整个 handler 的最后一个,在其received方法里面,会执行 handleRequest 方法,调用持有的 DubboProtocol.requestHandler的reply方法,执行invoke操作,获取方法结果res,执行channel.send
方法,将结果res写入通道。
这里的res 是调用结果,netty会调用启动时设置的 encode编码器,将其对象序列号 & 编码 发送。
channel 是 在 HeaderExchangeHandler 里面经过 包装过一次的 ExchangeChannel。 channel 链为:ExchangeChannel -> NettyChannel
四、总结
Dubbo Server 启动的过程,就是channel handler 层层包装。 Dubbo的装饰模式、链式调用, 遍布整个过程。 Invoke、Handler、Channel 等等,都是如此。 不记录下来,理解非常困难。
在 handler链的最高层是NettyServerHandler, 负责转换 Netty的具体channel与 Dubbo框架的Channel,应用不同的框架传输,应该都存在这样一个handler。
在handler链的最底部,是HeaderExchangeHandler, 调用Invoke,执行Filter链,获取服务结果,将结果写入channel,发送给客户端。
在handler链的中间,会有一个扩展,AllChannelHandler 位置。 让用户可以定制自己的hannler,执行特定操作。 默认的AllChannelHandler 是将后续执行放入线程池,异步化执行。
server启动时传入的encoder、decoder,会调用codec 对象,动态获取 序列化工程,序列号对象& 编码输出。
经过记录一遍,过程更清晰。
posted on 2019-06-24 18:39 qingcaolin 阅读(1819) 评论(0) 编辑 收藏 举报