微服务SpringCloud Alibaba ------(八)Sentinel

1. 服务雪崩

因服务提供者的不可用导致服务调用者不可用,并将不可用逐渐放大,就叫服务雪崩效应。
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求流量。 所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应产生了。

1.1. 解决方案

提高服务的稳定性、恢复性(Reliability&Resilience)

1.1.1. 常见的容错机制

  1. 超时机制
    在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。

  2. 服务限流
    限制某一段时间内只有指定数量的请求进入后台服务器,遇到流量高峰期或者流量突增时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮。

  3. 隔离
    原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。
    在这里插入图片描述
    3.1. 线程隔离
    为每一个服务接口单独开辟一个线程池,保持与其他服务接口线程的隔离,提高该服务接口的独立性和高可用。

    3.2. 信号隔离
    每次调用线程,当前请求通过计数信号量进行限制,当信号大于了最大请求数时,进行限制。

  4. 服务熔断
    远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。
    现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
    软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一 定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间
    后(例如10秒) ,断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用:如果调用依然不成功,断路器继续回到打开状态,过段时
    间再进入半开状态尝试——通过“跳闸”,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复"。
    所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。比如我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。

  5. 服务降级
    有服务熔断,必然要有服务降级。
    所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的allback (回退)回调,返回一个缺省值。例如: (备用接口/缓存/mock数据)。这样做,虽然服务水平
    下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。

2. Sentinel初体验

Sentinel是阿里巴巴开源的面向分布式服务架构的高可用防护组件。
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
点击查看官方文档

2.1. 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- sentinel核心库 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.0</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.18</version>
</dependency>

<!-- 如果要使用@SentinelResource -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.0</version>
</dependency>

2.2. 添加启动类


import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

/**
 * @Author Xxx
 * @Date 2021/11/24 18:49
 * @Version 1.0
 */
@SpringBootApplication
public class SentinelDemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SentinelDemoApplication.class, args);
    }

    // 注解支持的配置Bean
    @Bean
    public SentinelResourceAspect sentinelResourceAspect(){
    
    
        return new SentinelResourceAspect();
    }
}

2.3. 添加pojo及controller

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;

/**
 * @Author Xxx
 * @Date 2021/11/25 14:45
 * @Version 1.0
 */
@Data
@ToString
@AllArgsConstructor
public class User {
    
    

    private String username;

}
/**
 * @Author Xxx
 * @Date 2021/11/24 18:26
 * @Version 1.0
 */

@RestController
@Slf4j
public class HelloController {
    
    
    private static final String RESOURCE_NAME = "hello";
    private static final String USER_RESOURCE_NAME = "user";
    private static final String DEGRADE_RESOURCE_NAME = "degrade";
}

2.4. 通过代码实现限流

controller中添加

// 进行sentinel流控
@RequestMapping("/hello")
public String hello(){
    
    

    Entry entry = null;
    try {
    
    
        // sentinel根据资源进行限制
        entry = SphU.entry(RESOURCE_NAME);
        // 被保护的业务逻辑
        String str = "hello world";
        log.info("==={}===", str);
        return str;
    } catch (BlockException e) {
    
    
        // 资源访问阻止,被限流或者降级
        // 进行相应的处理操作
        log.info("block!");
        return "被流控了!";
    } catch (Exception ex){
    
    
        // 若需要配置限流规则,需要通过这种方式记录业务异常
        Tracer.traceEntry(ex, entry);
    }finally {
    
    
        if(entry != null){
    
    
            entry.exit();
        }
    }
    return null;

}

/**
 * 定义规则
 */
@PostConstruct
private static void initFlowRules(){
    
    

    // 流控规则
    List<FlowRule> rules = new ArrayList<>();

    // 流控
    FlowRule rule = new FlowRule();
    // 设置受保护的资源
    rule.setResource(RESOURCE_NAME);
    // 设置流控规则类型 QPS
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // 设置受保护的资源阈值
    // set limit QPS 1 to 20
    rule.setCount(1);
    rules.add(rule);

    // 加载配置好的规则
    FlowRuleManager.loadRules(rules);
}

