Dubbo服务的注册与发现
前言
最近参与的项目是一个基于Dubbo
的项目,在开发过程中有些同事对于Dubbo
服务的注册与发现机制,似乎不太了解。所以我抽空和他简单聊了聊我对Dubbo
机制的了解。
正文
Java SPI
SPI
即服务提供商接口,它是一种动态加载服务实现者的机制,通过JavaSPI
我们可以优雅地根据一个接口来获取该接口的所有实现类。
值得注意的是使用的Java SPI
需要有以下的前提:
1.需要在 classpath
下创建一个目录,该目录命名必须是:META-INF/service
2.在该目录下创建一个 properties
文件,该文件需要满足以下几个条件 :
- 文件名必须是扩展的接口的全路径名称
- 文件内部描述的是该扩展接口的所有实现类
- 文件的编码格式是
UTF-8
3.通过 java.util.ServiceLoader
的加载机制来发现
META-INF
文件夹相当于一个信息包,目录中的文件和目录获得Java 2
平台的认可与解释,用来配置应用程序、扩展程序、类加载器和服务。
我们最常用的mysql
驱动 mysql-connector-java
就是借助JavaSPI来实现的:
Java
定义了java.sql.Driver
用于API
接口用于进行数据库连接,但是没有去实现它。Java
通过SPI
机制可以适配不同的数据源,只要其驱动类实现java.sql.Driver
接口即可。
但是JavaSPI
存在一个弊端,Java SPI
在查找扩展实现类的时候遍历 SPI
的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。所以说 Java SPI
无法按需加载实现类。
Dubbo SPI
Dubbo
发现服务实现者的方式也是借助SPI
的设计思想,但与JavaSPI
还是有区别的。
Dubbo
对配置文件目录的约定,不同于 Java SPI
,Dubbo
分为了三类目录:
- META-INF/services/ 目录:该目录下的
SPI
配置文件是为了用来兼容Java SPI
。 - META-INF/dubbo/目录:该目录存放用户自定义的
SPI
配置文件。 - META-INF/dubbo/internal/ 目录:该目录存放
Dubbo
内部使用的SPI
配置文件。
Dubbo
对外接口和其实现类的对应关系是配置在文件中的,ExtensionLoader
会解析文件将数据添加到一个Map
中,我们把获取对外接口的实现类的过程称为获取扩展点,它可以分为三种:
自适应扩展点:它是根据class
进行匹配,同一个class
只能有一个
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension();
指定名称的扩展点
ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name);
激活扩展点
ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);
这里以 Dubbo Protocol(RPC协议) 为例
Dubbo
官方支持10种通讯协议,Dubbo
通过SPI
机制来实现适配不同协议
<dubbo:protocol name="dubbo" port="20880" threads="1000" />
Dubbo Protocol
会采用自适应的方式进行扩展
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Dubbo
默认提供一个dubbo
的Protocol
扩展点
@SPI("dubbo")
: 用来定义扩展点@Adaptive
:将目标标记实现了一个适配器类,会通过上面讲的三种方式去获取扩展点,如@Adaptive({Constants.PROXY_KEY})
为自适应扩展点
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
Dubbo SPI 的工作流程
Dubbo SPI
不会一下子去将实现类全部实例化,只会在使用到的时候才会去使用ExtensionLoader
去获取扩展点。并且通过反射和缓存机制,进一步提升了动态加载服务实现者的性能。
ExtensionLoader
去加载扩展点的流程可以概括为:
1.通过类名得到一个ExtensionLoader
2.通过定义的名字从ExtensionLoader
找到实现类实例
- 如果存在缓存,直接从缓存中获取实例
- 如果没有缓存,通过反射建个实例,然后执行 set
方法依赖注入。如果有找到包装类的话,再包一层。如果标记了@Adaptive
,会将实例放在缓存中。
Dubbo 注册和引用服务的方式
我们在实际的使用过程中并不需要关心 Dubbo SPI
,Dubbo
本身对Dubbo
注册和引用服务进行了更加简洁地封装。
Dubbo引用外部服务
@Reference
用在消费端,表明使用的是服务端的什么服务
@Reference(interfaceClass = IUserService.class,retries=0,check=false,timeout = 50000,mockClass=MockUserService.class)
private IUserService iUserService;
可等同于在xml
中这样配置
<!-- 消息推送 -->
<dubbo:reference id="user" interface="com.luo.api.service.IUserService" retries="0" timeout="50000"
check="false"
mock="com.luo.api.service.mockimpl.MockUserService" />
Dubbo注册对外服务
@Service
用在服务提供者中,在类或者接口中声明。
@Service(cluster ="failfast")
public class UserServiceImpl implements IUserService{
}
可等同于在xml
中这样配置
<!-- dubbo管理平台接口 -->
<bean id="UserService" class="com.luo.producer.rpcservice.UserServiceImpl" />
<dubbo:service interface="com.luo.api.service.IUserService" ref="UserService" cluster="failfast"/>