- 一、Sentinel 概述
- 1、Sentinel 的主要特性
- 2、Sentinel 分为两个部分
- 3、启动 Sentinel 控制台
- 二、SpringCloud 生成 sentinel 客户端
- 1、启动 Sentinel 客户端
- 2、nacos 控制台
- 3、Sentinel 控制台
- 三、接入限流埋点
- 三、模拟高并发
- 四、URL 流控规则
- 1、sentinel 流控测试
- 2、自定义限流处理逻辑
- ①、CustomUrlBlockHandler 覆盖默认处理类 DefaultBlockExceptionHandler 实现自定义限流处理逻辑
- ②、使用 @SentinelResource 注解实现自定义限流处理逻辑
- 3、sentinel 动态数据源配置
- 4、限流和熔断降级同时满足
- 五、熔断降级配置
- 1、熔断降级说明
- 2、慢调用比例
- 3、异常比例
- 4、异常数
- 五、Feign 支持、RestTemplate 支持
- 六、Endpoint 信息查看
该文章主要介绍 Sentinel 概念、 springcloud 对 Sentinel 的简单使用
官方网站:https://github.com/alibaba/Sentinel/wiki/控制台
Sentinel 是面向云原生微服务的流量监控,熔断降级组件。能够监控并保护你的微服务。
1、Sentinel 的主要特性 2、Sentinel 分为两个部分- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器
控制台简介以及安装启动:https://github.com/alibaba/Sentinel/wiki/控制台
jar 启动下载地址:https://github.com/alibaba/Sentinel/releases
源码构建:使用 maven 命令 mvn clean package
构建 Sentinel 源码
启动控制台:java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080。Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel
控制台其他可配置参数
配置项 | 类型 | 默认值 | 最小值 | 描述 |
---|---|---|---|---|
auth.enabled | boolean | true | - | 是否开启登录鉴权,仅用于日常测试,生产上不建议关闭 |
sentinel.dashboard.auth.username | String | sentinel | - | 登录控制台的用户名,默认为 sentinel |
sentinel.dashboard.auth.password | String | sentinel | - | 登录控制台的密码,默认为 sentinel |
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 |
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
sentinel.dashboard.autoRemoveMachineMillis | Integer | 0 | 300000 | 距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
server.servlet.session.cookie.name | String | sentinel_dashboard_cookie | - | 控制台应用的 cookie 名称,可单独设置避免同一域名下 cookie 名冲突 |
jar 包启动命令(云服务器需要放开端口号,window cmd 换行可以使用 ^)
java -Dserver.port=28080\
-Dcsp.sentinel.dashboard.server=localhost:28080\
-Dproject.name=sentinel-dashboard\
-Dauth.enabled=true\
-Dsentinel.dashboard.auth.username=sentinel\
-Dsentinel.dashboard.auth.password=123456\
-Dserver.servlet.session.timeout=7200\
-jar sentinel-dashboard-1.8.4.jar
访问:http://127.0.0.1:28080/ ⭐ 用户名:密码 🌙 sentinel:123456
pom.xml 依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
bootstrao.yaml
spring:
cloud:
sentinel:
enabled: true
transport:
port: 8723
dashboard: 127.0.0.1:28080
clientIp: 127.0.0.1
log:
dir: I:\nacos集群\boot-30001-sentinel
启动三个 SPRING-BOOT-SERVICE-DISCOVERY-CONSUMER 服务,配置 Sentinel 客户端端口为 8721、8722、8723
2、nacos 控制台 3、Sentinel 控制台如果在控制台没有找到应用,调用一下进行了 Sentinel 埋点的 URL 或方法,因为 Sentinel 使用了 lazy load 策略
- HTTP 埋点:Sentinel starter 默认为所有的 HTTP 服务提供了限流埋点,如果只想对 HTTP 服务进行限流,那么只需要引入依赖,无需修改代码。(简单说就是所有的 controller 层接口默认提供限流埋点)
- 自定义埋点:如果需要对某个特定的方法进行限流或降级,可以通过 @SentinelResource 注解来完成限流的埋点,示例代码如下:
@SentinelResource("resource") public String hello() { return "Hello"; }
该方法的作用,使用定时任务每秒发送 0~100 随机个请求,其中 127.0.0.1:20004
为服务消费者,使用 OpenFeign 调用 127.0.0.1:10001、127.0.0.1:20001、127.0.0.1:30001
三个服务提供者,将每秒发送请求数保存在 count.log 文件
public static void main(String[] args) throws IOException, InterruptedException {
String url = String.format("http://127.0.0.1:20004/hystrix/calculate?a=%s&b=%s&type=%s", 10, 5, "*");
CloseableHttpClient client = HttpClients.createDefault();
ExecutorService executorService = new ThreadPoolExecutor(150, 200, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
Timer timer = new Timer();
String path = TestQPS.class.getResource("/").getPath();
path = path.substring(0, path.indexOf("target")) + "/log/count.log";
BufferedWriter writer = new BufferedWriter(new FileWriter(path));
timer.schedule(new TimerTask() {
@SneakyThrows
@Override
public void run() {
int i = new Random().nextInt(100);
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis());
writer.write(date + ":\t" + i + "\n");
for (int j = 0; j < i; j++) {
executorService.execute(() -> {
try {
CloseableHttpResponse response = client.execute(new HttpGet(url));
String result = EntityUtils.toString(response.getEntity()) + "\n";
if (response.getStatusLine().getStatusCode() == 500) {
writer.write(result);
}
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
});
}
writer.flush();
}
}, 10, 1000);
}
查看 Sentinel 控制台实时监控
点击流控规则,选择 127.0.0.1:8722 客户端,点击新增流控规则,资源名填写需要限流的 URL 相对路径,单机阈值选择需要限流的阈值,点击新增进行确认
- 如果没使用 @SentinelResource 注解,则资源名为 url 地址
- 如果使用 @SentinelResource 注解,则资源名为 @SentinelResource 注解指定的名字
调用流控配置的资源http://127.0.0.1:20001/discovery/rule,当 QPS 超过 1 时,可以看到限流效果如下
限流默认处理类 DefaultBlockExceptionHandler
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
public DefaultBlockExceptionHandler() {
}
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
PrintWriter out = response.getWriter();
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
}
2、自定义限流处理逻辑
源码解析可参考:https://blog.csdn.net/LeoHan163/article/details/121865833
自定义限流处理逻辑可以有两种实现方式
- 方式一:新增 BlockExceptionHandler 实现类覆盖默认的 CustomUrlBlockHandler
- 方式二:使用 @SentinelResource 注解的 blockHandler 和 blockHandlerClass 两个参数指定限流处理逻辑方法
CustomUrlBlockHandler 实现类
@Component
public class CustomUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = null;
if (e instanceof FlowException) {
msg = "限流了,请稍后访问";
} else if (e instanceof DegradeException) {
msg = "降级了,返回默认数据";
} else if (e instanceof ParamFlowException) {
msg = "热点参数限流";
} else if (e instanceof SystemBlockException) {
msg = "系统规则(负载/...不满足要求)";
} else if (e instanceof AuthorityException) {
msg = "授权规则不通过";
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", msg);
// 返回默认数据
Map<Object, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("address", "西安");
jsonObject.put("mock", map);
response.getWriter().write(jsonObject);
}
}
Sentinel 控制台新增限流规则
访问测试当 QPS 大于 1,触发限流规则
@SentinelResource 注解参数详细解释参考:https://github.com/alibaba/Sentinel/wiki/注解支持
-
blockHandler必须有值,如果没配置,那么不执行 blockHandler,如果配置 blockHandler 也配置了blockHandlerClass,那么会从 blockHandlerClass 数组的第一个元素作为待执行的类;
-
如果配置 blockHandler 但是没有配置 blockHandlerClass,那么取当前执行方法的类为待执行类。
-
如果待执行类不是当前类,那么方法必须是一个静态方法,且方法名与 blockHandler 配置一致;如果待执行类的是当前类,那么方法静态或非静态方法都可以;
-
方法的返回值必须与 SentinelResource 注解的 blockHandler 方法返回值一样;
-
参数方面, blockHandler 方法比 SentinelResource 注解的方法的参数多一个,最后一个参数必须是 BlockException 类型,前面的参数必须与 SentinelResource 注解的方法参数完全一致。
总结:简单理解上图, blockHandler 判断是否有 BlockException,没有则进入你写的控制层;handleFallback 判断进入你写控制层抛出异常时如何处理
blockHandler 处理方法在当前类
@GetMapping(value = "/json", consumes = MediaType.APPLICATION_JSON_VALUE)
@SentinelResource(value = "json", blockHandler = "jsonHandleException")
public String selectStudent(@RequestBody Student student) {
return JSONObject.toJSONString(student);
}
public String jsonHandleException(@RequestBody Student student, BlockException ex) {
System.out.println("Oops: " + ex.getClass().getCanonicalName());
JSONObject jsonObject = new JSONObject();
jsonObject.put("param", student);
jsonObject.put("message", ex.getMessage());
jsonObject.put("exception", ex.getClass().getCanonicalName());
return JSONObject.toJSONString(jsonObject);
}
blockHandler 处理方法非当前类
@RestController
@RequestMapping("/discovery")
public class DiscoveryController {
@GetMapping(value = "/json", consumes = MediaType.APPLICATION_JSON_VALUE)
@SentinelResource(value = "json", blockHandler = "jsonHandleException", blockHandlerClass = ExceptionUtil.class)
public String selectStudent(@RequestBody Student student) {
return JSONObject.toJSONString(student);
}
}
public class ExceptionUtil {
public static String jsonHandleException(@RequestBody Student student, BlockException ex) {
System.out.println("Oops: " + ex.getClass().getCanonicalName());
JSONObject jsonObject = new JSONObject();
jsonObject.put("param", student);
jsonObject.put("message", ex.getMessage());
jsonObject.put("exception", ex.getClass().getCanonicalName());
return JSONObject.toJSONString(jsonObject);
}
}
如果说没有配置任何blockHandler处理逻辑,那么将走handleFallback逻辑。
3、sentinel 动态数据源配置sentinel 动态数据源配置,可解决服务关闭流控规则消失,sentinel 支持如下 6 种数据持久化方式
nacos 持久化, 参考:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
导入 nacos 数据源依赖
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
bootstrap.yaml 添加配置,参数详细解释参考上面的官方地址
spring:
cloud:
sentinel:
datasource:
ds:
nacos:
server-addr: 42.193.0.90:8848
username: nacos
password: nacos
data-id: sentinel-127-20001
group-id: DEFAULT_GROUP
namespace: 2022-4-24-sentinel
data-type: json
rule-type: flow
nacos 添加 sentinel 限流配置
配置成功重启服务限流规则依然存在,整合了Nacos做规则存储之后
- ⭐ Sentinel 控制台中修改规则:仅存在于服务的内存中,不会修改 Nacos 中的配置值,服务重启后恢复 Nacos 中配置。
- ⭐ Nacos 控制台中修改规则:服务的内存中规则会更新,Nacos中持久化规则也会更新,重启后依然保持。
@SentinelResource 配置 blockHandler 和 fallback
@RestController
@RequestMapping("/hystrix")
public class HystrixController {
/*
* 如果代码抛出 BlockException 异常则执行 ExceptionUtil 类的 calculateHandleException 方法并 return
* 如果代码未抛出 BlockException 异常则执行 FallbackUtil 类的 calculateFallback 方法并退出
*/
@GetMapping("/calculate")
@ApiAnnotation
@SentinelResource(value = "calculate", blockHandler = "calculateHandleException",blockHandlerClass = ExceptionUtil.class,fallbackClass = FallbackUtil.class,fallback = "calculateFallback")
public JSONObject calculate(String a, String b, String type) {
int result = 0;
int num1 = Integer.parseInt(a);
int num2 = Integer.parseInt(b);
switch (type) {
case "+":result = num1 + num2;break;
case "-":result = num1 - num2;break;
case "*":result = num1 * num2;break;
case "/":result = num1 / num2;break;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("result",result);
return jsonObject;
}
}
BlockException 异常处理逻辑
@Component
public class ExceptionUtil {
public static JSONObject calculateHandleException(String a, String b, String type, BlockException ex) {
JSONObject jsonObject = new JSONObject();
String msg="";
if (ex instanceof FlowException) {
msg = "限流了";
} else if (ex instanceof DegradeException) {
msg = "降级了";
}
jsonObject.put("msg", msg);
jsonObject.put("异常处理方法", "com.ye.util.ExceptionUtil.calculateHandleException()");
jsonObject.put("exception", ex.getClass().getCanonicalName());
return jsonObject;
}
}
calculateFallback 处理逻辑
public class FallbackUtil {
public static JSONObject calculateFallback(String a, String b, String type, Throwable throwable) {
JSONObject jsonObject = new JSONObject();
if (throwable instanceof ArithmeticException) {
jsonObject.put("fallback", "运算异常,calculateFallback");
jsonObject.put("throwable", throwable.getClass().getCanonicalName());
jsonObject.put("message", throwable.getMessage());
}
return jsonObject;
}
}
测试接口: http://127.0.0.1:20001/hystrix/calculate?a=10&b=0&type=/ 从传递参数看出 10/0 会抛出算书异常
-
QPS 等于 1 走 fallback
-
QPS 大于 1,走 blockHandler, 限流和熔断降级同时满足,优先限流
Sentinel 提供以下几种熔断策略
- 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
- 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
熔断降级规则(DegradeRule)包含下面几个重要的属性
sentinel 熔断规则 | Field | 说明 | 默认值 |
---|---|---|---|
资源名 | resource | 资源名,即规则的作用对象 | |
熔断策略 | grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
最大 RT | count | 慢调用比例模式下为慢调用临界 RT(响应超出该值计为慢调用,单位为 ms);异常比例/异常数模式下为对应的阈值 | |
熔断时长 | timeWindow | 熔断时长,单位为 s( 在这段时间内发生熔断、拒绝所有请求) | |
统计时长 | statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
比例阈值 | slowRatioThreshold | 范围:[0~1] ,慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入), 异常比例=发生异常的请求数÷请求总数 | |
最小请求数 | minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)(允许通过的最小请求数,在该数量内不发生熔断) | 5 |
如果使用了 @SentinelResource 指定了埋点的名字,则资源名为指定的名字,否则为 servletPath(即 URL 端口号后面的地址)
接口: http://127.0.0.1:20001/hystrix/calculate?a=10&b=5&type=/,该接口使用 @SentinelResource(value = “calculate”)指定
calculate 慢调用比例配置说明:统计 5000 ms 内,如果请求 /hystrix/calculate 接口(资源名对应)次数大于 1时,如果 90% 响应时长超过 1000 ms,则后续请求熔断 10 s,经过10s 熔断,如果下一个请求依然响应时长超过 1000 ms,则再熔断 10s
使用 TimeUnit.SECONDS.sleep(1) 睡眠测试或者程序打断点模拟请求超时
/hystrix/calculate
接口使用 @SentinelResource 自定义异常处理
@SentinelResource(value = "calculate", blockHandler = "calculateHandleException",blockHandlerClass = ExceptionUtil.class,fallbackClass = FallbackUtil.class,fallback = "calculateFallback")
public JSONObject calculate(String a, String b, String type) throws InterruptedException {}
测试接口
如果不使用 @SentinelResource 指定异常处理,并且没有覆盖默认异常处理类 DefaultBlockExceptionHandler 则返回结果:Blocked by Sentinel (flow limiting)
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
public DefaultBlockExceptionHandler() {
}
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
PrintWriter out = response.getWriter();
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
}
自定义阻塞异常处理类 DiyBlockHandler.java 覆盖默认的处理类 DefaultBlockExceptionHandler
@Component
public class DiyBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = null;
if (e instanceof FlowException) {
msg = "限流了,请稍后访问";
} else if (e instanceof DegradeException) {
msg = "降级了,返回默认数据";
} else if (e instanceof ParamFlowException) {
msg = "热点参数限流";
} else if (e instanceof SystemBlockException) {
msg = "系统规则(负载/...不满足要求)";
} else if (e instanceof AuthorityException) {
msg = "授权规则不通过";
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", msg);
// 返回默认数据
Map<Object, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("address", "西安");
jsonObject.put("mock", map);
response.getWriter().write(jsonObject);
}
}
3、异常比例
接口: http://127.0.0.1:20001/hystrix/calculate?a=10&b=5&type=/,该接口使用 @SentinelResource(value = “calculate”)指定,该接口传递的参数会有一个算术异常
calculate 异常比例配置说明:统计 5000 ms 内,如果请求 /hystrix/calculate 接口(资源名对应)次数大于 1时,如果超过 20% 请求抛出异常,则后续请求熔断 10 s,经过10s 熔断,如果下一个请求依然失败抛出异常,则再熔断 10s
接口: http://127.0.0.1:20001/hystrix/calculate?a=10&b=5&type=/,该接口使用 @SentinelResource(value = “calculate”)指定,该接口传递的参数会有一个算术异常
calculate 异常比例配置说明:统计 5000 ms 内,如果请求 /hystrix/calculate 接口(资源名对应)次数大于 2 时,如果抛出异常数大于 2,则后续请求熔断 10 s,经过10s 熔断,如果下一个请求依然失败抛出异常,则再熔断 10s
Feign 支持、RestTemplate 支持参考官方说明:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
Feign 支持
Feign 服务降级是服务级别的,Sentinel 服务降级方法级别的支持细粒度
RestTemplate 支持
- @SentinelRestTemplate 注解的属性支持限流(blockHandler, blockHandlerClass)和降级(fallback, fallbackClass)的处理。
- blockHandler 或 fallback 属性对应的方法必须是对应 blockHandlerClass 或 fallbackClass 属性中的静态方法。
- RestTemplate 调用被 Sentinel 熔断后,会返回 RestTemplate request block by sentinel 信息,或者也可以编写对应的方法自行处理返回信息。这里提供了 SentinelClientHttpResponse 用于构造返回信息
Sentinel Endpoint 里暴露的信息非常有用。包括当前应用的所有规则信息、日志目录、当前实例的 IP,Sentinel Dashboard 地址,Block Page,应用与 Sentinel Dashboard 的心跳频率等等信息。
Spring Boot 应用支持通过 Endpoint 来暴露相关信息,Sentinel Starter 也支持这一点。
在使用之前需要在 Maven 中添加 spring-boot-starter-actuator依赖,并在配置中允许 Endpoints 的访问。
- Spring Boot 1.x 中添加配置 management.security.enabled=false
- Spring Boot 2.x 中添加配置 management.endpoints.web.exposure.include=* (yaml 文件 * 需要带引号)
Spring Boot 1.x 可以通过访问 http://127.0.0.1:20001/sentinel 来查看 Sentinel Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:20001/actuator/sentinel 来访问。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)