使用Sentinel进行流量控制,断路和系统自适应保护

系列文章导航: Spring Cloud Alibaba微服务解决方案

目录

Sentinel引入

Sentinel规则

流控规则

降级规则

热点规则

系统规则

授权规则

集群流控

Sentinel API及@SentinelResource注解

Sentinel扩展

RestTemplate整合Sentinel

Feign整合Sentinel

Sentinel规则持久化

Sentinel异常提示优化

Sentinel支持Restful URL

Sentinel的CommonFilter


 

Sentinel引入

项目引入sentinel依赖


   org.springframework.cloud
   spring-cloud-starter-alibaba-sentinel
   0.9.0.RELEASE

应用端连接控制台配置项

使用Sentinel进行流量控制,断路和系统自适应保护_第1张图片

示例:

spring:  
  cloud:
    sentinel:
      transport:
        #指定sentinel控制台地址
        dashboard: localhost:8099
      #开启饥饿加载
      eager: true

启动Sentinel控制台

https://github.com/alibaba/Sentinel/releases 官方下载对应版本的控制台jar

控制台相关配置项如下,可以根据需要调整配置:

使用Sentinel进行流量控制,断路和系统自适应保护_第2张图片

启动示例: nohup java >log.out 2>&1 -Dserver.port=8099 -Dsentinel.dashboard.auth.username=xiawei -Dsentinel.dashboard.auth.password=xiawei -jar sentinel-dashboard-1.7.1.jar &

访问sentinel控制台,localhost:8099,用设置的用户密码登录

使用Sentinel进行流量控制,断路和系统自适应保护_第3张图片

由于sentinel默认是懒加载的,如果应用没有流量,sentinel控制台不会显示任何内容。可以配置开启饥饿加载。Sentinel的详细配置可以参考这篇文章。

 

Sentinel规则

下列各种规则都是基于Sentinel的控制台进行配置,代码中仅使用@SentinelResource指定了资源。Sentinel也支持基于代码配置规则,详情参见这篇文章。

流控规则

在sentinel控制台,选择簇点链路中针对特定的资源可以配置流控规则,如下图:

使用Sentinel进行流量控制,断路和系统自适应保护_第4张图片

针对来源:可以针对不同的微服务来源的调用,配置不同的流控规则,默认为default,不区分来源。支持针对来源需要在代码中                    扩展,详情可以参见本文授权规则一节。

阈值类型:QPS每秒请求数、线程数

是否集群:本文暂不讨论集群流控

流控模式:

       直接模式:达到设定阈值直接限流。

       关联模式:当关联的资源达到阈值,限流自身。适用于使用共同资源的API,有资源倾斜需求的场景。

       链路模式:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就限流),API级别的针对来源。

       链路模式需要使用@SentinelResource指定需要控制的API级资源,代码示例如下:

@RestController
@RequestMapping
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
    private final @NonNull TestServiceImpl testService;

    @GetMapping("/testa")
    public String testa() {
        testService.common();
        return "test-a";
    }

    @GetMapping("/testb")
    public String testb() {
        testService.common();
        return "test-b";
    }
}
@Slf4j
@Service
public class TestServiceImpl {

    @SentinelResource("common")
    public String common() {
        log.info("common...");
        return "common";
    }
}

注意:Sentinel 1.6.3版本以后,Sentinel Web过滤器默认收敛所有URL的入口上下文,因此互连限流不生效。1.7.0和1.7.1版本可以使用配置类的方式关闭URL PATH聚合,示例如下:

spring:
  cloud:
    sentinel:
      filter:
        enabled: false
@Configuration
public class FilterContextConfig {

