本文主要解读dubbo消费者是如何引用服务端接口的,是如何像本地调用一样调用远程服务的。

并试着从设计者的角度思考,为何这样设计。

  1. @Component
  2. public class DubboConsumer {
  3.  
  4. @Reference(check = false)
  5. private HelloService helloService;
  6.  
  7. public String sayHello(String name) {
  8. return helloService.sayHello(name);
  9. }
  10. }

拿这个实际的例子来说,DubboConsumer在创建实例的时候,是如何注入HelloService依赖的,因为HelloService实现类在远端的另一台服务器上。

我们都知道在Spring容器启动的时候,会加载所有类为BeanDefinition,然后在调getBean方法的时候,会对类进行实例化,并注入依赖。

使用@EnableDubbo表示要启动dubbo,会注册几个post processor,其中包括ReferenceAnnotationBeanPostProcessor,处理具有@Reference注解的属性,即在Spring bean实例化时,会注入通过dubbo方式生成的服务端引用。

1.先整体看看dubbo的引用

(1)从整体架构上看消费端引用创建的过程

很多人看到这张图,一脸懵逼,我也不例外,经过七七四十九天的磨练,终于搞清了咋回事,我将一点点拆解,分享给你。

首先是消费者获得服务端接口引用,核心入口是ReferenceConfig,整体可以划分为这7步:

  • 1.调ReferenceConfig的get方法,获取服务端接口实例;

  • 2.调Protocol的refer方法,得到调服务端接口使用的invoker

  • 3.拿到invoker后,封装在RegistryDirectory中,并通过RegistryProtocol完成对提供者以及相关配置的订阅

  • 4.实际invoker是通过DubboProtocol创建的DubboInvoker,其包含了消费者到提供者的连接客户端

  • 5.客户端的创建入口为Exchangers类,调用Exchanger(默认实现类为HeaderExchanger)的connect方法获取连接服务端的客户端

  • 6.真实的客户端的创建,是在Transporters类中发生的,会调Transporter(默认实现类为NettyTransporter)的connect方法创建NettyClient

  • 7.根据获取到的invoker,通过反射得到服务端接口的实例,从而可以像调本地方法一样调用远程

其中在代码调用关系上看,第2步是在第3步之后,这里这样划分是为了方便叙述,让官网的架构图更容易理解。

沿着调用链路,我们可以暂时不关系第3步,专注于invoker的生成,调用序列图如下:

上图描述了获取invoker过程中最关键的调用序列,包括客户端创建、服务端接口信息封装(invoker)、代理类生成。

(2)看看注册中心

我们知道客户端想要调服务端,首先需要知道服务的ip和端口号等信息,那在dubbo消费端启动的时候,我怎么知道服务端接口的信息呢?

最先想到的方式就是在创建invoker的入口,将服务端信息传进去,在new NettyClient时,建立与服务端的TCP连接。

但这种方式对使用者特别不方便,同时对代码也有侵入性。

那么是否可以将提供者的接口信息放在某个地方,在创建invoker的时候,从那里获取到接口信息,大家自然想到的是配置中心。

但配置中心需要人工设置值,然后推送到消费端,当接口非常多时,将无法维护。

所以引入了注册中心,消费端与服务端都可以与注册中心进行交互。

服务端可以将接口信息自动暴露到注册中心,消费者可以从注册中心获取到接口信息。

又有一个问题,如果消费者引用的接口发生变动,比如新增了一台提供者,或者服务端宕机了,消费者如何能够实时得感知到并及时做出调整呢?

这就需要消费者能够监听注册中心,注册中心发生变更,及时通知消费者

最终消费者、服务提供者和注册中心的关系如下图:

也就要考虑我们的第3步了,这时调用序列图变为:

红色线框部分即为与注册中心相关的调用,三个核心类:

RegistryProtocol:处理注册中心相关的Protocol实现,如获取注册中心实例,关联注册中心与invoker

ZookeeperRegistry:代表Zookeeper为注册中心的实体,封装了与Zookeeper交互操作,如订阅、监听等

RegistryDirectory:是一个目录实现类,顾名思义,它持有Invoker列表,同时还有到路由、负载均衡等

另外,RegistryDirectory实现了NotifyListener,在注册中心信息发生变更的时候,会调notify方法,更新RegistryDirectory中的invoker列表,从而实现了消费端对服务端接口的动态同步。

2.对源码庖丁解牛

dubbo消费端注入提供者服务引用,可以认为从ReferenceAnnotationBeanPostProcessor.doGetInjectedBean开始,

该类在dubbo-spring中,代码与注释如下:

  1. //ReferenceAnnotationBeanPostProcessor.
  2. @Override
  3. protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
  4. InjectionMetadata.InjectedElement injectedElement) throws Exception {
  5. /**
  6. * The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
  7. 获取@Service注解的服务bean name,即被引用的bean
  8. */
  9. String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
  10.  
  11. /**
  12. * The name of bean that is declared by {@link Reference @Reference} annotation injection
  13. 获取@Reference注解的服务引用bean name,即提供者服务bean name
  14. */
  15. String referenceBeanName = getReferenceBeanName(attributes, injectedType);
  16.  
  17. //获取ReferenceBean实例,ReferenceConfig的实例
  18. ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
  19. //注册ReferenceBean到Spring容器中
  20. registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);
  21.  
  22. cacheInjectedReferenceBean(referenceBean, injectedElement);
  23. //获取并创建提供者服务接口的代理类,即使用者最终得到的实例,通过该实例完成RPC透明化调用
  24. return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
  25. }
  26.  
  27. private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class<?> serviceInterfaceType) {
  28. //如果引用的服务接口在本地,则直接使用本地Spring容器中的服务实例
  29. if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean
  30. return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
  31. wrapInvocationHandler(referenceBeanName, referenceBean));
  32. } else { // ReferenceBean should be initialized and get immediately
  33. //获取远程服务接口实例
  34. return referenceBean.get();
  35. }
  36. }

接下来,我们按之前的调用序列图,一步步往下看。

(1)Reference的get

