基于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 Http的基础概念了解

    Java Http的基础概念了解

    这篇文章主要介绍了Java Http的基础概念,HTTP协议是建立在TCP协议之上的,这个程序是通过TCP编程来构建一个简单的Http服务器,需要的朋友可以参考下
    2023-04-04
  • Spring中IoC优点与缺点解析

    Spring中IoC优点与缺点解析

    这篇文章主要为大家详细解析了Spring中IoC优点与缺点,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • Java中的ConcurrentHashMap集合源码解析

    Java中的ConcurrentHashMap集合源码解析

    这篇文章主要介绍了Java中的ConcurrentHashMap集合源码解析,ConcurrentHashMap底层容器和HashMap相同,同样是Node数组+链表+红黑树,不同的是在原来的基础之上使用了Synchronized+CAS来保证线程安全,下面我们来进行源码分析,需要的朋友可以参考下
    2023-11-11
  • MyBatis-Plus多表联合查询并且分页(3表联合)

    MyBatis-Plus多表联合查询并且分页(3表联合)

    这篇文章主要介绍了MyBatis-Plus多表联合查询并且分页(3表联合),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java中的ArrayList集合源码解析

    Java中的ArrayList集合源码解析

    这篇文章主要介绍了Java中的ArrayList集合源码解析,ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可称之为动态数组,需要的朋友可以参考下
    2023-12-12
  • 剑指Offer之Java算法习题精讲链表与字符串及数组

    剑指Offer之Java算法习题精讲链表与字符串及数组

    跟着思路走,之后从简单题入手,反复去看,做过之后可能会忘记,之后再做一次,记不住就反复做,反复寻求思路和规律,慢慢积累就会发现质的变化
    2022-03-03
  • Spring根据XML配置文件 p名称空间注入属性的实例

    Spring根据XML配置文件 p名称空间注入属性的实例

    下面小编就为大家分享一篇Spring根据XML配置文件 p名称空间注入属性的实例,具有很好的参考价值。希望对大家有所帮助
    2017-11-11
  • 深入理解Java中的final关键字_动力节点Java学院整理

    深入理解Java中的final关键字_动力节点Java学院整理

    Java中的final关键字非常重要,它可以应用于类、方法以及变量。这篇文章中我将带你看看什么是final关键字以及使用final的好处,具体内容详情通过本文学习吧
    2017-04-04
  • 解析springboot集成AOP实现日志输出的方法

    解析springboot集成AOP实现日志输出的方法

    如果这需要在每一个controller层去写的话代码过于重复,于是就使用AOP定义切面 对其接口调用前后进行拦截日志输出。接下来通过本文给大家介绍springboot集成AOP实现日志输出,需要的朋友可以参考下
    2021-11-11
  • 新手Hadoop安装 环境搭建

    新手Hadoop安装 环境搭建

    这篇文章主要介绍了Hadoop的安装与环境搭建教程图解,本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下,希望能给您带来帮助
    2021-06-06

最新评论