dubbo rpc filter实现剖析(一)
2018-09-05 00:04 chen.simon 阅读(1364) 评论(0) 编辑 收藏 举报2.6.3版本,之前读的是2.4.9版本
本篇主要阐述dubbo rpc的filter的实现,包括作用,用法,原理,与Spring Cloud在这些能力的对比。
共提供了多少个?是哪些?发布时默认装配了哪些给他自身的扩展点机制?
从类与接口关系分析的结果文档中可以看到共20个:
241 Filter
--241.1 CacheFilter
--241.2 MonitorFilter
--241.3 AccessLogFilter
--241.4 ActiveLimitFilter
--241.5 ClassLoaderFilter
--241.6 CompatibleFilter
--241.7 ConsumerContextFilter
--241.8 ContextFilter
--241.9 DeprecatedFilter
--241.10 EchoFilter
--241.11 ExceptionFilter
--241.12 ExecuteLimitFilter
--241.13 GenericFilter
--241.14 GenericImplFilter
--241.15 TimeoutFilter
--241.16 TokenFilter
--241.17 TpsLimitFilter
--241.18 FutureFilter
--241.19 TraceFilter
--241.20 ValidationFilter
从发布的jar中的META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter中发现除了TpsLimitFilter之外,其余的都装上了。
cache=com.alibaba.dubbo.cache.filter.CacheFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter
这些filter都有什么作用?如何使用?实现原理是什么?Spring Cloud是否也提供了这些能力?有什么差异?
CacheFilter
作用
缓存调用结果,比如配置在consumer端, 比如我们通过id查某个用户的信息,对于特定的一个id,在consumer端第一次调用时会给provider端发请求,后面再调用时,直接用consumer端缓存的结果返回,你不再发请求给provider端。
使用方式
consumer侧的配置:
<dubbo:reference id="userService" interface="org.simonme.dubbo.demo.provider.service.UserService" filter="cache">
<dubbo:parameter key="cache" value="lru" />
</dubbo:reference>
能支持的全部cache定义在META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory 当然你也可以遵循dubbo扩展点机制进行扩展。
默认提供三种:
threadlocal=com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=com.alibaba.dubbo.cache.support.lru.LruCacheFactory
jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory
实现原理
缓存的key
是你远程方法调用时传递的所有参数按规则
组装成字符串作为key。
具体规则就是: 基本类型 直接拼接,复合类型转成json字符串后再拼接。
实现原理也不是很复杂,根据invoker的url找到其对应的cache对象,再跟据上述缓存的key
找到缓存的结果。
有个不是太要紧的小问题,因为是根据invoker的url找到其对应的cache对象的,又因为invoker的url中含有remote.timestamp参数,所以你如果启用了consumer侧的缓存,consumer一直在服务状态,此时provider服务做了重启,那么consumer侧的缓存失效,会重新调用provider端。
ThreadLocalCache 是ThreadLocal配合HashMap实现
LRUCache 是继承自LinkedHashMap,同时结合ReentrantLock实现的线程安全的lru cache(最近最少使用),LinkedHashMap自带lru性质,通过构造参数控制,默认是fifo。
jache是封装的JCache API (JSR 107)。
与Spring Cloud对比
Spring Cloud提供了consumer侧的缓存能力,Hystrix
组件支持用requestCache.enabled配置是否启用缓存,也支持用cacheKeyMethod注解指定getkey方法。
ValidationFilter
作用
在consumer和provider端提供了校验能力
使用方式
假设你要对consumer端进行校验,在配置文件中配置如下:
<dubbo:reference id="userService" interface="org.simonme.dubbo.demo.provider.service.UserService" filter="validation">
<dubbo:parameter key="validation" value="JValidator" />
<!-- 配置一个实现了javax.validation.spi.ValidationProvider<T>接口校验器 -->
<dubbo:parameter key="jvalidation" value="org.hibernate.validator.HibernateValidator" />
</dubbo:reference>
filter要是配置多个的话,用逗号拼接,但是逗号前后不能有空格。
此处使用了hibernate的validator ,在你需要校验的接口方法上加校验注解即可,示例如下:
public User queryUser(@Range(min=0,message="用户id值不能小于0")int id);
当consumer端调用时传递了校验不通过的参数时,会收到ConstraintViolationException的异常。
实现原理
dubbo对接了javax.validation.Validation,hibernate等都有对其对接的实现,按需使用即可。也就是说dubbo自己不做具体校验的事情。
与Spring Cloud对比
spring在很早就支持validator。
EchoFilter
作用
在provider端提供回声服务的服务端的实现。
使用方式
这个filter略有特殊,无需在provider端的dubbo:service标签的filter中去配置,只要你在consumer做了echo回声调用,他都会产生作用,调试的时候也能看到能走到EchoFilter中。
consumer端的示例代码:
public class HelloClientTest
{
@Autowired
private HelloService helloService;
@SuppressWarnings("static-access")
@Test
public void testSayHello()
{
System.out.println(((EchoService)helloService).$echo("aaaa"));
}
}
就是把你的service类强转成EchoService,至于为什么能强转,可以参见之前写的文章 reference bean发起调用
实现原理
直接看EchoFilter代码,很简单,不再多说。
与Spring Cloud对比
Spring Cloud貌似没有这个能力。
GenericFilter
作用
先讨论一个问题,rpc,最简单的场景是consumer端调用provider端的一个服务,这个服务双方都遵循一个接口实现,按最简单的dubbo的demo玩法,是需要consumer和provider两段都要有这个接口声明的(包括接口参数的类型的相关类),比如:
org.simonme.dubbo.demo.provider.service.UserService.queryUser(int) 这是一个查询用户的服务接口,但是,如果consumer端没有这个服务的接口声明及其相关联的bean类,也就是如果仅仅在provider端能找到这个接口类,在consumer工程里压根没有这个类,那是否还能进行调用?
dubbo是可以的。 就是通过在proivder侧用GenericFilter达成目的。
使用方式
需要在consumer端声明这个bean的时候,加上generic="true"
配置即可。
<dubbo:reference id="userService" interface="org.simonme.dubbo.demo.provider.service.UserService" generic="true">
</dubbo:reference>
generic还支持nativejava和bean两个可选的选项。nativejava对应byte[]类型的参数,bean对应com.alibaba.dubbo.common.beanutil.JavaBeanDescriptor类型的参数。
此外在使用这个服务时,形式略有不同:
@Autowired
private GenericService userService;
@SuppressWarnings("static-access")
@Test
public void testSayHello()
{
Object result = userService.$invoke("queryUser", new String[] { "int" }, new Object[]{100});
System.out.println(result);
System.out.println(result.getClass());
}
用GenericService
表示你这个未知的服务类型,用$invoke
这个特殊方法发起的你的服务方法调用。调用完之后,dubbo会把结果以hashmap的形式返回给你。比如,我这里是个User的bean,User有id和name字段,那么返回的map中就与id和value两个key,其对应的值就是bean的这两个字段的字段值。
为了便于理解,贴一下UserService的代码:
public interface UserService
{
public User queryUser(@Range(min=0,message="用户id值不能小于0")int id);
}
上面这个写法,解释了怎么在consumer端没有org.simonme.dubbo.demo.provider.service.UserService接口声明的时候,调用他的queryUser方法。provider端无需改动,和正常写rpc服务一样配置即可。
用法也可以参见官方文档,使用泛化调用。
实现原理
直接看GenericFilter代码,通过方法名(必须是$invoke),参数个数等判断是否命中,很简单,不再多说。
与Spring Cloud对比
Spring Cloud可以算有这个能力,因为Spring Cloud是http json的,http json天然不需要调用端有接口声明。
GenericImplFilter
作用
与 GenericFilter 类似,是一个相对的东西,他是用在provider端没有服务接口声明类时,使用的。使用方法可以参见官方文档实现泛化调用。不再多说。
TokenFilter
作用
官方文档说法是:
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者。
使用方式
provider端:在provider上配置token值。 TokenFilter会在provider侧校验。
<dubbo:service interface="org.simonme.dubbo.demo.provider.service.UserService" ref="m00001.app001.xx.userService" timeout="600000" token="123456">
consumer端可以用编程的方式获取后塞,
RpcContext.getContext().setAttachment("token", "a37b6115-c171-43cd-b65c-38b636ee96cc");
或者通过配置parameter,
<dubbo:parameter key="token" value="123456" />
或者啥都不要处理
,默认consumer会从provider服务url中解析到。provider的url中会含有token字段。
实现原理
如果 token 配置的是true, 那么在provider export服务时,ServiceConfig
会生成UUID,这个其实不是由注册中心生成的。
当然token也支持配置固定密码。
比对过程:
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {// 是 true或者default字段串就是表示默认
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
consumer端如果没有通过上述代码的方式或者parameter配置的方式传送token,那么consumer端会在调用时,先将从注册中心拿到的provider端的url中部分参数转换成attachment给consumer端用,这个部分参数就包括token。具体代码在DubboInvoker
中,如下:
public DubboInvoker(Class<T> serviceType, URL url, ExchangeClient[] clients, Set<Invoker<?>> invokers) {
super(serviceType, url, new String[]{Constants.INTERFACE_KEY, Constants.GROUP_KEY, Constants.TOKEN_KEY, Constants.TIMEOUT_KEY});// 此处会调用父类方法进行需要的参数从url转到attachment中
this.clients = clients;
// get version.
this.version = url.getParameter(Constants.VERSION_KEY, "0.0.0");
this.invokers = invokers;
}
当provider端使用注册中心,consumer试图不带token进行直接消费时,会被拒绝。 当consumer端也是连注册中心时,哪怕不送显式送token(实际上dubbo会自动送)也可以正常调用。但是如果consumer端用了注册中心,且显式送了token,那么就要送对。否则报错。
与Spring Cloud对比
Spring Cloud的注册中心eureka也是支持密码验证的。
AccessLogFilter
作用
用在provider端打印rpc请求日志,支持打到指定文件,支持异步。
使用方式
在provider侧配置名为accesslog的filter,若需要指定路径,则将accesslog参数设置成具体路径即可,默认需要将其配置成true。
<dubbo:service interface="org.simonme.dubbo.demo.provider.service.UserService" ref="m00001.app001.xx.userService" filter="accesslog"
timeout="600000" token="123456">
<dubbo:parameter key="accesslog" value="true" />
</dubbo:service>
实现原理
参见AccessLogFilter 代码,比较简单。
与Spring Cloud对比
zuul网关也支持。
zuul.debug.request=true #如果设置了这个,默认所有的请求都会debug
zuul.include-debug-header: true #打印头
未设置zuul.debug.request=true,可以用zuul_host:zuul_port/路径?debug=true debug你的指定请求
ActiveLimitFilter
作用
在consumer端实现并发数控制,能支持到方法级。
使用方式
在consumer侧配置
<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:service>
实现原理
ConcurrentHashMap 配合 AtomicInteger AtomicLong完成。相关代码参见 ActiveLimitFilter 与 RpcStatus。
与Spring Cloud对比
Spring Cloud也支持。
ExecuteLimitFilter 与之类似,只是用在了provider端。
ClassLoaderFilter
作用
保持 调用下层invoker前后的ClassLoader一致
ContextFilter
作用
在provider端,对一些dubbo自己使用的保留key进行过滤,防止别人误传。
实现原理
参见代码即可,比较简单。
与Spring Cloud对比
N/A