源码非常长(包含URL的构建等),这里做了精简,只保留了关键部分,如下:

  1. //ReferenceConfig
  2. public synchronized T get() {
  3. if (ref == null) {
  4. init();
  5. }
  6. return ref;
  7. }
  8.  
  9. public synchronized void init() {
  10. //1.参数准备和处理
  11. //2.创建代理
  12. ref = createProxy(map);
  13. }
  14.  
  15. private T createProxy(Map<String, String> map) {
  16. //获取注册中心
  17. ConfigValidationUtils.loadRegistries(this, false);
  18.  
  19. //1.根据注册中心构造注册URL,由此去构建invoker。存在多个注册中心时,会通过CLUSTER进行包装
  20. //此处,REF_PROTOCOL实例为RegistryProtocol,通过RegistryProtocol获取invoker
  21. invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
  22.  
  23. //2.创建远程invoker的代理类,便于消费者无侵入使用,默认PROXY_FACTORY=JavassistProxyFactory
  24. return (T) PROXY_FACTORY.getProxy(invoker);
  25. }

其中,代码中涉及到一些ReferenceConfig关键属性,前两个是通过SPI获取的Protocol和ProxyFactory,ref为接口的最终代理实例,invoker为引用服务的封装,如下:

  1. /**
  2. * Protocol的自适应类,与URL相关,协议类型为registry(如registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample),对应的类为RegistryProtocol;协议类型为dubbo,对应的类为DubboProtocol;
  3. * 同时为Protocol实例自动包装两个类,ProtocolFilterWrapper和ProtocolListenerWrapper
  4. */
  5. private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
  6.  
  7. /**
  8. * ProxyFactory自适应类,默认实现为JavassistProxyFactory
  9. */
  10. private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
  11.  
  12. /**
  13. * The interface proxy reference
  14. */
  15. private transient volatile T ref;
  16.  
  17. /**
  18. * The invoker of the reference service
  19. */
  20. private transient volatile Invoker<?> invoker;

在REF_PROTOCOL通过自适应获取的时候,会封装几个关键包装类,分别是ProtocolFilterWrapper、ProtocolListenerWrapper、QosProtocolWrapper:

过滤器包装类ProtocolFilterWrapper:对非注册invoker增加过滤器

  1. public class ProtocolFilterWrapper implements Protocol {
  2.  
  3. private final Protocol protocol;
  4. //ProtocolFilterWrapper为SPI Protocol的包装类
  5. public ProtocolFilterWrapper(Protocol protocol) {
  6. if (protocol == null) {
  7. throw new IllegalArgumentException("protocol == null");
  8. }
  9. this.protocol = protocol;
  10. }
  11.  
  12. @Override
  13. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  14. //注册URL不需要增加过滤器
  15. if (UrlUtils.isRegistry(url)) {
  16. return protocol.refer(type, url);
  17. }
  18. //其他invoker需要通过filter进行包装,实现过滤功能
  19. return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
  20. }
  21.  
  22. private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
  23. Invoker<T> last = invoker;
  24. List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
  25. //对filters循环遍历,构建被filter修饰的Invoker链,真实的invoker在链表尾部
  26. }
  27. }

监听包装类ProtocolListenerWrapper:对非注册invoker注册监听器

  1. public class ProtocolListenerWrapper implements Protocol {
  2. @Override
  3. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  4. if (UrlUtils.isRegistry(url)) {
  5. return protocol.refer(type, url);
  6. }
  7. //为普通的invoker增加监听,从InvokerListener接口看,只有引用invoker和destroy时会触发listener
  8. return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
  9. Collections.unmodifiableList(
  10. ExtensionLoader.getExtensionLoader(InvokerListener.class)
  11. .getActivateExtension(url, INVOKER_LISTENER_KEY)));
  12. }
  13. }

Qos包装类QosProtocolWrapper:该类只对注册URL时生效

  1. public class QosProtocolWrapper implements Protocol {
  2. @Override
  3. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  4. //只有对注册URL,才开启QOS
  5. if (UrlUtils.isRegistry(url)) {
  6. startQosServer(url);
  7. return protocol.refer(type, url);
  8. }
  9. return protocol.refer(type, url);
  10. }
  11. }

用张图总结上边的过程:

2)RegistryProtocol的refer

RegistryProtocol作为注册中心与invoker之间的沟通桥梁,代码如下:

  1. public class RegistryProtocol implements Protocol {
  2. @Override
  3. @SuppressWarnings("unchecked")
  4. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  5. url = getRegistryUrl(url);
  6. //1.根据URL获取注册中心,此处会创建注册中心客户端,并连接注册中心。如果之前已经创建过,则直接返回缓存值
  7. //此处默认使用ZookeeperRegistry,具体创建过程,后续再说,现在不用管
  8. Registry registry = registryFactory.getRegistry(url);
  9. //如果获取的是注册服务对应的invoker,则直接通过代理工厂生成代理对象
  10. if (RegistryService.class.equals(type)) {
  11. return proxyFactory.getInvoker((T) registry, type, url);
  12. }
  13.  
  14. //2.判断配置group,使用mergeable的cluster,可以暂时不关心
  15. // group="a,b" or group="*"
  16. Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
  17. String group = qs.get(GROUP_KEY);
  18. if (group != null && group.length() > 0) {
  19. if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
  20. return doRefer(getMergeableCluster(), registry, type, url);
  21. }
  22. }
  23. //3.调doRefer获取invoker
  24. return doRefer(cluster, registry, type, url);
  25. }
  26. }

最终会调doRefer方法,如下:

  1. private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
  2. //1.构造RegistryDirectory,其对注册中心、路由、配置、负载均衡、invoker列表等信息进行封装
  3. RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
  4. directory.setRegistry(registry);
  5. directory.setProtocol(protocol);
  6. // all attributes of REFER_KEY
  7. Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
  8. //2.构造消费者需要订阅的URL,用于后续订阅zk中配置、路由、provider等
  9. URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
  10. if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
  11. //3.获取并设置消费者的URL
  12. directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
  13. //3.1将消费者URL注册到注册中心,如果没有consumer节点,则创建
  14. registry.register(directory.getRegisteredConsumerUrl());
  15. }
  16. //4.构建路由链
  17. directory.buildRouterChain(subscribeUrl);
  18. //5 订阅注册中心的 provider、配置、路由等节点,当发生变动时,即时更新invoker信息
  19. directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
  20. PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
  21.  
  22. //6 cluster对目录进行封装,暴露给使用者只有一个invoker,在实际调用时,通过路由、负载均衡等,发送请求到某一个invoker
  23. Invoker invoker = cluster.join(directory);
  24. return invoker;
  25. }

