参考:
基本组成:
Client (客户端):服务调用方。
Server(服务端):服务提供方。
Client Stub(客户端存根):存放服务端的消息,负责将客户端的请求参数打包成网络消息,然后通过网络发送给服务提供方。
Server Stub(服务端存根):接收客户端发送的消息,再将客户端请求参数打包成网络消息,然后通过网络远程发送给服务方。
其它:
- 序列化协议:
- 网络协议:TCP, HTTP 等(TCP 更快(Dubbo),因为 HTTP header 内容过多(SpringCloud))
- 注册中心:Zookeeper CP 保证一致性,Eureka AP 保证可用性
Dubbo
zookeeper 作为注册中心:
- 提供者节点: /GLOBAL_REGISTRY/tuling.dubbo.server.UserService/providers
- 存放的内容是一个所有注册的提供者的 URL List [dubbo://127.0.0.1:12345/tuling.dubbo.server.UserService?application=myapp1&dubbo=2.0.2&protocal=dubbo&..., dubbo://..... ]
- 在提供者中进行负载均衡
- 消费者节点:/GLOBAL_REGISTRY/tuling.dubbo.server.UserService/consumers
- 存放的内容是一个所有注册的提供者的 URL List [consumer://127.0.0.1:12345/tuling.dubbo.server.UserService?application=myapp2&dubbo=2.0.2&protocal=dubbo&..., consumer://..... ]
- 提供者动态配置节点:/GLOBAL_REGISTRY/tuling.dubbo.server.UserService/configurators
- 消费者路由策略节点:/GLOBAL_REGISTRY/tuling.dubbo.server.UserService/routers
网络协议是 TCP
序列化协议是 Hessian
Dubbo序列化支持java、compactedjava、nativejava、fastjson、dubbo、fst、hessian2、kryo,其中默认hessian2。其中java、compactedjava、nativejava属于原生java的序列化。
- dubbo 序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它。
- hessian2 序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的,它是dubbo RPC默认启用的序列化方式。
- json 序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且 json这种文本序列化性能一般不如上面两种二进制序列化。
- java 序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。
JDK Serializable中通过serialVersionUID控制序列化类的版本,如果序列化与反序列化版本不一致,则会抛出java.io.InvalidClassException异常信息,提示序列化与反序列化SUID不一致。
dubbo序列化主要由Serialization(序列化策略)、DataInput(反序列化,二进制->对象)、DataOutput(序列化,对象->二进制流) 来进行数据的序列化与反序列化。
SPI 机制
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。
Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。
-
META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
-
META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
-
META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。
Java SPI
就是约定一个目录,根据接口名去那个目录找到文件,文件解析得到实现类的全限定名,然后循环加载实现类和创建其实例。
想一下 Java SPI 哪里不好
相信大家一眼就能看出来,Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。
所以说 Java SPI 无法按需加载实现类。
Dubbo SPI 示例
robot 接口及它的两个实例
需要在 Robot 接口上标注 @SPI 注解。
@SPI public interface Robot { void sayHello(); }
接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。
public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } }
Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。
# robot 接口实现类1
optimusPrime = org.apache.spi.OptimusPrime
# robot 接口实现类2 bumblebee = org.apache.spi.Bumblebee
下面来演示 Dubbo SPI 的用法:
public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); } }
测试结果如下:
源码分析:
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
- 通过 getExtensionClasses 获取所有的拓展类名称
- 通过反射创建拓展对象
- 向拓展对象中注入依赖
- 将拓展对象包裹在相应的 Wrapper 对象中