URL
Dubbo用URL 作为配置总线,贯穿整个体系。
- Dubbo 采用URL 的方法来作为约定的参数类型,被称为公共契约;通过URL 来交流、交互使得代码更加规范化、形成一种统一的格式;
- 同时,使用URL作为公共约束充分利用了我们对已有概念的印象,通俗易懂并且容易拓展。
参数
protocol: //username:password@host:port/path?key=value&key=value
- protocol:Dubbo中的各种协议,比如:dubbo、thrift、http
- username/password:用户名/密码
- host/port:主机/端口
- path:接口的名称
- parameters:参数键值对
总结
- 根据 URL 参数判断是本地暴露还是远程暴露 {
- 本地暴露:通过 exportLocal(), 构建本地 URL 并将 protocol 协议为 inJvm。
- 远程暴露:通过 RegistryProtocol.export() 注册中配置中心表中
- 通过 ProxyFactory.getInvoker() 生成 Invoker 并包装
- 通过 protocol.export() 生成 exporter,并将 serviceKey、exporter 存放在 exporterMap 中
源码总结
- serviceConfig.export() 加载配置信息,是否可以暴露服务,是否需要延迟注册服务;
- 调用doExport() 继续获取配置信息,检查配置
- 调用doExportUrls() 将服务的多个协议 暴露注册到多个注册中心
- 调用doExportUrlsFor1Protocol 将配置信息存放到map中,构建URL,从URL中获取scope 为空需要调用exportLocall 执行本地暴露、接着执行远程暴露
- 本地暴露和远程暴露,都会执行ProxyFactory.getInvoker()、 protocol.export()
- 根据ProxyFactory.getInvoker()判断是本地暴露还是远程暴露(有RegistryURL),通过URL的不同protocol协议返回不同的Invoker 对象,例如:injvm、dubbo等协议。这里就解释了为什么需要封装Invoker,因为Invoker 可能多种方式实现例如:本地暴露、远程暴露、集群暴露等
- 再通过包装Invoker 传递给protocol.export() ,根据Invoker里面的URL 参数得知具体的protocol 协议,通过Dubbo 的SPI 机制加载具体的实现类,最后返回exporter例如:InjvmProtocol.export()、 DubboProtocol.export()
- 并存放ExportMap,key:服务接口的全限定名, value:exporter (包装的Invoker)缓存。这里避免了注册中心宕机带来的印象,会从本地缓存中加载。
服务暴露流程
从代码流程的角度:
- 检测配置,如果有些配置空的话会默认创建,并且组装成URL ;
- 暴露服务,暴露到本地服务和远程的服务
- 注册服务至配置中心
从对象构建转换的角度:
- 将服务实现类通过Proxy组件转成Invoker
- 将Invoker通过具体的协议Protocol转换成Exporter,然后将Exporter通过Registry注册到注册中心。
Dubbo 为什么默认使用 javassist
- 代理:Javassist、JDK代理、ASM、Cglib
- Javassist代理,简单、快捷、字节码生成方便
- ASM 比 Javassist 更快,但是没有快一个数量级,而 Javassist 只需用字符串拼接就可以生产字节码,而 ASM 需要手工生成,成本较高,比较麻烦
源码解析过程
serviceConfig.export
- 加载配置信息,是否可以暴露服务,是否需要延迟注册服务
- 继续调用doExport方法
- 配置信息列表
public synchronized void export() {
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(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
doExport
- 继续获取配置信息,检查配置
- 继续调用doExportUrls方法
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
// 加载配置信息
···················
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
doExportUrls
- 将服务的多个协议 暴露注册到多个注册中心
- loadRegistries:根据配置组装成注册中心相关的URL
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=7960&qos.port=22222®istry=zookeeper×tamp=1598624821286
- 继续调用doExportUrlsFor1Protocol方法
private void doExportUrls() {
// 获取当前服务的注册中心,由于是个List可以有多个服务中心
List<URL> registryURLs = loadRegistries(true);
// 遍历多个协议,每个协议都需要向这些注册中心注册
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
doExportUrlsFor1Protocol
- 将配置信息存放到map中,构建URL
- 如果从URL 中获取到scope 为空,需要调用exportLocal 方法进行本地暴露
- 如果有注册中心会进行远程暴露,有监控中心会添加之后向其汇报
- 通过ProxyFactory.getInvoker() 将具体的实体类转化成Invoker
- 通过protocol.export() 将Invoker包装转成Exporter
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
if (methods != null && !methods.isEmpty()) {
···········
}
// 将配置信息存放到map中,构建URL
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
··············
String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
// scopre 为null,会进行本地暴露
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && !registryURLs.isEmpty()) {
// 有注册中心会进行远程暴露
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
// 有监控中心,添加后向其汇报
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// 将具体的实现类转换成Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 将其包装后转化成exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
exportLocal
- 构建本地的URL,其中的protocol协议变成 injvm
- 具体实现类通过ProxyFactory.getInvoker() 转换成Invoker,具体默认使用Javassist生成代理Invoker
- 本地调用的格式,相比远程暴露多加了RegistryURL,直接调用Invoker即可,通过不同的参数类型返回不同的Invoker
proxyFactory.getInvoker(ref, (Class) interfaceClass, local);
- Invoker 包装通过Protocol.export() 转化成exporter,会根据Invoker的参数选择不同的实现类,本地暴露会执行InjvmProtocol.export()
@SuppressWarnings({"unchecked", "rawtypes"})
private void exportLocal(URL url) {
// 构建本地的URL
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
// 将具体实现类 -> invoker -> exporter
Exporter<?> exporter = protocol.export(
// 调用本地方法的形式, 相比远程暴露少了registryURL
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
为什么需要本地暴露?
- 因为可能存在同一个JVM 内部引用自身服务的情况,因此暴露本地服务在内部调用的时候可以直接消费同一个JVM的服务, 避免了网络间的通信。
远程暴露
- 会先根据ProxyFactory.getInvoker() 里面的参数RegistryURL 拼接URL
- 通过URL的参数,可以得知先通过registry 协议找到RegistryProtocol.export() ,会根据Invoker里面的URL 具体protocol 协议执行不同的具体类,例如export=dubbo:// 会执行DubboProtocol.export()
proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
RegistryProtocol.export()
- 将包装的Invoker 转化成 exporter
- 获取注册中心的相关配置,如果需要注册则向配置中心注册
- 维护一个Map记录服务提供者,key:服务接口全限定名,value:包装过的Invoker
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// export invoker 根据dubbo:// 的暴露,并且还会打开端口等操作
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
// 获取注册中心的 URL 比如 zookeeper://127.0.0.1:2181/
URL registryUrl = getRegistryUrl(originInvoker);
// registry provider 根据URL 加载 Registry的实现类,比如:zookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 获取注册服务提供者的URL
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//to judge to delay publish whether or not
boolean register = registedProviderUrl.getParameter("register", true);
// 将提供者信息注册到服务提供者与消费者注册表中
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
if (register) {
// 向注册中心注册服务
register(registryUrl, registedProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// 获取订阅 URL
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
// 创建监听器,监听override
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
DubboProtocol.export()
- 根据Invoker获取 URL,将 key 与 Invoker 关联构造出 exporter 将其存储到 exporterMap 中,然后初始化配置
- 调用 openServer() 更新或者创建 server
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 获取 URL
URL url = invoker.getUrl();
// export service 存放到map中
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispatching event
····················
// 打开server
openServer(url);
optimizeSerialization(url);
return exporter;
}
// openServer
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client can export a service which's only for server to invoke
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
// 通过ip地址获取server
ExchangeServer server = serverMap.get(key);
if (server == null) {
// 没有则创建createServer
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
proxyFactory.getInvoker
- 主要作用是封装Invoker对象,因为export()会根据Invoker里面的URL 的protocol参数执行不同的protocol.export()
- 通过proxyFactory.getInvoker() 里面的具体实现类创建例如:JDK、Javassist、Stub;
- 实际上由Javassist 创建Invoker ,因为Javassist 动态代理比 JDK 代理快
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
为什么要封装成Invoker?
- 屏蔽调用细节,统一暴露出一个可执行体,这样调用者简单的使用它,向它发起Invoker 调用,它可能是一个本地的实现、远程的实现 或者 集群的实现
protocol.export
- protocol.export() 标注了@Adaptive 注解,会生成代理类
- 通过代理类会根据Invoker 里面的URL 参数得知具体的协议,然后通过Dubbo SPI机制选择对应的实现类进行export;
// Protocol接口
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
// InjvmExporter实现类
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}