在doRefer方法中,我们看到构建了RegistryDirectory(每个提供者的服务接口都对应一个RegistryDirectory),并通过RegistryDirectory关联了注册中心与invoker;同时完成消费者在注册中心的注册,以及对注册中心的订阅。

可能有人疑惑,invoker在哪,没看到怎么就去订阅了呢?

这个地方应该是设计者为了对代码复用进行的设计,因为在应用运行过程中,需要监听注册中心,判断提供者是否有变动。

这也是为什么RegistryDirectory实现监听接口,同时持有注册中心和invoker。在变动的情况下,会更新对应的invoker列表。

接下来,我们会考到会调到RegistryDirectory的notify接口。

首先看上边代码的第5步,通过RegistryDirectory实现消费者对注册中心的订阅,代码如下:

  1. //RegistryDirectory
  2. public void subscribe(URL url) {
  3. setConsumerUrl(url);
  4. CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
  5. serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
  6. //注册中心发起订阅,注册中心为ZookeeperRegistry,会调其父类FailbackRegistry.subscribe,其中包含了失败重试的策略
  7. registry.subscribe(url, this);
  8. }

其中registry为ZookeeperRegistry实例,其父类为FailbackRegistry,此处会调肤类的订阅方法。FailbackRegistry在注册中心实例的基础上,增加了失败重试的功能。

其中参数this是NotifyListener,即RegistryDirectory本身。

  1. //FailbackRegistry
  2. @Override
  3. public void subscribe(URL url, NotifyListener listener) {
  4. super.subscribe(url, listener);
  5. //1 从失败订阅列表中删除对应的订阅请求,取消定时重试
  6. removeFailedSubscribed(url, listener);
  7. try {
  8. //2 Sending a subscription request to the server side,调ZookeeperRegistry实现订阅
  9. doSubscribe(url, listener);
  10. } catch (Exception e) {
  11.  
  12. //3 Record a failed registration request to a failed list, retry regularly 失败后加入失败订阅列表进行重试
  13. addFailedSubscribed(url, listener);
  14. }
  15. }

订阅逻辑发生在doSubscribe中,由ZookeeperRegistry实例进行的实现。代码如下:

  1. //ZookeeperRegistry
  2. @Override
  3. public void doSubscribe(final URL url, final NotifyListener listener) {
  4. try {
  5. if (ANY_VALUE.equals(url.getServiceInterface())) {
  6. //省略
  7. } else {
  8. List<URL> urls = new ArrayList<>();
  9. //1 遍历provider、配置、路由node,并注册监听到这些节点上,当节点发生变化,会调用ZookeeperRegistry的notify接口
  10. for (String path : toCategoriesPath(url)) {
  11. ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
  12. if (listeners == null) {
  13. zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
  14. listeners = zkListeners.get(url);
  15. }
  16. ChildListener zkListener = listeners.get(listener);
  17. if (zkListener == null) {
  18. //2 创建zk监听器
  19. listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
  20. zkListener = listeners.get(listener);
  21. }
  22. //3 在zk创建provider、配置、路由对应的路径
  23. zkClient.create(path, false);
  24. //4 增加监听器
  25. List<String> children = zkClient.addChildListener(path, zkListener);
  26. if (children != null) {
  27. urls.addAll(toUrlsWithEmpty(url, path, children));
  28. }
  29. }
  30. //5 通知监听器,根据urls变化更新服务端invoker列表,在初次启动时,构建invoker
  31. notify(url, listener, urls);
  32. }
  33. } catch (Throwable e) {
  34. throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
  35. }
  36. }

我们看到,在ZookeeperRegistry的订阅方法中,其持有到zk的客户端,可以创建相应的节点,并实现监听。

同时,for循环是对provider、配置、路由进行创建于订阅,即完成消费者对提供者服务接口、配置、路由规则的订阅,从而可以实现对提供者变化时得到通知,配置货路由发生变化时也能得到通知。

第5步,notify方法,会调父类FailbackRegistry的notify方法,如下:

  1. //FailbackRegistry
  2. @Override
  3. protected void notify(URL url, NotifyListener listener, List<URL> urls) {
  4. try {
  5. //1 通知
  6. doNotify(url, listener, urls);
  7. } catch (Exception t) {
  8. //2 Record a failed registration request to a failed list, retry regularly 通知失败后,加入失败通知列表,用于重试
  9. addFailedNotified(url, listener, urls);
  10. logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
  11. }
  12. }

FailbackRegistry再调父类AbstractRegistry的notify方法,里边会调用监听器即RegistryDirectory,进行更新或构建invoker,代码如下:

  1. //AbstractRegistry
  2. /**
  3. * Notify changes from the Provider side.
  4. *
  5. * @param url consumer side url
  6. * @param listener listener
  7. * @param urls provider latest urls 最新的服务端URLs(包括provider、配置、路由的URL)
  8. */
  9. protected void notify(URL url, NotifyListener listener, List<URL> urls) {
  10. //1 keep every provider's category. 按类别划分URL
  11. Map<String, List<URL>> result = new HashMap<>();
  12. for (URL u : urls) {
  13. if (UrlUtils.isMatch(url, u)) {
  14. String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
  15. List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
  16. categoryList.add(u);
  17. }
  18. }
  19. if (result.size() == 0) {
  20. return;
  21. }
  22. //2 根据urls,通知变更所有invoker、配置等信息
  23. Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
  24. for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
  25. String category = entry.getKey();
  26. List<URL> categoryList = entry.getValue();
  27. categoryNotified.put(category, categoryList);
  28. //3 通知监听器RegistryDirectory,更新其中配置、路由、provider、invoker等信息
  29. listener.notify(categoryList);
  30. saveProperties(url);
  31. }
  32. }

第3步,调监听器RegistryDirectory,更新服务端信息

  1. //RegistryDirectory
  2. @Override
  3. public synchronized void notify(List<URL> urls) {
  4. //1 按类别(provider、配置、路由)划分需要更新的urls
  5. Map<String, List<URL>> categoryUrls = urls.stream()
  6. .filter(Objects::nonNull)
  7. .filter(this::isValidCategory)
  8. .filter(this::isNotCompatibleFor26x)
  9. .collect(Collectors.groupingBy(url -> {
  10. if (UrlUtils.isConfigurator(url)) {
  11. return CONFIGURATORS_CATEGORY;
  12. } else if (UrlUtils.isRoute(url)) {
  13. return ROUTERS_CATEGORY;
  14. } else if (UrlUtils.isProvider(url)) {
  15. return PROVIDERS_CATEGORY;
  16. }
  17. return "";
  18. }));
  19.  
  20. //2 更新配置信息
  21. List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
  22. this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
  23.  
  24. //3 更新路由信息
  25. List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
  26. toRouters(routerURLs).ifPresent(this::addRouters);
  27.  
  28. //4 获取最新的 providers URL
  29. List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
  30.  
  31. //5 更新消费端对应的invoker列表
  32. refreshOverrideAndInvoker(providerURLs);
  33. }

