需求描述
spring cloud 項目中feign 整合 hystrix經常使用,但是最近發現hystrix功能強大,但是對我們來說有些大材小用。
首先我只需要他的一個熔斷作用,就是說請求超時、異常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作、也不需要重試,hystrix 調用feign時候做了線程池隔離處理,這樣增加了項目復雜度(線程池參數配置、線程少了請求服務直接拒絕,多了線程得管理。。。)
目前feign 超時之后是直接拋異常的,這樣的話雖然是及時熔斷了,但是正常的程序邏輯不走了配置的fallback也沒有作用,這個配置項得配合 hystrix 才行。
我需要的是這樣的效果
1
2
3
4
5
|
try { feign.api(); } catch (){ return fallback(); } |
但是每個feign調用都手動加上try..catch 實在是太low了,最好能寫個類似切面一樣的玩意。
這時候就想到了 hystrix,既然人家框架已經做了,我直接看下代碼,copy不完了么
源碼學習
前兩天發布了一篇文章也是關于feign、hystrix 調用集成的
基于之前的分析關鍵代碼
1
|
HystrixInvocationHandler (feign.hystrix) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Override public Object invoke( final Object proxy, final Method method, final Object[] args) throws Throwable { ............. // setterMethodMap 封裝 hystrixCommand 配置信息(超時時間、是否重試.....) HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) { @Override protected Object run() throws Exception { .... HystrixInvocationHandler. this .dispatch.get(method).invoke(args); .... } @Override protected Object getFallback() { ......... } }; ...... return hystrixCommand.execute(); } |
按照之前分析源碼方式,直接看哪里被調用了就可以看到, hystrix 實際上自己封裝了一個 feign.Builer 類名是 feign.hystrix.HystrixFeign.Builder 用的是建造者模式,生成的類是在調用服務時用到
看到 關鍵的 build() 方法
1
2
3
4
5
6
7
8
9
10
11
|
Feign build( final FallbackFactory<?> nullableFallbackFactory) { super .invocationHandlerFactory( new InvocationHandlerFactory() { // 重新定義一個 InvocationHandler 實現 類似 aop效果 @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super .contract( new HystrixDelegatingContract(contract)); return super .build(); } |
spring 動態代理我這里不多說了,核心就是 InvocationHandler (如果是jdk動態代理的話),那么 feign 這里也是,我們看看feign 調用聲明是個接口,實際上是spring 動態代理生成了代理類,調用方法時實際調用的是
1
|
java.lang.reflect.InvocationHandler#invoke |
方案構想
那么我們只需要借鑒下 hystrix 的方式,自己實現一個feign.build ,將 InvocationHandler 換成自己的,
然后在我們自己的 InvocationHandler 中調用feign 官方的 InvocationHandler 就行,也就是
1
|
feign.hystrix.HystrixInvocationHandler#invoke |
這個方法中的
1
|
this .dispatch.get(method).invoke(args); |
這個代碼
方案具體代碼實現
方案一
自己實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import feign.Feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; /** * 自定義feign 構建 * @author hgf */ public class CusFeignBuilder extends Feign.Builder{ public CusFeignBuilder() { this .invocationHandlerFactory((target, dispatch) -> { Class<?> type = target.type(); FeignClient annotation = type.getAnnotation(FeignClient. class ); // 構造 fallback 實例 Object fallBackObj = null ; if (annotation != null && !annotation.fallback().equals( void . class )) { try { fallBackObj = annotation.fallback().newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } return new CusFeignInvocationHandler(target, dispatch, fallBackObj); }); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
import feign.Feign; import feign.InvocationHandlerFactory; import feign.Target; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FeignClient; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.eco.common.utils.Md5Util.logger; import static feign.Util.checkNotNull; /** * 自定義的feign調用 */ @Slf4j public class CusFeignInvocationHandler implements InvocationHandler { private final Target target; private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch; private final Object fallbackObj; private final Map<String, Method> fallbackMethodMap = new ConcurrentHashMap<>(); CusFeignInvocationHandler(Target target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, Object fallbackObj) { this .target = checkNotNull(target, "target" ); this .dispatch = checkNotNull(dispatch, "dispatch for %s" , target); this .fallbackObj = fallbackObj; } public Object feignInvoke(Object proxy, Method method, Object[] args) throws Throwable { if ( "equals" .equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[ 0 ] != null ? Proxy.getInvocationHandler(args[ 0 ]) : null ; return equals(otherHandler); } catch (IllegalArgumentException e) { return false ; } } else if ( "hashCode" .equals(method.getName())) { return hashCode(); } else if ( "toString" .equals(method.getName())) { return toString(); } return dispatch.get(method).invoke(args); } @Override public boolean equals(Object obj) { if (obj instanceof CusFeignInvocationHandler) { CusFeignInvocationHandler other = (CusFeignInvocationHandler) obj; return target.equals(other.target); } return false ; } @Override public int hashCode() { return target.hashCode(); } @Override public String toString() { return target.toString(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return feignInvoke(proxy, method, args); } catch (Throwable throwable) { String configKey = Feign.configKey(target.type(), method); logger.error( "{} 請求 出現異常 ==> {}" , configKey, throwable.getMessage()); try { return getFallbackReturn(method, args, throwable); } catch (Throwable e) { throw throwable; } } } /** * 反射調用 {@link FeignClient#fallback()}生成失敗返回值 * @param method 當前feign方法 * @param args 參數 * @param throwable 異常 */ public Object getFallbackReturn(Method method, Object[] args, Throwable throwable) throws Throwable { if (fallbackObj == null ) { throw new RuntimeException( "fallbackObj is null" ); } String configKey = Feign.configKey(target.type(), method); Method fallbackMethod = fallbackMethodMap.get(configKey); if (fallbackMethod == null ) { Class<?> declaringClass = method.getDeclaringClass(); FeignClient annotation = declaringClass.getAnnotation(FeignClient. class ); if (annotation == null ) { throw new RuntimeException( "FeignClient annotation not found" ); } // 失敗返回 Class<?> fallback = annotation.fallback(); fallbackMethod = fallback.getMethod(method.getName(), method.getParameterTypes()); fallbackMethodMap.put(configKey, fallbackMethod); } if (fallbackMethod == null ) { throw new RuntimeException( "fallbackMethodMap not found" ); } return fallbackMethod.invoke(fallbackObj, args); } } |
然后在 spring 容器中注冊這個bean就行
1
2
3
4
|
@Bean CusFeignBuilder cusFeignBuilder(){ return new CusFeignBuilder(); } |
方案二
集成 sentinel ,今天寫博客再回頭看源碼時候才發現的
加入依賴
1
2
3
4
|
< dependency > < groupId >com.alibaba.cloud</ groupId > < artifactId >spring-cloud-starter-alibaba-sentinel</ artifactId > </ dependency > |
配置開啟
1
|
feign.sentinel.enabled= true |
手動實現feign 接口,將實體類注冊到 spring 中
1
2
3
4
5
6
7
|
@Component public class DeviceApiFallBack implements DeviceApi{ @Override public ServerResponse<String> login(String appId) { return ServerResponse.createByErrorMessage( "請求失敗" ); } } |
其實看代碼知道原理一樣,無非實現方式不一樣
兩個方案其實都行,方案一自己實現代碼量多,方案二sentinel 官方實現,但是需要引入依賴,增加復雜度,而且 接口實現需要注冊到spring 中
目前我選的還是方案一,簡單。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/weixin_39660224/article/details/109497272