文章目录

  • 一、前言
  • 二、QosProtocolWrapper
  • 1. Qos 基础使用
  • 三、ProtocolListenerWrapper
  • 1. ListenerExporterWrapper
  • 2. ListenerInvokerWrapper
  • 3. ExporterListener & InvokerListener
  • 四、ProtocolFilterWrapper
  • 五、总结


一、前言

本系列为个人Dubbo学习笔记衍生篇,是正文篇之外的衍生内容,内容来源于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章。仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。


Dubbo笔记⑤ : 服务发布流程 - Protocol#exportDubbo笔记⑨ : 消费者启动流程 - RegistryProtocol#refer 中我们看到了官方给 Protocol 接口提供了多个包装类,本文来看一下这几个包装类的详细实现。


  • Protocol#export :在服务导出时会调用,完成了服务的导出。
  • Protocol#refer :在服务引用时会调用,完成了服务的引用。

二、QosProtocolWrapper

QoS(Quality of Service,服务质量)指一个网络能够利用各种基础技术,为指定的网络通信提供更好的服务能力,是网络的一种安全机制, 是用来解决网络延迟和阻塞等问题的一种技术。dubbo为用户提供类似的网络服务用来online和offline service来解决网络延迟,阻塞等问题。

QosProtocolWrapper 便完成了 Dubbo QOS 功能的处理。

QosProtocolWrapper部分代码实现如下:

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    	// 如果当前协议是 Registry 类型,则开启Qos, 这里的Qos只会启动一次
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        	// 启动Qos 服务,其中通过Cas 判断是否已经开启过,确保只启动一次
            startQosServer(invoker.getUrl());
            return protocol.export(invoker);
        }
        return protocol.export(invoker);
    }
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    	// 如果当前协议是 Registry 类型,则开启Qos, 这里的Qos只会启动一次
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            startQosServer(url);
            return protocol.refer(type, url);
        }
        return protocol.refer(type, url);
    }

    @Override
    public void destroy() {
        protocol.destroy();
        // 协议销毁时关闭Qos 服务
        stopServer();
    }

这里可以看到,当第一个服务向服务中心进行注册时或者第一次引用时,Dubbo 会启动Qos 服务,可以通过 qos.enable参数控制是否开启。


1. Qos 基础使用

Dubbo的QoS是默认开启的,端口为22222,可以通过配置修改端口

<dubbo:application name="springboot-dubbo-provider">
        <!--        修改 qos 端口号,默认 22222-->
        <dubbo:parameter key="qos.port" value="33333"/>
        <!--        关闭 qos 功能,默认true启用,false关闭-->
        <dubbo:parameter key="qos.enable" value="true"/>
        <!--        为了安全考虑,dubbo的qos默认是只支持本地连接的,如果要开启任意ip可连接,如下配置-->
        <dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
    </dubbo:application>

通过命令 telnet ip port 使用

telnet localhost 22222

如下:

dubbo proxy实现 dubbo:protocol_dubbo proxy实现


通过 help 命令获取帮助:

ls : 列出所有服务列表
online 服务名 : 上线某个服务
offline 服务名:下线某个服务
quit :退出服务

三、ProtocolListenerWrapper

ProtocolListenerWrapper 通过 ExporterListenerInvokerListener 接口进行了服务导出、引用等时机的监听。

ProtocolListenerWrapper部分代码实现如下:

// 服务导出
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    	// 如果 URl 协议是 registry 则不处理。
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        // 否则对 Exporter 进行增强,这里是获取 SPI 接口 ExporterListener的实现类作为入参
        return new ListenerExporterWrapper<T>(protocol.export(invoker),
                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                        .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }
	
	// 服务引用
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
   		 // 如果 URl 协议是 registry 则不处理。
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        // 否则对 Exporter 进行增强,这里是获取 SPI 接口 InvokerListener 的实现类作为入参
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                                .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
    }

这里我们可以看到在不是Registry 协议的情况下,ProtocolListenerWrapper 会在调用ProtocolListenerWrapper#export (服务导出) 和 ProtocolListenerWrapper#refer (服务引用)时进行包装。

这里我们看到两个增强类:

  • ListenerExporterWrapper :导出服务时使用 ListenerExporterWrapper 对 Exporter 进行包装增强,同时传递了 ExporterListener 接口的实现类。其接口如下:
@SPI
public interface ExporterListener {

    /**
     * The exporter exported.
     *
     * @param exporter
     * @throws RpcException
     * @see org.apache.dubbo.rpc.Protocol#export(Invoker)
     */
    void exported(Exporter<?> exporter) throws RpcException;

    /**
     * The exporter unexported.
     *
     * @param exporter
     * @throws RpcException
     * @see org.apache.dubbo.rpc.Exporter#unexport()
     */
    void unexported(Exporter<?> exporter);

}
  • ListenerInvokerWrapper :引用服务时使用 ListenerInvokerWrapper 对 Invoker进行包装增强, 同时传递了 InvokerListener 接口的实现类。其接口如下:
@SPI
public interface InvokerListener {

    /**
     * The invoker referred
     *
     * @param invoker
     * @throws RpcException
     * @see org.apache.dubbo.rpc.Protocol#refer(Class, org.apache.dubbo.common.URL)
     */
    void referred(Invoker<?> invoker) throws RpcException;

    /**
     * The invoker destroyed.
     *
     * @param invoker
     * @see org.apache.dubbo.rpc.Invoker#destroy()
     */
    void destroyed(Invoker<?> invoker);

}

下面我们来看看这两个增强的具体实现

1. ListenerExporterWrapper

ListenerExporterWrapper 的逻辑很简单,即在 初始化是 和调用 unexport 方法后调用 ExporterListener 的 exported 方法和 unexport 方法

public class ListenerExporterWrapper<T> implements Exporter<T> {

    private static final Logger logger = LoggerFactory.getLogger(ListenerExporterWrapper.class);

    private final Exporter<T> exporter;

    private final List<ExporterListener> listeners;

    public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {
        if (exporter == null) {
            throw new IllegalArgumentException("exporter == null");
        }
        this.exporter = exporter;
        this.listeners = listeners;
        if (listeners != null && !listeners.isEmpty()) {
            RuntimeException exception = null;
            for (ExporterListener listener : listeners) {
                if (listener != null) {
                    try {
                    	// 调用 exported 方法,因为在调用 Protocol#export 方法时会初始化ListenerExporterWrapper,所以在初始化的时候调用 exported 方法。
                        listener.exported(this);
                    } catch (RuntimeException t) {
                        logger.error(t.getMessage(), t);
                        exception = t;
                    }
                }
            }
            if (exception != null) {
                throw exception;
            }
        }
    }

    @Override
    public Invoker<T> getInvoker() {
        return exporter.getInvoker();
    }

    @Override
    public void unexport() {
        try {
            exporter.unexport();
        } finally {
            if (listeners != null && !listeners.isEmpty()) {
                RuntimeException exception = null;
                for (ExporterListener listener : listeners) {
                    if (listener != null) {
                        try {
                        	// 调用 unexported 方法
                            listener.unexported(this);
                        } catch (RuntimeException t) {
                            logger.error(t.getMessage(), t);
                            exception = t;
                        }
                    }
                }
                if (exception != null) {
                    throw exception;
                }
            }
        }
    }

}

2. ListenerInvokerWrapper

ListenerInvokerWrapper 与 ListenerExporterWrapper 逻辑类似,在 调用 初始化和 destroy() 方法后调用 ListenerInvokerWrapper 的 referred方法和 destroy方法。这里不再赘述。

public class ListenerInvokerWrapper<T> implements Invoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(ListenerInvokerWrapper.class);

    private final Invoker<T> invoker;

    private final List<InvokerListener> listeners;

    public ListenerInvokerWrapper(Invoker<T> invoker, List<InvokerListener> listeners) {
        if (invoker == null) {
            throw new IllegalArgumentException("invoker == null");
        }
        this.invoker = invoker;
        this.listeners = listeners;
        if (listeners != null && !listeners.isEmpty()) {
            for (InvokerListener listener : listeners) {
                if (listener != null) {
                    try {
                        listener.referred(invoker);
                    } catch (Throwable t) {
                        logger.error(t.getMessage(), t);
                    }
                }
            }
        }
    }
	
	//......
	
    @Override
    public void destroy() {
        try {
            invoker.destroy();
        } finally {
            if (listeners != null && !listeners.isEmpty()) {
                for (InvokerListener listener : listeners) {
                    if (listener != null) {
                        try {
                            listener.destroyed(invoker);
                        } catch (Throwable t) {
                            logger.error(t.getMessage(), t);
                        }
                    }
                }
            }
        }
    }

}

可以看到 ProtocolListenerWrapper 进行增强的最终目的是在服务发布、引用、销毁时调用ExporterListener、InvokerListener 对应的监听方法。

3. ExporterListener & InvokerListener

ExporterListener 和 InvokerListener 都是 SPI 接口,我们可以通过 SPI 的方式来自定义其实现类。

ExporterListener 默认没有实现。而InvokerListener 在 org.apache.dubbo.rpc.InvokerListener 文件中只有一个实现类 DeprecatedInvokerListener。其作用是校验调用方法是否已经被弃用,如果被弃用则阻拦调用。如下:

@Activate(Constants.DEPRECATED_KEY)
public class DeprecatedInvokerListener extends InvokerListenerAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(DeprecatedInvokerListener.class);

    @Override
    public void referred(Invoker<?> invoker) throws RpcException {
    	// 校验  deprecated  参数
        if (invoker.getUrl().getParameter(Constants.DEPRECATED_KEY, false)) {
            LOGGER.error("The service " + invoker.getInterface().getName() + " is DEPRECATED! Declare from " + invoker.getUrl());
        }
    }

}