第5步,通过refreshOverrideAndInvoker更新invoker列表

  1. private void refreshOverrideAndInvoker(List<URL> urls) {
  2. overrideDirectoryUrl();
  3. //更新invokers
  4. refreshInvoker(urls);
  5. }

继续往后走,就是具体更新逻辑

  1. private void refreshInvoker(List<URL> invokerUrls) {
  2. //invokerUrls为空,表示更新配置或路由
  3. Assert.notNull(invokerUrls, "invokerUrls should not be null");
  4. //1 如果只有一个invokerUrl,同时协议为empty,一般表示接口没有可用提供者,会注销所有invoker
  5. if (invokerUrls.size() == 1
  6. && invokerUrls.get(0) != null
  7. && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
  8. this.forbidden = true; // Forbid to access
  9. this.invokers = Collections.emptyList();
  10. routerChain.setInvokers(this.invokers);
  11. destroyAllInvokers(); // Close all invokers
  12. } else {
  13. //2 有可用的提供者,更新invoker的缓存urlInvokerMap
  14. this.forbidden = false; // Allow to access
  15. Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
  16. if (invokerUrls == Collections.<URL>emptyList()) {
  17. invokerUrls = new ArrayList<>();
  18. }
  19. //3。如果invokerUrls为空,则继续使用缓存的invokerUrls。否则使用最新的
  20. if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
  21. invokerUrls.addAll(this.cachedInvokerUrls);
  22. } else {
  23. this.cachedInvokerUrls = new HashSet<>();
  24. this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
  25. }
  26. if (invokerUrls.isEmpty()) {
  27. return;
  28. }
  29. //4 转换新的invokerUrls为invoker
  30. Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
  31.  
  32. List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
  33. // pre-route and build cache, notice that route cache should build on original Invoker list.
  34. // toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
  35. routerChain.setInvokers(newInvokers);
  36. this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
  37. //5 替换最新的invoker
  38. this.urlInvokerMap = newUrlInvokerMap;
  39.  
  40. try {
  41. //6 销毁不用的invoker
  42. destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
  43. } catch (Exception e) {
  44. logger.warn("destroyUnusedInvokers error. ", e);
  45. }
  46. }
  47. }

调toInvokers方法,将URL转换为invoker,并缓存起来

  1. private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
  2. for (URL providerUrl : urls) {
  3. // 构造InvokerDelegate,其中protocol.refer返回的为原invoker
  4. invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
  5. newUrlInvokerMap.put(key, invoker);
  6. }
  7. }

到此为止,dubbo完成了从注册中心获取服务端接口信息,并将其转换为invoker列表;

同时这些invoker列表存储在RegistryDirectory中,可以实时监听注册中心的变更。

在toInvokers方法中,会调用DubboProtocol的refer方法实现服务提供者URL到invoker的转变,具体见后边文章。

这里有个问题,为什么要封装invoker到在InvokerDelegate中呢?

官网的解释:

  1. The delegate class, which is mainly used to store the URL address sent by the registry,and can be reassembled on the basis of providerURL queryMap overrideMap for re-refer.

其实就是对invoker和服务端URL的封装,便于后续使用。

具体如何使用的,我们后边再说。

用张图总结上边的过程:起于RegistryProtocol,终于RegistryDirectory。

(3)DubboProtocol的refer

在调用DubboProtocol的refer方法的过程中,也还会调用ProtocolFilterWrapper、ProtocolListenerWrapper、QosProtocolWrapper这三个包装类,实现对invoker的拦截与监听。

首先会调到父类AbstractProtocol的refer方法,如下

  1. //AbstractProtocol
  2. @Override
  3. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
  4. //构造异步实现同步的invoker
  5. return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
  6. }

我们看到,会将DubboProtocol创建的invoker封装为AsyncToSyncInvoker,为何要进行这样封装呢?

原因是,dubbo调用底层是基于netty进行的,是异步的过程,AsyncToSyncInvoker可以实现同步的调用,具体细节后边再说,暂时可以不管,只知道进行封装就行了。

千呼万唤始出来,终于找到URL转换为invoker的根了,我们看到invoker实际就是DubboInvoker的实例

  1. //DubboProtocol
  2. @Override
  3. public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
  4. optimizeSerialization(url);
  5.  
  6. // create rpc invoker.创建真实的rpc invoker,其中包含客户端的创建
  7. DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
  8. invokers.add(invoker);
  9.  
  10. return invoker;
  11. }

到此,我们追踪到invoker它祖先DubboInvoker,在ReferenceConfig拿到的invoker是DubboInvoker经过层层包装的结果。

也正是由于这些包装,我们可以对invoker进行一些定制化和扩展性的控制。

用张图总结上边的过程:

(4) 创建Client

getClients(url)根据URL获取客户端,建立消费者与提供者之间的TCP连接。

默认情况下,对服务端是共享的,即一个消费者与提供者之间保持一个TCP连接。

  1. private ExchangeClient[] getClients(URL url) {
  2. // whether to share connection
  3.  
  4. boolean useShareConnect = false;
  5.  
  6. int connections = url.getParameter(CONNECTIONS_KEY, 0);
  7. List<ReferenceCountExchangeClient> shareClients = null;
  8. // if not configured, connection is shared, otherwise, one connection for one service
  9. //1 默认使用共享的1个客户端
  10. if (connections == 0) {
  11. useShareConnect = true;
  12. String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
  13. //2 默认保持消费者与提供者之间只有一个TCP连接
  14. connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
  15. DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
  16. //3 获取共享客户端
  17. shareClients = getSharedClient(url, connections);
  18. }
  19.  
  20. //4 构造ExchangeClient数组,若不共享,需要创建多个消费者与提供者之间的TCP连接
  21. ExchangeClient[] clients = new ExchangeClient[connections];
  22. for (int i = 0; i < clients.length; i++) {
  23. if (useShareConnect) {
  24. clients[i] = shareClients.get(i);
  25.  
  26. } else {
  27. clients[i] = initClient(url);
  28. }
  29. }
  30.  
  31. return clients;
  32. }

