SpringCloud 中集成Sentinel+Feign实现服务熔断降级

Sentine

1.背景

Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。这里你们可能会问:Sentinel 和以前经常使用的熔断降级库 Netflix Hystrix 有什么异同呢?Sentinel官网有一个对比和Hystrix迁移到sentinel的文章,这里摘抄一个总结的表格,具体的对比能够点此 连接 查看。 java

功能对比

 从对比的表格能够明显看到,Sentinel比Hystrix在功能性上还要强大一些。git

2.功能

 Sentinel 功能主要体如今三个方面github

2.1 流量控制

    对于系统来讲,任意时间到来的请求每每是随机不可控的,而系统的处理能力是有限的。咱们须要根据系统的处理能力对流量进行控制。 web

控制角度以下:spring

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系
  •  运行指标,例如 QPS、线程池、系统负载等
  •  控制的效果,例如直接限流、冷启动、排队等

2.2 熔断降级

        当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而致使级联故障。手段以下json

  • 经过并发线程数进行限制 :当线程数在特定资源上堆积到必定的数量以后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
  • 经过响应时间对资源进行降级:当依赖的资源出现响应时间过长后,全部对该资源的访问都会被直接拒绝,直到过了指定的时间窗口以后才从新恢复。

2.3 系统负载保护  

          Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防御中重要的一环。当系统负载较高的时候,若是还持续让请求进入,可能会致使系统崩溃,没法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。若是 这个时候其它的机器也处在一个边缘状态的时候,这个增长的流量就会致使这台机器也崩溃,最后致使整个集群不可用。api

          针对这个状况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围以内处理最多的请求。markdown

3.使用

3.1 依赖

    这里我使用sentinel 是基于gradle配置,兼容spring clould alibaba,因此添加以下依赖网络

compile'com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel:2.1.0.RELEASE'
compile group: 'com.alibaba.csp', name: 'sentinel-transport-simple-http', version: '1.6.3'

3.2 注解

      Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException等,固然也支持使用aop的方式,这里演示使用aop的方式,添加以下配置类架构

@Configuration
public class SentinelAspectConfiguration {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
}
}

 @SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项 。@SentinelResource 注解包含如下属性

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项EntryType.OUT/EntryType.IN(默认为 EntryType.OUT),对应入口控制/出口控制
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称。
  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数能够针对全部类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理
    • 返回值类型必须与原函数返回值类型一致
    • fllback 函数默认须要和原方法在同一个类中。若但愿使用其余类的函数,则能够指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,不然没法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,一般用于通用的 fallback 逻辑(便可以用于不少服务或方法)。默认 fallback 函数能够针对全部类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。函数签名和fallback一致
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

3.3 示例  

服务具体实现类

@Service
@Slf4j
public class HelloProviderServiceImpl implements HelloProviderService {

  @Autowired
  private ConfigurableEnvironment configurableEnvironment;

  // 对应的 `handleException` 函数须要位于 `ExceptionUtil` 类中,而且必须为 static 函数
  @Override
  @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {
      ExceptionUtil.class})
  public void test() {
    log.info("Test");
  }

  @Override
  @SentinelResource(value = "sayHi", blockHandler = "exceptionHandler", fallback = "helloFallback")
  public String sayHi(long time) {
    if (time < 0) {
      throw new IllegalArgumentException("invalid arg");
    }
    try {
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalArgumentException("inter arg");
    }
    return String.format("Hello time %d", time);
  }

// 这里俗称资源埋点,在设置限流策略的时候会根据此埋点来控制 @Override @SentinelResource(value
= "helloAnother", defaultFallback = "defaultFallback", exceptionsToIgnore = {IllegalStateException.class}) public String helloAnother(String name) { if (name == null || "bad".equals(name)) { throw new IllegalArgumentException("oops"); } if ("foo".equals(name)) { throw new IllegalStateException("oops"); } return "Hello, " + name; } // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数. public String helloFallback(long s, Throwable ex) { log.error("fallbackHandler:" + s); return "Oops fallbackHandler, error occurred at " + s; } //默认的 fallback 函数名称 public String defaultFallback() { log.info("Go to default fallback"); return "default_fallback"; } // Block 异常处理函数,参数最后多一个 BlockException,其他与原函数一致. public String exceptionHandler(long s, BlockException ex) { // Do some log here. return "Oops,exceptionHandler, error occurred at " + s; } }

服务接口

public interface HelloProviderService {
    public String sayHi(long t) throws InterruptedException;
    String helloAnother(String name);
    void test();
}
ExceptionUtil类
@Slf4j
public final class ExceptionUtil {
  public static void handleException(BlockException ex) {
     log.info("Oops: " + ex.getClass().getCanonicalName());
  }
}

controller 类

@RestController
@Slf4j
public class HelloProviderController {

  @Autowired
  HelloProviderServiceImpl helloServiceProviderService;

  @GetMapping("/sayHi")
  public String sayHi(@RequestParam(required = false) Long time) throws Exception {
    if (time == null) {
      time = 300L;
    }
    helloServiceProviderService.test();
    
return helloServiceProviderService.sayHi(time); } @GetMapping("baz/{name}") public String apiBaz(@PathVariable("name") String name) { return helloServiceProviderService.helloAnother(name); } }

3.4 Sentinel 控制台

一个轻量级的开源控制台,它提供机器发现以及健康状况管理、监控(单机和集群),规则管理和推送的功能。主要能够经过该控制台对服务端设置的资源埋点进行动态的限流配置推送,这样能够灵活的设置限流策略而不用在代码里写死

  • 提供web界面,可视化资源和流量监控、对资源埋点进行配置
  • 具体安装比较简单,因此这里再也不说起,能够参考连接 