    /**
     * @NOTE 在spring-cloud-alibaba v2.1.1.RELEASE及前,sentinel1.7.0及后,关闭URL PATH聚合需要通过该方式
     * spring-cloud-alibaba v2.1.1.RELEASE后,可以通过配置关闭:spring.cloud.sentinel.web-context-unify=false
     * 手动注入Sentinel的过滤器,关闭Sentinel注入CommonFilter实例,修改配置文件中的 spring.cloud.sentinel.filter.enabled=false
     * 入口资源聚合问题:https://github.com/alibaba/Sentinel/issues/1024 或 https://github.com/alibaba/Sentinel/issues/1213
     * 入口资源聚合问题解决:https://github.com/alibaba/Sentinel/pull/1111
     */
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
        return registration;
    }
}

在版本1.7.2及以后,可以直接修改配置spring.cloud.sentinel.web-context-unify为false来关闭URL PATH聚合。

流控效果:

       快速失败:超出规则配置的阈值时,直接拒绝请求,抛出异常。

       Warm Up:预热/冷启动当流量突然增大的时候,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。默认冷启动因子coldFactor 为 3,即请求 QPS 从 阈值/ 3 开始,经预热时长逐渐升至设定的 QPS 阈值。详情参见官方文档。

        排队等待:匀速排队,让请求以均匀的速度通过,阈值类型必须设置成QPS,否则无效。

 

降级规则

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

使用Sentinel进行流量控制,断路和系统自适应保护_第5张图片

降级策略

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

        使用Sentinel进行流量控制,断路和系统自适应保护_第6张图片

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

       使用Sentinel进行流量控制,断路和系统自适应保护_第7张图片

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

        使用Sentinel进行流量控制,断路和系统自适应保护_第8张图片

降级配置目前针对RT、异常比例、异常数有不同级别的统计粒度,无法配置,使用还不足够灵活。sentinel的后续版本已经计划处理相关问题,可以在这里关注进展。

 

热点规则

热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。

使用Sentinel进行流量控制,断路和系统自适应保护_第9张图片

热点规则可以设置API参数级的流量控制,还可以针对不同的参数值设置不同的限流阈值。设置的热点参数必须是基本类型或String。

对API进行热点规则控制,仅需要将API上加入@SentinelResource注解,然后在sentinel控制台针对指定的资源进行配置即可。

 

系统规则

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。 

使用Sentinel进行流量控制,断路和系统自适应保护_第10张图片

上述阈值类型的说明如下:

使用Sentinel进行流量控制,断路和系统自适应保护_第11张图片

使用Sentinel进行流量控制,断路和系统自适应保护_第12张图片

 

授权规则

授权规则可以在微服务的粒度级别控制是否允许调用方使用资源,可以配置白名单或黑名单。

使用Sentinel进行流量控制,断路和系统自适应保护_第13张图片

授权规则和流控规则的针对来源功能,需要在代码中进行扩展,示例如下:

@Component
public class MyRequestOriginParserConfig implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        //指定参数origin为来源参数,必填
        //实际项目中为了不侵入请求URL,可以设置在header中
        String origin = httpServletRequest.getParameter("origin");
        if (StringUtils.isBlank(origin)) {
            throw new IllegalArgumentException("origin must not be null .");
        }
        return origin;
    }
}

 

集群流控

要想使用集群流控功能,我们需要在应用端配置动态规则源,并通过 Sentinel 控制台实时进行推送。

使用Sentinel进行流量控制,断路和系统自适应保护_第14张图片

详细扩展方式参见这篇文章。

Sentinel API及@SentinelResource注解

Sentinel中的核心API主要有3个:SphU用于定义资源、Tracer用于指定统计异常、ContextUtil用于定义来源。

大致的使用方式如下:

@Slf4j
@RestController
@RequestMapping
public class TestController {

    @GetMapping("/test-sentinel-api")
    public String testSentinelApi(@RequestParam(required = false) String a) {
        String resourceName = "test-sentinel-api";
        //定义来源,针对流控规则中的针对来源
        ContextUtil.enter(resourceName, "test-wfw");

        //定义一个sentinel保护的资源,名称是test-sentinel-api
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName);
            //被保护的业务逻辑
            if (StringUtils.isBlank(a)) {
                throw new IllegalArgumentException("参数a为空");
            }
            return a;
        } catch (BlockException block) {
            //限流或者降级
            log.warn("被限流或者降级");
            return "被限流或者降级";
        } catch (IllegalArgumentException illeaga) {
            //将异常加入限流/降级监控统计
            //统计IllegalArgumentException的发生次数、占比...
            Tracer.trace(illeaga);
            return "参数非法。";
        } finally {
            if (entry != null) {
                entry.exit();
            }
            ContextUtil.exit();
        }
    }
}

