第一节 使用介绍
sentinel gateway 集成 sentinel
sentinel gateway 如果需要使用sentinel来设置自定义限流规则, 需要添加以下依赖(不使用adapter的方式)
网关项目使用的依赖版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除默认 logging 实现 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--nacos discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 加入sentinel 持久化到nacos的数据源依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--zuul server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- Spring Boot log4j2 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Log4j2 异步支持 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>
<!--spring2.0 集成 redis 所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib-ext-spring</artifactId>
<version>1.0.2</version>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- spring-boot-configuration-processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 加入sentinel spring cloud版本的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 加入sentinel 与 spring cloud zuul的整合依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
</dependencies>
网关硬编码方式加载限流和api分组信息
对于网关如果整合sentinel, 我们可以去github上搜索sentinel, 找到对应的demo上借鉴, 点击访问github, 也可以去sentinel官网上查找, 使用git clone将源码克隆到本地, 分支我选择1.6.3的tag分支
限流自定义响应
如果请求被限流时, 默认会响应429的响应, 并提示当前已经限流, 如果需要自定义限流响应, 可以创建一个类, 并实现ZuulBlockFallbackProvider
接口, 并加载到ZuulBlockFallbackManager
, 如下
/**
* 登录限流异常响应提供器.
*
* @author Taylor.
* @version 1.0
* @since 2022-10-08
*/
public class LoginLimitFallbackProvider implements ZuulBlockFallbackProvider {
@Override
public String getRoute() {
// 指定该异常响应仅仅该资源名的异常使用
return "login-verification";
}
@Override
public BlockResponse fallbackResponse(String route, Throwable cause) {
return new BlockResponse(500, "限流啦小伙子", route);
}
}
底层将会调用BlockResponse的toString方法来输出到浏览器, 默认BlockResponse toString 输出以JSON的形式输出, 如果我们想要自定义输出格式, 我的方式是继承该BlockResponse, 并重写toString方法, 并且设置我们需要的属性变量, 如:
/**
* 限流响应实体.
*
* @author Taylor. you can send some prombles to <a href="taylor_xie@intretech.com">my email</a>.
* @version 1.0
* @since 2022-10-08
*/
public class MyBlockResponse<T> extends BlockResponse {
/**
* 响应数据
*/
private final T data;
private final Integer code;
public MyBlockResponse(int code, T data, String message, String route) {
super(HttpStatus.OK.value(), message, route);
this.data = data;
this.code = code;
}
@Override
public String toString() {
HttpResult<T> tHttpResult = new HttpResult<>();
tHttpResult.setCode(code);
tHttpResult.setData(data);
tHttpResult.setMsg(getMessage());
return JSON.toJSONString(tHttpResult);
}
}
这样的话调用toString就走我们自定义的方式啦~~
api 分组声明
如果我们需要针对某一个url进行限流, 可以使用api 分组声明资源, 通过demo可以看到, 我们是直接使用GatewayApiDefinitionManager.loadApiDefinitions
方法加载aip分组信息
如果需要对api分组的资源进行限流, 我们还需要使用FlowRuleManager.loadRules
加载限流信息, 加载过程放置在spring容器类的构造函数之后加载配置文件,示例如下
/**
* sentinel相关配置类.
*
* @author Taylor.
* @version 1.0
* @since 2022-10-08
*/
@Configuration
public class SentinelConfiguration {
@PostConstruct
void init() {
// 初始化api分组信息
// 初始化限流规则
// 初始化限流异常响应处理器, 当请求被限流时, 通过resource名称, 找到匹配的异常处理器, 响应数据
}
// 初始化api分组信息
private void initApiDefinitions() {
Set<ApiDefinition> apiDefinitions = new HashSet<ApiDefinition>() {
{
add(new ApiDefinition().setApiName("login-pattern").setPredicateItems(new HashSet<ApiPredicateItem>() {
{
add(new ApiPathPredicateItem()
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT)
.setPattern("/smart/api/v1/users/sessions"));
}
}));
}
};
GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
}
// 初始化限流规则
private void initFlowRules() {
Set<GatewayFlowRule> gatewayFlowRules = new HashSet<GatewayFlowRule>() {{
add(new GatewayFlowRule("login-pattern")
.setIntervalSec(10)
.setCount(5)
.setBurst(5)
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
);
}};
GatewayRuleManager.loadRules(gatewayFlowRules);
}
// 初始化限流异常响应处理器, 当请求被限流时, 通过resource名称, 找到匹配的异常处理器, 响应数据
private void initFallbackProviders() {
ZuulBlockFallbackManager.registerProvider(new LoginLimitFallbackProvider());
}
}
启动服务, 浏览器访问网关, 网关将请求转发给上游服务, 在10s内访问6次以上, 将会返回我们自定义的响应信息.
使用sentinel 控制台方式设置限流规则和api分组信息
使用控制台进行限流规则的设置将会变得非常方便, 我们可以在sentinel 官方网站或者github上找到已经编译好的jar包,直接下载下来, 然后启动jar包, 命令如下:
-Dserver.port=8080 -Dscp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard
启动后访问http://localhost:8080, 可以看到sentinel的登录界面
默认密码: 账号: sentinel 密码: sentinel
进入控制台, 这时什么都没有, 我们需要在网关上配置一个配置项来连接sentinel dashboard服务, 并在启动网关程序后, 通过浏览器请求一次(懒加载形式), 才能在控制台上看到信息
spring:
cloud:
nacos:
transport:
dashboard: localhost:8080 # 连接sentinel dashboard
点击api管理, 可以看到如下界面
如果需要定义api分组信息, 只需要在右上方点击新增api分组, 并设置相应参数, 还可以新增多个匹配规则, 如图
还需要定义限流信息, 点击流控管理界面右上角的新增网关流控规则
请求网关, 可以看到在10秒内请求第6次之后, 响应自定义的异常限流信息
第二节 源码介绍
使用sentinel dashboard方式来设置api分组和限流的方式虽然很方便, 也很人性化, 但是也有缺点, 也就是默认设置的规则在程序是存储在内存的, 而不是存在磁盘上或者数据库的, 所以, 我们需要自己在sentinel 控制台项目中修改他们的代码
网关整合sentinel源码刨析
sentinel gateway 整合接收sentinel dashboard请求源码
sentinel 客户端与sentinel控制台之间的通信使用http请求进行通信, 打开HttpEventTask可以看到, sentinel dashboard与本地服务最底层方式就是使用socket的方式通信, 将其封装为一个PrintWriter对象, 并且通过一个线程来处理请求
- 从PrintWriter对象中调用readLine方法取出一行, 并调用processQueryString将数据封装为
CommandRequest
对象 - 调用HttpCommandUtils.getTarget方法返回commandName(内部就是从request中获取metaData属性(map对象), 并且通过
command-target
这个key获取value - 通过
SimpleHttpCommandCenter.getHander
方法获取对应能够支持处理这个request的处理器,SimpleHttpCommandCenter
内部维护了一个map, 里面存储着几十个处理对应commandName对应的处理器, key为commandName, value为对应的处理器 - 调用处理器的
handle
方法, 将request对象传入
4.1 以api分组修改为例, 当commandName为gateway/updateApiDefinitions
时, 将会从map中找到对应的UpdateGatewayApiDefinitionGroupCommandHandler
对象, 并调用它的handle方法(可以查看CommandHandler
接口下的实现类)
4.2 从request中取出data的数据, 该数据存储的就是dashboard设置的api分组信息字符串(JSON)
4.3 调用parseJson
方法, 将其转为Set<ApiDefinition>
, 并使用GatewayApiDefinitionManager.loadApiDefinitions
方法加载控制台传来的api分组信息
限流异常自定义响应源码
如果当限流被触发后, 我们可以从源码类FallBackProviderHandler看到, 方法afterSingletonInstantiated方法中会找容器获取ZuulBlockProvider的所有子类, 所以我们需要自定义异常响应类后, 将其注入到spring容器中, 如果我们还是直接在PostConstruct注解的方法中直接使用ZuulBlockFallbackManager.registerProvider直接注册的话是没有效果的, 因为在FallBackProviderHandler的afterSingletonInstantiated方法中会找到容器中的自定义异常响应对象后将其覆盖掉之前注入的对象
sentinel dashboard api分组和限流规则源码
api分组定义接口
打开从github上克隆的项目, 进入sentinel-dashboard子项目(我选择的tag是1.6.3), 打开 com.alibaba.scp.sentinel.controller.gateway包(该包主要处理网关限流和网关api分组的接口), 打开GatewayApiController
类
该类内部有两个比较重要的依赖对象, 一个是InMemApiDefinitionStore
, 用于以内存的形式存储当前已经定义的api分组信息, 一个是SentinelApiClient
, 用于对api分组进行增删改时, 将api分组信息通过http将数据发送到sentinel客户端
该类接口主要为对api分组的信息进行增删改查的功能
-
queryApis 查询当前内存中已经存储的api分组信息
1.1 调用queryApi方法, 数据将会从指定的app(application name 微服务名称), ip, port对应的服务上请求获取对应的api分组信息, 调用SentinelApiClient类的fetchApi获取对应服务的所有api分组信息
1.2 进入fetchApi, 内部调用了私有方法executeCommand, 该方法返回的是一个CompletableFuture对象, 封装url相关信息, 并封装为HttpGet对象, 通过httpClient请求到sentinel客户端收到结果后调用CompletableFutrue对象的thenApply方法, 对响应的数据解析为ApiDefinitionEntity实体对象集合, 设置app名称, ip和port, 返回出去
在这个过程中, 程序是阻塞等待的, 因为调用了fetchApis后, 还链式调用了get方法, 该方法是阻塞直到获取响应的数据
-
addApi 新增api分组信息
-
updateApi 修改api分组信息
-
deleteApi 删除api分组信息
GatewayFlowRuleController
内部的逻辑也类似于GatewayApiController
后续进行更新…