dubbo rpc filter實現剖析(一)

2.6.3版本,以前讀的是2.4.9版本
本篇主要闡述dubbo rpc的filter的實現,包括做用,用法,原理,與Spring Cloud在這些能力的對比。html

共提供了多少個?是哪些?發佈時默認裝配了哪些給他自身的擴展點機制?

從類與接口關係分析的結果文檔中能夠看到共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 ValidationFilterjava

從發佈的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.MonitorFilterspring

這些filter都有什麼做用?如何使用?實現原理是什麼?Spring Cloud是否也提供了這些能力?有什麼差別?

CacheFilter

做用

緩存調用結果,好比配置在consumer端, 好比咱們經過id查某個用戶的信息,對於特定的一個id,在consumer端第一次調用時會給provider端發請求,後面再調用時,直接用consumer端緩存的結果返回,你再也不發請求給provider端。apache

使用方式

consumer側的配置:編程



能支持的所有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.JCacheFactoryjson

實現原理

緩存的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方法。app

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

相關文章
相關標籤/搜索