2.5. 通过注解实现限流

controller中添加

/**
 * 定义规则
 */
@PostConstruct
private static void initFlowRules(){
    
    

    // 流控规则
    List<FlowRule> rules = new ArrayList<>();

    // 通过@SentinelResource来定义资源并配置降级和流控的处理方法
    FlowRule rule2 = new FlowRule();
    // 设置受保护的资源
    rule2.setResource(USER_RESOURCE_NAME);
    // 设置流控规则类型 QPS
    rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // 设置受保护的资源阈值
    // set limit QPS 1 to 20
    rule2.setCount(1);
    rules.add(rule2);

    // 加载配置好的规则
    FlowRuleManager.loadRules(rules);
}

/**
 * @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
 * 1. 添加依赖<artifactId>sentinel-annotation-aspectj</artifactId>
 * 2. 配置Bean — SentinelResourceAspect
 * value = 定义资源
 * blockHandler = 流控降级的处理方法(默认该方法必须声明在同一个类中)
 * clockHandlerClass = 通过这个属性来指定blockHandler的类,但是方法必须static修饰
 * fallback = 当接口出现异常,就可以交给fallback指定的方法进行处理
 * exceptionsToIgnore = 哪些异常不处理
 * 如果blockHandler和fallback都指定了,异常和限流同时触发时blockHandler优先级更高
 * @param id
 * @return
 */
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME, exceptionsToIgnore = {
    
    ArithmeticException.class}, fallback = "fallbackForGetUser",blockHandler = "blockHandlerForGetUser")
public User getUser(String id){
    
    
	// 可以放开测试fallback
    // int a = 1 / 0;

    return new User("zhangsan");
}

public User fallbackForGetUser(String id, Throwable ex){
    
    
    ex.printStackTrace();
    return new User("异常");
}

/**
 * 1. 方法必须被public修饰
 * 2. 返回值必须和原方法一致,包含原方法的参数
 * 3. 可以在参数最后添加BlockException类,可以区分是什么规则的处理方法
 * @param id
 * @param ex
 * @return
 */
public User blockHandlerForGetUser(String id, BlockException ex){
    
    
    ex.printStackTrace();
    return new User("流控!");
}

2.6. 实现熔断降级

controller中添加

@PostConstruct
public void initDegradeRule(){
    
    
    // 降级规则
    List<DegradeRule> degradeRules = new ArrayList<>();
    DegradeRule degradeRule = new DegradeRule();
    degradeRule.setResource(DEGRADE_RESOURCE_NAME);
    // 设置熔断规则:异常数
    degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    // 触发熔断异常数
    degradeRule.setCount(2);
    // 触发熔断的最小请求数
    degradeRule.setMinRequestAmount(2);
    // 统计时常(毫秒)
    degradeRule.setStatIntervalMs(60 * 1000);
    // setStatIntervalMs = 60 * 1000 , setMinRequestAmount = 2 , setCount = 2 则表示1分钟内执行了2次 出现了两次异常 就会熔断
    // 熔断持续时常(秒)
    // 一旦触发熔断,再次请求对应接口会调用降级方法(blockHandler)
    // setTimeWindow时间过去后会转为半开状态,如果第一次请求异常则继续熔断,不会根据设置的条件判定
    degradeRule.setTimeWindow(10);
    degradeRules.add(degradeRule);
    DegradeRuleManager.loadRules(degradeRules);

}

@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME, entryType = EntryType.IN, blockHandler = "blockHandlerForFb")
public User degrade(String id) throws InterruptedException {
    
    
    // 异常数\比例
    throw new RuntimeException("异常");

    /* 慢调用比例
    TimeUnit.SECONDS.sleep(1);
    return new User("正常");
             */
}

