Dubbo的Filter在使用的過程中是我們擴展最頻繁的內容,而且Dubbo的很多特性實現也都離不開Filter的工作,今天一起來看一下Filter的具體實現。
Filter(過濾器)在很多框架中都有使用過這個概念,基本上的作用都是類似的,在請求處理前或者處理后做一些通用的邏輯,而且Filter可以有多個,支持層層嵌套。
Dubbo的Filter概念基本上符合我們正常的預期理解,而且Dubbo官方針對Filter做了很多的原生支持,目前大致有20來個吧,包括我們熟知的RpcContext,accesslog功能都是通過filter來實現了,下面一起詳細看一下Filter的實現。
Dubbo的Filter實現入口是在ProtocolFilterWrapper,因為ProtocolFilterWrapper是Protocol的包裝類,所以會在加載的Extension的時候被自動包裝進來(理解這里的前提是理解Dubbo的SPI機制),然后我們看一下這個Filter鏈是如何構造的。
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//向注冊中心引用服務的時候并不會進行filter調用鏈
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
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.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i --) {
final Filter filter = filters.get(i);
//復制引用,構建filter調用鏈
final Invoker<T> next = last;
//這里只是構造一個最簡化的Invoker作為調用鏈的載體Invoker
last = new Invoker<T>() {
public Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
看到上面的內容,我們大致能明白實現是這樣子的,通過獲取所有可以被激活的Filter鏈,然后根據一定順序構造出一個Filter的調用鏈,最后的調用鏈大致是這樣子:Filter1->Filter2->Filter3->......->Invoker,這個構造Filter鏈的邏輯非常簡單,重點就在于如何獲取被激活的Filter鏈。
//將key在url中對應的配置值切換成字符串信息數組
public List<T> getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
//所有用戶自己配置的filter信息(有些Filter是默認激活的,有些是配置激活的,這里這里的names就指的配置激活的filter信息)
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
//如果這些名稱里不包括去除default的標志(-default),換言之就是加載Dubbo提供的默認Filter
if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
//加載extension信息
getExtensionClasses();
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
//name指的是SPI讀取的配置文件的key
String name = entry.getKey();
Activate activate = entry.getValue();
//group主要是區分實在provider端生效還是consumer端生效
if (isMatchGroup(group, activate.group())) {
T ext = getExtension(name);
//這里以Filter為例:三個判斷條件的含義依次是:
//1.用戶配置的filter列表中不包含當前ext
//2.用戶配置的filter列表中不包含當前ext的加-的key
//3.如果用戶的配置信息(url中體現)中有可以激活的配置key并且數據不為0,false,null,N/A,也就是說有正常的使用
if (! names.contains(name)
&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) {
exts.add(ext);
}
}
}
//根據Activate注解上的order排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
//進行到此步驟的時候Dubbo提供的原生的Filter已經被添加完畢了,下面處理用戶自己擴展的Filter
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i ++) {
String name = names.get(i);
//如果單個name不是以-開頭并且所有的key里面并不包含-'name'(也就是說如果配置成了"dubbo,-dubbo"這種的可以,這個if是進不去的)
if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
//可以通過default關鍵字替換Dubbo原生的Filter鏈,主要用來控制調用鏈順序
if (Constants.DEFAULT_KEY.equals(name)) {
if (usrs.size() > 0) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
//加入用戶自己定義的擴展Filter
T ext = getExtension(name);
usrs.add(ext);
}
}
}
if (usrs.size() > 0) {
exts.addAll(usrs);
}
return exts;
}
基本上到這里就能看到Filter鏈是如何被加載進來的,這里設計的非常靈活,忍不住要感嘆一下:通過簡單的配置‘-’可以手動剔除Dubbo原生的一定加載Filter,通過default來代替Dubbo原生的一定會加載的Filter從而來控制順序。這些設計雖然都是很小的功能點,但是總體的感覺是十分靈活,考慮的比較周到,非常值得我這種菜鳥學習。
知道了Filter構造的過程之后,我們就詳細看幾個比較重要的Filter信息。
Filter在作用端區分的話主要是區分為consumer和provider,下面我們就以這個為區分來分別介紹一些重點的Filter。
Cunsumer
ConsumerContextFilter (默認觸發)
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//在當前的RpcContext中記錄本地調用的一次狀態信息
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {
((RpcInvocation)invocation).setInvoker(invoker);
}
try {
return invoker.invoke(invocation);
} finally {
RpcContext.getContext().clearAttachments();
}
}
其實簡單來看這個Filter的話是十分簡單,它又是怎么將客戶端設置的隱式參數傳遞給服務端呢?載體就是Invocation對象,在客戶端調用Invoker.invoke方法時候,會去取當前狀態記錄器RpcContext中的attachments屬性,然后設置到RpcInvocation對象中,在RpcInvocation傳遞到provider的時候會通過另外一個過濾器ContextFilter將RpcInvocation對象重新設置回RpcContext中供服務端邏輯重新獲取隱式參數。這就是為什么RpcContext只能記錄一次請求的狀態信息,因為在第二次調用的時候參數已經被新的RpcInvocation覆蓋掉,第一次的請求信息對于第二次執行是不可見的。
ActiveLimitFilter (當配置了actives并且值不為0的時候觸發)
ActiveLimitFilte主要用于限制同一個客戶端對于一個服務端方法的并發調用量。(客戶端限流)
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
//主要記錄每臺機器針對某個方法的并發數量
RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
if (max > 0) {
long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
long start = System.currentTimeMillis();
long remain = timeout;
int active = count.getActive();
if (active >= max) {
synchronized (count) {
//這個while循環是必要的,因為在一次wait結束后,可能線程調用已經結束了,騰出來consumer的空間
while ((active = count.getActive()) >= max) {
try {
count.wait(remain);
} catch (InterruptedException e) {
}
//如果wait方法被中斷的話,remain這時候有可能大于0
//如果其中一個線程運行結束自后調用notify方法的話,也有可能remain大于0
long elapsed = System.currentTimeMillis() - start;
remain = timeout - elapsed;
if (remain <= 0) {
throw new RpcException("...");
}
}
}
}
}
try {
//調用開始和結束后增減并發數量
long begin = System.currentTimeMillis();
RpcStatus.beginCount(url, methodName);
try {
Result result = invoker.invoke(invocation);
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;
} catch (RuntimeException t) {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
throw t;
}
} finally {
//這里很關鍵,因為一個調用完成后要通知正在等待執行的隊列
if(max>0){
synchronized (count) {
count.notify();
}
}
}
}
FutureFilter
Future主要是處理事件信息,主要有以下幾個事件:
- oninvoke 在方法調用前觸發(如果調用出現異常則會直接觸發onthrow方法)
- onreturn 在方法返回會觸發(如果調用出現異常則會直接觸發onthrow方法)
- onthrow 調用出現異常時候觸發
public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
// 這里主要處理回調邏輯,主要區分三個時間:oninvoke:調用前觸發,onreturn:調用后觸發 onthrow:出現異常情況時候觸發
fireInvokeCallback(invoker, invocation);
//需要在調用前配置好是否有返回值,已供invoker判斷是否需要返回future.
Result result = invoker.invoke(invocation);
if (isAsync) {
asyncCallback(invoker, invocation);
} else {
syncCallback(invoker, invocation, result);
}
return result;
}
private void fireInvokeCallback(final Invoker<?> invoker, final Invocation invocation) {
final Method onInvokeMethod = (Method)StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_METHOD_KEY));
final Object onInvokeInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_INSTANCE_KEY));
if (onInvokeMethod == null && onInvokeInst == null ){
return ;
}
if (onInvokeMethod == null || onInvokeInst == null ){
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() +" has a onreturn callback config , but no such "+(onInvokeMethod == null ? "method" : "instance")+" found. url:"+invoker.getUrl());
}
//由于JDK的安全檢查耗時較多.所以通過setAccessible(true)的方式關閉安全檢查就可以達到提升反射速度的目的
if (onInvokeMethod != null && ! onInvokeMethod.isAccessible()) {
onInvokeMethod.setAccessible(true);
}
//從之類可以看出oninvoke的方法參數要與調用的方法參數一致
Object[] params = invocation.getArguments();
try {
onInvokeMethod.invoke(onInvokeInst, params);
} catch (InvocationTargetException e) {
fireThrowCallback(invoker, invocation, e.getTargetException());
} catch (Throwable e) {
fireThrowCallback(invoker, invocation, e);
}
}
//fireReturnCallback的邏輯與fireThrowCallback基本一樣,所以不用看了
private void fireThrowCallback(final Invoker<?> invoker, final Invocation invocation, final Throwable exception) {
final Method onthrowMethod = (Method)StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_METHOD_KEY));
final Object onthrowInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_INSTANCE_KEY));
if (onthrowMethod == null && onthrowInst == null ){
return ;
}
if (onthrowMethod == null || onthrowInst == null ){
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() +" has a onthrow callback config , but no such "+(onthrowMethod == null ? "method" : "instance")+" found. url:"+invoker.getUrl());
}
if (onthrowMethod != null && ! onthrowMethod.isAccessible()) {
onthrowMethod.setAccessible(true);
}
Class<?>[] rParaTypes = onthrowMethod.getParameterTypes() ;
if (rParaTypes[0].isAssignableFrom(exception.getClass())){
try {
//因為onthrow方法的參數第一個值必須為異常信息,所以這里需要構造參數列表
Object[] args = invocation.getArguments();
Object[] params;
if (rParaTypes.length >1 ) {
//原調用方法只有一個參數而且這個參數是數組(單獨拎出來計算的好處是這樣可以少復制一個數組)
if (rParaTypes.length == 2 && rParaTypes[1].isAssignableFrom(Object[].class)){
params = new Object[2];
params[0] = exception;
params[1] = args ;
}else {//原調用方法有多于一個參數
params = new Object[args.length + 1];
params[0] = exception;
System.arraycopy(args, 0, params, 1, args.length);
}
} else {//原調用方法沒有參數
params = new Object[] { exception };
}
onthrowMethod.invoke(onthrowInst,params);
} catch (Throwable e) {
logger.error(invocation.getMethodName() +".call back method invoke error . callback method :" + onthrowMethod + ", url:"+ invoker.getUrl(), e);
}
} else {
logger.error(invocation.getMethodName() +".call back method invoke error . callback method :" + onthrowMethod + ", url:"+ invoker.getUrl(), exception);
}
}
同步異步的主要處理區別就是同步調用的話,事件觸發是直接調用的,沒有任何邏輯;異步的話就是首先獲取到調用產生的Future對象,然后復寫Future的done()方法,將fireThrowCallback和fireReturnCallback邏輯引入即可。
Provider
ContextFilter
ContextFilter和ConsumerContextFilter是結合使用的,之前的介紹中已經看了ConsumerContextFilter,下面再簡單看一下ContextFilter,來驗證剛才講到的邏輯。
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Map<String, String> attachments = invocation.getAttachments();
if (attachments != null) {
//隱式參數重剔除一些核心消息
attachments = new HashMap<String, String>(attachments);
attachments.remove(Constants.PATH_KEY);
attachments.remove(Constants.GROUP_KEY);
attachments.remove(Constants.VERSION_KEY);
attachments.remove(Constants.DUBBO_VERSION_KEY);
attachments.remove(Constants.TOKEN_KEY);
attachments.remove(Constants.TIMEOUT_KEY);
}
//這里又重新將invocation和attachments信息設置到RpcContext,這里設置以后provider的代碼就可以獲取到consumer端傳遞的一些隱式參數了
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setAttachments(attachments)
.setLocalAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {
((RpcInvocation)invocation).setInvoker(invoker);
}
try {
return invoker.invoke(invocation);
} finally {
RpcContext.removeContext();
}
}
EchoFilter
回響測試主要用來檢測服務是否正常(網絡狀態),單純的檢測網絡情況的話其實不需要執行真正的業務邏輯的,所以通過Filter驗證一下即可.
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
if(inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1 )
return new RpcResult(inv.getArguments()[0]);
return invoker.invoke(inv);
}
ExecuteLimitFilter
服務端接口限制限流的具體執行邏輯就是在ExecuteLimitFilter中,因為服務端不需要考慮重試等待邏輯,一旦當前執行的線程數量大于指定數量,就直接返回失敗了,所以實現邏輯相對于ActiveLimitFilter倒是簡便了不少。
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0);
if (max > 0) {
RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());
if (count.getActive() >= max) {
throw new RpcException("...");
}
}
long begin = System.currentTimeMillis();
boolean isException = false;
RpcStatus.beginCount(url, methodName);
try {
Result result = invoker.invoke(invocation);
return result;
} catch (Throwable t) {
isException = true;
if(t instanceof RuntimeException) {
throw (RuntimeException) t;
}
else {
throw new RpcException("unexpected exception when ExecuteLimitFilter", t);
}
}
finally {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isException);
}
}
ExceptionFilter
Dubbo 對于異常的處理有自己的一套規則:
- 如果是checked異常則直接拋出
- 如果是unchecked異常但是在接口上有聲明,也會直接拋出
- 如果異常類和接口類在同一jar包里,直接拋出
- 如果是JDK自帶的異常,直接拋出
- 如果是Dubbo的異常,直接拋出
- 其余的都包裝成RuntimeException然后拋出(避免異常在Client出不能反序列化問題)
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
Result result = invoker.invoke(invocation);
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = result.getException();
// 如果是checked異常,直接拋出
if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// 運行時異常,并且在方法簽名上有聲明,直接拋出
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException e) {
return result;
}
// 未在方法簽名上定義的異常,在服務器端打印ERROR日志
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// 異常類和接口類在同一jar包里,直接拋出
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
return result;
}
// 是JDK自帶的異常,直接拋出
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo本身的異常,直接拋出
if (exception instanceof RpcException) {
return result;
}
// 否則,包裝成RuntimeException拋給客戶端
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return result;
}
}
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
throw e;
}
}
基本到這里的話把比較重要的Filter內容都有講解到了,我們可以根據自己的需求非常輕易地擴展適合自己業務使用的Filter。
本文最后,還是習慣性的撒花~~~~