共享客户端是如何实现的呢?

DubboProtocol持有客户端缓存referenceClientMap,key为服务端host:port,value为ExchangeClient的封装类ReferenceCountExchangeClient列表,其中包含引用计数。

  1. /**
  2. * <host:port,Exchanger>
  3. */
  4. private final Map<String, List<ReferenceCountExchangeClient>> referenceClientMap = new ConcurrentHashMap<>();
  5.  
  6. private List<ReferenceCountExchangeClient> getSharedClient(URL url, int connectNum) {
  7. //1 共享的client,key为提供者ip和端口号
  8. String key = url.getAddress();
  9. List<ReferenceCountExchangeClient> clients = referenceClientMap.get(key);
  10.  
  11. if (checkClientCanUse(clients)) {
  12. batchClientRefIncr(clients);
  13. return clients;
  14. }
  15.  
  16. locks.putIfAbsent(key, new Object());
  17. synchronized (locks.get(key)) {
  18. clients = referenceClientMap.get(key);
  19. // dubbo check
  20. if (checkClientCanUse(clients)) {
  21. batchClientRefIncr(clients);
  22. return clients;
  23. }
  24.  
  25. // connectNum must be greater than or equal to 1
  26. connectNum = Math.max(connectNum, 1);
  27.  
  28. // If the clients is empty, then the first initialization is
  29. if (CollectionUtils.isEmpty(clients)) {
  30. //2 构建引用计数的ExchangeClient
  31. clients = buildReferenceCountExchangeClientList(url, connectNum);
  32. referenceClientMap.put(key, clients);
  33.  
  34. } else {
  35. for (int i = 0; i < clients.size(); i++) {
  36. ReferenceCountExchangeClient referenceCountExchangeClient = clients.get(i);
  37. // If there is a client in the list that is no longer available, create a new one to replace him.
  38. if (referenceCountExchangeClient == null || referenceCountExchangeClient.isClosed()) {
  39. clients.set(i, buildReferenceCountExchangeClient(url));
  40. continue;
  41. }
  42. //引用计数增1
  43. referenceCountExchangeClient.incrementAndGetCount();
  44. }
  45. }
  46.  
  47. /**
  48. * I understand that the purpose of the remove operation here is to avoid the expired url key
  49. * always occupying this memory space.
  50. */
  51. locks.remove(key);
  52.  
  53. return clients;
  54. }
  55. }

在buildReferenceCountExchangeClientList方法中,会调用initClient方法,创建客户端。

  1. /**
  2. * Create new connection
  3. *
  4. * @param url
  5. */
  6. private ExchangeClient initClient(URL url) {
  7.  
  8. // client type setting.
  9. //1 client类型,默认为netty
  10. String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));
  11. //2 编码方式为DubboCodec
  12. url = url.addParameter(CODEC_KEY, DubboCodec.NAME);
  13. // enable heartbeat by default
  14. //3 增加心跳检测,默认事件间隔为1分钟
  15. url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT));
  16.  
  17. ExchangeClient client;
  18. try {
  19. // connection should be lazy
  20. if (url.getParameter(LAZY_CONNECT_KEY, false)) {
  21. //4 构造懒连接,在使用的时候才会真正创建服务端连接
  22. client = new LazyConnectExchangeClient(url, requestHandler);
  23.  
  24. } else {
  25. //5 默认直接创建连接
  26. client = Exchangers.connect(url, requestHandler);
  27. }
  28.  
  29. } catch (RemotingException e) {
  30. throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
  31. }
  32.  
  33. return client;
  34. }

接下来,我们看看客户端是如何创建的。

(5)ExchangeClient的connect

上边所有的代码都是在协议层(Protocol),接下来主要聚焦在交换层(Exchanger)。

客户端创建是通过工具类Exchangers进行创建,通过URL获取Exchanger(默认实现为HeaderExchanger)

  1. public class Exchangers {
  2. public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
  3.  
  4. url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
  5. //先获取Exchanger,默认为HeaderExchanger
  6. return getExchanger(url).connect(url, handler);
  7. }
  8. }

从代码可以看出,HeaderExchanger为消费端和服务端创建的关键类,其创建client和server,分别为HeaderExchangeClient和HeaderExchangeServer。

  1. public class HeaderExchanger implements Exchanger {
  2.  
  3. public static final String NAME = "header";
  4.  
  5. @Override
  6. public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
  7. //1 Transporters.connect创建连接,返回Client;
  8. //2 构造HeaderExchangeClient,其中包含HeaderExchangeChannel,用于发送请求,并且失败重试
  9. return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
  10. }
  11.  
  12. @Override
  13. public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
  14. return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
  15. }
  16.  
  17. }

又出现一个问题,为什么通过Transporter connect获取到的client,要经过HeaderExchangeClient封装呢?

按官网的描述,是为上层的调用构建request- response的语义,相当于对netty client的封装。除此之外,HeaderExchangeClient中还有重新建立连接的功能,具体后边有时间再说怎么进行重新连接的。

(6)Transporter的connect

到这个地方,我们来到了传输层(Transporter),即用于发送和接受数据的地方。

工具类Transporters,创建客户端连接。

  1. public class Transporters {
  2. public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
  3.  
  4. ChannelHandler handler;
  5. if (handlers == null || handlers.length == 0) {
  6. handler = new ChannelHandlerAdapter();
  7. } else if (handlers.length == 1) {
  8. handler = handlers[0];
  9. } else {
  10. handler = new ChannelHandlerDispatcher(handlers);
  11. }
  12. //获取Transporter,默认为NettyTransporter
  13. return getTransporter().connect(url, handler);
  14. }
  15. }

Transporter的默认实现类为NettyTransporter,可以构建NettyClient和NettyServer,他们是数据发送和接受的执行实体,代码如下:

  1. public class NettyTransporter implements Transporter {
  2.  
  3. public static final String NAME = "netty";
  4.  
  5. @Override
  6. public RemotingServer bind(URL url, ChannelHandler listener) throws RemotingException {
  7. //构建NettyServer
  8. return new NettyServer(url, listener);
  9. }
  10.  
  11. @Override
  12. public Client connect(URL url, ChannelHandler listener) throws RemotingException {
  13. //构建NettyClient
  14. return new NettyClient(url, listener);
  15. }
  16.  
  17. }

