最近在看RPC框架,选择先看dubbo,再看netty,最后手写一个RPC的学习路线,结合雷神关于dubbo的讲解,对源码流程进行一个简单的记录
所谓RPC就是远程服务调用的意思,那么dubbo怎么完成远程调用的呢?
原理图如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fEwo2Az-1623312993257)(java源码解析之dubbo.assets/dubbo-architecture.jpg)]
Provider:被调用方
Consumer:调用方
Registry:注册中心
Container:启动容器
服务暴露:Provider注册到Registry中,并绑定端口,监听请求
服务引用:Consumer从Registry中获取到
以SpringBoot框架整合为例
导入dubbo的starter依赖
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
我们知道,通过@Service(dubbo的不是spring的)可以让被注释的类被识别为Provider,并将服务暴露出去
那么dubbo是怎么做到的呢?
@EnableDubbo
—>@Import(DubboComponentScanRegistrar.class)
DubboComponentScanRegistrar
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
registerReferenceAnnotationBeanPostProcessor(registry);
}
registerServiceAnnotationBeanPostProcessor实际上是ServiceAnnotationBeanPostProcessor,其实现了BeanDefinitionRegistryPostProcessor接口
熟悉Spring源码的可以知道,BeanDefinitionRegistryPostProcessor是一个FactoryPostProcessor
所以会在invokeBeanFactoryPostProcessors(beanFactory)的时候被调用
FactoryPostProcessor会被调用其postProcessBeanDefinitionRegistry
方法,也就意味着registerServiceAnnotationBeanPostProcessor的该方法会被调用
而registerServiceAnnotationBeanPostProcessor的postProcessBeanDefinitionRegistry方法就会扫描包,将带有@Service注解的类注册为一个Bean,类型为ServiceBean
加入到BeanDefinitionMap中
至此,@Service注解被解析完成,创建该ServiceBean的时候,就会进行服务暴露
我们知道@Service注释了一个Provider,需要被注册到registry中才能被Consumer感知,并且还要启动netty监听Consumer的请求,即注册到注册中心和开启服务器称为服务暴露,那么dubbo怎么做的?
接着上面的,此时BeanDefinitionMap中已经有Provider的信息了
因此Spring执行```finishBeanFactoryInitialization(beanFactory)``时会创建单例Bean
熟悉 IOC 原理的可以知道,创建 Bean 时会经过 实例化-》初始化
的过程,并且在初始化时,会调用Bean的afterPropertiesSet()
方法
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
·····························
}
else {
((InitializingBean) bean).afterPropertiesSet();
}
}
从注释解析过程中我们知道,Provider对应的Bean的类型为ServiceBean
,其通过afterPropertiesSet()方法中调用了export()
完成暴露
但debug时并不是在这一步完成的暴露,可以发现ServiceBean
还实现了ApplicationListener
接口
因此Refresh
结束后还会调用onApplicationEvent
,其中也完成了暴露
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
debug时,暴露入口主要是这个
从上述入口进入,会来到doExportUrls()
方法,该方法视为总暴露的入口,接下来我们看看该方法如何完成注册中心注册以及服务器开启
左边方法栈+行号
找到此处的protocol是通过SPI机制获得的,在此不是重点,但可以知道的是此时的protocol指的是RegistryProtocol
,该类的export中的register
方法能够完成注册中心的注册
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
···························
if (register) {
register(registryUrl, registedProviderUrl);
}
}
而RegistryProtocol
的export
中还有doLocalExport(originInvoker);
方法,完成服务器的开启
@SuppressWarnings("unchecked")
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
可以看到同样调用了protocol.export
此时的protocol
为DubboProtocol
DubboProtocol
的export
方法如下
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
·······························
openServer(url);
optimizeSerialization(url);
return exporter;
}
开启服务器的入口为openServer()
就不展开了,会一直调用到nettyServer
并bind
一个端口
至此服务暴露
就完成了,我们回到第 6 步,这里将exporter
即provider
放入一个集合中,用于接受请求后进行匹配,这在服务调用时会用到
我们知道,Consumer只需要像@Autowired一样使用@Reference注解,Spring会为其修饰的变量注入一个代理对象,通过该代理对象就能够在调用方法时调用远程对应的方法,那么这个过程是如何实现的呢?
@EnableDubbo
方法了,如果使用的话,则会像扫描@Service一样,扫描类上是否有@ReferencepopulateBean
方法完成属性注入AutowireAnnotationBeanPostProcessor
来进行自动注入,如果注入过程中发现待注入的bean没有被实例化,那么会先实例化(这部分属于循环依赖的原理)AutowireAnnotationBeanPostProcessor
的处理器,通过debug我们发现了,为ReferenceAnnotationBeanPostProcessor
ReferenceAnnotationBeanPostProcessor
开始源码解析FactoryBean
的接口,因此可以通过对getObject方法打断点来查看整个方法调用链
orderService
进行属性注入时,通过ReferenceAnnotationBeanPostProcessor
解析了@Reference
,从而创建了对应的ReferenceBean
上述记录了一下如何探索源码的,那么接下来就从头分析源码,我们以下述代码为例,往order服务中注入user服务,而user服务是一个已经暴露好的Provider
@Service
@Component
public class OrderServiceImp implements OrderService {
@Reference
UserService userService;
@Override
public String getOrder(Long orderId) {
return "已获取到订单";
}
@Override
public String getUser(Long userId){
return userService.getUser(userId);
}
}
finishBeanFactoryInitialization
根据BeanDefinitionMap(BDM)创建Bean
实例化orderServiceImp后,会调用populateBean
方法进行属性输入
populateBean
方法中,对每个属性的xx
注解会根据对应的 xxAnnotationBeanPostProcessor
进行解析,该过程对应下述代码段的bp
变量,主要调用postProcessPropertyValues
方法完成解析
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
··········
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
可以debug查看bp可能的取值,其中就包括了常见的@Autowired
和@Reference
的PostProcessor
对于@Reference
注解,会调用ReferenceAnnotationBeanPostProcessor
完成注入,其对应的postProcessPropertyValues()
最终会调用ReferenceBean
的getObejct()
方法获取一个代理对象
而获取时要进行初始化,调用链为getObejct()
->get()
->init()
,我们从init()
中看看dubbo是怎么生成一个代理对象的
init()
整个方法都在往map
中设置属性,最后调用createProxy(map)
,看方法名可知,该方法能够根据map创建一个代理对象
在createProxy(map)
中我们又看见类似与服务暴露时的protocol对象,invoker = refprotocol.refer(interfaceClass, urls.get(0))
,同样的,这里也是先调用了RegistryProtocol
的refer
方法然后调用DubboProtocol
的refer
方法,继续探究
先进入RegistryProtocol
的refer()->dorefer()
registry.register(subscribeUrl)
将subscribeUrl
注册到注册中心,subscribeUrl为自己consumer的url
directory.subscribe(subscribeUrl)
根据subscribeUrl
向注册中心获取provider
的地址
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
·······························
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
我们看看directory.subscribe()->dosubscribe()
做了什么
zkClient.addChildListener()
传入providers路径path
以及zk
,能够获取到providers的地址children
从注册中心获取到地址后,会进入到DubboProtocol
的refer
方法(发布与订阅模式,如果获取不到地址是不会跳转到DubboProtocol的,因此debug时得开启Provider)
该方法最终能够获得一个invoker
,其中封装了与provider服务器建立的连接
@Override
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
getClients(url)
就是通过url与provider服务建立起连接,注意了,在服务引用时dubbo就已经将连接建立起来
最终,invoker会被返回到第六步,作为ReferenceBean的代理对象保存在Spring容器中
也就意味着,当orderServiceImp
要调用userService
的方法时,会使用到其建立好的连接,直接向provider发送请求获取方法调用的结果,这部分在服务调用部分会说明
当调用到userService.getUser(userId)
时就会执行代理对象的invoke
方法(Proxy动态代理的基础内容不赘述)
进而调用invoker.invoke(new RpcInvocation(method, args)).recreate()
,可以见得RpcInvocation对象将方法名和参数存了起来
进而list(invocation)
从directory
获得能够处理invocation的invokers,并进行doInvoke
相当于directory
在服务引用阶段,就将invocation与invokers做了某种关联,以至于可以匹配
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
LoadBalance loadbalance = null;
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && !invokers.isEmpty()) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
doInvoke()
为服务调用的核心方法
Invoker
传入负载均衡器loadbalance
,根据负载均衡策略从copyinvokers
中选出一个invoker
invoker.invoke(invocation)
进行执行,其中invocation有对应的方法名和参数
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
//
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
Result result = invoker.invoke(invocation);
//
return result;
} catch (RpcException e) {
//
而invoker.invoke(invocation)
就是使用服务引用时绑定好的client
与Provider
进行交互
需要注意的是,Provider
只是暴露了整个服务类,并没有暴露具体方法,因此当Consumer
通过invoker将invocation发送给Provider时,Provider需要根据invocation传进来的方法名找到服务类中对应的方法进行处理
我们给Provider被调用方法打上断点,令Consumer进行调用,即可看到Provider的调用链
根据netty的原理,现在channel已经创建好了,那么channel上的事件一旦发生,会交由handler进行处理
请求先是到达HeaderExchangeHandler
,其中handleRequest
方法完成请求处理
handleRequest
调用了Object result = handler.reply(channel, msg)
获取最终结果,handler为DubboProtocol
getInvoker
选出invokermethod
invoker.invoke
完成远程调用至此,服务调用完结
服务暴露
服务引用
服务调用
看完dubbo源码,可以感觉到RPC其实整个流程不算很复杂,那么根据上述小结可以仿着写一个RPC框架。在这里参考的是javaguide的rpc框架,主要进行一个解析,并对比其与dubbo哪些地方不同
我们先简化RPC三个步骤主要完成哪些事情
服务暴露
服务引用
服务调用
自定义一个BeanPostProcessor,并重写 postProcessBeforeInitialization
服务器是手动主动完成启动的,通过从容器中获取nettyRpcServer调用其start方法
至此服务暴露功能结束
自定义一个BeanPostProcessor(与服务暴露用同一个),并重写postProcessAfterInitialization
new RpcClientProxy(rpcClient, rpcServiceConfig)
生成一个代理对象并绑定ClientdeclaredField.set(bean, clientProxy)
一个BeanPostProcessor基本完成了引用的功能
invoke方法中,先根据参数以及方法名构建一个请求rpcRequest
rpcRequestTransport.sendRpcRequest(rpcRequest);
异步将请求发送给当前代理对象绑定好的client
consumer与provider服务器建立连接后,consumer发送数据,provider服务器监听到事件,转发给某个eventloop中的Handler完成处理,在这里是NettyRpcServerHandler
NettyRpcServerHandler.channelRead
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OLpDxd1P-1623312993284)(java源码解析之dubbo.assets/image-20210610131431594.png)]
其中,rpcRequestHandler.handle为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VXy2lZgp-1623312993286)(java源码解析之dubbo.assets/image-20210610131914845.png)]
这里的serviceProvider是服务暴露时,在Bean后置处理器处理时存好的