基于SpringCloud手写一个简易版Sentinel
作者:唐宋xy
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
不可否认的是,Sentinel功能丰富,并且在提供好用的dashboard提供配置,但是Sentinel在集成到项目中时需要引入多个依赖,并且需要阅读相关文档,以及dashboard中的相关配置才可以接入到项目中,这个过程还是较为复杂的。
如果我们的项目并不需要这么多的功能,只是需要当某个方法或者某个功能发生异常的时候可以实现降级,并不是直接中断程序,该业务功能不是主流程,那么我们为了实现这样一个小功能的时候,将Sentinel集成到项目中的过程显然是较为复杂的,那么这个时候,就需要我们实现一个简答的功能降级的通用方式,下面就一起看看一个简易版的Sentinel的实现
当然,实现这个功能,只需要一个try-catch就可以搞定个,但是我们需要的是try-catch吗?No! 我们需要的是优雅~ 我想你也不想看到满屏的try-catch吧,如果哪天这个方法无需降级的时候,再去一行一行删代码吗?
示例代码已收录到Github: github.com/chenliang15…
定义注解
第一步,定义一个通用注解,这个注解可以帮助我们无侵入性的实现功能降级,并且提供丰富的属性,让注解的通用性和灵活性更强
@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注解保证流程的完善和降级方案,保证用户的体验和程序的正常。
以上就是基于SpringCloud手写一个简易版Sentinel的详细内容,更多关于SpringCloud手写Sentinel的资料请关注脚本之家其它相关文章!