Dubbo 延迟与粘滞连接

前言

大家好,今天开始给大家分享 — Dubbo 专题之 Dubbo 延迟和粘滞连接。在前一个章节中我们介绍了 Dubbo 并发控制,Dubbo 为我们提供两大类的配置:消费端的配置和服务提供端配置,我们分别可以对服务提供端和服务消费端进行并发数量的控制。同时我们也例举了常见的使用场景并且进行了源码解析来分析其实现原理。有的小伙伴学习了并发控制可能会想到:如果我们的服务消费端有大量的服务需要引用,那我们的 Dubbo 应用程序可能启动相当的缓慢其原因是:当我们消费端应用启动的时候需要获取远程服务的代理对象的引用,如果我们每一个获取的远程代理对象都在启动的时候创建连接,这样必定会影响我们的应用程序启动,幸好我们的 Dubbo 提供一种配置的方式解决这个问题。那同时也延伸出另外一个问题,就是我们对某个服务的调用能不能一直分配到上次调用的服务提供者呢?带着这些疑问我们开始本章节学习,我们会通过介绍什么是延迟和粘滞连接?怎样通过参数配置改变默认行为?来解决这些问题。下面就让我们快速开始吧!

1. 延迟和粘滞连接简介

通过前面对 Dubbo 相关章节的介绍我相信大家应该有个基本的概念就是我们 Dubbo 中服务消费方持有服务提供端的服务引用,这个引用又通过层层的代理最终通过我们的 TCP/IP (底层使用Netty进行网络通讯)与远程服务端进行通讯。在 Dubbo 中为了优化消费端获取服务提供端的引用对象时候创建底层的物理连接,比如在与 Spring 集成中我们的引用对象需要通过 Spring 容器进行对外发布 Bean 实例,然而此时我们并没有真正的使用代理对象,只是将代理对象交给 Spring 管理。为了使代理对象在真正使用的时候才去创建底层的物理从而减少底层连接的创建和释放这就叫做延迟连接。同理粘滞连接的意思就是尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。下图简单的描述了粘滞连接在第二次、第三次调用服务的时候调用原服务提供者:

粘滞连接

2. 配置方式

下面我们主要通过 XML 和 注解的方式进行配置介绍:

2.1 延迟连接:

  1. XML 方式
<dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.common.api.BookFacade" lazy="true" ></dubbo:reference>
  1. 注解方式
@Reference(lazy = true)

2.2 粘滞连接

  1. XML 方式
<dubbo:reference id="bookFacade" interface="com.muke.dubbocourse.common.api.BookFacade" sticky="true" />

方法级别控制:

<dubbo:reference id="xxxService" interface="com.muke.dubbocourse.common.api.BookFacade">
    <dubbo:mothod name="queryAll" sticky="true" />
</dubbo:reference>
  1. 注解方式
@Reference(sticky = true)

从上面的配置中我们可以简单的总结:延迟连接通过lazy进行配置,粘滞连接使用sticky进行配置。

3. 使用场景

根据前面的介绍我们大概理解了什么是延迟和粘滞连接。其中延迟连接就是为了减少无用的连接而在真正使用对象时候才创建连接,而粘滞连接是为了多次调用都尽可能地使用同一个服务提供者也有减少服务连接创建的作用。下面我们简单的介绍几种常见使用场景:

  1. 当我们的服务消费端需要大量的引用服务提供者或者创建远程连接成本非常高(一般指耗时时间)时我们可以考虑开启延迟连接。

  2. 假设我们的应用有大量的静态数据需要加载到应用本地缓存( JVM 缓存)时当第一次调用Service A进行缓存加载,那么在第二次调用的时候我们期望也调用刚才已经存在缓存的服务 Service A 这样提高了服务的访问速度。这种场景可以使用粘滞连接。

  3. 如果我们的应用调用过程存在某种状态,例如:调用服务 Service A 进行用户登录返回 token,那第二次调用查询用户信息的时候需要根据携带的token来查询用户的登录状态,此时如果访问 Service A 那么用户登录的 token 信息是存的,如果访问到 Service B 这时就不存在 token (假设这里没有使用分布式缓存)。这种场景可以使用粘滞连接。

4. 示例演示

下面我以获取图书列表为例进行演示。项目结构如下:

idea

我们的延迟连接主要配置在服务消费端dubbo-consumer-xml.xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer" logger="log4j"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!--lazy="true"延迟连接-->
    <dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.common.api.BookFacade" lazy="true" ></dubbo:reference>

</beans>

