1. 整体过程
在看源码之前,我们不妨想一下,服务引入的流程。是不是就是以下几步:
- 消费者(consumer)从注册中心获取服务地址
- 消费者根据从注册中心提供的信息,获得接口与具体实现类实例
- 消费者通过代理方式,调用接口提供的方法(实际上是调用实现类的方法)
1.1 服务目录RegistryDirectory
消费端每个服务对应⼀个服务⽬录RegistryDirectory。
⼀个服务⽬录中包含了:
- serviceType:表示服务接⼝
- serviceKey:表示引⼊的服务key,serviceclass+version+group
- queryMap:表示引⼊的服务的参数配置
- onfigurators:动态配置
- routerChain:路由链
- invokers:表示服务⽬录当前缓存的服务提供者Invoker
- ConsumerConfigurationListener:监听本应⽤的动态配置
- ReferenceConfigurationListener:监听所引⼊的服务的动态配置
2. 服务引入的入口
服务引入对应的Bean是ReferenceBean。下面是该类的类图:
这里看到该类实现了FactoryBean接口,这里复习一下Spring的知识点。
2.1 BeanFactory与Factory区别
BeanFactory是Spring里的顶级工厂类,就是生产Bean的工厂。 FactoryBean是Spring提供的一个扩展功能,可以按照用户自定义的定义Bean,在真正要获取这个Bean的时候容器会调用FactoryBean.getObject()方法。
举个例子,BeanFactory就像一个制定好各个标准的造车厂,提供的汽车不支持定制化,而FactoryBean就像是一辆可以自己定制的汽车,按照用户的喜好,自己指定车型、性能、颜色等等。
2.2 服务引入的三种方式
- 本地引入:先去本地缓存找找看有没有本地服务
- 直接远程引入:由 Consumer 直接配置写死 Provider 的地址,然后直连即可(测试的时候用)
- 通过注册中心引入:Consumer 通过注册中心得知 Provider 的相关信息,然后进行服务的引入,这里还包括多注册中心,同一个服务多个提供者的情况,如何抉择如何封装,如何进行负载均衡、容错并且让使用者无感知
3. 服务引入源码流程
3.1 ReferenceBean.getObject()
public Object getObject() {
return get();
}
3.2 get()方法
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
}
if (ref == null) {
//初始化方法
init();
}
return ref;
}
3.3 init()方法
这个方法很长,具体分以下三步:
- 根据服务信息,生成一个map
- 向本地的缓存中注册消费者服务
- 生成代理
3.3.1 生成配置信息map
这个过程太长了,只贴一个map的信息。
3.3.2 向本地缓存注册服务
向ConcurrentMap<String, ConsumerModel> consumers里注册。
3.3.3 生成代理createProxy()
这方法比较长,我们来一点一点分析。
3.3.3.1 本地引入
这里就是从map里的信息判断一下是不是本地引入,就是去之前服务暴露的exporterMap拿到服务。
if (shouldJvmRefer(map)) {
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());
}
}
3.3.3.2 点对点引入(不从配置中心获取)
这里如果配置了url,就证明是点对点引入,就是把url拼接一下。
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));
}
}
}
}
3.3.3.3 从注册中心引入
这里urls里最终是这样
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-zyz-consumer-demo&dubbo=2.0.2&pid=2924&qos.enable=false&refer=application%3Ddubbo-zyz-consumer-demo%26dubbo%3D2.0.2%26init%3Dfalse%26interface%3Dcom.zyz.DemoService%26methods%3DsayHello%2CsayHelloAsync%26pid%3D2924%26qos.enable%3Dfalse%26register.ip%3D192.168.31.158%26release%3D2.7.5%26revision%3Ddefault%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1617280229760%26version%3Ddefault®istry=zookeeper&release=2.7.5×tamp=1617282302206
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
//判断服务配置是不是已经存在
checkRegistry();
//拿到注册中心地址
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
for (URL u : us) {
//如果配置了监控中心
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.");
}
}
至此三种引入都分析完毕。
3.3.4 RegistryProtocol.refer()方法
接下来会判断urls的长度是不是等于1,如果等于1就根据SPI机制,根据传入的URL找到对应的实现类,执行对应方法,这里会执行RegistryProtocol.refer(
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//获取注册url
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);
}
3.3.5 doRefer()方法
可以看到生成了 RegistryDirectory这个directory塞了注册中心实例,它自身也实现了NotifyListener 接口,因此注册中心的监听其实是靠这家伙来处理的。
然后向注册中心注册自身的信息,并且向注册中心订阅了 providers 节点、 configurators 节点 和 routers 节点,订阅了之后 RegistryDirectory 会收到这几个节点下的信息,就会触发 DubboInvoker 的生成了,即用于远程调用的 Invoker。
拿到了Provider的信息之后就可以通过监听触发 DubboProtocol.refer了。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//创建一个开篇提到的服务目录
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
//放入注册中心实例
directory.setRegistry(registry);
//放入动态生成的protocol
directory.setProtocol(protocol);
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
//生成服务消费者链接
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
//向注册中心注册消费者,在consumers目录下创建新节点
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
//订阅注册中心的providers目录、configurators目录和routers目录
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
//封装多个invoker
Invoker invoker = cluster.join(directory);
return invoker;
}
3.3.6 AbstractProtocol.refer
DubboProtocol中并没有refer⽅法,是在它的⽗类AbstractProtocol中才有的refer⽅法。
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}
这里通过protocolBindingRefer()调用DubboProtocol里的这里通过protocolBindingRefer()方法。
3.3.7 DubboProtocol.protocolBindingRefer()方法
这里会创建rpc的invoker
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
//创建rpc的invoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
3.3.8 getClients
由于是网络连接,这里会创建一个客户端。
private ExchangeClient[] getClients(URL url) {
// whether to share connection
boolean useShareConnect = false;
int connections = url.getParameter(CONNECTIONS_KEY, 0);
List<ReferenceCountExchangeClient> shareClients = null;
// if not configured, connection is shared, otherwise, one connection for one service
if (connections == 0) {
useShareConnect = true;
/**
* The xml configuration should have a higher priority than properties.
*/
String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
shareClients = getSharedClient(url, connections);
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (useShareConnect) {
clients[i] = shareClients.get(i);
} else {
clients[i] = initClient(url);
}
}
return clients;
}
这里不细分析了,最终会调用Netty的客户端,new NettyClient(url, listener)。
3.3.9 存储服务信息
这里是从2.7版本之后改的,在这里把服务信息存入到本地,这样就不用每次都从注册中心获取了。(除非配置中心的地址有变化)
最终放入这个缓存里ConcurrentNavigableMap<String, String> serviceDefinitions。
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);
}
3.3.10 getProxy()
最终创建代理。
4. 总结
整体流程图