基于SpringCloud手写一个简易版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的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringCloud整合Nacos实现流程详解

    1: Nacos搭建可以参考 https://www.jb51.net/article/196842.htm SpringCloud 版本 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> &

  • SpringCloud微服务之Config知识总结

    一.什么是Spring Cloud Config? Spring Cloud Config 可以为微服务架构中的应用提供集中化的外部配置支持,它分为服务端和客户端两个部分. Spring Cloud Config 服务端被称为分布式配置中心,它是个独立的应用,可以从配置仓库获取配置信息并提供给客户端使用. Spring Cloud Config 客户端可以通过配置中心来获取配置信息,在启动时加载配置. Spring Cloud Config 的配置中心默认采用Git来存储配置信息,所以天然就支持

  • spring cloud整合ribbon问题及解决方案

    SpringCloud 整合ribbon的时候出现了这个问题 java.lang.IllegalStateException: No instances available for localhost at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:89) ~[spring-cloud-netflix-ribbon-2.0.2.RE

  • es(elasticsearch)整合SpringCloud(SpringBoot)搭建教程详解

    注意:适用于springboot或者springcloud框架 1.首先下载相关文件 2.然后需要去启动相关的启动文件 3.导入相关jar包(如果有相关的依赖包不需要导入)以及配置配置文件,并且写一个dao接口继承一个类,在启动类上标注地址 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> &l

  • SpringCloud整合Consul的实现

    下载安装Consul 访问Consul 官网 下载 Consul 的最新版本,我这里是 consul_1.9.1. 这里以 Windows 为例,下载下来是一个 consul_1.9.1_windows_amd64.zip 的压缩包,解压是是一个 consul.exe 的执行文件. 启动Consul cd 到对应的目录下,使用 cmd 启动 Consul cd E:\迅雷下载\consul_1.9.1_windows_amd64 #cmd启动: consul agent -dev # -dev表

  • SpringBoot2.0整合SpringCloud Finchley @hystrixcommand注解找不到解决方案

    hystrix参数使用方法 通过注解@HystrixCommand的commandProperties去配置, 如下就是hystrix命令超时时间命令执行超时时间,为1000ms和执行是不启用超时 @RestController public class MovieController { @Autowired private RestTemplate restTemplate; @GetMapping("/movie/{id}") @HystrixCommand(commandPro

  • SpringCloud2020整合Nacos-Bootstrap配置不生效的解决

    因为公司现在换成了nacos,所以自己写了demo学习一下.结果第一步就走不下去.在使用nacos-config读取nacos配置时.发现bootstrap.yml一直不生效. 按照网上的解决方法引入依赖. <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> </dependency&g

  • SpringCloud整合分布式服务跟踪zipkin的实现

    1.zipkin zipkin是Twitter的一个开源项目,它基于Google Dapper实现.我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源.除了面向开发的API接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等. zipkin的架构图如下: 由上面的架构图可以

  • 浅析springcloud 整合 zipkin-server 内存日志监控

    Zipkin Zipkin是一款开源的分布式实时数据追踪系统(Distributed Tracking System),基于 Google Dapper的论文设计而来,由 Twitter 公司开发贡献.其主要功能是聚集来自各个异构系统的实时监控数据. Zipkin主要包括四个模块  - Collector           接收或收集各应用传输的数据  - Storage            存储接受或收集过来的数据,当前支持Memory,MySQL,Cassandra,ElasticSea

  • SpringCloud服务注册和发现组件Eureka

    本篇文章,我们来讲解springcloud的服务注册和发现组件,上一章节我们讲解了如何搭建springcloud的多模块项目,已经新建了springcloud-eureka-server,springcloud-eureka-client两个模块,本章节就在这基础上直接使用. 想要了解的请参考:一起来学Spring Cloud | 第一章 :如何搭建一个多模块的springcloud项目 一.Eureka简介: 1.1 什么是eureka Eureka是一个基于REST的服务,主要用于AWS云中

  • SpringCloud实现Eureka服务注册与发现

    GitHub地址:https://github.com/yudiandemingzi/spring-cloud-study 一.Eureka概述 1.Eureka特点 (1) Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移. (2) Eureka 主管服务注册与发现,在微服务中,以后了这两者,只需要使用服务的标识符(==就是那个在每个服务的yml文件中取得服务名称==), 就可以访问到服务,不需要修改服务调用的配置文件. (3) Eureka遵循AP原则(

随机推荐