Sentinel实战
一、Sentinel简介
Sentinel是阿里开源的面向服务流量治理的框架,官方原文是Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel有两个重要的基本概念:
资源
资源就是需要进行流量管理的事物,可以是服务名也可以是接口地址URL等,如果你想根据某个接口进行限流,那资源就是该接口。
规则
规则就是进行流量管理的规则,比如用哪种限流算法,是根据QPS限流还是根据线程数限流等。
更多可以查看官方介绍https://sentinelguard.io/zh-cn/docs/introduction.html。
二、需求背景
微服务网关一般都会有限流的功能,防止后端服务被瞬时超高流量击垮,刚好之前开源的ship-gate网关支持自定义插件,那么就可以通过给ship-gate增加限流功能来学习Sentinel。
完整需求如下:
ship-gate需要支持服务(如订单服务)维度的限流,限流方式包括根据QPS和线程数这两种,本次改造内容全部在ship-server子模块。
三、编码实现
ship-server是基于Spring WebFlux实现的,Sentinel提供的有开源框架的适配sentinel-spring-webflux-adapter模块,但是看了下其实现与现有的设计不兼容,于是决定用原生的核心库sentinel-core。
同时ship-gate也需要Sentinel的控制台功能监控限流情况,所以pom文件需要引入以下依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.6</version>
</dependency>
- 配置类增加限流配置
/**
* Created by 2YSP on 2020/12/27
*/
@ConfigurationProperties(prefix = "ship.gate")
public class ServerConfigProperties {
/**
* 负载均衡算法,默认轮询
*/
private String loadBalance = LoadBalanceConstants.ROUND;
/**
* 网关超时时间,默认3s
*/
private Long timeOutMillis = 3000L;
/**
* 缓存刷新间隔,默认10s
*/
private Long cacheRefreshInterval = 10L;
/**
* 限流方式QPS或THREAD,默认QPS
*/
private String rateLimitType = "QPS";
/**
* 限流数量
*/
private Integer rateLimitCount;
// 省略getter setter方法
}
ShipPluginEnum增加对应枚举
public enum ShipPluginEnum {
/**
* DynamicRoute
*/
DYNAMIC_ROUTE("DynamicRoute", 2, "动态路由插件"),
/**
* Auth
*/
AUTH("Auth", 1, "鉴权插件"),
RATE_LIMIT("RateLimit", 0, "限流插件");
private String name;
private Integer order;
private String desc;
ShipPluginEnum(String name, Integer order, String desc) {
this.name = name;
this.order = order;
this.desc = desc;
}
// 省略getter方法
}
注意: order越小越先执行(与Spring规则一致),限流插件最先执行所以定义为0
2.新建RateLimitPlugin类,继承AbstractShipPlugin
/**
* @Author: Ship
* @Description:
* @Date: Created in 2020/12/29
*/
public class RateLimitPlugin extends AbstractShipPlugin {
private final Logger logger = LoggerFactory.getLogger(RateLimitPlugin.class);
private static Map<String, Integer> rateLimitTypeMap = new HashMap<>();
static {
rateLimitTypeMap.put(ServerConstants.LIMIT_BY_QPS, RuleConstant.FLOW_GRADE_QPS);
rateLimitTypeMap.put(ServerConstants.LIMIT_BY_THREAD, RuleConstant.FLOW_GRADE_THREAD);
}
public RateLimitPlugin(ServerConfigProperties properties) {
super(properties);
}
@Override
public Integer order() {
return ShipPluginEnum.RATE_LIMIT.getOrder();
}
@Override
public String name() {
return ShipPluginEnum.RATE_LIMIT.getName();
}
@Override
public Mono<Void> execute(ServerWebExchange exchange, PluginChain pluginChain) {
String appName = pluginChain.getAppName();
initFlowRules(appName);
if (SphO.entry(appName)) {
// 务必保证finally会被执行
try {
/**
* 被保护的业务逻辑
*/
return pluginChain.execute(exchange, pluginChain);
} finally {
SphO.exit();
}
}
throw new ShipException(ShipExceptionEnum.REQUEST_LIMIT_ERROR);
}
private void initFlowRules(String resource) {
Assert.hasText(properties.getRateLimitType(), "config ship.gate.rateLimitType required!");
Assert.notNull(properties.getRateLimitCount(), "config ship.gate.rateLimitCount required!");
List<FlowRule> list = new ArrayList<>();
FlowRule flowRule = new FlowRule();
flowRule.setResource(resource);
flowRule.setGrade(rateLimitTypeMap.get(properties.getRateLimitType()));
flowRule.setCount(properties.getRateLimitCount().doubleValue());
list.add(flowRule);
FlowRuleManager.loadRules(list);
}
}
因为如果请求被限流了,RateLimitPlugin会抛出异常,为了处理这种异常需要增加全局异常处理配置。
类似SpringMVC框架,只需要实现WebExceptionHandler接口,然后在配置类注册对应的bean即可。
ShipExceptionHandler
public class ShipExceptionHandler implements WebExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(ShipExceptionHandler.class);
@Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
logger.error("ship server exception msg:{}", throwable.getMessage());
if (throwable instanceof ShipException) {
ShipException shipException = (ShipException) throwable;
return ShipResponseUtil.doResponse(serverWebExchange, new ApiResult(shipException.getCode(), shipException.getErrMsg()));
}
String errorMsg = "system error";
if (throwable instanceof IllegalArgumentException) {
errorMsg = throwable.getMessage();
}
return ShipResponseUtil.doResponse(serverWebExchange, new ApiResult(5000, errorMsg));
}
}
配置类WebConfig
/**
* @Author: Ship
* @Description:
* @Date: Created in 2020/12/25
*/
@Configuration
@EnableWebFlux
@EnableConfigurationProperties(ServerConfigProperties.class)
public class WebConfig {
@Bean
public PluginFilter pluginFilter(@Autowired ServerConfigProperties properties) {
return new PluginFilter(properties);
}
/**
* set order -2 to before DefaultErrorWebExceptionHandler(-1) ResponseStatusExceptionHandler(0)
* @return
*/
@Order(-2)
@Bean
public ShipExceptionHandler shipExceptionHandler(){
return new ShipExceptionHandler();
}
}
注意: 这里踩了一个小坑,开始发现自己写的WebExceptionHandler不生效,请求错误响应还是原来的格式,后来发现需要添加@order注解指定bean注入的优先级比默认的小。
- 插件责任链注册插件RateLimitPlugin
/**
* @Author: Ship
* @Description:
* @Date: Created in 2020/12/25
*/
public class PluginFilter implements WebFilter {
private ServerConfigProperties properties;
public PluginFilter(ServerConfigProperties properties) {
this.properties = properties;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String appName = parseAppName(exchange);
if (CollectionUtils.isEmpty(ServiceCache.getAllInstances(appName))) {
throw new ShipException(ShipExceptionEnum.SERVICE_NOT_FIND);
}
PluginChain pluginChain = new PluginChain(properties, appName);
pluginChain.addPlugin(new DynamicRoutePlugin(properties));
pluginChain.addPlugin(new AuthPlugin(properties));
pluginChain.addPlugin(new RateLimitPlugin(properties));
return pluginChain.execute(exchange, pluginChain);
}
private String parseAppName(ServerWebExchange exchange) {
RequestPath path = exchange.getRequest().getPath();
String appName = path.value().split("/")[1];
return appName;
}
}
最后,Sentinel控制台支持JVM参数和sentinel.properties文件两种配置方式,方便起见在resource目录增加sentinel.properties配置文件,内容如下:
project.name=ship-server
csp.sentinel.dashboard.server=127.0.0.1:8080
至此,代码已经写完了,下面进入测试环节。
四、测试总结
- 启动nacos
- 启动ship-admin,ship-gate-example工程
- 在t_plugin表插入限流插件
INSERT INTO `ship`.`t_plugin`(`id`, `name`, `code`, `description`, `created_time`) VALUES (3, '限流', 'RateLimit', '限流插件', '2023-04-16 11:06:39');
然后t_app_plugin表增加插件配置(插件管理这块暂时没有后台功能后面考虑增加)
通过nacos控制台查看实例详情,发现服务实例的插件配置已经有了。

