手记

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. 示例演示

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

我们的延迟连接主要配置在服务消费端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. 了解延迟和粘滞连接实现原理

作者

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

0人推荐
随时随地看视频
慕课网APP