Sentinel 是由 Alibaba 开发的一个轻量级的 流量控制 和 熔断降级 的框架,主要用于保护微服务架构下的应用,避免因为高并发、系统异常等导致服务崩溃
1、理解Sentinel并初步使用
流量控制(Flow Control):限制某些资源的访问次数,以防止流量过大导致服务崩溃。比如限制某个接口的每秒请求次数(QPS),避免过高的并发请求压力。
熔断降级(Circuit Breaker):当某个服务或接口调用失败率过高时,自动进入熔断状态,停止请求,避免继续恶化系统状况。
实时监控与动态调整:Sentinel 提供了一个 Dashboard,用于实时查看流量情况、熔断状态、规则等,并支持动态调整流量控制规则,确保系统始终保持在安全的运行状态
1.1 Sentinel 的基本工作流程
Sentinel 通过定义 资源(Resource),并通过为资源设置 流量控制规则,来限制访问量和对服务的保护。
-
定义资源:你可以把每个微服务的接口或方法看作一个资源(例如,某个 REST API)。在 Sentinel 中,资源是需要被保护的单位。
-
定义流量控制规则:为每个资源设置流量控制规则(比如 QPS、并发数限制等)。当请求超过限制时,Sentinel 会进行降级或者流量控制处理。
-
请求到达时判断:当外部请求访问一个资源时,Sentinel 会检查该资源的流量控制规则。如果请求数超过阈值,Sentinel 会启动流量控制策略,如拒绝请求或执行降级逻辑。
-
流量控制/熔断:
- 流量控制:超出设定的流量限制时,Sentinel 会进行流量切断或拒绝。
- 熔断降级:当某个资源失败率过高时,Sentinel 会进行熔断,拒绝请求直到系统恢复。
-
实时监控:Sentinel 提供了一个 Dashboard,实时显示各个资源的流量、状态、规则、异常等信息,帮助开发者做出动态调整。
1.2 下载并启动sentinel
1.3 在两个微服务yml中都配置Sentinel 端口号
1.4 默认web接口当作资源,然后使用注解手动添加资源
1.5 启动项目,并访问createOrder
1.6 规则的解释
-
流控规则(流控):限制每秒请求数(QPS),例如在高并发情况下,可能会因为流量过大而限制访问。
-
熔断规则(熔断):当请求失败率达到某一阈值时,开启熔断,暂停或拒绝一些请求,避免系统过载。
-
热点规则(热点):针对请求中的热点数据(例如某个高频访问的资源或API),进行特殊的流控策略,以保证系统稳定。
-
授权规则(授权):确保只有经过授权的请求可以访问特定的资源或服务,防止未授权访问。
测试流控规则:
每秒只能一个这样的请求,否则限流失效,这是web接口异常默认的返回结果
2、Sentinel异常处理
也就是说,如果出现异常,我们要返回给前端json数据,所以要进行异常处理
类似于这样:
2.1 处理web接口异常(默认就是拦截器实现)
现在我们不使用默认的,重写异常拦截器
package com.zhenbang.exception;
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhenbang.common.AjaxResult;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import java.io.PrintWriter;
@Component // 只需要将异常放到容器中就能生效
public class MyBlockExceptionHandler implements BlockExceptionHandler {
ObjectMapper objectMapper = new ObjectMapper();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, String s, BlockException e) throws Exception {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
// too many requests
response.setStatus(429);
AjaxResult error = AjaxResult.error(500,"系统繁忙,请稍后再试:" + e.getClass());
writer.write(objectMapper.writeValueAsString(error));
writer.flush();
writer.close();
}
}
在模型层统一定义返回前端的对象
package com.zhenbang.common;
import lombok.Data;
@Data
public class AjaxResult {
private Integer code;
private String msg;
private Object data;
public static AjaxResult success(Object data) {
AjaxResult result = success();
result.setData(data);
return result;
}
public static AjaxResult success() {
AjaxResult result = new AjaxResult();
result.setCode(200);
result.setMsg("success");
return result;
}
public static AjaxResult success(Integer code, Object data, String msg) {
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
public static AjaxResult error() {
AjaxResult result = new AjaxResult();
result.setCode(500);
return result;
}
public static AjaxResult error(Integer code, String msg) {
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
异常就从原来的变成我们自定义的
- 微服务重启之后,又重新注册到boadrd上,所以要重新设置流控
2.2 处理@SentinelResource异常
默认:
第一步:
package com.zhenbang.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//@ControllerAdvice
//@ResponseBody
@RestControllerAdvice
public class GlobalExceptionHandler {
// @ExceptionHandler(Throwable.class)
// public String error(Throwable e) {
// return "";
// }
}
第二步:声明兜底处理
@SentinelResource(value = "createOrder",blockHandler="createOrderFallback") // 这个注解声明它是一个资源
@Override
public Order createOrder(Long productId, Long userId) {
Order oder = new Order();
oder.setId(1L);
// 获取产品信息
// Product productFromRemote = getProductFromRemote(productId);
Product productFromRemote = productFeignClient.getProductById(productId);
oder.setTotalAmount(productFromRemote.getPrice().multiply(new BigDecimal(productFromRemote.getNum())));
oder.setUserId(userId);
oder.setNickName("zhansan");
oder.setAddress("Beijing");
// TODO 远程查询商品列表
// List<Product> 数组变为List的API
oder.setProductList(Arrays.asList(productFromRemote));
return oder;
}
public Order createOrderFallback(Long productId, Long userId, BlockException e)
{
Order oder = new Order();
oder.setId(0L);
oder.setTotalAmount(new BigDecimal("0"));
oder.setUserId(0L);
oder.setNickName("未知");
oder.setAddress("异常信息"+e.getClass());
return oder;
}
2.3 openfeign远程调用异常处理
这个之前在05OpenFeign演示过,也是写一个兜底回调函数
OpenFeign回调
3、规则
3.1 流控规则
- QPS 与 并发线程数
- 并发线程数要配合线程池使用,效率低
流控模式中 有 直接、关联、链路
- 链路流控模式的使用
2.
3.新增
@GetMapping("/seckill/{productId}/{userId}")
Order seckillOrder(@PathVariable("productId") Long productId, @PathVariable("userId") Long userId){
Order order = orderService.createOrder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
- 关联流控模式
意思就是当readDb和writeDb都访问一个数据库,读关联写,当读高并发,没有问题,但是,当写多的时候的,读会被限制住
流控效果 快速失败、冷启动、匀速启动
续