前言
本周空闲时间利用了百分之六七十的样子. 主要将 Dubbo 官网文档和本地代码 debug 结合起来学习, 基本看完了服务导出, 服务引入以及服务调用的过程, 暂未涉及路由, 字典等功能. 下面对这一周的收获进行一下总结梳理.
一, 基于事件驱动的服务导出
提起服务导出, 不要被它的名字误导了, 通俗点说就是服务的暴露和注册. 服务的暴露是指将服务端的端口开放, 等待消费端来连接. 服务的注册即将服务信息注册到注册中心. 针对服务暴露和注册的具体流程, 可参见博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11275478.html , 讲述的比较详细, 暂不赘述.
注重提一下的是 Dubbo 启动服务暴露和注册的时机, 是采用的事件驱动来触发的, 跟 SpringBoot 有点神似. 这种通过事件驱动来触发特定逻辑的方式, 在实际开发工作中也可以灵活使用.
二, 服务引入及 SPI
对于 Dubbo 的 SPI 自适应扩展, 可参见博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/11219611.html, 但此篇文章当时写的比较浅显, 还未悟得全部.
下面以 Protocol 类为例, 看一下在 ServiceConfig 类中的成员变量 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 是什么样子.
- package org.apache.dubbo.rpc;
- import org.apache.dubbo.common.extension.ExtensionLoader;
- public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
- public void destroy() {
- throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
- }
- public int getDefaultPort() {
- throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
- }
- public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
- if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
- if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
- org.apache.dubbo.common.URL url = arg0.getUrl();
- String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
- if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
- org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
- return extension.export(arg0);
- }
- public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
- if (arg1 == null) throw new IllegalArgumentException("url == null");
- org.apache.dubbo.common.URL url = arg1;
- String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
- if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
- org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
- return extension.refer(arg0, arg1);
- }
- public java.util.List getServers() {
- throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
- }
- }
这就是 getAdaptiveExtension() 之后得到的代理类, 可见在初始化 ServiceConfig 时先获取的 protocol 只是一个代理 Protocol 类, 程序运行时再通过传入的 Url 来判断具体使用哪个 Protocol 实现类. 这才是 SPI 自适应扩展的精髓所在.
除此之外, 在通过 getExtension 方法获取最终实现类时, 还要经过 wrapper 类的包装. 详见 ExtensionLoader 类中的如下方法:
- private T createExtension(String name) {
- Class<?> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw findException(name);
- }
- try {
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- }
- injectExtension(instance);
- Set<Class<?>> wrapperClasses = cachedWrapperClasses;
- if (CollectionUtils.isNotEmpty(wrapperClasses)) {
- for (Class<?> wrapperClass : wrapperClasses) {
- instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
- }
- }
- initExtension(instance);
- return instance;
- } catch (Throwable t) {
- throw new IllegalStateException("Extension instance (name:" + name + ", class:" +
- type + ") couldn't be instantiated: " + t.getMessage(), t);
- }
- }
如果接口存在包装类, 则在第 16 行进行 wrapper 类的处理, 将当前 instance 封装进包装类中, 再返回包装类的实例, 即通过这一行代码实现了扩展类的装饰器模式改造.
此处同样以 Protocol 类为例, Url 中的协议是 registry, 那么我最终执行到 RegistryProtocol 的 export 方法时栈调用路径是这样的:
即中间经过了三层 Wrapper 的封装, 每层都有自己特定的功能, 且各层之间互不影响. Dubbo 在很多自适应扩展接口处加了类似这样的装饰扩展, 程序的可扩展设计还可以这样玩, Interesting!
服务引入的流程大体是这样的: 消费端从注册中心获取服务端信息, 封装成 Invoker, 再封装成代理类注入消费端 Spring 容器. 流程比较简单, 可自行根据上一节的内容 debug 调试.
三, 服务调用的疑问
之前未看 Dubbo 源码时一直有一个疑问: dubbo 的消费端代理类调用服务端接口进行消费时, 是通过 netty 将消息发送过去的, 服务端在接收到消息后, 是如何调用的服务端目标类中的方法? 反射吗? 反射可以调用到方法, 但是没法解决依赖的问题, 而且正常情况服务端调用应该也是 Spring 容器中已经实例化好的的服务对象, 那是如何通过 netty 的消息找到 Spring 中的对象的?
实际 dubbo 处理的很简单, 只要在服务暴露的时候将暴露的服务自己存起来就好了, 等消费端传过来消息的时候, 直接去 map 里面取, 取到的就是 Spring 中封装的那个服务对象, very easy.
服务调用的流程大体是这样的: 调用之后通过 client 远程连接到 server, 在 server 端维护了暴露服务的一个 map, 服务端接收到请求后去 map 获取 Exporter,exporter 中有服务端封装好的 Invoker, 持有 Spring 中的服务 bean, 最终完成调用. 中间还涉及很多细节, 比如 netty 的封装与调用, 序列化反序列化, 负载均衡和容错处理等.
小结
Dubbo 作为一个优秀的 rpc 服务框架, 其优势不止在于它的 rpc 过程, 还在于更多细节模块的实现以及可扩展的设计, 比如序列化处理, 负载均衡, 容错, netty 的线程调度, 路由, 字典... 内容挺多的, 后面打算针对 dubbo 的四大负载均衡算法做一下研究, 浅尝辄止, 不求甚解!
posted on 2020-03-29 00:02 淡墨痕 阅读 (...) 评论 (...) 编辑 收藏
来源: https://www.cnblogs.com/zzq6032010/p/12588538.html