前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo学习(七)服务引用-consumer

dubbo学习(七)服务引用-consumer

作者头像
虞大大
发布2020-11-06 08:12:53
4740
发布2020-11-06 08:12:53
举报
文章被收录于专栏:码云大作战码云大作战

一、服务引用

在消费端dubbo的使用中我们会把需要引用的provider服务配置在dubbo-consumer.xml中来进行引用,这一篇主要来分析dubbo的provider服务在消费端的服务引用。

在消费端的应用中的服务引用配置:

代码语言:javascript
复制
<dubbo:reference id="testApi" interface="com.ywl.dubbo.TestApi"
                 check="false" timeout="3000"/>

· 服务引用配置的解析

和dubbo-provider服务api配置类似,也会依赖dubbo.schema的自定义标签,这里使用了reference元素,那么该服务引用配置的解析就由以下代码来完成:

registerBeanDefinitionParser("reference",

new DubboBeanDefinitionParser(ReferenceBean.class, false));

代码语言:javascript
复制

· ReferenceBean

public class ReferenceBean<T> extends ReferenceConfig<T>

implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

该服务引用实现了FactoryBean接口,那么意味着该服务引用可以通过getObject方法来获取该bean的实例。

二、服务引用原理-refer

1、通过getObject获取服务实例

代码语言:javascript
复制
public Object getObject() throws Exception {
    return get();
}

2、开始创建服务代理类-createProxy

代码语言:javascript
复制
private T createProxy(Map<String, String> map) {
    //...
    if (isJvmRefer) {
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
        //...
        if (urls.size() == 1) {            //获取服务的invoker对象
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        }        //...
    }
    //将invoker对象 生成为动态代理类
    return (T) proxyFactory.getProxy(invoker);
}

3、客户端引用远程服务-refer

代码语言:javascript
复制
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);      //建立zookeeper连接    Registry registry = registryFactory.getRegistry(url);
    //如果是RegistryService类型,则直接返回RegistryService的invoker对象
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }
    //...
    return doRefer(cluster, registry, type, url);
}
代码语言:javascript
复制
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);
    directory.setProtocol(protocol);
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {        //向注册中心注册消费者信息-持久化节点
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }    //订阅服务提供方
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));
    //路由选择    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);    return invoker;
}

如上所示,客户端引用远程服务主要干了这几件事:

(1)注册消费者信息-持久化结点,具体的结点为/dubbo/com.ywl.dubbo.TestApi/consumers。

(2)订阅服务提供方结点,providers,routes,configurations。

(3)路由选择

· 订阅服务提供方

收到订阅后的处理相关的核心代码:

代码语言:javascript
复制
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    //...
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {        //category 为 providers,configurators,routersString category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        //将消费者注册的url信息缓存到本地文件中        saveProperties(url);
        listener.notify(categoryList);
    }
}
代码语言:javascript
复制
public synchronized void notify(List<URL> urls) {
    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
代码语言:javascript
复制
    //...
代码语言:javascript
复制
    //更新configurations信息
    if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
        this.configurators = toConfigurators(configuratorUrls);
    }
    //更新routes信息
    if (routerUrls != null && !routerUrls.isEmpty()) {
        List<Router> routers = toRouters(routerUrls);
        if (routers != null) { // null - do nothing
            setRouters(routers);
        }
    }
代码语言:javascript
复制
    //更新invoker信息
代码语言:javascript
复制
    refreshInvoker(invokerUrls);
}

refreshInvoker方法的主要目的是将invokerURL列表转换为invoker列表。

dubbo的refreshInvoker方法中的注释中标注了以下的转换规则:

(1)如果url已经被转换为invoker,则不再重新引用,则直接从缓存中获取,如果url中任意一个参数变更也会重新引用。

(2)如果传入的invoker列表不为空,则表示最新的invoker列表。

(3)如果传入的invoker列表为空,则表示只是下发的ovverride规则或route规则,需要重新交叉对比,决定是否需要重新引用。

· 路由选择-cluster.join

代码语言:javascript
复制
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    return new FailoverClusterInvoker<T>(directory);
}

join方法主要是初始化对应的路由信息,因为在配置文件中未配置所以进入了默认的路由规则实现 - FailoverCluster,在后续dubbo调用中的负载均衡策略需要,dubbo的路由策略会单独写一篇文章来分析。

dubbo路由规则实现:

(1)Failback Cluster

表示失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

服务提供方配置:<dubbo:service cluster="failback" />

服务消费方配置:<dubbo:reference cluster="failback" />

(2)Failover Cluster

表示失败自动切换,当出现失败会重试其他服务器。可人为设置重试次数,通常可以用于读操作,但是如果发生重试会延长接口的执行时间。如果retries设置为0时和failfast规则一样。

服务提供方配置:<dubbo:service cluster="failover" retries="2" />

服务消费方配置:<dubbo:reference cluster="failover" retries="2" />

或者不配置,则默认为该实现。

(3)Failfast Cluster

表示快速失败,只会发起一次调用,失败则立即报错。通常用于非幂等性的写操作,比如新增记录。

服务提供方配置:<dubbo:service cluster="failfast" />

服务消费方配置:<dubbo:reference cluster="failfast" />

(4)Failsafe Cluster

表示失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

服务提供方配置:<dubbo:service cluster="failsafe" />

服务消费方配置:<dubbo:reference cluster="failsafe" />

(5)forking Cluster

并行调用服务器,只要一个成功即返回。由于这样需要浪费更多服务资源,所以通常用于实时性较高的读操作。

服务提供方配置:<dubbo:service cluster="forking" />

服务消费方配置:<dubbo:reference cluster="forking" />

(6)mergeable Cluster

聚合集群,将集群中的调用结果聚合起来返回聚合后的结果。通常用于接口一样,但有多种实现,用group区分的接口,比如菜单服务。

服务消费方配置:<dubbo:reference group="*" merger="true" /> 或

<dubbo:reference group="aaa,bbb" merger="true" />等

(7)Broad Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

服务提供方配置:<dubbo:service cluster="broadcast" />

服务消费方配置:<dubbo:reference cluster="broadcast" />

以上的路由策略,在一般情况下采用failover(读操作)或failfast(写操作)规则就可以满足需求。

4、生成代理对象 - ProxyFactory.getProxy(invoker)

return (T) Proxy.newProxyInstance(

Thread.currentThread().getContextClassLoader(), interfaces,

new InvokerInvocationHandler(invoker));

代码语言:javascript
复制

最终采用了JDK动态代理生成代理对象。

三、总结

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档