当前位置:首页 > 人工智能

微服务限流容错降级Sentinel实战

 一、微服务限什么是流容雪崩效应?

业务场景,高并发调用

正常情况下,错降微服务A B C D 都是微服务限正常的。 随着时间推移,流容在某一个时间点 微服务A突然挂了,错降此时的微服务限微服务B 还在疯狂的调用微服务A,由于A已经挂了,流容所以B调用A必须等待服务调用超时。错降而我们知道每次B -> A 的微服务限适合B都会去创建线程(而线程由计算机的资源,比如cpu、流容内存等)。错降由于是微服务限高并发场景,B 就会阻塞大量的流容线程。那边B所在的错降机器就会去创建线程,但是计算机资源是有限的,最后B的服务器就会宕机。(说白了微服务B 活生生的被猪队友微服务A给拖死了) 由于微服务A这个猪队友活生生的把微服务B给拖死了,源码下载导致微服务B也宕机了,然后也会导致微服务 C D 出现类似的情况,最终我们的猪队友A成功的把微服务 B C D 都拖死了。这种情况也叫做服务雪崩。也有一个专业术语(cascading failures)级联故障。

二、容错三板斧

2.1 超时

简单来说就是超时机制,配置以下超时时间,假如1秒——每次请求在1秒内必须返回,否则到点就把线程掐死,释放资源!

思路:一旦超时,就释放资源。由于释放资源速度较快,应用就不会那么容易被拖死。

代码演示:(针对调用方处理)

// 第一步:设置RestTemplate的超时时间 @Configuration public class WebConfig {      @Bean     public RestTemplate restTemplate() {          //设置restTemplate的超时时间         SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();         requestFactory.setReadTimeout(1000);         requestFactory.setConnectTimeout(1000);         RestTemplate restTemplate = new RestTemplate(requestFactory);         return restTemplate;     } } // 第二步:进行超时异常处理 try{      ResponseEntity<ProductInfo> responseEntity= restTemplate.getForEntity(uri+orderInfo.getProductNo(), ProductInfo.class);     productInfo = responseEntity.getBody(); }catch (Exception e) {      log.info("调用超时");     throw new RuntimeException("调用超时"); } // 设置全局异常处理 @ControllerAdvice public class NiuhExceptionHandler {      @ExceptionHandler(value = { RuntimeException.class})     @ResponseBody     public Object dealBizException() {          OrderVo orderVo = new OrderVo();         orderVo.setOrderNo("-1");         orderVo.setUserName("容错用户");         return orderVo;     } } 

 2.2 舱壁隔离模式

有兴趣可以先了解一下船舱构造——一般来说,现代的轮船都会分很多舱室,舱室直接用钢板焊死,彼此隔离。这样即使有某个/某些船舱进水,也不会营销其它舱室,浮力够,船不会沉。

代码中的舱壁隔离(线程池隔离模式)

M类使用线程池1,N类使用线程池2,彼此的高防服务器线程池不同,并且为每个类分配的线程池大小,例如 coreSIze=10。

举例子:M类调用B服务,N类调用C服务,如果M类和N类使用相同的线程池,那么如果B服务挂了,N类调用B服务的接口并发又很高,你又没有任何保护措施,你的服务就很可能被M类拖死。而如果M类有自己的线程池,N类也有自己的线程池,如果B服务挂了,M类顶多是将自己的线程池占满,不会影响N类的线程池——于是N类依然能正常工作。

思路:不把鸡蛋放在一个篮子里,你有你的线程池,我有我的线程池,你的线程池满类和我也没关系,你挂了也和我也没关系。服务器托管

2.3 断路器模式

现实世界的断路器大家肯定都很了解,每个人家里都会有断路器。断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。

软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阀值,就“跳闸”,断路器打开——次数,请求直接返回,而不去调用原本调用的逻辑。

跳闸一段时间后(例如15秒),断路器会进入半开状态,这是一个瞬间态,此时允许一个请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过“跳闸”,应用可以保护自己,而且避免资源浪费;而通过半开的设计,可以实现应用的“自我修复”

三、Sentinel 流量控制、容错、降级

3.1 什么是Sentinel?

A lightweight powerful flow control component enabling reliability and monitoring for microservices.(轻量级的流量控制、熔断降级 Java 库) github官网地址:https://github.com/alibaba/Sentinelwiki:https://github.com/alibaba/Sentinel/wiki/

Hystrix 在 Sentinel 面前就是弟弟

Sentinel的初体验

niuh04-ms-alibaba-sentinel-helloworld

V1版本:

第一步:添加依赖包

<!--导入Sentinel的相关jar包--> <dependency>     <groupId>com.alibaba.csp</groupId>     <artifactId>sentinel-core</artifactId>     <version>1.7.1</version> </dependency> 

 第二步:controller