四、ProtocolFilterWrapper

ProtocolFilterWrapper 是对 Dubbo org.apache.dubbo.rpc.Filter 的处理。关于 Dubbo Filter的介绍,如有需要详参:Dubbo笔记 ⑰ :Dubbo Filter 详解

ProtocolFilterWrapper#getDefaultPort 和 ProtocolFilterWrapper#destroy 都是直接透传给下一层 Protocol。所以我们这里来看ProtocolFilterWrapper 的其他方法实现:

@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
    }

可以看到在 ProtocolFilterWrapper#referProtocolFilterWrapper#export 方法中都会调用 ProtocolFilterWrapper#buildInvokerChain 方法,所以ProtocolFilterWrapper#buildInvokerChain 方法才是核心,从方法名字也能看出来其功能是构造调用过滤器链路。

下面我们来看看该方法的具体实现:

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        // 通过 SPI 扩展机制获取到 Filter 实现类,这里我们 在  中介绍过
        // 通过这种方式可以获取多个SPI 实现类
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result result = filter.invoke(next, invocation);
                        if (result instanceof AsyncRpcResult) {
                            AsyncRpcResult asyncResult = (AsyncRpcResult) result;
                            asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
                            return asyncResult;
                        } else {
                            return filter.onResponse(result, invoker, invocation);
                        }
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        return last;
    }

可以看到,ProtocolFilterWrapper 的作用对Invoker进行请求过滤,在 Invoker 执行方法前后,会调用 Filter的相应方法,完成过滤请求。

这里需要注意的是:

  1. 这里需要注意,针对每一个 Filter,都会生成一个Invoker,即假设存在FilterA、FilterB.则会创建 FilteAInvoker、FilterBInvoker进行嵌套调用。
  2. 关于 ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); 的源码分析详参 Dubbo笔记衍生篇②:Dubbo SPI 原理五#1#1.2 章节
  3. 默认提供的Filter 如下,服务导出和服务引用时都会调用 ProtocolFilterWrapper#buildInvokerChain。也即是说 Filter 对 服务提供者和消费者都生效,但并非所有的Filter都可作用于提供者和消费者,有的 Filter 只需要作用于消费者,有的则只需要作用于生产者。这里则是通过@Activate 注解来根据情况激活 Filter。仅@Activate 满足当前情况的Filter 才会被激活:
cache=org.apache.dubbo.cache.filter.CacheFilter
validation=org.apache.dubbo.validation.filter.ValidationFilter
echo=org.apache.dubbo.rpc.filter.EchoFilter
generic=org.apache.dubbo.rpc.filter.GenericFilter
genericimpl=org.apache.dubbo.rpc.filter.GenericImplFilter
token=org.apache.dubbo.rpc.filter.TokenFilter
accesslog=org.apache.dubbo.rpc.filter.AccessLogFilter
activelimit=org.apache.dubbo.rpc.filter.ActiveLimitFilter
classloader=org.apache.dubbo.rpc.filter.ClassLoaderFilter
context=org.apache.dubbo.rpc.filter.ContextFilter
consumercontext=org.apache.dubbo.rpc.filter.ConsumerContextFilter
exception=org.apache.dubbo.rpc.filter.ExceptionFilter
executelimit=org.apache.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=org.apache.dubbo.rpc.filter.DeprecatedFilter
compatible=org.apache.dubbo.rpc.filter.CompatibleFilter
timeout=org.apache.dubbo.rpc.filter.TimeoutFilter
trace=org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=org.apache.dubbo.monitor.support.MonitorFilter

其中

  • MonitorFilter 和监控中心进行交互
  • FutureFilter用来实现异步调用
  • GenericFilter用来实现泛化调用
  • ActiveLimitFilter用来控制消费端最大并发调用量
  • ExecuteLimitFilter用来控制服务提供方最大并发处理量等。

五、总结

  • QosProtocolWrapper :完成了 Dubbo QOS 功能的处理
  • ProtocolListenerWrapper :增加了服务操作时候的监听功能
  • ProtocolFilterWrapper:完成了 Dubbo 拦截链的实现。

需要注意的是, 在上面的三个包装类中,ProtocolListenerWrapper 、ProtocolFilterWrapper 都没在协议类型是 Registry 时直接放行,而QosProtocolWrapper 也仅仅是开启了Qos 服务后放行。

也即是说,这三个包装类在协议类型是Registry 时都没有干涉服务流程。这是由于RegistryProtocol 的特殊性决定的。如下图,如果协议类型是 Registry,则会二次调用Wrapper,第二次调用时才会真正执行包装过程。

dubbo proxy实现 dubbo:protocol_apache_02


以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》