写在前面
以下参考自
Dubbo
官方文档;文章基于dubbo-spring-boot-starter
2.7.0,配置是基本配置;
首先看下服务导出这个过程在 Spring 中是如何被触发的:
Dubbo
服务导出过程始于 Spring 容器发布刷新事件,Dubbo
在接收到事件后,会立即执行服务导出逻辑。
待导出服务的实现类 UserServiceImpl代码 :
package com.duofei.service;
import org.apache.dubbo.config.annotation.Service;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 用户服务实现类
* @author duofei
* @date 2020/1/16
*/
@Service(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {
@Override
public List<String> getDefaultUserNames() {
return Stream.of("num1", "num2").collect(Collectors.toList());
}
}
文档中提到服务导出的入口方法是 ServiceBean
的 onApplicationEvent
方法,这首先得保证 ServiceBean
能够正常注入 Spring 容器中;那这部分工作由谁来完成呢?ServiceAnnotationBeanPostProcessor
类负责将带有 org.apache.dubbo.config.annotation.Service
注解的类注册为普通的 bean 和 ServiceBean
,也就是上面的 UserServiceImpl
最后会注入两个 bean 到 Spring 容器中,分别是 userServiceImpl
(class=com.duofei.service.UserServiceImpl
) 和 ServiceBean:com.duofei.service.UserService
(class=org.apache.dubbo.config.spring.ServiceBean
);所以最终服务导出的功能将由 ServiceBean
来完成。
导出服务
先看图:
组装 URL
需要导出的服务在被包装为 ServiceBean
放入Spring 容器,而 ServiceBean
本身又实现了很多能够获取回调的接口,其中服务导出需要关注的是实现 ApplicationListener<ContextRefreshedEvent>
接口的 onApplicationEvent
方法:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
if 判断语句中是来源于服务的配置,我们需要关注的是 export 方法:
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
// 以下来自于 父类 ServiceConfig
public synchronized void export() {
checkAndUpdateSubConfigs();
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) {
return;
}
if (delay != null && delay > 0) {
delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (path == null || path.length() == 0) {
path = interfaceName;
}
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
doExportUrls();
}
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
在判断完是否需要延迟加载以后,服务导出所做的工作:
- 加载注册中心URL;
- 多协议多注册中心导出服务;
URL 的组装非常复杂,但也很重要,它主要集中在 doExportUrlsFor1Protocol
方法的前半段,鉴于过长的代码,这里就不粘贴。
Dubbo
使用 URL 作为配置载体,采用 URL 作为配置信息的统一格式,所有拓展点都通过传递 URL 携带配置信息。
导出服务
导出服务的具体代码在 doExportUrlsFor1Protocol
的后半段。
导出服务到本地
当配置不是远程的,则导出到本地,查看 exporLocal
方法:
private void exportLocal(URL url) {
// 如果 URL 的协议等于 injvm ,说明已经导出到本地了,无需再次导出
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
// 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法,最终返回 InjvmExporter
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
这里使用的是 InjvmProtocol
,最终产生的 Exporter
是 InjvmExporter
,保存下 exporter
;
这里就利用到了 dubbo 的 SPI 机制,简单来说,在这里,它会调用传入的参数的 getUrl 方法,然后调取 URL 的 getProtocol 方法,通过得到的值,来选择具体的实现类。因为这里可以发现使用了 invjm 协议,自然会去调用 InjvmProtocol 实现类。具体的原理参考我这篇博文:Dubbo系列之Dubbo SPI
导出服务
如果配置不是本地,则导出到远程,在我的 UserService
配置中,scope 为 null,所以既导出到了本地,又导出到了远程,这个需要注意;
因为这里的协议为 registry ,所以自然会调用 RegistryProtocol 的 export 方法。
RegistryProtocol
的 export 方法中的 doLocalExport
方法调用涉及到了导出服务,然后后续的其它代码则负责服务注册:
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
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) {
// 创建 Invoker 为委托对象
final Invoker<?> invokerDelegete = new InvokerDelegate<T>(originInvoker, providerUrl);
// 调用 protocol 的 export 方法导出服务,此处调用的是 DubboProtocol
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
接下来跳转到 DubboProtocol
的 export
方法:
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//本地存根相关
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
// 启动服务器
openServer(url);
// 优化序列化
optimizeSerialization(url);
return exporter;
}
重点关注启动服务器代码:
private void openServer(URL url) {
// 查找服务器 key,host:port,用于标识当前的服务器实例
String key = url.getAddress();
// 客户端可以导出仅用于服务器调用的服务
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
// 从缓存中获取
ExchangeServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
// 创建服务器实例
serverMap.put(key, createServer(url));
}
}
} else {
// 服务器已创建,则根据 url 中的配置重置服务器
server.reset(url);
}
}
}
客户端会缓存服务器实例,使用 host:port
标识当前的服务器实例,服务器实例存在时,则会根据当前 URL 重置服务器,否则新建服务器实例:
private ExchangeServer createServer(URL url) {
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// 添加心跳检测配置到 url 中
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 获取 server 参数,默认为 netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
// 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
}
// 添加编码解码器参数
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
// 创建 ExchangeServer
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
// 获取 client 参数,可指定 netty, mina
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
创建服务器实例的详细代码就不再追述了,如果是 Netty,则最终会创建成功 NettyServer
。
向注册中心注册服务
这一步就是 RegistryProtocol
实现类中 export
方法剩下的代码所做的事了:
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 获取注册中心 url:zookeeper://
URL registryUrl = getRegistryUrl(originInvoker);
// 获取提供者 url : dubbo://
URL providerUrl = getProviderUrl(originInvoker);
// 获取可订阅的 url: provider://
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
// 监听用于重写URL
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// 导出服务
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 获取注册中心,此处是 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 注册到注册中心的 url
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//判断是否需要延迟发布
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
// 使用注册中心注册 提供者URL
register(registryUrl, registeredProviderUrl);
// Invoker 设置注册标记位
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
这里主要做的事情是向注册中心注册 URL ,一定要理解清楚 Dubbo
的 SPI
机制。
总结
分析过程中发现,一些可拓展点大量运用了 SPI
机制,你会发现,对于解耦以及拓展,该机制用起来真的很棒;但需要注意分析,否则有可能真的找不到具体调用的实现类;
dubbo
中拥有大量的配置,最终这些配置又会以 URL
为载体,ServiceBean
是针对单个服务的,每个服务都将会有以上过程,但又可以因为配置选择是否执行某些过程,以上过程大致分为:
- 配置检查;
- 组装 URL;
- 多协议多注册中心导出服务;
- 导出服务,如创建
NettyServer
; - 向注册中心注册服务,如注册服务
URL
到zookeeper
;
- 导出服务,如创建
过程的简单,并不意味着实现的容易,尤其是在多协议多注册中心导出服务这一步,很复杂,但仍然不丧失了灵活性,这还是多亏了 SPI
的自适应拓展机制;还有一些点并没有提到,如 Filter
是如何同 Invoker
构造链的,这个可以参考 ProtocolFilterWrapper
类的 export
方法,它介绍了如何将 filter
同 Invoker
构造成执行链;
推荐博文
参考博文
我与风来
认认真真学习,做思想的产出者,而不是文字的搬运工
错误之处,还望指出