文章目录
- 一、前言
- 二、QosProtocolWrapper
- 1. Qos 基础使用
- 三、ProtocolListenerWrapper
- 1. ListenerExporterWrapper
- 2. ListenerInvokerWrapper
- 3. ExporterListener & InvokerListener
- 四、ProtocolFilterWrapper
- 五、总结
一、前言
本系列为个人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
通过 ExporterListener
、InvokerListener
接口进行了服务导出、引用等时机的监听。
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#refer
和 ProtocolFilterWrapper#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的相应方法,完成过滤请求。
这里需要注意的是:
- 这里需要注意,针对每一个 Filter,都会生成一个Invoker,即假设存在FilterA、FilterB.则会创建 FilteAInvoker、FilterBInvoker进行嵌套调用。
- 关于
ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
的源码分析详参 Dubbo笔记衍生篇②:Dubbo SPI 原理 中 五#1#1.2 章节 - 默认提供的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 核心技术内幕》