3.5 降级策略

  • 平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)以内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算做 4900 ms,若须要变动此上限能够经过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

  • 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,而且每秒异常总数占经过量的比值超过阈值(DegradeRule 中的 count)以后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)以内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],表明 0% - 100%。

  • 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值以后会进行熔断。注意因为统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

  • 能够启用Sentinel 控制台,在控制台上直接配置熔断降级规则。

    • 打开控制台界面,点击簇点链路,选择程序里的资源埋点,点击降级
    • 配置降级规则
    • 配置RT模式测试,控制台输入RT和窗口时间
      • url:ip:port/sayHi?time=delayTime, 当 1s 内持续进入 5 个请求 平均delayTime>RT 进入降级服务
    • 配置异常比例,控制台输入异常比例
      • url:ip:port/baz/bad, 当资源的每秒请求量 >= 5,而且每秒异常总数占经过量的比值设定的异常比例 将在接下来设置的窗口时间内进入降级服务

Feign

  1. 背景 

    Feign是Netflix公司开源的轻量级的一种负载均衡的HTTP客户端,,使用Feign调用API就像调用本地方法同样,从避免了 调用目标微服务时,须要不断的解析/封装json 数据的繁琐。 Spring Cloud引入Feign而且集成了Ribbon实现客户端负载均衡调用。 通俗一点讲:能够像调用本地方法同样的调用远程服务的方法。
固然其中也有很多坑等踩。

   2.使用

 Sentinel 适配了 Fegin组件。若是想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还须要 2 个步骤:

  • 配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabled=true

  • 加入 openfeign starter 依赖使 sentinel starter 中的自动化配置类生效:
    compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '2.1.3.RELEASE'

2.1示例

    添加接口 EchoService类,该接口经过@FeignClient(name = "service-provider")注解来绑定该接口对应service01服务

@FeignClient(name = "nacos-provider-sentianel1", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {

  @GetMapping(value = "/sayHi")
  String sayHi(@RequestParam(value = "time", required = false) Long time);

  @RequestMapping("/api/{name}")
  String apiBaz(@PathVariable("name") String name);
}

    其中 @FeignClient 中name 中的值做为 提供服务提供方的名称,该接口中配置当前服务须要调用nacos-provider-sentianel1服务提供的接口。nacos-provider-sentianel1注册到注册服务上,我这里使用的是Nacos.

   服务配置以下

  nacos-provider-sentianel1 中的controller是这个样子的,这里能够看到 和EchoService中的方法签名都是一致的

@RestController
public class HelloProviderController2 { @GetMapping("/echo") public String helloConsumer(@RequestParam(required = false) Long time) { return "echo"; } @GetMapping("/api/{name}") public String apiBaz(@PathVariable("name") String name) { return "another provider " + name; } }

添加 EchoServiceFallback,这里是fegin的Fallback机制,主要用来作容错处理。由于在网络请求时,可能会出现异常请求,若是还想再异常状况下使系统可用,那么就须要容错处理。

@Component。
public class EchoServiceFallback implements EchoService {

  @Override
  public String sayHi(Long time) {
    return "sayHi fallback";
  }

  @Override
  public String apiBaz(String name) {
    return "apiBaz fallback";
  }
}

添加FeignConfiguration 

@Configuration
public class FeignConfiguration {
  @Bean
  public EchoServiceFallback echoServiceFallback() {
    return new EchoServiceFallback();
  }
}

  在上文HelloProviderServiceImpl的基础上添加EchoService调用

@Service
@Slf4j
public class HelloProviderServiceImpl implements HelloProviderService {

  @Autowired
  private ConfigurableEnvironment configurableEnvironment;

  @Autowired
  EchoService echoService;

  // 对应的 `handleException` 函数须要位于 `ExceptionUtil` 类中,而且必须为 static 函数
  @Override
  @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {
      ExceptionUtil.class})
  public void test() {
    log.info("Test");
  }

  @Override
  @SentinelResource(value = "sayHi", blockHandler = "exceptionHandler", fallback = "helloFallback")
  public String sayHi(long time) {
    if (time < 0) {
      throw new IllegalArgumentException("invalid arg");
    }
    try {
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalArgumentException("inter arg");
    }
    return String.format("Hello time %d", time);
  }

  @Override
  @SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback",
      exceptionsToIgnore = {IllegalStateException.class})
  public String helloAnother(String name) {
    if (name == null || "bad".equals(name)) {
      throw new IllegalArgumentException("oops");
    }
    if ("foo".equals(name)) {
      throw new IllegalStateException("oops");
    }
    return "Hello, " + name;
  }

  // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
  public String helloFallback(long s, Throwable ex) {
    log.error("fallbackHandler:" + s);

    return "Oops fallbackHandler, error occurred at " + s;
  }

  //默认的 fallback 函数名称
  public String defaultFallback() {
    log.info("Go to default fallback");
    return echoService.apiBaz("bad");
    //return "default_fallback";
  }

  // Block 异常处理函数,参数最后多一个 BlockException,其他与原函数一致.
  public String exceptionHandler(long s, BlockException ex) {
    // Do some log here.
    return "Oops,exceptionHandler, error occurred at " + s;
  }
}

  这里咱们在defaultFallback中使用 echoService.apiBaz("bad") 来调用nacos-provider-sentianel1 的apiBaz方法

  在sentinel控制台中配置helloAnother的降级规则,当触发降级后,将会调用acos-provider-sentianel1服务的apiBaz方法,返回结果。

总结

      使用sentinel控制系统流量,当系统流超出当前服务的接受范围的时候,能够经过Feign 调用降级服务,这样就可构成一个最基础的熔断降级模块,固然Feign中还集成了Ribbon,能够经过配置实现客户端负载均衡调用。

相关文章
相关标签/搜索