Dubbo系列之 (五)服务订阅(2)
辅助链接
Dubbo系列之 (一)SPI扩展
Dubbo系列之 (二)Registry注册中心-注册(1)
Dubbo系列之 (三)Registry注册中心-注册(2)
Dubbo系列之 (四)服务订阅(1)
Dubbo系列之 (五)服务订阅(2)
服务订阅,阅读代码前的一些思考?
思考的过程和设计思想如下:
1、我们想要进行远程服务的调用,那么肯定要建立网络连接,不妨改用TCP长连接,并设计通信协议,并封装为一个类,不妨叫做ExchangeClient。用它来进行网络通信。
2、有了可以进行远程通信的服务对象ExchangeClient后,我们可以把远程服务封装为一个Invoker对象,这个Invoker对象内部采用自已定义的协议与远程服务器通信,不妨叫做DubboInvoker,因为采用了dubbo协议来进行网络通信的。
3、有了这个DubboInvoker 我就可以根据dubbo协议与远程服务通信了,但是我还想在本地增加一些过滤器Filter,或者监听器Listener。没关系,直接通过责任链模式,把这些Filter与这个DubboInvoker进行链接。返回的一个ProtocolFilterWrapper对象。
4、同理,如果需要一些监听器的功能怎么办,同样进行一次封装。把ProtocolFilterWraper封装到Listener类型的Invoker对象,不妨叫做ListenerInvokerWrapper。
5、现在考虑远程服务提供者有很多个,那么我对每个远程服务都需要有一个ListenerInvokerWrapper的对象。如下:
Demoservice::196.254.324.1 ListenerInvokerWrapper1
Demoservice::196.254.324.2 ListenerInvokerWrapper2
Demoservice::196.254.324.3 ListenerInvokerWrapper3
Demoservice::196.254.324.4 ListenerInvokerWrapper4
Demoservice::196.254.324.5 ListenerInvokerWrapper5
.....
6、服务太多了,在本地这样创建太费事了。引入了注册中心,直接把服务注册到服务中心上,然后客户端直接从注册中心拉取。我们把拉取到的服务,统称为服务目录。并且它是从注册中心拉取到的,那么不妨名字就叫做RegistryDirectory。那么这个服务目录里肯定包含了上面的远程服务调用对象ListenerInvokerWrapper。我们把这些对象放到服务目录的成员上,名字就叫做urlInvokerMap。key: Demoservice::xxxx。value:ListenerInvokerWrapper。
7、现在我们可以在本地调用RegistryDirectory对象,与远程服务通信了,想调哪个服务就从
urlInvokerMap取出一个进行调用即可。但是每次指定一个远程服务器,不仅太麻烦了,而且也会造成流量不均匀,负责不平衡。那么我们就通过通过负载均衡策略来选择一个服务调用。就取名LoadBalance吧。他有个方法select。入参就是我们的服务目录RegistryDirectory。那么通过LoadBalance.select(RegistryDirectory) 得到一个我们想要的通信的远程服务即可。目前负载均衡算法有一致性Hash算法,随机算法、权重轮训算法、最短响应时间算法、最少活跃数算法。
8、有了负载均衡算法LoadBalance后,我想要这样的功能,当服务调用失败的时候,我可以重试,或者直接直接失败。那我就把有这种能力服务调用,称为一个集群Cluster。他有一个方法叫做join。入参还是服务目录RegistryDirectory。返回一个具有快速失败、或者重试的服务调用,不妨叫AbstractClusterInvoker。每个不同的策略都去实现它。并且这个对象内部通过LoadBalance来选择一个服务进行调用,失败后的策略(是否重试或失败)由我决定。
9、目前我们已经有了一个XXXclusterInvoker 对象,它具有快速失败或者重试等功能,且具有负载均衡算法的远程服务调用对象。但是有时,这些远程服务提供者这的qps不达标,或者新上线的服务有问题,或者远程服务调用失败后,可以在本地模拟的调用,返回一个mock对象。那么我们重新对XXXclusterInvoker进行封装,就命名为MockClusterInvoker,具有Mock功能,且具有集群能力。它持有我们的服务目录RegistryDirectory和XXXclusterInvoker对象。
10、目前我们已经有了一个MockClusterInvoker对象。但是这个invoker对象和我们像本地一样调用服务还是有点差别,最后我们直接通过Java的动态代理计算Proxy.newInstance()来创建一个具体的服务对象DemoService,并且在InvokeHandler内部调用我们的MockClusterInvoker对象的invoke 方法。
11、比如我们的DubboInvoker是通过Java 异步线程CompletableFuture实现的话,如果需要转为同步,还可以对其封装从异步转为同步的Invoker,不妨命名为AsyncToSyncInvoker。
则最终在服务消费端呈现给我们如下一个远程服务代理对象。
ReferenceBean#getObject()
在上一章节,已经说明了getObject()对象的调用时机,内部调用的ReferenceConfig#init方法,该init()方法主要做了如下几件事情:
1、配缺省的配置进行填充,比如registry,application等属性。
2、校验配置是否填写正确,比如<dubbo:reference />中的stub 和mock 是否配置,配置了是否正确。
3、通过SPI机制获取Protocol$Adaptive自适应协议,通过Protocol$Adaptive#refer()方法得到一个MockClusterInvoker对象。该方法的调用内容基本和上面的猜想设计一直。
1)和注册中心建立tcp连接。
2)把当前的订阅服务注册到注册中心上的consumer节点上。
3)从注册中心中把订阅的服务列表拉取到本地,即RegistryDirectory。
4)根据上面类似猜想创建MockClusterInvoker返回。
4、通过SPI机制获取ProxyFactory$Adaptive自适应代理工厂,然后通过这个代理工厂创建动态代理对象,并把这个代理对象赋值给ref属性。
REF_PROTOCOL.refer(interfacere,registryUrl)
服务的订阅核心就是这条语句,这条语句博大精深。仅仅一条语句把所有的订阅工作完成了。
1、首先根据SPI机制获取自适应的协议对象。语句如下:
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
该语句创建了Protocol$Apdative。它有个自适应refer方法如下:
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (type == null)
throw new IllegalArgumentException("url == null");
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return extension.refer(type, url);
}
2、Protocol$Apdative#refer()方法内部又通过参数的url的协议头和SPI机制获取一个具体的协议。显而易见,url.getProtocol()返回的是registry。因为当前是服务订阅。所以是registry打头。那么返回的Protocol具体类型就是RegistryProtocol。但是Protocol扩展点有包裹类型:ProtocolListenerWrapper、ProtocolFilterWrapper。所以最终返回的是ProtocolListenerWrapper类型的协议。查看这个2个包裹类型的refer()方法:
类ProtocolListenerWrapper
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (UrlUtils.isRegistry(url)) {
return protocol.refer(type, url);
}
return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
Collections.unmodifiableList(
ExtensionLoader.getExtensionLoader(InvokerListener.class)
.getActivateExtension(url, INVOKER_LISTENER_KEY)));
}
类ProtocolFilterWrapper
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (UrlUtils.isRegistry(url)) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}
3、所以Protocol$Apdative#refer()内部的getExtension返回的是ProtocolListenerWrapper的Protocol。又因为url是注册url,所以满足UrlUtils.isRegistry(url)==true.直接进行一次传递调用。
4、最终调到RegistryProtocol#refer()。代码如下:
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = getRegistryUrl(url);
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
String group = qs.get(GROUP_KEY);
if (group != null && group.length() > 0) {
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
return doRefer(cluster, registry, type, url);
}
即得到注册中心Registry,一般是ZookeeperRegistry。获取注册中心的内容在之前的章节已见过,就不在多说了。接着会调用doRefer()方法。
在看doRefer()方法之前,我们来看下其定义:
Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url);
出参:
返回值就是我们需要的Invoker对象。
入参:
cluster:集群对象Cluster$Adaptive,通过Spi获取.内部getExtension获取Cluster,默认为FailoverCluster。
registry:注册中心
type:订阅的接口类型
url:服务注册链接注册中心URL。
5、Cluster的join 接口如下:
Invoker join(Directory directory) throws RpcException。
Cluster$Adaptive#join()内部实际是默认调用的是FailoverCluster#join()。
并且Cluster扩展点也有其Wrapper类,即MockClusterWrapper。所以Cluster$Adaptive#join()的方法调用
Cluster extension = ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(extName);
返回的extension是MockClusterWrapper,MockClusterWrapper#join()代码如下:
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
所以Cluster$Adaptive#join()返回的Invoker类型是MockClusterInvoker。MockClusterWrapper持有的cluster是FailoverCluster,所以MockClusterInvoker内部持有invoker类型是FailoverClusterInvoker。
6、源码doRefer()
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// new 一个服务目录,订阅服务类型为type 的 RegistryDirectory
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 设置注册中心
directory.setRegistry(registry);
//设置协议,即Protocol$Adaptive
directory.setProtocol(protocol);
// all attributes of REFER_KEY
//获取订阅参数
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
//构建订阅URL ,以consumer//打头
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
//把该url注册到注册中心上
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(subscribeUrl);
registry.register(directory.getRegisteredConsumerUrl());
}
//设置路由链
directory.buildRouterChain(subscribeUrl);
//重点,重中之重。这里订阅服务,并且会拉取远程服务invoker 到directory对象的urlInvokerMap成员中。
directory.subscribe(toSubscribeUrl(subscribeUrl));
//由上面分析,得到是MockClusterInvoker
Invoker<T> invoker = cluster.join(directory);
//查找注册协议监听器,没有设置为空
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
if (CollectionUtils.isEmpty(listeners)) {
return invoker;
}
// 如果有其监听器进行监听器onRefer()调用,并返回RegistryInvokerWrapper包裹类型。
RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
for (RegistryProtocolListener listener : listeners) {
listener.onRefer(this, registryInvokerWrapper);
}
return registryInvokerWrapper;
}
本地动态代理对象创建createProxy()
/**
* 核心,通过配置的元信息,创建一个代理对象
* @param map
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
private T createProxy(Map<String, String> map) {
// 首先判断本地是否有Service提供者,
if (shouldJvmRefer(map)) {
//如果有,导出jvm导出refer
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
invoker = REF_PROTOCOL.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
urls.clear();
//指定服务提供者URL。点对点比如在<dubbo:reference url="dubbo://xxxxx:12222">
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (StringUtils.isEmpty(url.getPath())) {
url = url.setPath(interfaceName);
}
if (UrlUtils.isRegistry(url)) {
urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
// if protocols not injvm checkRegistry
//如果不是jvm 协议,一般是dubbo
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
//检测注册中心
checkRegistry();
//根据注册中心地址,得到注册服务
//registry://106.52.187.48:2181/org.apache.dubbo.registry.RegistryService
// ?application=dubbo-demo-annotation-consumer&dubbo=2.0.2&pid=9757®istry=zookeeper×tamp=1597380362736
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
for (URL u : us) {
//对每个注册中心URL,得到监控URL
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
if (monitorUrl != null) {
map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
}
//如果注册中心之一一个的话,一般就一个注册中心
if (urls.size() == 1) {
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
//多个注册中心时,Protocol$Adaptive
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
//把其得到的Invoker 填入invokers
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (UrlUtils.isRegistry(url)) {
registryURL = url; // use last registry url
}
}
//多注册中心,多订阅场景
if (registryURL != null) { // registry url is available
// for multi-subscription scenario, use 'zone-aware' policy by default
URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
// The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
//通过集群,返回一个invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
if (shouldCheck() && !invoker.isAvailable()) {
invoker.destroy();
throw new IllegalStateException("Failed to check the status of the service "
+ interfaceName
+ ". No provider available for the service "
+ (group == null ? "" : group + "/")
+ interfaceName +
(version == null ? "" : ":" + version)
+ " from the url "
+ invoker.getUrl()
+ " to the consumer "
+ NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
/**
* @since 2.7.0
* ServiceData Store
*/
/**
*
* 这里是发布元数据信息
*/
String metadata = map.get(METADATA_KEY);
WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
if (metadataService != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
metadataService.publishServiceDefinition(consumerURL);
}
// create service proxy
//通过动态代理把invoker 转化为具体的服务类型
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}
上面核心的代码invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))已分析,接下下来就是通过PROXY_FACTORY.getProxy()创建活动,之后服务调用上进行分析。其他雷士元数据的注册,等之后讲解配置中心时进行讲解。
接下来,以一个图解来描述服务订阅的过程。在下一章节来描述如何具体的拉取远程服务invoker到服务目录RegistryDirectory上的urlInvokerMap。
Dubbo系列之 (五)服务订阅(2)的更多相关文章
- Dubbo源码(五) - 服务目录
前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊聊Dubbo的服务目录(Directory).下面是官方文档对服务目录的定义: 服务目 ...
- Dubbo系列讲解之服务注册【3万字长文分享】 23/100 发布文章
服务注册的几个步骤 对于RPC框架的服务注册,一般包含了如下的流程: 加载服务提供者,可能是通过xml配置的,也可能是通过扫描注解的 实例化服务提供者,并以服务接口作为key,实现类作为value ...
- Dubbo系列之 (六)服务订阅(3)
辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...
- Dubbo系列之 (四)服务订阅(1)
辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...
- dubbo系列四、dubbo服务暴露过程源码解析
一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...
- Dubbo 系列(05-1)服务发布
目录 Dubbo 系列(05-1)服务发布 Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 1.1 服务暴露整体机制 2. 源码分析 2.1 前置工作 2.2 ...
- Dubbo 系列(07-2)集群容错 - 服务路由
目录 Dubbo 系列(07-2)集群容错 - 服务路由 1. 背景介绍 1.1 继承体系 1.2 SPI 2. 源码分析 2.1 创建路由规则 2.2 RouteChain 2.3 条件路由 Dub ...
- Dubbo 系列(07-1)集群容错 - 服务字典
Dubbo 系列(07-1)集群容错 - 服务字典 [toc] Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 本篇文章,将开始分析 Dubbo 集群容错方面的 ...
- Dubbo系列之服务暴露过程
点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...
随机推荐
- 21天学通C++(C++程序的组成部分)
C++程序被组织成类,而类由成员函数和成员变量组成. 本章学习: 1)C++程序的组成部分. 2)各部分如何协同工作. 3)函数及其用途. 4)基本输入输出操作. C++程序划分为两个部分,以#大头的 ...
- rsync 的用法
rsync官方网站: https://www.samba.org/ftp/rsync/rsync.html rsync是可以实现增量备份的工具.配合任务计划,rsync能实现定时或间隔同步,配合ino ...
- PHP date_timezone_get() 函数
------------恢复内容开始------------ 实例 返回给定 DateTime 对象的时区: <?php$date=date_create(null,timezone_open( ...
- PHP xml_set_element_handler() 函数
定义和用法 xml_set_element_handler() 函数规定在 XML 文档中元素的起始和终止调用的函数. 如果成功,该函数则返回 TRUE.如果失败,则返回 FALSE.高佣联盟 www ...
- 6.28 NOI模拟赛 好题 状压dp 随机化
算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...
- luogu P3403 跳楼机 同余最短路
LINK:跳楼机 很早之前就想学的一个东西.发现这个东西果然神奇. 我们要找到 所有的 w满足 \(w=1+ax+by+cz\).且 \(1\leq w\leq h\) 暴力枚举是不行的. 做法是这样 ...
- 唯一约束 UNIQUE KEY
目录 什么是唯一约束 与主键的区别 创建唯一约束 唯一性验证 什么是唯一约束 Unique Key:它是 MySQL 中的唯一约束,是指在所有记录中字段的值不能重复出现.例如,为 id 字段加上唯一性 ...
- 笨办法学python3练习代码11-12:print()
ex11.py print("How old are you? ",end = " ") #加入end = " ",则函数不再自动换行 ag ...
- 用 Python 制作关不掉的端午安康弹窗
端午节又称端阳节.龙舟节.重午节.龙节.正阳节.天中节等,端午节源自天象崇拜,由上古时代祭龙演变而来,因传说战国时期的楚国诗人屈原在五月五日跳汨罗江自尽,后来人们亦将端午节作为纪念屈原的节日,在端午节 ...
- DotNet Core
安装 dotnet add package Pomelo.EntityFrameworkCore.MySql 使用 MySQL 作为后端 在继承 DbContext 类中重写 OnConfig ...