前言
前面几篇文章结合rpc框架的基础需求,讲解了Dubbo中对于这些需求的抽象。Dubbo现在能够被如此多的线上应用所采用,跟最近几年微服务的广泛推广有很大的关系。微服务绝不仅仅是把服务拆小,改成远程调用这么简单,必须有配套的服务治理的功能,比如监控、熔断限流等。这篇文章就来分解下Dubbo是怎样来支持这些功能的。
Filter详解
之前讲到Dubbo对注册中心的支持是通过抽象出一个Directory接口来实现的。相对来说,注册中心的职责功能是比较明确的,主流的注册中心实现对外的接口相对统一,区别是在内部实现上。但是对于微服务相关的其它功能,因为需求个性化太强,却很难做这样的抽象。Dubbo是通过Filter来实现对扩展功能的支持。
Filter原理
首先来回顾下Dubbo的调用关系图:
Consumer Cluster
Consumer端所有的远程调用通过Invoker来发起,而Dubbo通过在Invoker上加上Filter链,在调用前后可以添加扩展逻辑。同样,在服务提供端的Invoker上也有一样的逻辑。熟悉web开发的肯定对这种方式不陌生,Servlet中的Filter就是这么实现的。
首先来看下Filter接口的定义:
@SPI
public interface Filter {
/**
* Make sure call invoker.invoke() in your implementation.
*/
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
Filter接口在Invoker被调用的前后被调用,在添加自己的逻辑之后,通过调用invoker参数的invoke()方法,来将请求继续传下去,这个跟Servlet中的FilterChain有点类似,但又不完全一样,下面会解析源代码。
Filter和Invoker集成
Filter要对用户无感知,就要将Filter和Invoker集成到一起,伪装成一个Invoker,这一点是通过ProtocolFilterWrapper
类来实现的,这个类是Protocol的实现类。之前的文章已经讲过,Dubbo在初始化代理的时候,是通过Protocol来获取Invoker的,比如服务提供方使用dubbo协议来暴露服务,那么Consumer端就通过DubboProtocol来获取Invoker的引用,这段逻辑不熟悉的可以到第一篇复习一下。
其实,在DubboProtocol的外层还有一个装饰类ProtocolFilterWrapper
来将Filter集成进去。至于代理在获取的时候是怎么得到ProtocolFilterWrapper
的,这个跟Dubbo的扩展加载机制有关,后续的文章会讲到。
ProtocolFilterWrapper
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (UrlUtils.isRegistry(invoker.getUrl())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (UrlUtils.isRegistry(url)) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}
}
从上面的代码可以看到,export和refer都是调用的封装的protocol的方法,对于dubbo协议,这个protocol就是一个DubboProtocol的实例。这里用了buildInvokerChain()方法来将Filter和原始的Invoker做了绑定。export()方法中,只会绑定用于Provider端的Filter,refer()方法中只会绑定用于Consumer端的Filter。
Filter链
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
//加载所有可用的Filter
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;
//将filter装饰成一个invoker
last = new Invoker<T>() {
...
...
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
//调用filter的invoker方法,filter完成自己的逻辑后必须调用next.invoke()
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
...
...
throw e;
} finally {
}
return asyncResult.whenCompleteWithContext((r, t) -> {
...
...
});
}
...
};
}
}
return last;
}
上面的代码为了简单,将异步处理的代码省略掉了。跟Servlet中额外定义一个FilterChain,通过FilterChain来调用Filter不同,Dubbo中直接将Filter封装成一个Invoker,然后将多个Invoker连接在一起。对于Consumer端的代理类来说,从protocol获取到一个的Invoker,调用invoke()方法,实际上是调用的这个链条的header,然后header再把请求传递下去。这样就把Filter的整个逻辑对Proxy透明化。
大概的逻辑图如下:
FilterChain
Filter列表
除了用户可以自定义自己的Filter之外,Dubbo自身很多功能也依赖了Filter方式来实现,比如服务监控。下面简单列举下比较重要的:
Filter | 注释 |
---|---|
AccessLogFilter | 访问日志记录,默认写到文件中 |
ActiveLimitFilter | Consumer端限流,限制并发请求数 |
ExecuteLimitFilter | Provider端限流,作用同ActiveLimitFilter |
MonitorFilter | Dubbo监控,将收集到的指标数据发给MonitorService |
MetricsFilter | 对接Ali开源的Metric监控,类似于dropwizard metrics,是现在比较主流的监控指标收集上报方式 |
GenericFilter | 泛化调用支持,多用于调用方没有服务提供方api的情况,只需要使用map传递参数就可调用。典型应用如一个支持dubbo的 job 调度中心,不需要把api的jar上传就可以调用dubbo接口 |
TokenFilter | token校验,用于限制接口的访问权限,只允许携带合法token的consumer调用 |
总结
Dubbo通过Filter机制提高了用户扩展的灵活性,而自身也受益于该机制来满足微服务治理的需求。
通过前面4篇文章简单的分解了下Dubbo框架,希望能够对Dubbo建立一个大体的框架图,使阅读源码或者理解源码分解文章更有帮助。当然,dubbo也一直再发展过程中,很多特性暂时没有提及,比如最新版本支持将配置信息从url中分离出来放到配置中心;还有异步化调用支持等。在白话Dubbo部分,Dubbo的核心SPI机制也没有涉及,个人认为放到源码详解部分更为合适,感谢继续关注后续文章。
网友评论