基于SpringCloud手写一个简易版Sentinel

 更新时间:2021年05月20日 09:37:57   作者:唐宋xy  
SpringCloud Alibaba Sentinel是当前最为流行一种熔断降级框架,简单易用的方式可以快速帮助我们实现服务的限流和降级,保证服务的稳定性。

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的资料请关注脚本之家其它相关文章!

相关文章

  • Java基础学习之标签

    Java基础学习之标签

    在Java中,标签必须在循环之前使用, 一个循环之中嵌套另一个循环的开关,从多重嵌套中continue或break,该文详细介绍了标签的相关知识,对正在学习java基础的小伙伴们还很有帮助,需要的朋友可以参考下
    2021-05-05
  • java编程几行代码实现买菜自由

    java编程几行代码实现买菜自由

    这篇文章主要为大家介绍了java编程几行代码实现买菜自由,需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • Java判断上传图片格式的实例代码

    Java判断上传图片格式的实例代码

    这篇文章主要介绍了Java判断上传图片格式的实例代码,在文中给大家提到了Java使用文件头的类型判断上传的文件是否为图片类型,需要的朋友可以参考下
    2019-06-06
  • Java 常量池的实例详解

    Java 常量池的实例详解

    这篇文章主要介绍了Java 常量池的实例详解的相关资料,Java的常量池中包含了类、接口、方法、字符串等一系列常量值,需要的朋友可以参考下
    2017-09-09
  • Maven下载和配置环境教程

    Maven下载和配置环境教程

    这篇文章主要为大家详细介绍了Maven下载和配置环境教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • Java实例化一个抽象类对象的方法教程

    Java实例化一个抽象类对象的方法教程

    大家都知道抽象类无法实例化,就无法创建对象。所以下面这篇文章主要给大家介绍了关于Java实例化一个抽象类对象的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-12-12
  • Java Objects工具类原理及用法详解

    Java Objects工具类原理及用法详解

    这篇文章主要介绍了Java Objects工具类原理及用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • ArrayList底层操作机制源码解析

    ArrayList底层操作机制源码解析

    这篇文章主要介绍了ArrayList底层操作机制源码解析,当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍,需要的朋友可以参考下
    2023-09-09
  • java中实现自定义注解方式

    java中实现自定义注解方式

    注解是Java中的一种元数据,可以修饰方法、类、参数和包等,自定义注解需要public修饰符、@interface关键字,以及注解名称和类型元素,元注解如@Target、@Retention等用于修饰注解,指定注解的适用范围和生命周期,自定义注解的使用涉及到通过反射解析注解
    2024-11-11
  • java中的Reference类型用法说明

    java中的Reference类型用法说明

    这篇文章主要介绍了java中的Reference类型用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09

最新评论