可以看到,底层的客户端是NettyClient,它持有URL和一系列ChannelHandler。

我们接下来看看客户端是怎么进行实例化的。

  1. public class NettyClient extends AbstractClient {
  2. public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
  3. // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
  4. // the handler will be warped: MultiMessageHandler->HeartbeatHandler->handler
  5. super(url, wrapChannelHandler(url, handler));
  6. }
  7. }

NettyClient实例化主要通过父类来完成的,在调父类之前,通过wrapChannelHandler给ChannelHandler封装了2个Handler,分别是MultiMessageHandler和HeartbeatHandler,用于多消息处理和心跳检测处理。

接下来看下父类进行了什么操作。

  1. public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
  2. super(url, handler);
  3.  
  4. needReconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);
  5.  
  6. initExecutor(url);
  7.  
  8. try {
  9. //1 打开client
  10. doOpen();
  11. } catch (Throwable t) {
  12. close();
  13. }
  14. try {
  15. //2 client发起连接
  16. connect();
  17. } catch (RemotingException t) {
  18. close();
  19. } catch (Throwable t) {
  20. close();
  21. }
  22. }

可这是一个模板方法,doOpen和connect借助子类,即NettyClient完成的,我们再看看具体是怎么完成客户端创建的。

  1. //NettyClient
  2. @Override
  3. protected void doOpen() throws Throwable {
  4. //NettyClientHandler 为dubbo主要处理器
  5. final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
  6. bootstrap = new Bootstrap();
  7. bootstrap.group(nioEventLoopGroup)
  8. .option(ChannelOption.SO_KEEPALIVE, true)
  9. .option(ChannelOption.TCP_NODELAY, true)
  10. .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
  11. //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
  12. .channel(NioSocketChannel.class);
  13.  
  14. bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.max(3000, getConnectTimeout()));
  15. bootstrap.handler(new ChannelInitializer() {
  16.  
  17. @Override
  18. protected void initChannel(Channel ch) throws Exception {
  19. int heartbeatInterval = UrlUtils.getHeartbeat(getUrl());
  20.  
  21. if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
  22. ch.pipeline().addLast("negotiation", SslHandlerInitializer.sslClientHandler(getUrl(), nettyClientHandler));
  23. }
  24.  
  25. NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
  26. ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
  27. .addLast("decoder", adapter.getDecoder())
  28. .addLast("encoder", adapter.getEncoder())
  29. //空闲处理器,用于心跳检测
  30. .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
  31. .addLast("handler", nettyClientHandler);
  32.  
  33. String socksProxyHost = ConfigUtils.getProperty(SOCKS_PROXY_HOST);
  34. if(socksProxyHost != null) {
  35. int socksProxyPort = Integer.parseInt(ConfigUtils.getProperty(SOCKS_PROXY_PORT, DEFAULT_SOCKS_PROXY_PORT));
  36. Socks5ProxyHandler socks5ProxyHandler = new Socks5ProxyHandler(new InetSocketAddress(socksProxyHost, socksProxyPort));
  37. ch.pipeline().addFirst(socks5ProxyHandler);
  38. }
  39. }
  40. });
  41. }

上边的代码是不是非常熟悉,这就是netty创建客户端的标准代码,如果不熟悉也没关系,以后有机会再分享下netty。

ChannelPipeline中配置了编解码器、空闲处理器、处理dubbo消息的NettyClientHandler。

doOpen代码只是初始化好了Bootstrap,连接发生在doConnect方法中,如下:

  1. //NettyClient
  2. @Override
  3. protected void doConnect() throws Throwable {
  4. long start = System.currentTimeMillis();
  5. //与netty服务端连接,闭关获取ChannelFuture
  6. ChannelFuture future = bootstrap.connect(getConnectAddress());
  7. boolean ret = future.awaitUninterruptibly(getConnectTimeout(), MILLISECONDS);
  8.  
  9. if (ret && future.isSuccess()) {
  10. Channel newChannel = future.channel();
  11. Channel oldChannel = NettyClient.this.channel;
  12. oldChannel.close();
  13. NettyClient.this.channel = newChannel;
  14. }
  15. }

至此,我们了解了dubbo消费者是如何通过Exchanger和Transport,利用底层netty创建客户端连接的。

将创建好的客户端,封装到Protocol层获取到的invoker,在消费者发起调用的时候,直接可以请求到服务端。

用张图总结上边Exchanger和Transport两层的过程:

通过Protocol、Exchange、Transport三层的支撑下,完成了最开始图中的1-6步,获得到了代表服务端的invoker。

为了减少dubbo框架对使用者的代码侵入,还需要对服务端接口进行代理,

这样真正做到消费者如同调用本地一样,调用远程服务,接下来我们看看是如何代理的。

(7)ProxyFactory的getProxy

生成服务端接口代理,主要涉及ReferenceConfig中createProxy的第二步getProxy。

  1. return (T) PROXY_FACTORY.getProxy(invoker);

经过StubProxyFactoryWrapper包装类,最终调用到默认实现JavassistProxyFactory,其通过反射获取服务端接口的实现。代码如下:

  1. public class JavassistProxyFactory extends AbstractProxyFactory {
  2.  
  3. @Override
  4. @SuppressWarnings("unchecked")
  5. public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
  6. //通过反射创建invoker的代理,处理器为InvokerInvocationHandler
  7. return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
  8. }
  9.  
  10. @Override
  11. public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  12. // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
  13. final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
  14. return new AbstractProxyInvoker<T>(proxy, type, url) {
  15. @Override
  16. protected Object doInvoke(T proxy, String methodName,
  17. Class<?>[] parameterTypes,
  18. Object[] arguments) throws Throwable {
  19. return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
  20. }
  21. };
  22. }
  23.  
  24. }

该类包含两个方法,getProxy和getInvoker,前者用于消费端引用获取代理类,后者用于服务端暴露服务时获取对应的invoker。

