手写一个简易版Sentinel
Posted 唐宋xy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写一个简易版Sentinel相关的知识,希望对你有一定的参考价值。
SpringCloud Alibaba Sentinel是当前最为流行一种熔断降级框架,简单易用的方式可以快速帮助我们实现服务的限流和降级,保证服务的稳定性。
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
不可否认的是,Sentinel功能丰富,并且在提供好用的dashboard提供配置,但是Sentinel在集成到项目中时需要引入多个依赖,并且需要阅读相关文档,以及dashboard中的相关配置才可以接入到项目中,这个过程还是较为复杂的。
如果我们的项目并不需要这么多的功能,只是需要当某个方法或者某个功能发生异常的时候可以实现降级,并不是直接中断程序,该业务功能不是主流程,那么我们为了实现这样一个小功能的时候,将Sentinel
集成到项目中的过程显然是较为复杂的,那么这个时候,就需要我们实现一个简答的功能降级的通用方式,下面就一起看看一个简易版的Sentinel
的实现
当然,实现这个功能,只需要一个try-catch就可以搞定个,但是我们需要的是try-catch吗?No! 我们需要的是优雅~ 我想你也不想看到满屏的try-catch吧,如果哪天这个方法无需降级的时候,再去一行一行删代码吗?
代码已收录到Github: https://github.com/chenliang15405/common-study/tree/main/resource-degrade
定义注解
第一步,定义一个通用注解,这个注解可以帮助我们无侵入性的实现功能降级,并且提供丰富的属性,让注解的通用性和灵活性更强
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface DegradeResource {
// 降级的方法名称
String fallback();
// 降级的类名称,可选
Class<?>[] fallbackClass() default {};
// 指定降级异常,可选
Class<? extends Throwable>[] exceptionHandle() default {};
}
-
fallback:降价方法的名称,需要指定降级方法的名称,才可以在发生异常时调用降级方法,必选参数。
降级方法须知:
- 必须为public
- 方法返回类型、方法参数必须和原始方法保持一致,最后一个参数允许多一个
Throwable
,用来接收发生的异常
-
fallbackClass:指定降级方法所在的class,可选参数,如果不指定则默认降级方法在当前class中
-
exceptionHandle:指定异常处理,当发生指定的异常时才选择进行降级,可选参数,数组类型,可以接收多个异常类型
定义切面处理器
当资源降级注解定义之后,我们就需要一个切面处理器,对定义的降级注解做切面处理,当调用的方法上有@DegradeResource
注解时,会通过切面处理器进行处理
@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();
// 获取当前执行的方法名称
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;
}
}
总体的流程为:当扫描到切面时,第一步先正常执行方法,当方法发生异常时,判断当前是否制定异常,如果没有指定异常处理类型,那么就默认走降级方法,如果当前指定了降级的异常处理类型,那么就判断当前方法抛出的异常是否为需要处理的异常,如果是则调用降级方法,如果不是需要处理的异常,那么就抛出异常。
符合当前场景的需要,简单化的异常降级
测试降级
总共测试了3中方式的异常降级,分别为默认所有异常降级、指定异常降级、指定降级方法的处理类
@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,将降级方法统一放置指定的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可以只接受原始函数的参数
*/
public String findByIdFromCacheFallback1(String id) {
return "fallback1 = " + id;
}
/**
* fallback method 不仅可以接收原始方法的参数,还可以接收具体的Exception
*
*/
public String findByIdFromCacheFallback2(String id, Throwable e) {
System.out.println("fallback method exception:" + e);
return "fallback2 = " + id;
}
}
结果:
可以看到,当发生异常时,可以通过降级保证主流程的通常,对于非主流程的功能,我们可以通过@DegradeResource
注解保证流程的完善和降级方案,保证用户的体验和程序的正常。
你Get到了吗~
微信公众号「指尖上的代码」,欢迎关注~
原创不易, 点个赞再走呗~ 欢迎关注,给你带来更精彩的文章!
你的点赞和关注是写文章最大的动力~
以上是关于手写一个简易版Sentinel的主要内容,如果未能解决你的问题,请参考以下文章
手写实现自定义简易版Spring (实现IoC 和 AOP)