public User blockHandlerForFb(String id, BlockException ex)  {
    
    
    ex.printStackTrace();
    return new User("降级!");
}

3. 控制台部署

jar包下载地址:https://github.com/alibaba/Sentinel/releases

版本关系见

https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

启动控制台命令

java -jar sentinel-dashboard-1.8.1.jar

用户可以通过如下参数进行配置:
-Dsentinel.dashboard.auth.username=sentinel用于指定控制台的登录用户名为sentinel;
-Dsentinel.dashboard.auth.password=123456用于指定控制台的登录密码为123456;如果省略这两个参数,默认用户和密码均为sentinel;
-Dserver.servlet.session.timeout=7200用于指定Spring Boot服务端session的过期时间,如7200表示7200秒;60m 表示60分钟,默认为30分钟;
-Dserver.port=8858 用于指定sentinel控制台端口

java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=xushu Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.1.jar

为了方便快捷启动可以在桌面创建bat文件

java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=xushu -Dsentinel.dashboard.auth.password=123456 -jar D:\server\sentinel-dashboard-1.8.1.jar
pause

访问http://127.0.0.1:8080/#/login,默认用户密码:sentinel/sentinel

3.1. 客户端整合

3.1.1. 添加依赖

<!-- 整合Sentinel控制台 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.1</version>
</dependency>

启动时加入JVM参数-Dcsp.sentinel.dashboard.server=consoleIp:port指定控制台地址和端口。
在这里插入图片描述

若启动多个应用,则需要通过-Dcsp.sentinel.api.port=xxxx指定客户端监控API的端口(默认是8719)。
从1.6.3版本开始,控制台支持网关流控规则管理。您需要在接入端添加-Dcsp.sentinel.app.type=1启动参数以将您的服务标记为API Gateway,在接入控制台时您的服务会自动注册为网关类型,然后您即可在控制台配置网关规则和API分组。
除了修改JVM参数,也可以通过配置文件取得同样的效果。更详细的信息可以参考启动配置项

4. Spring Cloud Alibaba整合Sentinel

4.1. 添加依赖

<!-- sentinel启动器,spring-cloud-starter-alibaba-nacos-discovery已经管理好版本 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

4.2. 添加配置文件

application.yml

spring:
  application:
    name: 服务名称
  cloud:
    sentinel:
      transport:
        dashboard: 控制台IP:端口

4.3. 流控规则

  • 流量控制(flow control),其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。==== FlowRule RT(响应时间) 1/0.2s =5
    最普适的场景

    • Provider(服务提供)端控制脉冲流量
    • 针对不同调用来源进行流控
    • Web接口流控

    如何配置规则

    • 梳理核心接口
    • 通过事前压测评估核心接口容量,配置QPS阈值

4.4. QPS流控

当QPS超过阈值,会采取措施进行流控,策略包含:直接拒绝、Warm Up、匀速排队。

  1. 快速失败:为默认的流控方式:直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT),拒绝后抛出FlowException。适用于系统处理能力已知(系统处理能力的水位已知)
  2. Warm Up(激增流量),即预热/冷启动方式。通过冷启动,让通过的流量缓慢增加,逐渐增加到阈值上线(给系统一个预热时间),避免冷启动系统被压垮。
    冷加载因子:codeFactor默认是3,即请求QPS从threshold/3开始,经预热市场逐渐升至设定的阈值。
  3. 匀速排队(脉冲流量):让请求匀速通过,对应的是令牌桶算法(注意:匀速排队模式暂时不支持 QPS > 1000 的场景。)

流控效果为快速失败时的脉冲流量(类型=QPS、阈值=5),可以看到部分成功部分失败
在这里插入图片描述
流控效果为匀速排队时的脉冲流量(类型=QPS、阈值=5、超时时间5000毫秒),可以看到所有的请求都成功了

在这里插入图片描述

4.4.1. 新建接口