此处关注getProxy方法,通过Proxy.getProxy反射获取代理,并且InvokerInvocationHandler为代理处理器。代码如下:

  1. public class InvokerInvocationHandler implements InvocationHandler {
  2. private static final Logger logger = LoggerFactory.getLogger(InvokerInvocationHandler.class);
  3. private final Invoker<?> invoker;
  4.  
  5. public InvokerInvocationHandler(Invoker<?> handler) {
  6. this.invoker = handler;
  7. }
  8.  
  9. @Override
  10. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  11. if (method.getDeclaringClass() == Object.class) {
  12. return method.invoke(invoker, args);
  13. }
  14. String methodName = method.getName();
  15. Class<?>[] parameterTypes = method.getParameterTypes();
  16. if (parameterTypes.length == 0) {
  17. if ("toString".equals(methodName)) {
  18. return invoker.toString();
  19. } else if ("$destroy".equals(methodName)) {
  20. invoker.destroy();
  21. return null;
  22. } else if ("hashCode".equals(methodName)) {
  23. return invoker.hashCode();
  24. }
  25. } else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
  26. return invoker.equals(args[0]);
  27. }
  28. RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), args);
  29. rpcInvocation.setTargetServiceUniqueName(invoker.getUrl().getServiceKey());
  30.  
  31. return invoker.invoke(rpcInvocation).recreate();
  32. }
  33. }

其中invoke方法,就是使用者调接口时,会被代理到该方法上。

我们看到服务接口等信息被封装到RpcInvocation中,通过持有的invoker进行调用。

调用关系如下图:

3.用一个例子,追踪数据的流转

举一个简单的例子,定义接口

  1. public interface HelloService {
  2. String sayHello(String name);
  3. }

服务提供者

  1. @Service
  2. public class HelloServiceImpl implements HelloService {
  3.  
  4. @Override
  5. public String sayHello(String name) {
  6. return "hello:"+name;
  7. }
  8. }

dubbo-provider.properties配置:

  1. dubbo.application.name=dubbo-annotation-provider
  2. dubbo.protocol.name=dubbo
  3. dubbo.protocol.port=20885

启动类:

  1. public class DubboProviderMain {
  2. public static void main(String[] args) throws Exception {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
  4. context.start();
  5.  
  6. System.in.read();
  7. }
  8.  
  9. @Configuration
  10. @EnableDubbo(scanBasePackages = "com.exm.service.impl")
  11. @PropertySource("classpath:/dubbo-provider.properties")
  12. static class ProviderConfiguration {
  13. @Bean
  14. public RegistryConfig registryConfig() {
  15. RegistryConfig registryConfig = new RegistryConfig();
  16. registryConfig.setAddress("zookeeper://127.0.0.1:2181?timeout=10000");
  17. return registryConfig;
  18. }
  19. }
  20. }

服务消费者

  1. @Component
  2. public class DubboConsumer {
  3.  
  4. @Reference(check = false)
  5. private HelloService helloService;
  6.  
  7. public String sayHello(String name) {
  8. return helloService.sayHello(name);
  9. }
  10. }

配置:

  1. dubbo.application.name=dubbo-annotation-consumer
  2. dubbo.registry.address=zookeeper://127.0.0.1:2181

