目录
1 RPC调用过程
1. 调用者(客户端Client)以本地调用的方式发起调用;
2. Client stub(客户端存根)收到调用后,负责将被调用的方法名、参数等打包编码成特定格式的能进行网络传输的消息体;
3. Client stub找到服务地址,并将消息体发送给服务端;
4. Server stub(服务端存根)收到通过网络接收到消息后按照相应格式进行拆包解码,获取方法名和参数;
5. Server stub解码结果进行本地调用;
6. 被调用者(Server)本地调用执行后将结果返回给server stub;
7. Server stub将返回值打包编码成消息,并通过网络发送给客户端;
8. Client stub收到消息后,进行拆包解码,返回给Client;
9. Client得到本次RPC调用的最终结果。
2 netty通信框架简介
服务提供者和调用者通过nio框架Netty进行通信。NIO是非阻塞IO,和传统IO有很大区别
传统IO模型如下
BIO服务器给每个请求分配一个线程来处理请求。这样的话每个服务器不可能处理太多请求。
NIO服务器
每个请求通过Channel(通道)来发送数据,通过Buffer(缓冲区)进行传输,这些通道注册到Selector(多路复用器)上,某个通道的任意一个状态好了,可以单开一个线程来处理。
通道状态包括Connect(连接就绪),Accept(接收就绪),Read(读就绪),Write(写就绪)
Netty基本模型
Netty对NIO做的进一步封装,是一个高性能NIO框架。
1. Netty服务器启动,监听一个端口,如dubbo框架,监听20880,初始化一个通道(NioServerSocketChannel)
2. 把NioServerSocketChannel注册到Selector上,轮询accept事件,即通道准备接收数据的事件
3. accept事件触发后处理通道的信息,建立起与客户端连接的Channel(NioSocketChannel)
4. 把NioSocketChannel注册到另一个Selector上,这个Selector轮询read和write事件
5. 读就绪或写就绪后就会把任务抛给任务队列执行
3 dubbo原理
3.1 框架设计
dubbo框架整体分为3层,业务层、RPC层、远程通信层
- 业务层
对于开发者来说只是使用到业务层,写一个接口和一个实现,想要远程调用只需要调接口方法就行了
- RPC层
RPC层分为Config层、Proxy层、Registry层、Cluster层、Monitor层、Protocol层
Config:配置层,封装配置文件配置的信息
Proxy:服务代理层,通过代理层生成客户端、服务端代理对象,通过代理对象来完成调用
Registry:注册中心层,服务提供者需要注册到注册中心,调用者需要从注册中心订阅需要的服务
Cluster:路由层,有可能一个服务在多台机器上都有,这一层可以做到负载均衡
Monitor:监控中心层,每一次调用信息都会发给监控层,监控层收到数据就会在界面上展示监控数据
Protocol:远程调用层,封装整个RPC调用。
- 远程通信层
Exchange:信息交换层,创建一个客户端,一个服务端,两者架起管道进行数据传输
Transport:传输层,真正传输数据的一层,Transport底层就是Netty框架
Serial:序列化层,整个传输过程中发送数据需要序列化,接收数据需要进行反序列化
3.2 服务暴露流程
ServiceBean实现了ApplicationListener接口的onApplicationEvent方法,spring容器启动完后会执行服务暴露方法
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
private void doExportUrls() {
// 获取注册中心地址
List<URL> registryURLs = this.loadRegistries(true);
Iterator i$ = this.protocols.iterator();
// 把服务暴露在协议端口。可以配置多个暴露端口
while(i$.hasNext()) {
ProtocolConfig protocolConfig = (ProtocolConfig)i$.next();
this.doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
doExportUrlsFor1Protocol方法关键代码:
// 获取执行器
Invoker<?> invoker = proxyFactory.getInvoker(this.ref, this.interfaceClass, registryURL.addParameterAndEncoded("export", url.toFullString()));
// 进一步包装
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 暴露invoker
Exporter<?> exporter = protocol.export(wrapperInvoker);
this.exporters.add(exporter);
可以看到,invoker实际上就是一个代理对象,封装了我们写的Service实现类
protocol.export过程:通过RegistryProtocol.export()暴露
先通过然后通过DubboProtocol暴露
DubboProtocol.export重要代码:
// 启动Netty服务器,监听20880
this.openServer(url);
this.optimizeSerialization(url);
然后通过ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);注册服务提供者
public static void registerProvider(Invoker invoker, URL registryUrl, URL providerUrl) {
// 封装invoker
ProviderInvokerWrapper wrapperInvoker = new ProviderInvokerWrapper(invoker, registryUrl, providerUrl);
// 获取服务名(service接口全类名:版本)
String serviceUniqueName = providerUrl.getServiceKey();
Set<ProviderInvokerWrapper> invokers = (Set)providerInvokers.get(serviceUniqueName);
if (invokers == null) {
providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet());
invokers = (Set)providerInvokers.get(serviceUniqueName);
}
// 把invoker放到内存
invokers.add(wrapperInvoker);
}
至此,服务暴露完成,梳理下服务暴露过程如下:
1 Spring容器启动完成,触发onApplicationEvent方法,执行doExport方法开始暴露服务
2 获取注册中心地址,把服务暴露在协议端口
3 获取执行器,然后进一步包装,通过RegistryProtocol.export暴露包装后的执行器
4 通过DubboProtocol启动netty服务器
5 通过ProviderConsumerRegTable注册服务提供者
3.3 服务引用流程
从ReferenceBean说起,这个类实现了FactoryBean类的getObject方法,通过工厂方式获取一个个引用的服务
public Object getObject() throws Exception {
return this.get();
}
public synchronized T get() {
if (this.destroyed) {
throw new IllegalStateException("Already destroyed!");
} else {
if (this.ref == null) {
this.init();
}
return this.ref;
}
}
看一下init重要代码
// 创建代理对象,map里存放注册中心地址、调用的方法、调用的接口、版本等
this.ref = this.createProxy(map);
createProxy方法:
if (this.urls.size() == 1) {
// 远程引用调用的接口
this.invoker = refprotocol.refer(this.interfaceClass, (URL)this.urls.get(0));
} else {
通过RegistryProtocol.refer()引用
doRefer方法:
// 订阅服务
directory.subscribe(subscribeUrl.addParameter("category", "providers,configurators,routers"));
// 进入Dubbo.refer方法,连接netty服务器,获取invoker,封装的有注册中心地址,dubbo协议地址
Invoker invoker = cluster.join(directory);
// 把invoker注册到内存
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
public static void registerConsumer(Invoker invoker, URL registryUrl, URL consumerUrl, RegistryDirectory registryDirectory) {
ConsumerInvokerWrapper wrapperInvoker = new ConsumerInvokerWrapper(invoker, registryUrl, consumerUrl, registryDirectory);
String serviceUniqueName = consumerUrl.getServiceKey();
Set<ConsumerInvokerWrapper> invokers = (Set)consumerInvokers.get(serviceUniqueName);
if (invokers == null) {
consumerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet());
invokers = (Set)consumerInvokers.get(serviceUniqueName);
}
invokers.add(wrapperInvoker);
}
服务引用流程过程:
1 ReferenceBean通过工厂模式调用getObject,然后创建代理对象
2 通过protocol引用远程服务
3 通过DubboProtocol和远程netty服务器建立连接,创建客户端
4 通过RegistryProtocol从注册中心订阅服务,把创建的invoker信息注册到注册表中
5 返回invoker代理对象
3.4 服务调用过程
1 代理对象把请求方法、请求参数进行封装
2 如果有Filter,执行Filter相关处理
3 执行ClusterInvoker,如果封装了多个Invoker会通过负载均衡策略选出一个invoker来执行
4 调用其他filter进行调用信息统计
5 协议invoker进行调用(DubboInvoker、RmiInvoker、HessianInvoker等)
6 Client通过Netty客户端连接到服务端发送请求