文章目录
Dubbo服务暴露原理
关注可以查看更多粉丝专享blog~
前面已经讲过Dubbo配置解析的原理,今天分析一下Dubbo服务暴露原理,Dubbo服务的暴露和消费原理离不开配置解析。
整体上看,Dubbo框架实现服务暴露分为两大部分:
- 将服务持有的实例通过代理转换成Invoker。
- 将Invoker通过具体的协议(如:Dubbo)转换成Exporter。
这里的Invoker可以简单理解成一个真实的服务对象实例,是Dubbo框架实体域,所有的模型都会向它靠拢,可向它发起invoke调用,它可能是一个本地的实现,也可能是一个远程的实现,还可能是一个集群的实现。
服务暴露的核心类ServiceConfig
- 通过反射获取配置对象并放到map中用于后续构造URL参数。
- 区分全局配置,默认在属性前面添加default.前缀,当框架获取URL中的参数时,如果不存在则会自动尝试获取default.前缀对应的值。
- 处理本地JVM协议暴露。
- 追加监控上报地址,框架会在拦截器中执行数据上报。
- 通过动态代理创建Invoker对象,在服务端生成的是AbstractProxyInvoker实例,所有真实的方法调用都会委托给代理,然后代理转发给服务ref调用。目前的两种代理:
- JavassistProxyFactory:创建Wrapper子类,在子类中实现invokeMethod方法,方法体内会为每个ref方法都做方法名和方法参数匹配校验,如果匹配则直接调用即可,相比JdkProxyFactory省去了反射调用的开销。
- JdkProxyFactory:通过反射获取真是对象的方法,然后调用即可。
- 暴露服务(端口打开等),然后进行服务元数据注册。
- 最后处理没有注册中心的场景,直接进行服务暴露,不需要元数据注册,因为直接暴露的URL信息是以具体的RPC协议开头的,并不是以注册中心协议开头的。
// 注册中心URL示例
registry://host:port/com.alibaba.dubbo.registry.RegistryService?protocol=zookeeper&export=dubbo://ip:port/xxx?...
// 直接暴露URL示例
dubbo://ip:port/xxx?timeout=1000&...
框架真正进行服务暴露的入口点在ServiceConfig#doExport中,无论是XML还是注解,都会转换成ServiceBean,它继承自ServiceConfig,主要处理思路就是遍历所有方法,如果没有值则尝试从-D选项中获取,如果还没有则自动从配置文件dubbo.properties中读取。
ServiceConfig#doExportUrls(服务暴露入口)
// ServiceConfig#doExportUrls()
private void doExportUrls() {
// 获取当前服务对应注册中心实例
List<URL> registryURLs = loadRegistries(true);
// 如果服务指定暴露多个协议(Dubbo、REST),则依次暴露服务
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
ServiceConfig#doExportUrlsFor1Protocol(真实服务暴露实现逻辑)
// ServiceConfig#doExportUrlsFor1Protocol
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 如果协议名称为空则默认使用dubbo协议
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
// 构造配置信息
Map<String, String> map = new HashMap<String, String>();
// 设置side=provider
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
// 设置version
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
// 设置时间戳
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 读其他配置信息到map,用于后续构造
appendParameters(map, application);
appendParameters(map, module);
// 读取全局配置信息会自动添加前缀
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
...
URL url = new URL(name, host, port, path, map);
...
String scope = url.getParameter(Constants.SCOPE_KEY);
// 没有配置时不要导出
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// 如果配置不是远程的,则导出为本地(仅当配置是远程的时候才导出为远程)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// 如果配置不是本地的就导出到远程(只有当配置是本地的时候才导出到本地)
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());
}
// 对于providers,用于启用自定义代理来生成invoker
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
// 通过动态代理转换成Invoker,registryURL存储的是注册中心地址,使用export作为key追加服务元数据信息
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 服务暴露后向注册中心注册服务信息
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);
}
ServiceConfig#exportLocal(暴露本地服务(injvm))
// ServiceConfig#exportLocal
private void exportLocal(URL url) {
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
// 将协议设置为injvm进行导出
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
// 缓存到exporters,unexport可以使用
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
注册中心暴露服务
在将服务实例ref转换成Invoker之后,如果有注册中心时,则会通过RegistryProtocol#export进行更细粒度的控制,比如进行服务暴露再注册服务元数据。注册中心暴露服务的时候依次作了以下5件事情:
- 委托具体协议(Dubbo)进行服务暴露,创建NettyServer监听端口和保存服务实例。
- 创建注册中心对象,与注册中心建立TCP连接。
- 注册服务元数据到注册中心。
- 订阅configurators节点,监听服务动态属性变更事件。
- 服务销毁收尾工作,比如关闭端口、反注册服务信息等。
// RegistryProtocol#export
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 打开端口,把服务实例储存到map
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
URL registryUrl = getRegistryUrl(originInvoker);
// 创建注册中心实例,暴露服务
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
boolean register = registeredProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
// 服务暴露之后,注册元数据
if (register) {
register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// 订阅覆盖数据
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
// 确保每次导出时都返回一个新的export实例,内部监听configurators节点,以及unexport之后的操作
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
拦截器初始化
在进行服务暴露前,框架会做拦截器初始化,Dubbo在加载protocol扩展点时会自动注入ProtocolListenerWrapper和ProtocolFilterWrapper。在ProtocolListenerWrapper实现中,在对服务提供者进行暴露时回调对应的监听器方法。ProtocolFilterWrapper会调用下一级ProtocolListenerWrapper#export方法,在该方法内部会触发buildInvokerChain进行拦截器构造。
// ProtocolFilterWrapper---> ProtocolListenerWrapper ---> DubboProtocol
// 协议拦截器扩展 ProtocolFilterWrapper#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
// 先构造拦截器(会过滤provider端分组),然后触发Dubbo协议暴露
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 获取url中(key:service.filter)指定的filter扩展类 (使用Dubbo SPI)
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
// 将真实的Invoker(服务对象ref,放到拦截器末尾)
final Invoker<T> next = last;
// 为每一个filter生成一个exporter依次串起来
last = new Invoker<T>() {
...
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 每次调用都会传递给下一个拦截器
return filter.invoke(next, invocation);
}
...
};
}
}
return last;
}
// 协议监听器扩展 ProtocolListenerWrapper#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
// 获取url中(key:exporter.listener)指定的扩展类(使用Dubbo SPI)
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
// Dubbo协议暴露 DubboProtocol#export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// 根据服务分组、版本、接口和端口构造key
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// 把exporter储存到单例DubboProtocol中
exporterMap.put(key, exporter);
...
// 服务初次暴露会创建监听服务器
openServer(url);
optimizeSerialization(url);
return exporter;
}
// DubboProtocol#openServer
private void openServer(URL url) {
// 查找服务
String key = url.getAddress();
// 客户端可以导出仅供服务器调用的服务
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
// 创建服务并缓存
serverMap.put(key, createServer(url));
} else {
// 服务器支持重置,与覆盖一起使用
server.reset(url);
}
}
}
// DubboProtocol#createServer
private ExchangeServer createServer(URL url) {
...
ExchangeServer server;
try {
// 创建NettyServer并初始化Handler
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
...
return server;
}
相关文章:
Davids原理探究:Dubbo源码编译(2.7.8)
Davids原理探究:Dubbo SPI和Java SPI实现原理
Davids原理探究:Dubbo注册中心(ZooKeeper、Redis)实现原理
Davids原理探究:Dubbo配置解析原理
Davids原理探究:Dubbo服务暴露原理
Davids原理探究:Dubbo服务消费原理
Davids原理探究:Dubbo优雅停机原理
Davids原理探究:Dubbo调用流程图
Davids原理探究:Dubbo路由实现原理
Davids原理探究:Dubbo负载均衡实现原理
Davids原理探究:Dubbo过滤器原理