启动类:

  1. public class DubboConsumerMain {
  2. public static void main(String[] args) throws IOException, InterruptedException {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
  4. context.start();
  5. DubboConsumer service = context.getBean(DubboConsumer.class);
  6. while (true) {
  7. System.in.read();
  8. try {
  9. String hello = service.sayHello("world");
  10. System.out.println("result :" + hello);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16.  
  17. @Configuration
  18. @PropertySource("classpath:/dubbo-consumer.properties")
  19. @ComponentScan("com.exm.bean")
  20. @EnableDubbo
  21. static class ConsumerConfiguration {
  22.  
  23. }
  24. }

跟踪代码,将流转的URL记录如下,可以参考着阅读源码

  1. //注册协议的URL
    RegistryProtocol#Invoker<T> refer(Class<T> type, URL url)
  2. type:interface com.exm.service.HelloService
  3. url:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-annotation-consumer&dubbo=2.0.2&pid=44159&refer=application%3Ddubbo-annotation-consumer%26check%3Dfalse%26dubbo%3D2.0.2%26init%3Dfalse%26interface%3Dcom.exm.service.HelloService%26methods%3DsayHello%26pid%3D44159%26register.ip%3D192.168.1.65%26release%3D2.7.5%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1654352381559&registry=zookeeper&release=2.7.5&timestamp=1654352409705


  4. //costumer的URL
  5. RegistryDirectory#void subscribe(URL url)
  6. ZookeeperRegistry#void doSubscribe(final URL url, final NotifyListener listener)
  7. url:consumer://192.168.1.65/com.exm.service.HelloService?application=dubbo-annotation-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.2&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44159&release=2.7.5&side=consumer&sticky=false&timestamp=1654352381559


  8. //provider、配置、路由对应的URL
  9. FailbackRegistry#void notify(URL url, NotifyListener listener, List<URL> urls)
  10. url:consumer://192.168.1.65/com.exm.service.HelloService?application=dubbo-annotation-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.2&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44159&release=2.7.5&side=consumer&sticky=false&timestamp=1654352381559
  11. urls:
  12. 0: dubbo://192.168.1.65:20885/com.exm.service.HelloService?anyhost=true&application=dubbo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44153&release=2.7.5&side=provider&timestamp=1654352300763
  13. 1: empty://192.168.1.65/com.exm.service.HelloService?application=dubbo-annotation-consumer&category=configurators&check=false&dubbo=2.0.2&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44159&release=2.7.5&side=consumer&sticky=false&timestamp=1654352381559
  14. 2: empty://192.168.1.65/com.exm.service.HelloService?application=dubbo-annotation-consumer&category=routers&check=false&dubbo=2.0.2&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44159&release=2.7.5&side=consumer&sticky=false&timestamp=1654352381559
  15.  
  16. RegistryDirectory#void refreshInvoker(List<URL> invokerUrls)
  17. invokerUrls:dubbo://192.168.1.65:20885/com.exm.service.HelloService?anyhost=true&application=dubbo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44153&release=2.7.5&side=provider&timestamp=1654352300763
  18.  
  19. InvokerDelegate#new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl)
  20. //url表示消费端,要创建服务端的连接
  21. url:dubbo://192.168.1.65:20885/com.exm.service.HelloService?anyhost=true&application=dubbo-annotation-consumer&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44159&register.ip=192.168.1.65&release=2.7.5&remote.application=dubbo-annotation-provider&side=consumer&sticky=false&timestamp=1654352300763
  22. providerUrl:dubbo://192.168.1.65:20885/com.exm.service.HelloService?anyhost=true&application=dubbo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44153&release=2.7.5&side=provider&timestamp=1654352300763
  23.  
  24. DubboProtocol#Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url)
  25. DubboProtocol#ExchangeClient initClient(URL url)
  26. url:dubbo://192.168.1.65:20885/com.exm.service.HelloService?anyhost=true&application=dubbo-annotation-consumer&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44259&register.ip=192.168.1.65&release=2.7.5&remote.application=dubbo-annotation-provider&side=consumer&sticky=false&timestamp=1654352300763
  27.  
  28. Exchangers#ExchangeClient connect(URL url, ExchangeHandler handler)
  29. NettyTransporter#Client connect(URL url, ChannelHandler listener)
  30. AbstractClient#AbstractClient(URL url, ChannelHandler handler)
  31. url:dubbo://192.168.1.65:20885/com.exm.service.HelloService?anyhost=true&application=dubbo-annotation-consumer&check=false&codec=dubbo&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&heartbeat=60000&init=false&interface=com.exm.service.HelloService&methods=sayHello&pid=44259&register.ip=192.168.1.65&release=2.7.5&remote.application=dubbo-annotation-provider&side=consumer&sticky=false&timestamp=1654352300763

到此为止,将dubbo时如何为消费端创造远程引用实例的(invoker+代理),可能依然有讲述不清晰的地方,请大家指出来,一块研读学习。

dubbo的消费者是怎么获取提供者服务接口引用的?的更多相关文章

  1. dubbo开发中使用到的一些服务配置方式

    通过之前的学习了解了dubbo的常规的使用,下面我们看看特殊情况或者说真实环境下使用dubbo的一些配置实例. 一.一个接口有多个实现时可以使用group来区分 1.服务提供者配置 <?xml  ...

  2. Dubbo的@Reference和@Service说明---1Reference用在消费者2Service用在提供者【import com.alibaba.dubbo.config.annotation.Service;】

    @Reference 用在消费端,表明使用的是服务端的什么服务@RestControllerpublic class RemoteUserController { @Reference(version ...

  3. Dubbo框架应用之(二)--服务治理

    Dubbo服务治理全貌图 当我们现有ITOO平台系统的业务随着用户的逐渐增大,设计的业务越来越广,系统会异常的复杂,在大规模服务之前,我们可以采用的是RMI或Hessian等工具,暴露和引用远程服务, ...

  4. Dubbo中消费者初始化的过程解析

    首先还是Spring碰到dubbo的标签之后,会使用parseCustomElement解析dubbo标签,使用的解析器是dubbo的DubboBeanDefinitionParser,解析完成之后返 ...

  5. dubbo初认知(dubbo和springCloud关系,在微服务架构中的作用等)(持续更新中)

    一:dubbo是什么? dobbuo是阿里开源的一个高性能优秀的服务框架, 可通过高性能的 RPC 实现服务的输出和输入功能,使得应用可以和 高性能的rpc实现输入和输出的功能,可以了  Spring ...

  6. Dubbo+Zookeeper+SpringMVC+Maven整合实现微服务项目

    互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,Dubbo是一个分布式服务框架,在这种情况下诞生的.现在核心业务抽取出来,作为独立的服务,使 ...

  7. dubbo作为消费者注册过程分析

    请支持原创: http://www.cnblogs.com/donlianli/p/3847676.html   作者当前分析的版本为2.5.x.作者在分析的时候,都是带着疑问去查看代码,debug进 ...

  8. 【Dubbo 源码解析】05_Dubbo 服务发现&引用

    Dubbo 服务发现&引用 Dubbo 引用的服务消费者最终会构造成一个 Spring 的 Bean,具体是通过 ReferenceBean 来实现的.它是一个 FactoryBean,所有的 ...

  9. dubbo作为消费者注册过程分析--????

    请支持原创: http://www.cnblogs.com/donlianli/p/3847676.html   作者当前分析的版本为2.5.x.作者在分析的时候,都是带着疑问去查看代码,debug进 ...

随机推荐

  1. centos7.3 安装oracle 详细过程

    centos7.3安装oracle详细过程1.下载Oracle安装包:linux.x64_11gR2_database_1of2.zip 和 linux.x64_11gR2_database_2of2 ...

  2. 搭建springboot集成mybatis

    1.new project创建新项目选择spring initializr: 2.选择依赖需要选择web.mybatis.mysql就够了,后续需要其他的直接pom引入依赖就好了: 3.自己在java ...

  3. Sqlalchemy异步操作不完全指北

    异步SQLAlchemy SQLAlchemy作为一款通用的Python Orm工具,在最近的版本也支持了异步操作.但网上很多资料都不是很齐全,API也不是很好查询的情况下,我便有了整理一份基础文档的 ...

  4. 从实例学习 Go 语言、"基础与进阶" 学习笔记及心得体会、Go指南

    第一轮学习 golang "基础与进阶"学习笔记,Go指南练习题目解析.使用学习资料 <Go-zh/tour tour>.记录我认为会比较容易忘记的知识点,进行补充,整 ...

  5. IDEA小技巧:Markdown里的命令行可以直接运行了

    作为一名开发者,相信大部分人都喜欢用Markdown来写文章和写文档. 如果你经常用开源项目或者自己维护开源项目,肯定对于项目下的README文件也相当熟悉了吧,通常我们会在这里介绍项目的功能.如何使 ...

  6. [源码解析] TensorFlow 之 分布式变量

    [源码解析] TensorFlow 之 分布式变量 目录 [源码解析] TensorFlow 之 分布式变量 1. MirroredVariable 1.1 定义 1.2 相关类 1.2.1 类体系 ...

  7. CentOS下Apache Doris Oracle ODBC外表使用指南

    1.软件环境 操作系统:CentOS 7.8 Apache Doris :0.15 Postgresql数据库:oracle 19c UnixODBC:2.3.1 Oracle ODBC :insta ...

  8. Java并发编程扩展(线程通信、线程池)

    之前我说过,实现多线程的方式有4种,但是之前的文章中,我只介绍了两种,那么下面这两种,可以了解了解,不懂没关系. 之前的文章-->Java并发编程之多线程 使用ExecutorService.C ...

  9. 【第三课】常用的Linux命令(学习笔记)

    4月8日 学习笔记打卡

  10. docker 灵活的构建 php 环境

    地址: https://github.com/ydtg1993/server           使用docker搭建灵活的线上php环境 有时候你可能不太需要一些别人已经集成了的包或者镜像      ...