最近工作中由于涉及到微服务这块,所以把Dubbo又重新回顾了一遍,现在做个总结。
为什么使用RPC框架?
一个老生常谈的问题,为什么使用RPC框架?如果你的系统是单机系统,完全可以不用考虑这个问题,但是这些年微服务盛行,SOAP,企业级总线这些过于重量级,大厂都不一定能玩得转的玩意,所以何况是中小企业。所以退而求其次选择RESTful或者轻量级RPC框架,Dubbo之所以能在国内脱颖而出,肯定是有它的原因,在这里我们就不详细说了。
Dubbo能为我们做什么?
其实应该说作为一款企业级RPC框架应该具备哪些基本的功能:
1.基本的远程调用
2.微服务管理
3.监控功能
4.负载均衡
5.网关限流
6.高并发,高可用
基本的远程调用
这个是我们今天要谈的重点,Dubbo是怎么使用远程调用的?
说到远程调用,我们可以采用HTTP,TCP/IP等协议,我们在大学的网络课程里面学过,这些协议承载了太多内容,很多东西都是我们不需要的,我们调用选择一个方法其实只是传几个参数而已,没有必要用HTTP,TCP/IP传输太多我们不需要的内容吧,所以Dubbo选择自己实现了自己的协议,那就是Dubbo协议。
现在协议有了,那么协议的内容通过什么方式传输呢?既然HTTP,TCP/IP不能用,那么我们只能选择socket,但是socket太低端,需要自己考虑的东西太多,所以当然是选择业内作为成熟的框架Netty。
那么Netty是如何实现网络通信的?我们先看服务端实现代码,这个段代码来自Dubbo的Netty Server 类doOpen方法
NettyHelper.setNettyLoggerFactory();
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, this.getUrl().getPositiveParameter("iothreads", Constants.DEFAULT_IO_THREADS));
this.bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(this.getUrl(), this);
this.channels = nettyHandler.getChannels();
this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(NettyServer.this.getCodec(), NettyServer.this.getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
this.channel = this.bootstrap.bind(this.getBindAddress());
如果你对netty不熟悉的话可能看不懂上面的代码,我来解释一下:
ServerBootstrap为netty的启动类,传入参数是ChannelFactory,这个类有两个构造函数,netty使用reactor模式进行网络通信,我们可以看到传了两个线程池,一个是boss,一个worker,顾名思义boss是老板,worker是打工仔,boss负责请求的接待,然后将工作分配给worker进行处理,最后一个参数是需要分配的io线程数的个数,这个是从URL里面获取的,这个相当于Dubbo的一个全局上下文,这个参数是netty 的workers的数量,所有workers共享worker线程池,说白就是这些workers轮流去干活,干玩活把任务丢给worker线程池进行处理,有兴趣的同学可以去看下Netty源代码。
接着我们看到setPipelineFactory方法,这个是Netty内部实现的管道机制,简单来说就是服务端如果有数据处理就把数据丢到这些管道依次执行,nettyHandler是Dubbo实现的一个管道处理方法。这些细节其实我们不用关注太多,其实说来就是Netty帮我们创建了一个 socket链接,如果有请求连接进来,或者有数据进来,我们只需要关注具体的实现业务逻辑,其它方面Netty已经帮我们处理好了。
说到了服务端的实现,那么客户端是如何做的呢?
聪明的你可能已经想到,既然有NettyServer,那么肯定有NettyClient,没错!下面我们来看NettyClient的doOpen方法实现:
NettyHelper.setNettyLoggerFactory();
this.bootstrap = new ClientBootstrap(channelFactory);
this.bootstrap.setOption("keepAlive", true);
this.bootstrap.setOption("tcpNoDelay", true);
this.bootstrap.setOption("connectTimeoutMillis", this.getTimeout());
final NettyHandler nettyHandler = new NettyHandler(this.getUrl(), this);
this.bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(NettyClient.this.getCodec(), NettyClient.this.getUrl(), NettyClient.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
注意以上代码是基于2.8.4,对于最新的Dubbo可能不是这样,但是原理是一样的,如果你有理解上面Server端的初始化过程,那么这段代码应该不难看懂。
所以RPC不过如此吧,但是这只是Dubbo的冰山一脚,本篇文章只是为了说明原理,列举了底层核心代码而已,在实现方法Dubbo使用了CompleteFuture来实现异步调度。
其它方面
Dubbo作为一款企业级别的框架,当然不会只能上面两段代码解决所有问题,为了考虑扩展性,Dubbo提供了注册中心,以及自己一套URL的上下文传输机制,简单来说URL就是DubboInvoker,DubboInvoker就是Dubbo的核心,说白了,DubboInvoker就是封装上面两端代码,使RPC使用对我们开发人员透明,我们不用去关注底层的网络传输,序列化,就是代理调用机制。为了实现扩展Dubbo参考TCP/IP协议,实现了Exchanger和Transporter两层,刚才列举上面6点中的第5点是在这两层实现的。
技术方面,Dubbo实现了自己的IOC机制SPI扩展,为了实现高并发,Dubbo当然会实现自己的线程调度机制以及高效的时间轮算法,具体可以参考我之前写的《Dubbo的线程模型》。
至于负载均衡,集群,以及配置中心的实现也是其可圈可点的地方,有机会我下次再分享吧,今天主要是介绍Dubbo的底层原理。