通过lazy="true"配置消费端引用服务提供者服务时开启延迟连接。下面我们继续看看粘滞连接配置:

    <!--sticky="true"开启粘滞连接 -->
    <dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.common.api.BookFacade" sticky="true" ></dubbo:reference>

通过sticky="true"配置消费端引用服务提供者服务时开启粘滞连接。

Tips:这里在演示粘滞连接的时候小伙伴们在部署应用的时候至少需要部署两个或以上的实例才能看出效果。如果我们开启粘滞连接那么我们可以看到总是访问同一个服务提供者。

5. 实现原理

下面我们通过源码的方式简单的分析它们的实现原理。

首先是延迟连接其核心方法org.apache.dubbo.rpc.protocol.dubbo. DubboProtocol#protocolBindingRefer代码如下:

    /***
     *
     * 协议绑定并创建连接
     *
     * @author liyong
     * @date 16:11 2020-03-08
     * @param serviceType
     * @param url
     * @exception
     * @return org.apache.dubbo.rpc.Invoker<T>
     **/
    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);

        // 创建DubboInvoker并且创建网络连接
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

这里的 DubboInvoker 是我们远程 RPC 调用的封装,这里org.apache.dubbo.rpc.protocol.dubbo. DubboProtocol#getClients方法创建客户端连接并且绑定请求处理器核心代码如下:

/***
     *
     * 获取客户端连接,并且绑定了请求处理器
     *
     * @author liyong
     * @date 16:55 2020-03-08
     * @param url
     * @exception
     * @return org.apache.dubbo.remoting.exchange.ExchangeClient[]
     **/
    private ExchangeClient[] getClients(URL url) {
        //...
        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;
    }

其中initClient方法创建连接核心代码如下:

      private ExchangeClient initClient(URL url) {

        //...

        ExchangeClient client;
        try {
            //是否延迟连接 lazy="true"
            if (url.getParameter(LAZY_CONNECT_KEY, false)) {
                //这里并没有创建连接对象
                client = new LazyConnectExchangeClient(url, requestHandler);

            } else {
                //HeaderExchangeClient连接到服务器并绑定请求处理器
                client = Exchangers.connect(url, requestHandler);
            }

        } catch (RemotingException e) {
           //...
        }

        return client;
    }

根据我们的配置获取LAZY_CONNECT_KEY参数(lazy)的值,当我们配置为true时创建LazyConnectExchangeClient对象,为false是创建物理连接。

下面继续讨论粘滞连接核心方法org.apache.dubbo.rpc.cluster.support. AbstractClusterInvoker#select如下:

protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {

        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        String methodName = invocation == null ? StringUtils.EMPTY_STRING : invocation.getMethodName();

        //获取粘滞连接配置
        boolean sticky = invokers.get(0).getUrl()
                .getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY);

        //ignore overloaded method
        if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
            stickyInvoker = null;
        }
        //开启粘滞连接配置 且存在已经创建的stickyInvoker粘滞连接
        if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
            //有效性检测
            if (availablecheck && stickyInvoker.isAvailable()) {
                return stickyInvoker;
            }
        }

        //根据负载均衡策略进行服务选择
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);

        if (sticky) {
            //如果配置粘滞连接为true 则当前选择的Invoker保存在stickyInvoker粘滞连接变量
            stickyInvoker = invoker;
        }
        return invoker;
    }

从上面的代码我们可以看出从 URL 中获取 sticky 配置判断是否开启粘滞连接,如果开启那么在第一次获取 InvokerstickyInvokernull 创建一个 Invoker 代理对象,当第二次获取 Invoker 时会判断是否存在 stickyInvoker 如果存在且没有被排除则继续使用前面保存下来的 Invoker 代理对象也就是stickyInvoker

Tips:这里 URL 包括我们配置的 XML 或注解配置中的参数、通讯协议、序列化方式等等参数,可以参考前面章节详细描述。

6. 小结

在本小节中我们主要学习了 Dubbo 延迟和粘滞连接,同时我们也分析了延迟和粘滞连接的实现原理。延迟连接本质上是延迟 Dubbo 底层物理连接的创建,而粘滞连接本质上是重复利用已创建的连接。

本节课程的重点如下:

  1. 理解 Dubbo 延迟和粘滞连接

  2. 了解了延迟和粘滞连接使用方式

  3. 了解延延迟和粘滞连接使用场景

  4. 了解延迟和粘滞连接实现原理

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。关注公众号:青年IT男 获取最新技术文章推送!

博客地址: http://youngitman.tech

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容