@RestController @Slf4j public class HelloWorldSentinelController {      @Autowired     private BusiServiceImpl busiService;     /**      * 初始化流控规则      */     @PostConstruct     public void init() {          List<FlowRule> flowRules = new ArrayList<>();         /**          * 定义 helloSentinelV1 受保护的资源的规则          */         //创建流控规则对象         FlowRule flowRule = new FlowRule();         //设置流控规则 QPS         flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);         //设置受保护的资源         flowRule.setResource("helloSentinelV1");         //设置受保护的资源的阈值         flowRule.setCount(1);         flowRules.add(flowRule);         //加载配置好的规则         FlowRuleManager.loadRules(flowRules);     }     /**      * 频繁请求接口 http://localhost:8080/helloSentinelV1      * 这种做法的缺点:      * 1)业务侵入性很大,需要在你的controoler中写入 非业务代码..      * 2)配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则      * @return      */     @RequestMapping("/helloSentinelV1")     public String testHelloSentinelV1() {          Entry entity =null;         //关联受保护的资源         try {              entity = SphU.entry("helloSentinelV1");             //开始执行 自己的业务方法             busiService.doBusi();             //结束执行自己的业务方法         } catch (BlockException e) {              log.info("testHelloSentinelV1方法被流控了");             return "testHelloSentinelV1方法被流控了";         }finally {              if(entity!=null) {                  entity.exit();             }         }         return "OK";     } } 

 测试效果:

http://localhost:8080/helloSentinelV1

V1版本的缺陷如下:

业务侵入性很大,需要在你的controoler中写入 非业务代码. 配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则

V2版本:基于V1版本,再添加一个依赖

<dependency>     <groupId>com.alibaba.csp</groupId>     <artifactId>sentinel-annotation-aspectj</artifactId>     <version>1.7.1</version> </dependency> 

 编写controller

// 配置一个切面 @Configuration public class SentinelConfig {      @Bean     public SentinelResourceAspect sentinelResourceAspect() {          return new SentinelResourceAspect();     } } /**  * 初始化流控规则  */ @PostConstruct public void init() {      List<FlowRule> flowRules = new ArrayList<>();     /**      * 定义 helloSentinelV2 受保护的资源的规则      */     //创建流控规则对象     FlowRule flowRule2 = new FlowRule();     //设置流控规则 QPS     flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS);     //设置受保护的资源     flowRule2.setResource("helloSentinelV2");     //设置受保护的资源的阈值     flowRule2.setCount(1);     flowRules.add(flowRule2); } /**  * 频繁请求接口 http://localhost:8080/helloSentinelV2  * 优点: 需要配置aspectj的切面SentinelResourceAspect ,添加注解@SentinelResource  *     解决了v1版本中 sentinel的业务侵入代码问题,通过blockHandler指定被流控后调用的方法.  * 缺点: 若我们的controller中的方法逐步变多,那么受保护的方法也越来越多,会导致一个问题  * blockHandler的方法也会越来越多   引起方法急剧膨胀 怎么解决  *  * 注意点:  *   blockHandler 对应处理 BlockException 的函数名称,  *   可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,  *   参数类型需要和原方法相匹配并且最后加一个额外的参数,  *   类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中  * @return  */ @RequestMapping("/helloSentinelV2") @SentinelResource(value = "helloSentinelV2",blockHandler ="testHelloSentinelV2BlockMethod") public String testHelloSentinelV2() {      busiService.doBusi();     return "OK"; } public String testHelloSentinelV2BlockMethod(BlockException e) {      log.info("testRt流控");     return "testRt降级 流控...."+e; } 