- 在application.yml增加限流配置后就可以启动ship-server工程了
ship:
gate:
load-balance: round
time-out-millis: 3000
cache-refresh-interval: 10
rate-limit-type: QPS
rate-limit-count: 4
这里配置了QPS限制在4以内,相当于每秒最多4个请求通过。
官网下载sentinel-dashboard.jar(下载地址:https://github.com/alibaba/Sentinel/releases),然后
打开命令行输入java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar启动。游览器访问http://localhost:8080/#/login(默认账号密码都是sentinel),就可以看到后台界面了。

因为ship-server还没收到请求,所以左侧菜单栏开始不会显示配置的projectName,相当于懒加载的原理。使用wrk压测工具对接口进行压测,命令如下:
wrk -c 100 -t 20 -d 60s http://localhost:9000/order/user/test
IDEA控制台日志如下


第一张图说明触发了限流的保护机制,第二张图说明QPS确实被限制到了4以内,看日志打印的频率猜测用的是固定窗口计数器算法实现的,测试成功。
同时在Sentinel控制台也能看到监控数据

总结:
Sentinel还具备其他很强大的功能,需要慢慢摸索,Sentinel控制台有一个把配置推送到注册中心,然后服务监听流量规则配置的方案有时间看下怎么玩,如果觉得这篇文章对您有用希望可以点个赞让更多人看到。
参考文档:
https://sentinelguard.io/zh-cn/docs/dashboard.html
Sentinel实战的更多相关文章
- SpringCloudAlibaba分布式流量控制组件Sentinel实战与源码分析(上)
概述 定义 Sentinel官网地址 https://sentinelguard.io/zh-cn/index.html 最新版本v1.8.4 Sentinel官网文档地址 https://senti ...
- SpringCloudAlibaba分布式流量控制组件Sentinel实战与源码分析-中
实战示例 控制台初体验 Sentinel的控制台启动后,控制台页面的内容数据都是空的,接下来我们来逐步操作演示结合控制台的使用,在上一节也已说明整合SpringCloud Alibaba第一步先加入s ...
- asp.net core 实战之 redis 负载均衡和"高可用"实现
1.概述 分布式系统缓存已经变得不可或缺,本文主要阐述如何实现redis主从复制集群的负载均衡,以及 redis的"高可用"实现, 呵呵双引号的"高可用"并不是 ...
- net core 实战之 redis 负载均衡和"高可用"实现
net core 实战之 redis 负载均衡和"高可用"实现 1.概述 分布式系统缓存已经变得不可或缺,本文主要阐述如何实现redis主从复制集群的负载均衡,以及 redis的& ...
- 第六章· Redis高可用sentinel
sentinel介绍 sentinel实战及配置讲解 sentinel介绍 什么是sentinel? Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Mas ...
- SpringCloudGateway微服务网关实战与源码分析 - 中
实战 路由过滤器工厂 路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应.路由过滤器的作用域是特定的路由.SpringCloud Gateway包括许多内置的GatewayFilter ...
- Redis分布式篇
Redis分布式篇 1 为什么 需要 Redis 集群 1.1 为什么需要集群? 1.1.1 性能 Redis 本身的 QPS 已经很高了,但是如果在一些并发量非常高的情况下,性能还是会受到影响. ...
- 图灵学院JAVA互联网架构师专题学习笔记
图灵学院JAVA互联网架构师专题学习笔记 下载链接:链接: https://pan.baidu.com/s/1xbxDzmnQudnYtMt5Ce1ONQ 密码: fbdj如果失效联系v:itit11 ...
- Spring Boot 如何快速集成 Redis 哨兵?
上一篇:Spring Boot 如何快速集成 Redis? 前面的分享栈长介绍了如何使用 Spring Boot 快速集成 Redis,上一篇是单机版,也有粉丝留言说有没有 Redis Sentine ...
- Redis-sentinel 哨兵(HA)
Sentinel 介绍 Redis-Sentinel 是 Redis 官方推荐的高可用性(HA)解决方案,当用 Redis 做 Master-slave 的高可用方案时,假如Master 宕机了,Re ...
随机推荐
- layui.dtree的学习,自定义扩展toolbar按钮(toolbarExt)
学习layui.dtree请前往 http://www.wisdomelon.com/DTreeHelper/ 记录一下dtree的自定义扩展toolbar按钮(toolbarExt) html代码: ...
- Selenium显式、隐式等待
显式等待: 显式等待是你在代码中定义等待一定条件发生后再进一步执行你的代码.简单的说就是在指定时间内,一直等待某个条件成立,条件成立后立即执行定位元素的操作:如果超过这个时间条件仍然没有成立,则会抛出 ...
- 记录一次HAWQ手工启动
一.环境变量初始化 使用hawq的命令必须先进行环境变量的初始化 命令不能以 root 用户执行,应该以 gpadmin 用户执行 source /usr/local/apache-hawq/gree ...
- oneDNN
目录 oneDNN卷积思路 debug捆绑套路 jit_avx2_convolution_fwd_t::execute_forward( 整个文件oneDNN/src/cpu/x64/jit_avx2 ...
- MATLAB默认路径修改
笔者曾尝试在软件界面的"设置路径"或者Parallel中修改默认路径,但多次尝试均失败.后来经人提点,MATLAB默认文件夹路径可以在桌面图标属性中"起始位置" ...
- Docker部署NextCloud
docker run -d -p 80:80 nextcloud 数据库可以选Mysql或者Pg 下载客户端 https://nextcloud.com/
- 7&的2022年终总结
7&的2022年终总结 文章目录 7&的2022年终总结 1.前言 2.技术 3.生活 4.展望未来 博客搬家的需要: var code = "49d515c3-0238-4 ...
- asp.net core项目部署IIS
1.下载对应版本的webhost 我的是2.2 下载地址:https://download.visualstudio.microsoft.com/download/pr/ba001109-03c6-4 ...
- typescript 的动态引入组件
环境: Arco Pro + Vue3 vite自身对动态字符串形式的组件引入是有限制的, 以下写法会报错 官方文档中也对此有做说明, 只能通过固定形式去引用 以下形式不会报错, 但这种固定格式的局限 ...
- Vue2使用axios,request.js和vue.config.js
1.配置request.js,用来请求数据 import axios from 'axios' // 1:利用axios对象的方法create,创建一个axios实例 // 2:request就是ax ...