在每个需要保护的业务逻辑中加入上述的模板代码对开发体验来说非常不友好,我们可以使用@SentinelResource来实现相应的功能。示例如下:

@Slf4j
@RestController
@RequestMapping
public class TestController {

    @GetMapping("/test-sentinel-api")
    @SentinelResource(
            //定义资源名称
            value = "test-sentinel-api",
            //定义限流/降级异常处理方法,需在本类中实现该方法,且方法签名与资源一致,可以在参数最后添加异常类参数
            //也可以将限流/降级处理方法抽出,创建独立的类实现,并将方法加上 static修饰
            //在此处指定blockHandlerClass 或 fallbackClass类即可
            //blockHandlerClass = x.class
            //fallbackClass = y.class
            blockHandler = "block",
            fallback = "fallback"
            //设置忽略的异常
            //exceptionsToIgnore = {IllegalMonitorStateException.class}
            //设置需要trace的异常
            //exceptionsToTrace = {IllegalArgumentException.class}
    )
    public String testSentinelApi(@RequestParam(required = false) String a) {
        if (StringUtils.isBlank(a)) {
            throw new IllegalArgumentException("参数a为空");
        }
        return a;
    }

    public String block(String a, BlockException b) {
        log.warn("限流或者降级了...block...", b);
        return "限流或者降级了...block...";
    }

    public String fallback(String a, Throwable throwable) {
        log.warn("限流或者降级了...fallback...", throwable);
        return "限流或者降级了...fallback...";
    }
}

 

Sentinel扩展

RestTemplate整合Sentinel

RestTemplate整合Sentinel,只需要在RestTemplate创建时加入@SentinelRestTemplate注解即可:

 @Bean
 @LoadBalanced
 @SentinelRestTemplate
 public RestTemplate restTemplate() {
     return new RestTemplate();
 }

同样,@SentinelRestTemplate注解也可以支持blockHandler和fallback,类似于@SentinelResource的使用方式。

 

Feign整合Sentinel

Feign整合Sentinel,只需要在配置文件中加入属性配置feign.sentinel.enable=true即可。

注意:使用spring cloud alibaba 2.2.1RELEASE版本时,对应的spring cloud版本为 Hoxton.SR3 ,但是将feign整合Sentinel时,加入feign.sentinel.enable=true会导致项目启动报出Caused by: java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidateMetadata(Ljava/lang/Class;)Ljava/util/List错误。这是因为版本兼容性问题导致,feign在2.2.2RELEASE版本修改了一处接口的方法名称的命名错误,而Sentinel并没有进行对应修改。可以在pom中手动配置open feign的版本,将其降级为2.2.0RELEASE解决该问题。


        
            
                org.springframework.cloud
                spring-cloud-openfeign-dependencies
                2.2.0.RELEASE
                pom
                import
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            

    

如需实现自定义异常处理逻辑,需要改造FeignClient,指定fallback调用类。

示例如下:

feign:
  sentinel:
    #为feign整合sentinel
    enable: true
  httpclient:
    enabled: true
    #最大连接数
    max-connections: 200
    #单个路径最大连接数
    max-connections-per-route: 50

 

@FeignClient(name = "user-center",
//使用fallbackFactory可以获取异常,使用fallbackFactory时就不要使用fallback
fallbackFactory = FeignClientFallbackFactory.class)
public interface UserCenterFeignClient {
    @GetMapping("/users/{id}")
    UserDTO findById(@PathVariable Integer id);
}
@Slf4j
@Component
public class FeignClientFallbackFactory implements FallbackFactory {
    @Override
    public UserCenterFeignClient create(Throwable throwable) {
        return id -> {
            log.warn("远程调用被限流/降级了...",throwable);
            return null;
        };
    }
}

 

