Dubbo
服务的注册
DubboNamespaceHandler 标签扩展 在 dubbo-config -> dubbo-config-spring src/main/resources/META-INF/spring.handlers
1:注册一个 ServiceBean
通过 DubboComponentScan 扫描相对应包下面的类 注册一个 ServiceAnnotationBeanPostProcessor -> ServiceClassPostProcessor
ServiceClassPostProcessor
postProcessBeanDefinitionRegistry 调用 registerInfrastructureBean 注册一个 DubboBootstrapApplicationListener 监听的bean
postProcessBeanDefinitionRegistry 方法执行 registerServiceBeans 方法执行 registerServiceBean 通过 buildServiceBeanDefinition 注册一个 ServiceBean
2:ServiceBean 实现了 InitializingBean, DisposableBean,ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware
3:DubboBootstrapApplicationListener
* 元数据/远程配置信息的初始化
* 拼接url()
* 如果是dubbo协议,则启动netty server
* 服务启动在前 服务注册在后
DubboBootstrap.start 使用cas 保证只执行一次
exportServices 发布服务
一个dubbo服务需要发布几次,取决于协议的配置数,如果一个dubbo服务配置了3个协议,rest、webservice、dubbo。
sc.export();
doExport
doExportUrls 主要流程,根据开发者配置的协议列表,遍历协议列表逐项进行发布。
ServiceConfig.doExportUrlsFor1Protocol
* 生成url
* 根据url中配置的协议类型,调用指定协议进行服务的发布
* 启动服务
* 注册服务
生成一个 invoker对象 这个对象是 RegistryProtocol
invoker对象 用注册的URL 里面去包含一个 协议服务的URL 例如
registry://192.168.31.131:2181/org.apache.dubbo.registry.RegistryService?application=spring-boot-dubbo-sample-provider&dubbo=2.0.2&export=rest%3A%2F%2F192.168.238.1%3A20883%2Fcom.gupaoedu.springboot.dubbo.springbootdubbosampleprovider.services.IDemoService%3Fanyhost%3Dtrue%26application%3Dspring-boot-dubbo-sample-provider%26bind.ip%3D192.168.238.1%26bind.port%3D20883%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.gupaoedu.springboot.dubbo.springbootdubbosampleprovider.services.IDemoService%26metadata-type%3Dremote%26methods%3DgetTxt%26pid%3D8384%26qos.accept.foreign.ip%3Dfalse%26qos.enable%3Dtrue%26qos.port%3D8888%26release%3D2.7.8%26side%3Dprovider%26timestamp%3D1630243493484&pid=8384&qos.accept.foreign.ip=false&qos.enable=true&qos.port=8888®istry=zookeeper&release=2.7.8×tamp=1630243493483
前面是dubbo 后面export之后为协议服务信息 上面为dubbo协议发布服务
PROXY_FACTORY.getInvoker() 服务端执行真正的逻辑的地方 因为 exporterMap 存储的是 invoker对象
Invoke本质上应该是一个代理,经过层层包装最终进行了发布。当消费者发起请求的时候,会获得这个invoker进行调用。最终发布出去的invoker, 也不是单纯的一个代理,也是经过多层包装
4:RegistryProtocol
export 进行服务的发布和注册
registryUrl 服务注册的url
providerUrl 服务发布的url
doLocalExport 使用给定的协议(例如:dubbo,rest等)进行服务发布的方法
getRegistry 根据invoker中的url获取Registry实例: 例如:zookeeperRegistry、NacosRegistry、RedisRegistry
getUrlToRegistry 获取要注册到注册中心的URL
register(registryUrl, registeredProviderUrl); 服务注册
5:服务发布 DubboProtocol 由 doLocalExport 方法进入具体的服务发布协议类里面的 export 方法
exporterMap.put(key, exporter); 服务对应的 invoker 和 key(全类名+端口) 服务端调用的时候 exporter包装的 invoker 对象
openServer(url); 发布服务监听
createServer:
Exchangers.bind
HeaderExchanger new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
getTransporter().bind(url, handler)
NettyTransporter.bind()
AbstractServer.doOpen
doOpen() 用 netty 监听
optimizeSerialization(url);
6:服务注册 register(registryUrl, registeredProviderUrl);
ListenerRegistryWrapper(ZookeeperRegistry) 包装类 执行 registry.register(url);
FailbackRegistry.register 最终调用 ZookeeperRegistry.doRegister 方法
ProtocolListenerWrapper, 用于服务export时候插入监听机制
QosprotocolWrapper, 如果当前配置了注册中心,则会启动一个Qosserver.qos是dubbo的在线运维命令,
dubbo2.5.8新版本重构了telnet模块,提供了新的telnet命令支持,新版本的telnet端口与dubbo协议的端口是不同的端口,默认为22222
ProtocolFilterWrapper,对invoker进行filter的包装,实现请求的过滤接着,在getExtension->createExtension方法中,会对cacheWrapperClass集合进行判断,如果集合不为空,则进行包装
服务的消费
先通过 StaticDirectory 的负载均衡找到 某一个注册中心
然后通过 RegistryDirectory 的负载均衡找到 具体某个服务进行服务访问
最主要就是 Invoker 接口
里面是 MockClusterInvoker(FailoverCluster)
ReferenceAnnotationBeanPostProcessor 里面的 doGetInjectedBean
referenceBean.get() 进入 ReferenceConfig 的 init 方法
createProxy 方法 @Reference注入的一个对象实例本质上就是一个动态代理类,通过调用这个类中的方法,会触发handler.invoke(), 而这个handler就是InvokerInvocationHandler
1:REF_PROTOCOL.refer 进入到 QosProtocolWrapper(ProtocolFilterWrapper(ProtocolListenerWrapper(RegistryProtocol))) 经过包装 最后进入 RegistryProtocol.refer
最后包装一个 MockClusterWrapper(FailoverCluster()) 进入到 doRefer 方法
doRefer:
1:初始化了一个 RegistryDirectory,然后通过Cluster.join来返回一个Invoker对象
RegistryDirectory.subscribe: 订阅注册中心指定节点的变化,如果发生变化,则通知到RegistryDirectory。Directory其实和服务的注册以及服务的发现有非常大的关联.
这个方法里面会发布一个监听 RegistryDirectory既是目录 也是一个监听
registry.subscribe(url, this) //订阅 -> 这里的registry 注册中心为Zookeeper 那就是 zookeeperRegsitry
因为模板模式 实际进入的是 FailbackRegistry.subscribe 方法 这里可以实现重试机制
FailbackRegistry.subscribe:它的主要作用就是实现具有故障恢复功能的服务订阅机制,简单来说就是如果在订阅服务注册中心时出现异常,会触发重试机制
然后进入 zookeeperRegsitry.doSubscribe
ZookeeperRegistry.doSubscribe:这个方法是订阅,逻辑实现比较多,可以分两段来看 我们只需要关心指定service层发起的订阅即可
1:针对所有service层发起的订阅
2:针对所有service层发起的定于
toCategoriesPath 会返回三个结果,分别是/providers、/configurations、/routers。 也就是服务启动时需要监听这三个节点下的数据变化
构建一个listeners集合,其中key是NotifyListener,其中前面我们知道RegistryDirectory实现了这个接口,所以这里的key应该是RegistryDirectory //value表示针对这个RegistryDirectory注册的子节点监听
//调用notify方法触发监听
上述代码中,我们重点关注 listener.notify,它会触发一个事件通知,消费端的listener是最开始传递过来的 RegistryDirectory,所以这里会触发 RegistryDirectory.notify
RegistryDirectory.notify
refreshOverrideAndInvoker() -> refreshInvoker() 完成对服务提供着的地址包装成 invoker 的过程
refreshInvoker():当注册中心的服务地址发生变化时,会触发更新。
而更新之后并不是直接把url地址存储到内存,而是把url转化为invoker进行存储,
这个invoker是作为通信的调用器来构建的领域对象,所以如果地址发生变化,那么需要把老的invoker销毁,然后用心的invoker替代。
toInvokers(invokerUrls) invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
通过 protocol.refer 根据协议 找到合适的protocol 对象 初始化 构建 invoker
Dubbo协议下 最终调用 AbstractProtocol.refer -> protocolBindingRefer 返回一个 AsyncToSyncInvoker 对象
protocolBindingRefer :在构建 DubboInvoker 时,会构建一个 ExchangeClient,通过 getClients(url) 方法,这里基本可以猜到到是服务的通信建立
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
getClients 这里面是获得客户端连接的方法
判断是否为共享连接,默认是共享同一个连接进行通信
是否配置了多个连接通道 connections,默认只有一个共享连接
getSharedClient
buildReferenceCountExchangeClient 开始初始化客户端连接.
也就是说,dubbo消费者在启动的时候,先从注册中心上加载最新的服务提供者地址,然后转化成invoker,但是转化的时候,也会同步去建立一个连接。
而这个连接默认采用的是共享连接,因此就会存在对于同一个服务提供者,假设客户端依赖了多个@DubboReference,那么这个时候这些服务的引用会使用同一个连接进行通信。
initClient 进入到初始化客户端连接的方法了,根据url中配置的参数进行远程通信的构建
Exchangers.connect 创建一个客户端连接
NettyTransport.connect 使用netty构建了一个客户端连接
简单总结一下,RegistryDirectory.subscribe,其实总的来说就相当于做了两件事
1:定于指定注册中心的以下三个路径的子节点变更事件
2:触发时间变更之后,把变更的节点信息转化为Invoker
2:cluster.join(directory)
AbstractCluster:this.buildClusterInterceptors(this.doJoin(directory), directory.getUrl().getParameter("reference.interceptor"))
把 RegistryDirectory 作为参数传递进去了,那么意味着返回的 invoker 中可以拿到真正的服务提供者地址,然后进行远程访问
进入到 AbstractCluster.join 返回一个 包装类 MockClusterInvoker(FailoverClusterInvoker) 用于对服务提供着的负载均衡,容错的 RegistryDirectory 的 invoker
2:Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
把刚才doRefer方法生成的含有 RegistryDirectory invoker 包装成 StaticDirectory 并生成 ZoneAwareCluster 用于对注册中心的负载均衡与容错 invoker
3:生成一个 InvokerInvocationHandler 的 invoker
InvokerInvocationHandler.invoker
其中invoker这个对象 应该是: MockClusterInvoker,因为它是通过 MockClusterWrapper 来进行包装的。
1:AbstractCluster$InterceptorInvokerNode.invoker
为什么到这个类,看过前面生成动态代理过程的同学应该都知道,invoker会通过一个Interceptor进行包装。构建了一个拦截器链。
这个拦截器是的组成是: ConsumerContextClusterInterceptor -next-> ZoneAwareClusterInvoker
其中before方法是设置上下文信息,接着调用interceptor.interceppt方法进行拦截处理
2:ZoneAwareClusterInvokerAbstractClusterInvoker.invoke
3:ZoneAwareClusterInvoker.doInvoke
ZonAwareCluster,就是之前我们说过的,如果一个服务注册在多个注册中心,那么消费者去消费时,会根据区域进行路由,选择一个注册中心进行服务消费
4:MockClusterInvoker
1. 是否客户端强制配置了mock调用,那么在这种场景中主要可以用来解决服务端还没开发好的时候
2. 是否出现了异常,如果出现异常则使用配置好的Mock类来实现服务的降级
5:AbstractClusterInvoker.invoke
6:AbstractCluster$InterceptorInvokerNode.invoker
再次进入到这个方法中,不过此时的调用链路发生了变化 这个拦截器是的组成是: ConsumerContextClusterInterceptor -next-> FailoverClusterInvoke
继续进入到 AbstractClusterInvoker 中的invoke,但是此时 AbstractClusterInvoker 是通过 FailoverClusterInvoker 来实现的,所以再次调用doInvoke时,会调用 FailoverClusterInvoker 中的 doInvoke 方法
FailoverClusterInvoker.doInvoke
AsyncToSyncInvoker.invoke
经过装饰器、过滤器对invoker进行增强和过滤之后,来到了AsyncToSyncInvoker.invoke方法,这里采用的是异步的方式来进行通信
AbstractInvoker.invoke
DubboInvoker继承了AbstractInvoker这个抽象类,而DubboInvoker中没有invoke这个方法,所以这里调用的是AbstractInvoker.invoke方法。
DubboInvoker.doInvoke 最终进入 DubboInvoker
进入到DubboInvoker这个方法中,那么意味着正式进入到服务通信层面了。
前面的很多细节分析,无非就是做了三件事
多注册中心的拦截以及分发、负载均衡以及集群容错、请求过滤和包装
发起远程请求。
CompletableFuture<AppResponse> appResponseFuture = currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
ReferenceCountExchangeClient.request
currentClient还记得是一个什么对象吗?它实际是一个ReferenceCountExchangeClient(HeaderExchangeClient())
所以它的调用链路是ReferenceCountExchangeClient->HeaderExchangeClient->HeaderExchangeChannel->(request方法)
最终,把构建好的RpcInvocation,组装到一个Request对象中进行传递 channel.send(req);
ReferenceCountExchangeClient 用来记录调用次数
HeaderExchangeClient 用来开启心跳机制、以及启动失败重连任务
Dubbo 时间轮
在ZookeeperRegistory 的父类 FailbackRegistry 中
FailbackRegistry.subscribe:它的主要作用就是实现具有故障恢复功能的服务订阅机制,简单来说就是如果在订阅服务注册中心时出现异常,会触发重试机制
Dubbo中的扩展点
指定名称的扩展点 ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("name");
找到 Protocol 的全路径名称, 在/META-INF/dubbo/intenal 在指定文件中找到“name”对应的实现类.
ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalance);
在resource/META-INF/dubbo/ org.apache.dubbo.rpc.cluster.LoadBalance 实现扩展点
源码:
getExtension
createExtension
getExtensionClasses 返回一个map对象, key = name(扩展点的名字), clazz=name对应的扩展点类
loadExtensionClasses
根据默认配置的查找路径进行查找并解析strategies 对应的是不同扫描路径下的策略
META-INF/dubbo
META-INF/dubbo/internal
META-INF/services
总结:加载指定路径下的文件内容,保存到集合中会对存在依赖注入的扩展点进行依赖注入会对存在Wrapper类的扩展点,实现扩展点的包装
自适应扩展点 ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
在运行期间,根据上下文来决定当前返回哪个扩展点。
getAdaptiveExtension
@Adaptive 该注解可以声明在类级别上也可以声明在方法级别实现原理如果修饰在类级别,那么直接返回修饰的类如果修饰在方法界别,动态创建一个代理类(javassist)
Protocol protocol =ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
dubbo:// -> Protocol$Adaptive -> DubboProtocol
registry:// -> Protocol$Adaptive -> RegistryProtocol
provider:// -> Protocol$Adaptive -> ProviderProtocol
如果当前加载的扩展点存在自适应的类,那么直接返回否则,会动态创建一个字节码,然后进行返回.
激活扩展点 ExtensionLoader.getExtensionLoader(Protocol.class).getActiveExtension()
相当于Spring中的conditional
Activate 注解中没有 value 属性的就直接加载 有就不加载
扩展点的特征: 在类级别标准 @SPI(RandomLoadBalance.NAME)`.其中,括号内的数据,表示当前扩展点的默认扩展点。