/**
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    
    
    @RequestMapping("/flow")
    @SentinelResource(value = "flow", blockHandler = "flowBlockHandler")
    public String flow(){
    
    
        return "正常访问";
    }

    public String flowBlockHandler(BlockException e){
    
    
        e.printStackTrace();
        return "流控";
    }
}

4.4.2. 添加流控规则

在这里插入图片描述
在这里插入图片描述
新增后可在左侧流控规则中查看
在这里插入图片描述

4.5. 线程数流控

并发数控制用于保护业务线程池不被慢调用耗尽,可以采用线程池隔离的隔离方案(不同业务使用不同的线程池)。这种方法的缺点和代价是:线程数目太多,线程上下文切换的overhead比较大。

4.5.1. 新建接口

/**
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    
    

    @RequestMapping("/flowThread")
    @SentinelResource(value = "flowThread", blockHandler = "flowBlockHandler")
    public String flowThread() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(3);
        return "正常访问";
    }

    public String flowBlockHandler(BlockException e){
    
    
        e.printStackTrace();
        return "流控";
    }

}

4.5.2. 添加流控规则

在这里插入图片描述

4.5.3. 测试注意项

通过两个浏览器来访问http://127.0.0.1:8060/order/flowThread

4.6. BlockException异常统一处理

springwebmvc接口资源限流入口再HandlerInterceptor的实现类AbstractSentinelInterceptor的preHandle方法中,对异常的处理是BlockExceptionHandle的实现类

代码


/**
 * @Author Xxx
 * @Date 2021/11/26 18:12
 * @Version 1.0
 */
@Data
public class Result<T> {
    
    

    private Integer code;

    private String msg;

    private T data;

    public Result(Integer code, String msg, T data) {
    
    
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Result(Integer code, String msg) {
    
    
        this.code = code;
        this.msg = msg;
    }

    public static Result error(Integer code, String msg){
    
    
        return new Result(code, msg);
    }
}





/**
 * @Author Xxx
 * @Date 2021/11/26 18:07
 * @Version 1.0
 */
@Component
@Slf4j
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    
    

    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
    
    
        // getRule() 资源 规则的详细信息
        log.info("MyBlockExceptionHandler => {}",e.getRule());

        Result r = null;

        if(e instanceof FlowException){
    
    
            r = Result.error(100, "接口被限流了");

        }else if(e instanceof DegradeException){
    
    
            r = Result.error(101, "服务被降级了");

        }else if(e instanceof ParamFlowException){
    
    
            r = Result.error(102, "热点参数限流了");

        }else if(e instanceof SystemBlockException){
    
    
            r = Result.error(103, "触发系统保护规则了");

        }else if(e instanceof AuthorityException){
    
    
            r = Result.error(104, "授权规则不通过");

        }
        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(httpServletResponse.getWriter(), r);
    }

}


/**
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    
    

    @RequestMapping("/add")
    public String add(){
    
    
        log.debug("下单成功!");
        return "下单成功!" ;
    }

}

添加流控规则后测试
使用了@SentinelResource注解的资源BlockExceptionHandler将无法拦截

4.7. 流控模式

基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。

4.7.1. 直接

资源调用达到设置的阈值后直接被流控抛出异常

4.7.2. 关联

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db和write_db 这两个资源分别代表数据库读写,我们可以给read_db设置限流规则来达到写优先的目的:设置strategy为RuleConstant.STRATEGY_ RELATE同时设置refResource为write_db。 这样当写库操作过于频繁时,读数据的请求会被限流。

添加代码

/**
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
    
    

    @RequestMapping("/add")
    public String add(){
    
    
        log.debug("下单成功!");
        return "下单成功!" ;
    }

    @RequestMapping("/get")
    public String get(){
    
    
        log.debug("查询订单!");
        return "查询订单!" ;
    }
}

在这里插入图片描述

4.7.3. 链路

根据调用链路入口限流。
NodeSelectorSlot中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。 这棵树的根节点是一个名字为machine-root的虚拟节点,调用链的入口都是这个虚节点的子节点。

添加代码


public interface IOrderService {
    
    

    String getUser();
}



/**
 * @Author Xxx
 * @Date 2021/11/26 21:13
 * @Version 1.0
 */