Sentinel规则持久化

Sentinel默认将规则存储在内存中,在应用重启后已配置的规则会丢失,不适用于生产环境。

Sentinel提供了两种模式的扩展,用以支持规则持久化:

pull模式

  • 优点
    • 简单易懂
    • 没有多余依赖(比如配置中心、缓存等)
  • 缺点
    • 由于规则是用 FileRefreshableDataSource 定时更新的,所以规则更新会有延迟。如果FileRefreshableDataSource定时时间过大,可能长时间延迟;如果FileRefreshableDataSource过小,又会影响性能;
    • 规则存储在本地文件,如果有一天需要迁移微服务,那么需要把规则文件一起迁移,否则规则会丢失。

push模式

  • 优点
    • 规则持久化
    • 一致性好
    • 性能优秀
  • 缺点
    • 改动多、并且麻烦
    • 引入额外的依赖

生产环境推荐使用push模式。需要改造sentinel-dashboard控制台。详情参见这篇文章。

 

Sentinel异常提示优化

在实际项目使用中,我们期望流控、降低、授权、系统、热点等规则控制抛出的异常能够被区分,让应用知晓是因为何种控制规则不通过进行的处理。我们可以使用@SentinelResource、@FeignClient、@SentinelRestTemplate等注解中的异常处理回调来支持不同细粒度的异常处理。也可以使用UrlBlockHandler来统一处理,示例如下:

@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
        JSONObject res = new JSONObject();
        if (e instanceof FlowException) {
            res.put("code", "100");
            res.put("msg", "限流了...");
        } else if (e instanceof DegradeException) {
            res.put("code", "101");
            res.put("msg", "降级了...");
        } else if (e instanceof ParamFlowException) {
            res.put("code", "102");
            res.put("msg", "热点参数限流...");
        } else if (e instanceof SystemBlockException) {
            res.put("code", "103");
            res.put("msg", "系统规则(负载/...)不满足要求");
        } else if (e instanceof AuthorityException) {
            res.put("code", "104");
            res.put("msg", "授权规则不通过...");
        }

        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
        httpServletResponse.setContentType("application/json;charset=utf-8");

        JSON.writeJSONString(httpServletResponse.getWriter(), res);
    }
}

 

Sentinel支持Restful URL

由于sentinel不支持占位符,在使用restful风格的接口时,不同的PathVariable参数会在sentinel中当做不同的资源,这样会导致无法配置统一的流控规则。

使用Sentinel进行流量控制,断路和系统自适应保护_第15张图片

针对这种情况,Sentinel提供了UrlCleaner接口,支持用户将url统一处理,示例如下:

@Component
public class MyUrlCleaner implements UrlCleaner {
    @Override
    public String clean(String originUrl) {
        String[] split = originUrl.split("/");
        return Arrays.stream(split).map(str -> {
            if (NumberUtils.isNumber(str)) {
                return "{number}";
            }
            return str;
        }).reduce((prefix, replace) -> prefix + "/" + replace).orElse("");
    }
}

经过UrlCleaner处理后,sentinel将含有路径参数的url进行统一,可以针对统一后的资源配置规则

使用Sentinel进行流量控制,断路和系统自适应保护_第16张图片

 

Sentinel的CommonFilter

前度扩展Sentinel时使用的UrlBlockHandler(异常处理)、UrlCleaner(资源统一)、RequestOriginParser(支持针对来源),核心逻辑都是使用的Sentinel Api完成,对源码感兴趣的朋友可以查看CommonFilter类。

使用Sentinel进行流量控制,断路和系统自适应保护_第17张图片

使用Sentinel进行流量控制,断路和系统自适应保护_第18张图片 如果遇到特殊场景,Sentinel支持不足够满足需求时,可以自定义CommonFilter来进行扩展。

你可能感兴趣的:(Spring,Cloud,Alibaba微服务解决方案)