基于SpringCloud手寫一個簡易版Sentinel
Sentinel 是什么?
隨著微服務(wù)的流行,服務(wù)和服務(wù)之間的穩(wěn)定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統(tǒng)負(fù)載保護(hù)等多個維度保護(hù)服務(wù)的穩(wěn)定性。
不可否認(rèn)的是,Sentinel功能豐富,并且在提供好用的dashboard提供配置,但是Sentinel在集成到項目中時需要引入多個依賴,并且需要閱讀相關(guān)文檔,以及dashboard中的相關(guān)配置才可以接入到項目中,這個過程還是較為復(fù)雜的。
如果我們的項目并不需要這么多的功能,只是需要當(dāng)某個方法或者某個功能發(fā)生異常的時候可以實現(xiàn)降級,并不是直接中斷程序,該業(yè)務(wù)功能不是主流程,那么我們?yōu)榱藢崿F(xiàn)這樣一個小功能的時候,將Sentinel集成到項目中的過程顯然是較為復(fù)雜的,那么這個時候,就需要我們實現(xiàn)一個簡答的功能降級的通用方式,下面就一起看看一個簡易版的Sentinel的實現(xiàn)
當(dāng)然,實現(xiàn)這個功能,只需要一個try-catch就可以搞定個,但是我們需要的是try-catch嗎?No! 我們需要的是優(yōu)雅~ 我想你也不想看到滿屏的try-catch吧,如果哪天這個方法無需降級的時候,再去一行一行刪代碼嗎?
示例代碼已收錄到Github: github.com/chenliang15…
定義注解
第一步,定義一個通用注解,這個注解可以幫助我們無侵入性的實現(xiàn)功能降級,并且提供豐富的屬性,讓注解的通用性和靈活性更強
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface DegradeResource {
// 降級的方法名稱
String fallback();
// 降級的類名稱,可選
Class<?>[] fallbackClass() default {};
// 指定降級異常,可選
Class<? extends Throwable>[] exceptionHandle() default {};
}
- fallback:降價方法的名稱,需要指定降級方法的名稱,才可以在發(fā)生異常時調(diào)用降級方法,必選參數(shù)。
降級方法須知:
必須為public
方法返回類型、方法參數(shù)必須和原始方法保持一致,最后一個參數(shù)允許多一個Throwable,用來接收發(fā)生的異常
- fallbackClass:指定降級方法所在的class,可選參數(shù),如果不指定則默認(rèn)降級方法在當(dāng)前class中
- exceptionHandle:指定異常處理,當(dāng)發(fā)生指定的異常時才選擇進(jìn)行降級,可選參數(shù),數(shù)組類型,可以接收多個異常類型
定義切面處理器
當(dāng)資源降級注解定義之后,我們就需要一個切面處理器,對定義的降級注解做切面處理,當(dāng)調(diào)用的方法上有@DegradeResource注解時,會通過切面處理器進(jìn)行處理
@Aspect
public class DegradeResourceAspect {
@Around("@annotation(degradeResource)")
public Object doAround(ProceedingJoinPoint pjp, DegradeResource degradeResource) throws Throwable {
try {
return pjp.proceed();
} catch(Throwable e){
// need to trace exception list
Class<? extends Throwable>[] exceptions = degradeResource.exceptionHandle();
if(exceptions.length > 0) {
List<Class<? extends Throwable>> exceptionList = Arrays.asList(exceptions);
// 判斷是否為同一個個異常
if (exceptionBelongTo(e, exceptionList)) {
return handleFallbackMethod(pjp, degradeResource, e);
} else {
throw e;
}
}
return handleFallbackMethod(pjp, degradeResource, e);
}
}
/**
* if the throw exception is belong to exception trace list
*
* @param e
* @param exceptionList
* @return
*/
private boolean exceptionBelongTo(Throwable e, List<Class<? extends Throwable>> exceptionList) {
for (Class<? extends Throwable> aClass : exceptionList) {
if(aClass.isAssignableFrom(e.getClass())) {
return true;
}
}
return false;
}
/**
* invoke fallback method
*
*/
private Object handleFallbackMethod(ProceedingJoinPoint pjp, DegradeResource degradeResource, Throwable e) throws Throwable {
// fallback method
String fallback = degradeResource.fallback();
if(StringUtils.isEmpty(fallback)) {
throw e;
}
// fallback class
Class<?> clazz = degradeResource.fallbackClass().length > 0 ? degradeResource.fallbackClass()[0] : pjp.getTarget().getClass();
// 獲取當(dāng)前執(zhí)行的方法名稱
Method fallbackMethod = findFallbackMethod(pjp, clazz, fallback);
if(Objects.isNull(fallbackMethod)) {
throw e;
}
// fallback method args
Object[] args;
Object[] originArgs = pjp.getArgs();
int paramCount = fallbackMethod.getParameterTypes().length;
if(originArgs.length == paramCount) {
args = originArgs;
} else {
// fill throwable to fallback method args
args = Arrays.copyOf(originArgs, originArgs.length + 1);
args[args.length - 1] = e;
}
// if static
if(Modifier.isStatic(fallbackMethod.getModifiers())) {
return fallbackMethod.invoke(null, args);
}
return fallbackMethod.invoke(clazz.newInstance(), args);
}
private Method findFallbackMethod(ProceedingJoinPoint pjp, Class<?> clazz, String fallbackName) {
MethodSignature signers = (MethodSignature) pjp.getSignature();
Class<?>[] originParams = signers.getParameterTypes();
Class<?>[] paramsWithException = Arrays.copyOf(originParams, originParams.length + 1);
paramsWithException[paramsWithException.length - 1] = Throwable.class;
// find fallback method with origin params
Method method = findMethod(clazz, originParams, fallbackName, signers.getReturnType());
if(method == null) {
// find fallback method with exception params
method = findMethod(clazz, paramsWithException, fallbackName, signers.getReturnType());
}
return method;
}
private Method findMethod(Class<?> clazz, Class<?>[] paramsType, String fallbackName, Class<?> returnType) {
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
if(method.getName().equals(fallbackName)
&& returnType.isAssignableFrom(method.getReturnType())
&& Arrays.equals(paramsType, method.getParameterTypes())) {
return method;
}
}
return null;
}
}
總體的流程為:當(dāng)掃描到切面時,第一步先正常執(zhí)行方法,當(dāng)方法發(fā)生異常時,判斷當(dāng)前是否制定異常,如果沒有指定異常處理類型,那么就默認(rèn)走降級方法,如果當(dāng)前指定了降級的異常處理類型,那么就判斷當(dāng)前方法拋出的異常是否為需要處理的異常,如果是則調(diào)用降級方法,如果不是需要處理的異常,那么就拋出異常。
符合當(dāng)前場景的需要,簡單化的異常降級
測試降級
總共測試了3中方式的異常降級,分別為默認(rèn)所有異常降級、指定異常降級、指定降級方法的處理類
@Service
public class DegradeResourceTestService {
@DegradeResource(fallback = "findByIdFromCacheFallback1")
public String findById(String id) {
int i = Integer.parseInt(id);
System.out.println("id=" + id);
return "ok = " + id;
}
@DegradeResource(fallback = "findByIdFromCacheFallback2", exceptionHandle = {NumberFormatException.class})
public String findByIdWithException(String id) {
int i = Integer.parseInt(id);
System.out.println("id=" + id);
return "ok = " + id;
}
/**
* 支持指定fallback method的class,將降級方法統(tǒng)一放置指定的class中
*
*/
@DegradeResource(fallback = "findByIdFromCacheFallback3", exceptionHandle = {NumberFormatException.class},
fallbackClass = {FallbackClassService.class})
public String findByIdWithFallbackClass(String id) {
int i = Integer.parseInt(id);
System.out.println("id=" + id);
return "ok = " + id;
}
/**
* fallback method可以只接受原始函數(shù)的參數(shù)
*/
public String findByIdFromCacheFallback1(String id) {
return "fallback1 = " + id;
}
/**
* fallback method 不僅可以接收原始方法的參數(shù),還可以接收具體的Exception
*
*/
public String findByIdFromCacheFallback2(String id, Throwable e) {
System.out.println("fallback method exception:" + e);
return "fallback2 = " + id;
}
}
結(jié)果:

可以看到,當(dāng)發(fā)生異常時,可以通過降級保證主流程的通常,對于非主流程的功能,我們可以通過@DegradeResource注解保證流程的完善和降級方案,保證用戶的體驗和程序的正常。
以上就是基于SpringCloud手寫一個簡易版Sentinel的詳細(xì)內(nèi)容,更多關(guān)于SpringCloud手寫Sentinel的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringCloud中使用Sentinel實現(xiàn)限流的實戰(zhàn)
- Sentinel 整合SpringCloud的詳細(xì)教程
- Java的springcloud Sentinel是什么你知道嗎
- Java之springcloud Sentinel案例講解
- Java之SpringCloudAlibaba Sentinel組件案例講解
- SpringCloud-Alibaba-Sentinel-配置持久化策略詳解
- SpringCloud-Alibaba-Sentinel服務(wù)降級,熱點限流,服務(wù)熔斷
- Springcloud sentinel安裝和使用方法解析
- springcloud3 Sentinel的搭建及案例操作方法
相關(guān)文章
Java基礎(chǔ)學(xué)習(xí)之標(biāo)簽
在Java中,標(biāo)簽必須在循環(huán)之前使用, 一個循環(huán)之中嵌套另一個循環(huán)的開關(guān),從多重嵌套中continue或break,該文詳細(xì)介紹了標(biāo)簽的相關(guān)知識,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們還很有幫助,需要的朋友可以參考下2021-05-05