@Service
public class OrderServiceImpl implements IOrderService {
    
    

    @SentinelResource(value = "getUser")
    public String getUser() {
    
    
        return "查询用户!";
    }

}



/**
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
    
    

    @Autowired
    private IOrderService orderService;

    @RequestMapping("/test1")
    public String test1(){
    
    
        return orderService.getUser();
    }

    @RequestMapping("/test2")
    public String test2(){
    
    
        return orderService.getUser();
    }
}

在这里插入图片描述
测试会发现链路规则不生效

注意,高版本此功能直接使用不生效,如何解决?
从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0版本开始(对应SCA的2.1.1.RELEASE), 官方在CommonFilter引了WEB_CONTEXT_UNIFY参数,用于控制是否收敛context.将其配置为false 即可根据不同的URL进行链路限流。
SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。
目前官方还未发布SCA 2.1.2.RELEASE,所以我们只能使用2.1.1.RELEASE, 需要写代码的形式实

spring:
	cloud:
		sentinel:
			web-context-unify: false #默认为true 将调用链路收敛

测试,仅在此场景全局异常处理器拦截不到BlockException, 对应@SentinelResource指定的资源必须在@SentinelResource注解中指定blockHandler处理BlockException
总结:为了解决链路规则引入ComonFilter的方式,除了此处问题,还会导致更多的问题,不建议使用ComonFilter的方式。 流控链路模式的问题等待官方后续修复,或者使用AHAS。

4.8. 熔断策略

4.8.1. 慢调用比例

慢调用比例(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT (即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
在这里插入图片描述

// 模拟慢调用接口
@RequestMapping("/flowThread")
public String flowThread() throws InterruptedException {
    
    
    TimeUnit.SECONDS.sleep(2);
    return "正常访问";
}

4.8.2. 异常比例

异常比例(ERROR RTIO):当单位统计时长(statIntervalMs) 内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0 - 1.0],代表0% - 100%。
在这里插入图片描述

// 模拟异常接口
@RequestMapping("/err")
public String err()  {
    
    
    int a = 1 / 0;
    return "err";
}

4.8.3. 异常数

异常数(ERROR COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对Sentinel限流降级本身的异常(BlockException) 不生效。
在这里插入图片描述

// 模拟异常接口
@RequestMapping("/err")
public String err()  {
    
    
    int a = 1 / 0;
    return "err";
}

4.9. 热点参数流控

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
在这里插入图片描述

4.9.1. 配置一个热点流控接口

在这里插入图片描述

/**
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
    
    

    // 测试热点参数流控接口,这种流控只能使用@SentinelResource来实现
    @RequestMapping("/get/{id}")
    @SentinelResource(value = "getById", blockHandler = "HotBlockHandle")
    public String getById(@PathVariable("id") int id){
    
    
        return "正常访问";
    }

    public String HotBlockHandle(@PathVariable("id") int id, BlockException ex){
    
    
        return "热点参数流控!";
    }

}

4.10. 系统保护规则

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

  • Load自适应(仅对Linux/Unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量由系统的maxQps * minRt 估算得出。设定参考值一般是CPU cores * 2.5。
    https://www.cnblogs.com/gentlemanhai/p/8484839.html
  • CPU usage (1.5.0+ 版本):当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
  • 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。

5. Openfeign 整合 Sentinel

5.1. 第一步

添加依赖

<!--openfeign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!--sentinel依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

5.2. 第二步

application.yml添加配置项

spring:
  cloud:
    # 参考 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc#%E5%85%B3%E4%BA%8E-nacos-discovery-starter-%E6%9B%B4%E5%A4%9A%E7%9A%84%E9%85%8D%E7%BD%AE%E9%A1%B9%E4%BF%A1%E6%81%AF
    nacos:
      discovery:
        # Nacos Server 启动监听的ip地址和端口
        server-addr: 127.0.0.1:8848
        # 注册的服务名
        service: order-openfeign-sentinel
        # 命名空间ID,Nacos通过不同的命名空间来区分不同的环境,进行数据隔离,服务消费时只能消费到对应命名空间下的服务。
        namespace: public
        # nacos服务的账号
        username: nacos
        # nacos服务的密码
        password: nacos
feign:
  sentinel:
    # feign整合sentinel 默认值为false
    enabled: true

5.3. 第三步

添加代码

/**
 * spring启动类
 * @Author Xxx
 * @Date 2021/11/18 17:17
 * @Version 1.0
 */