 测试效果:

http://localhost:8080/helloSentinelV2

V3版本 基于V2缺点改进

/**  * 初始化流控规则  */ @PostConstruct public void init() {      List<FlowRule> flowRules = new ArrayList<>();     /**      * 定义 helloSentinelV3 受保护的资源的规则      */     //创建流控规则对象     FlowRule flowRule3 = new FlowRule();     //设置流控规则 QPS     flowRule3.setGrade(RuleConstant.FLOW_GRADE_QPS);     //设置受保护的资源     flowRule3.setResource("helloSentinelV3");     //设置受保护的资源的阈值     flowRule3.setCount(1);     flowRules.add(flowRule3); } /**  * 我们看到了v2中的缺点,我们通过blockHandlerClass 来指定处理被流控的类  * 通过testHelloSentinelV3BlockMethod 来指定blockHandlerClass 中的方法名称  * ***这种方式 处理异常流控的方法必须要是static的  * 频繁请求接口 http://localhost:8080/helloSentinelV3  * @return  */ @RequestMapping("/helloSentinelV3") @SentinelResource(value = "helloSentinelV3",blockHandler = "testHelloSentinelV3BlockMethod",blockHandlerClass = BlockUtils.class) public String testHelloSentinelV3() {      busiService.doBusi();     return "OK"; } // 异常处理类 @Slf4j public class BlockUtils {      public static String testHelloSentinelV3BlockMethod(BlockException e){          log.info("testHelloSentinelV3方法被流控了");         return "testHelloSentinelV3方法被流控了";     } } 

 测试效果:

http://localhost:8080/helloSentinelV3

缺点:不能动态的添加规则。如何解决问题?

3.2 如何在工程中快速整合Sentinel

<!--加入sentinel--> <dependency>     <groupId>com.alibaba.cloud</groupId>     <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--加入actuator--> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 

 添加Sentinel后,会暴露/actuator/sentinel 端点http://localhost:8080/actuator/sentinel

而Springboot默认是没有暴露该端点的,所以我们需要自己配置

server:   port: 8080 management:   endpoints:     web:       exposure:         include: * 

 

3.3 我们需要整合Sentinel-dashboard(哨兵流量卫兵)

下载地址:

https://github.com/alibaba/Sentinel/releases (我这里版本是:1.6.3)

第一步:执行 java -jar sentinel-dashboard-1.6.3.jar 启动(就是一个SpringBoot工程) 第二步:访问我们的sentinel控制台(1.6版本加入登陆页面)http://localhost:8080/ ,默认账户密码:sentinel/sentinel

第三步:我们的微服务 niuh04-ms-alibaba-sentinel-order 整合 sentinel,我们也搭建好了Sentinel控制台,为微服务添加sentinel的控制台地址 spring:   cloud:     sentinel:       transport:         dashboard: localhost:9999 

 四、Sentinel监控性能指标详解

4.1 实时监控面板

在这个面板中我们监控我们接口的 通过的QPS 和 拒绝的QPS,在没有设置流控规则,我们是看不到拒绝的QPS。

4.2 簇点链路

用来线上微服务的所监控的API

4.3 流控设置

簇点链路 选择具体的访问的API,然后点击“流控按钮”

含义:

资源名:为我们接口的API /selectOrderInfoById/1

针对来源:这里是默认的 default(标识不针对来源),还有一种情况就是假设微服务A需要调用这个资源,微服务B也需要调用这个资源,那么我们就可以单独的为微服务A和微服务B进行设置阀值。

阀值类型:分为QPS和线程数,假设阀值为2

QPS类型:指的是每秒钟访问接口的次数 > 2 就进行限流

线程数:为接受请求该资源,分配的线程数 > 2 就进行限流

流控模式

1.直接:这种很好理解,就是达到设置的阀值后直接被流控抛出异常

疯狂的请求这个路径

2.关联

业务场景:我们现在有两个API,第一个是保存订单,一个是查询订单,假设我们希望有限操作“保存订单”

测试:写两个读写测试接口

/**  * 方法实现说明:模仿  流控模式【关联】  读接口  * @author:hejianhui  * @param orderNo  * @return:  * @exception:  * @date:2019/11/24 22:06  */ @RequestMapping("/findById/{ orderNo}") public Object findById(@PathVariable("orderNo") String orderNo) {      log.info("orderNo:{ }","执行查询操作"+System.currentTimeMillis());     return orderInfoMapper.selectOrderInfoById(orderNo); } /**  * 方法实现说明:模仿流控模式【关联】   写接口(优先)  * @author:hejianhui  * @return:  * @exception:  * @date:2019/11/24 22:07  */ @RequestMapping("/saveOrder") public String saveOrder() throws InterruptedException {      //Thread.sleep(500);     log.info("执行保存操作,模仿返回订单ID");     return UUID.randomUUID().toString(); } 

 测试代码:写一个for循环一直调用我们的写接口,让写接口QPS达到阀值

public class TestSentinelRule {      public static void main(String[] args) throws InterruptedException {          RestTemplate restTemplate = new RestTemplate();         for(int i=0;i<1000;i++) {              restTemplate.postForObject("http://localhost:8080/saveOrder",null,String.class);             Thread.sleep(10);         }     } } 

 此时访问我们的读接口:此时被限流了。

3.链路

用法说明,本地实验没成功,用alibaba 未毕业版本0.9.0可以测试出效果,API级别的限制流量

代码:

@RequestMapping("/findAll") public String findAll() throws InterruptedException {      orderServiceImpl.common();     return "findAll"; } @RequestMapping("/findAllByCondtion") public String findAllByCondtion() {      orderServiceImpl.common();     return "findAllByCondition"; } @Service public class OrderServiceImpl {      @SentinelResource("common")     public String common() {          return "common";     } } 

 根据流控规则来说: 只会限制/findAll的请求,不会限制/findAllByCondtion规则

流控效果

1.快速失败(直接抛出异常)每秒的QPS 操作过1 就直接抛出异常

源码: com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

2.预热(warmUp)源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController>

当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步增加,经过预期的时间后,到达系统处理请求个数的最大值。Warm Up (冷启动,预热)模式就是为了实现这个目的。

冷加载因子:codeFacotr 默认是3

默认 coldFactor 为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阀值。

上图设置:就是QPS从100/3=33开始算, 经过10秒钟,达到一百的QPS 才进行限制流量。

详情文档:

https://github.com/alibaba/Sentinel/wiki/限流---冷启动

排队等待源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

这种方式适合用于请求以突刺状来到,这个时候我们不希望一下子把所有的请求都通过,这样可能会把系统压垮;同时我们也期待系统以稳定的速度,逐步处理这些请求,以起到“削峰填谷”的效果,而不是拒绝所有请求。

选择排队等待的阀值类型必须是****QPS

上图设置:单机阀值为10,表示每秒通过的请求个数是10,也就是每个请求平均间隔恒定为 1000 / 10 = 100 ms,每一个请求的最长等待时间(maxQueueingTimeMs)为 20 * 1000ms = 20s。,超过20s就丢弃请求。

详情文档: https://github.com/alibaba/Sentinel/wiki/流量控制-匀速排队模式

4.4 降级规则

rt(平均响应时间)

平均响应时间(DEGRADE_GRADE_RT):当 1s 内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阀值(count,以 ms 为单位),那么在接下来的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。

注意:Sentinel 默认同级的 RT 上限是4900ms,超出此阀值都会算做4900ms,若需要变更此上限可以通过启动配置项:-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)

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

4.5 热点参数

业务场景:秒杀业务,比如商场做促销秒杀,针对苹果11(商品id=1)进行9.9秒杀活动,那么这个时候,我们去请求订单接口(商品id=1)的请求流量十分大,我们就可以通过热点参数规则来控制 商品id=1 的请求的并发量。而其他正常商品的请求不会受到限制。那么这种热点参数规则使用。

五、Sentinel-dashboard 控制台 和 我们的微服务通信原理

5.1 控制台如何获取到微服务的监控信息?

5.2 在控制台配置规则,如何把规则推送给微服务的?

我们通过观察到sentinel-dashboard的机器列表上观察注册服务微服务信息。我们的 控制台就可以通过这些微服务的注册信息跟我们的具体的微服务进行通信.

 

5.3 微服务整合sentinel时候的提供的一些接口API地址: http://localhost:8720/api

5.4 我们可以通过代码设置规则(我们这里用流控规则为例)

@RestController public class AddFlowLimitController {      @RequestMapping("/addFlowLimit")     public String addFlowLimit() {          List<FlowRule> flowRuleList = new ArrayList<>();         FlowRule flowRule = new FlowRule("/testAddFlowLimitRule");         //设置QPS阈值         flowRule.setCount(1);         //设置流控模型为QPS模型         flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);         flowRuleList.add(flowRule);         FlowRuleManager.loadRules(flowRuleList);         return "success";     }     @RequestMapping("/testAddFlowLimitRule")     public String testAddFlowLimitRule() {          return "testAddFlowLimitRule";     } } 

 添加效果截图: 执行:

http://localhost:8080/addFlowLimit

Sentinel具体配置项:

https://github.com/alibaba/Sentinel/wiki/启动配置项

5.5 对SpringMVC端点保护关闭(一般应用场景是做压测需要关闭)

spring:   cloud:     nacos:       discovery:         server-addr: localhost:8848     sentinel:       transport:         dashboard: localhost:9999       filter:         enabled: true  #关闭Spring mvc的端点保护 

那么我们的这种类型的接口 不会被sentinel保护

只有加了 @SentinelResource 的注解的资源才会被保护

六、Ribbon整合Sentinel

6.1 第一步:加配置

<!--加入ribbon--> <dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <!--加入sentinel--> <dependency>     <groupId>com.alibaba.cloud</groupId>     <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--加入actuator--> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 

 6.2 第二步:加注解

在我们的RestTemplate组件上添加@SentinelRestTemplate注解。并且我们可以通过在@SentinelRestTemplate 同样的可以指定我们的 blockHandlerClass、fallbackClass、blockHandler、fallback 这四个属性

@Configuration public class WebConfig {      @Bean     @LoadBalanced     @SentinelRestTemplate(             blockHandler = "handleException",blockHandlerClass = GlobalExceptionHandler.class,             fallback = "fallback",fallbackClass = GlobalExceptionHandler.class     )     public RestTemplate restTemplate() {          return new RestTemplate();     } } 

分享到:

滇ICP备2023006006号-16