Dubbo笔记衍生篇③:ProtocolWrapper

一、前言

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


Dubbo笔记⑤ : 服务发布流程 - Protocol#export
Dubbo笔记⑨ : 消费者启动流程 - 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

如下:
在这里插入图片描述
通过 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 实现类,这里我们 在 https://blog.csdn.net/qq_36882793/article/details/114597666 中介绍过
        // 通过这种方式可以获取多个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,第二次调用时才会真正执行包装过程。
在这里插入图片描述


以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://blog.csdn.net/qq_33513250/article/details/102978132
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值