@SpringBootApplication
//@EnableDiscoveryClient
@EnableFeignClients
public class OrderOpenfeignSentinelApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run( OrderOpenfeignSentinelApplication.class, args );
    }

}





// OpenFeign接口 fallback用于熔断
// name = 服务名称 / path = Controller类ReqeustMapper
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {
    
    

  // 声明需要调用的rest接口对应的方法
  @RequestMapping("/reduct2")
  public String reduct2();

}





/**
 * 熔断处理类,fallback指定的类必须实现@FeignClient标记的接口
 * @Author Xxx
 * @Date 2021/11/29 15:49
 * @Version 1.0
 */
@Component
public class StockFeignServiceFallback implements StockFeignService {
    
    

    public String reduct2() {
    
    
        return "降级了!";
    }

}





/**
 * 接口
 * @Author Xxx
 * @Date 2021/11/18 17:10
 * @Version 1.0
 */
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
    
    

    // 第三步、注入feign接口,并调用方法
    @Resource
    private StockFeignService stockFeignService;

    @RequestMapping("/add")
    public String add(){
    
    
        log.debug("下单成功!");

        String result = stockFeignService.reduct2();
        return "hello feign!" + result ;
    }
}

stock-service服务controller代码


/**
 * @Author Xxx
 * @Date 2021/11/18 17:14
 * @Version 1.0
 */
@RestController
@RequestMapping("/stock")
@Slf4j
public class StockController {
    
    

    @Value("${server.port}")
    private String port;

    @RequestMapping("/reduct2")
    // 用于测试Openfeign整合Sentinel
    public String reduct2(){
    
    
        int a = 1 / 0;
        return "扣减库存成功!端口:" + port;
    }

}
如此一来就能对远程服务调用的异常来进行降级,可以尝试修改feign.sentinel.enabled=false/true来测试

6. 规则持久化

在这里插入图片描述

6.1. 第一步

添加依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!--sentinel规则持久化依赖-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

6.2. 第二步

添加application.yml

spring:
  application:
    name: order-sentinel
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8858
      web-context-unify: true #默认为true 将调用链路收敛
      # 持久化规则配置 参考https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
      datasource:
        flow-rule: #可以自定义
          nacos:
            server-addr: 127.0.0.1:8848
            # nacos服务的账号
            username: nacos
            # nacos服务的密码
            password: nacos
            data-id: order-sentinel-flow-rule
            rule-type: flow
    # 参考 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-docs/src/main/asciidoc-zh/nacos-discovery.adoc#%E5%85%B3%E4%BA%8E-nacos-discovery-starter-%E6%9B%B4%E5%A4%9A%E7%9A%84%E9%85%8D%E7%BD%AE%E9%A1%B9%E4%BF%A1%E6%81%AF
    nacos:
      discovery:
        # Nacos Server 启动监听的ip地址和端口
        server-addr: 127.0.0.1:8848
        # nacos服务的账号
        username: nacos
        # nacos服务的密码
        password: nacos

7. 代码下载

https://download.csdn.net/download/qq_42017523/52033780

猜你喜欢

转载自blog.csdn.net/qq_42017523/article/details/121519054