您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“怎么自定義feign調(diào)用實(shí)現(xiàn)hystrix超時(shí)、異常熔斷”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
spring cloud 項(xiàng)目中feign 整合 hystrix經(jīng)常使用,但是最近發(fā)現(xiàn)hystrix功能強(qiáng)大,但是對我們來說有些大材小用。
首先我只需要他的一個(gè)熔斷作用,就是說請求超時(shí)、異常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作、也不需要重試,hystrix 調(diào)用feign時(shí)候做了線程池隔離處理,這樣增加了項(xiàng)目復(fù)雜度(線程池參數(shù)配置、線程少了請求服務(wù)直接拒絕,多了線程得管理。。。)
目前feign 超時(shí)之后是直接拋異常的,這樣的話雖然是及時(shí)熔斷了,但是正常的程序邏輯不走了配置的fallback也沒有作用,這個(gè)配置項(xiàng)得配合 hystrix 才行。
我需要的是這樣的效果
try{ feign.api(); }catch(){ return fallback(); }
但是每個(gè)feign調(diào)用都手動(dòng)加上try..catch 實(shí)在是太low了,最好能寫個(gè)類似切面一樣的玩意。
這時(shí)候就想到了 hystrix,既然人家框架已經(jīng)做了,我直接看下代碼,copy不完了么
前兩天發(fā)布了一篇文章也是關(guān)于feign、hystrix 調(diào)用集成的
基于之前的分析關(guān)鍵代碼 HystrixInvocationHandler (feign.hystrix)
@Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { ............. // setterMethodMap 封裝 hystrixCommand 配置信息(超時(shí)時(shí)間、是否重試.....) 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(); }
按照之前分析源碼方式,直接看哪里被調(diào)用了就可以看到, hystrix 實(shí)際上自己封裝了一個(gè) feign.Builer 類名是 feign.hystrix.HystrixFeign.Builder 用的是建造者模式,生成的類是在調(diào)用服務(wù)時(shí)用到
看到 關(guān)鍵的 build() 方法
Feign build(final FallbackFactory<?> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { // 重新定義一個(gè) InvocationHandler 實(shí)現(xiàn) 類似 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 動(dòng)態(tài)代理我這里不多說了,核心就是 InvocationHandler (如果是jdk動(dòng)態(tài)代理的話),那么 feign 這里也是,我們看看feign 調(diào)用聲明是個(gè)接口,實(shí)際上是spring 動(dòng)態(tài)代理生成了代理類,調(diào)用方法時(shí)實(shí)際調(diào)用的是
java.lang.reflect.InvocationHandler#invoke
那么我們只需要借鑒下 hystrix 的方式,自己實(shí)現(xiàn)一個(gè)feign.build ,將 InvocationHandler 換成自己的,
然后在我們自己的 InvocationHandler 中調(diào)用feign 官方的 InvocationHandler 就行,也就是 feign.hystrix.HystrixInvocationHandler#invoke這個(gè)方法中的this.dispatch.get(method).invoke(args);這個(gè)代碼
方案一
自己實(shí)現(xiàn)
import feign.Feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; /** * 自定義feign 構(gòu)建 * @author hgf */ public class CusFeignBuilder extends Feign.Builder{ public CusFeignBuilder() { this.invocationHandlerFactory((target, dispatch) -> { Class<?> type = target.type(); FeignClient annotation = type.getAnnotation(FeignClient.class); // 構(gòu)造 fallback 實(shí)例 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); }); } }
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調(diào)用 */ @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("{} 請求 出現(xiàn)異常 ==> {}", configKey, throwable.getMessage()); try { return getFallbackReturn(method, args, throwable); } catch (Throwable e) { throw throwable; } } } /** * 反射調(diào)用 {@link FeignClient#fallback()}生成失敗返回值 * @param method 當(dāng)前feign方法 * @param args 參數(shù) * @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 容器中注冊這個(gè)bean就行
@Bean CusFeignBuilder cusFeignBuilder(){ return new CusFeignBuilder(); }
方案二
集成 sentinel ,今天寫博客再回頭看源碼時(shí)候才發(fā)現(xiàn)的
加入依賴
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
配置開啟
feign.sentinel.enabled=true
手動(dòng)實(shí)現(xiàn)feign 接口,將實(shí)體類注冊到 spring 中
@Component public class DeviceApiFallBack implements DeviceApi{ @Override public ServerResponse<String> login(String appId) { return ServerResponse.createByErrorMessage("請求失敗"); } }
其實(shí)看代碼知道原理一樣,無非實(shí)現(xiàn)方式不一樣
兩個(gè)方案其實(shí)都行,方案一自己實(shí)現(xiàn)代碼量多,方案二sentinel 官方實(shí)現(xiàn),但是需要引入依賴,增加復(fù)雜度,而且 接口實(shí)現(xiàn)需要注冊到spring 中
目前我選的還是方案一,簡單。
“怎么自定義feign調(diào)用實(shí)現(xiàn)hystrix超時(shí)、異常熔斷”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。