从浅入深 学习 SpringCloud 微服务架构(十三)SCG 网关中使用 sentinel 限流
一、SCG 网关中使用 sentinel 限流:入门案例
1、基于 Sentinel 的限流:
1) Sentinel 支持对 Spring Cloud Gateway, Zuul 等主流的 API Gateway 进行限流。
2) 从 Sentinel-1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
- route 维度 : 即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeld
- 自定义 API 维度 : 用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
3)Sentinel1.6.0 引入了 Sentinel APl Gateway Adapter Common 模块,此模块中包含网关限流的规则
和自定义 API的实体和管理逻辑:
- GatewayF1owu1e : 网关限流规则,针对 APl Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
- ApiDefinition : 用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 AP| 叫 my_api,请求 path 模式为 /foo/**和 /baz/**的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API分组维度进行限流。
2、SCG 网关中使用 sentinel 限流:入门案例:环境搭建。
2.1 在子工程(子模块) api_gateway_service 的 pom.xml 中导入 Sentinel 限流 依赖坐标。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>djh.it</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api_gateway_service</artifactId>
<dependencies>
<!-- springcloudgateway 的内部是通过 netty + webflux 实现。
webflux 实现和 springmvc 存在冲突,需要注销掉父工程中的 web 依赖,在各子模块中导入 web 依赖。
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 引入 EurekaClient 依赖坐标 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 配置 SCG 网关 filter 限流 依赖坐标:redis 监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 配置 SCG 网关 filter 限流 依赖坐标:redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- sentinel 网关限流 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.6.3</version>
</dependency>
</dependencies>
</project>
<!-- spring_cloud_demo\api_gateway_service\pom.xml -->
2.2、在子工程 api_gateway_service(子模块)中,创建 Sentinel 限流 配置类 GatewayConfiguration.java
/**
* spring_cloud_demo\api_gateway_service\src\main\java\djh\it\gateway\GatewayConfiguration.java
*
* 2024-5-8 创建 Sentinel 限流 配置类 GatewayConfiguration.java
*/
package djh.it.gateway;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
//配置初始化的限流参数,用于指定资源的限流规则:1)资源名称(路由id),2)配置统计时间,3)配置限流阈值。
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("product-service").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
}
2.3、在子工程 api_gateway_service(子模块)中,修改 application.yml 配置文件,
## spring_cloud_demo\api_gateway_service\src\main\resources\application.yml
server:
port: 8088 # 启动端口 命令行注入。
spring:
application:
name: api-gateway-service #spring应用名, # 注意 FeignClient 不支持名字带下划线
redis: # 引入 redis
host: localhost
pool: 6379
database: 0
# 配置 SpringCloudGateway 的路由
cloud:
gateway:
routes: # 配置路由,路由Id,路由到微服务的 uri, 断言(判断条件)
- id: product-service # 保持唯一
uri: lb://service-product # lb:// 根据微服务名称从注册中心拉取服务请求路径。
predicates: # 断言(判断条件)设置
- Path=/product-service/** # 将当前请求转发到 http://127.0.0.1/product/1
filters: # 配置路由过滤器 http://localhost:8088/product-service/product/1 --> http://127.0.0.1:9001/product/1
- RewritePath=/product-service/(?<segment>.*), /$\{segment} # 路径重写的过滤器。
eureka: # 配置 Eureka
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true # 使用ip地址注册。
3、重新启动 父工程 spring_cloud_demo 下 全部子项目(eureka,product,order,gateway)的启动类,进行测试:
1)浏览器地址栏输入(正常访问):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
2)浏览器地址栏输入(快速多刷新几次,发现不能正常访问):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
二、SCG 网关中使用 sentinel 限流:限流异常提示
1、SCG 网关中使用 sentinel 限流,自定义异常提示
当触发限流后页面显示的是 Blocked bySentinel: FlowException。为了展示更加友好的限流提示 Sentinel 支持自定义异常处理。
可以在 Gatewayca11backmanager 注册回调进行定制:
setB1ockHandler : 注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为 B1ockRequestHandler。
默认实现为 Defau1tBlockRequestHandler 当被限流时会返回类似于下面的错误信息 : B1ocked by sentine1:FlowException 。
2、在子工程 api_gateway_service(子模块)中,修改 Sentinel 限流 配置类 GatewayConfiguration.java 添加 自定义限流 处理器方法。
/**
* spring_cloud_demo\api_gateway_service\src\main\java\djh\it\gateway\GatewayConfiguration.java
*
* 2024-5-8 创建 Sentinel 限流 配置类 GatewayConfiguration.java
*/
package djh.it.gateway;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
//配置初始化的限流参数,用于指定资源的限流规则:1)资源名称(路由id),2)配置统计时间,3)配置限流阈值。
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("product-service").setCount(1).setIntervalSec(1));
// rules.add(new GatewayFlowRule("order_api").setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
// 自定义限流 处理器方法
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code",001);
map.put("message", "对不起,接口限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
// @PostConstruct
// private void initCustomizedApis(){
// Set<ApiDefinition> definitions = new HashSet<>();
// ApiDefinition api1 = new ApiDefinition("product_api")
// .setPredicateItems(new HashSet<ApiPredicateItem>(){{
// add(new ApiPathPredicateItem().setPattern("/product-service/product/**")
// .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
// }});
// ApiDefinition api2 = new ApiDefinition("order_api")
// .setPredicateItems(new HashSet<ApiPredicateItem>(){{
// add(new ApiPathPredicateItem().setPattern("/order-service/order"));
// }});
// definitions.add(api1);
// definitions.add(api2);
// GatewayApiDefinitionManager.loadApiDefinitions(definitions);
// }
}
3、重新启动 父工程 spring_cloud_demo 下 全部子项目(eureka,product,order,gateway)的启动类,进行测试:
1)浏览器地址栏输入(正常访问):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
2)浏览器地址栏输入(快速多刷新几次,不能正常访问,异常页面是我们自定义的方法):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
三、SCG 网关中使用 sentinel 限流:自定义分组限流
1、在子工程 api_gateway_service(子模块)中,修改 Sentinel 限流 配置类 GatewayConfiguration.java 添加 自定义 API 限流分组方法。
/**
* spring_cloud_demo\api_gateway_service\src\main\java\djh\it\gateway\GatewayConfiguration.java
*
* 2024-5-8 创建 Sentinel 限流 配置类 GatewayConfiguration.java
*/
package djh.it.gateway;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
//配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//配置限流过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
//配置初始化的限流参数,用于指定资源的限流规则:1)资源名称(路由id),2)配置统计时间,3)配置限流阈值。
@PostConstruct
public void initGatewayRules(){
Set<GatewayFlowRule> rules = new HashSet<>();
// rules.add(new GatewayFlowRule("product-service")
// .setCount(1)
// .setIntervalSec(1));
rules.add(new GatewayFlowRule("product-api")
.setCount(1).setIntervalSec(1));
rules.add(new GatewayFlowRule("order_api")
.setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
// 自定义限流 处理器方法
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code",001);
map.put("message", "对不起,接口限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//自定义API限流分组方法:1)定义分组,2)对小组配置限流规则。
@PostConstruct
private void initCustomizedApis(){
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product-api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/product-service/product/**") // 以 /product-service/product/ 开头的所有 url
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("order_api")
.setPredicateItems(new HashSet<ApiPredicateItem>(){{
add(new ApiPathPredicateItem().setPattern("/order-service/order")); // 完成匹配 /order-service/order
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
2、重新启动 父工程 spring_cloud_demo 下 全部子项目(eureka,product,order,gateway)的启动类,进行测试:
1)浏览器地址栏输入(正常访问):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
2)浏览器地址栏输入(快速多刷新几次,不能正常访问,返回异常页面是我们自定义的方法):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
四、SCG 网关高可用:概述
1、网关高可用
高可用 HA(High Availability) 是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。
我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。
方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他 backup 能够顶上。
2、我们实际使用 Spring Cloud Gateway 的方式如下图:
3、不同的客户端使用不同的负载将请求分发到后端的 Gateway,Gateway 再通过 HTTP 调用后端服务,最后对外输出。因此为了保证 Gateway 的高可用性,前端可以同时启动多个 Gateway 实例进行负载,在 Gateway 的前端使用 Nginx 或者 F5 进行负载转发以达到高可用性。
五、SCG 网关高可用:ngnix 结合网关集群构造高可用网关
1、在 idea 的 Run Dashboard 面板上,复制一个 GatewayServerApplication 网关服务,重命名为:GatewayServerApplication(2) 并在 application.yml 配置文件修改端口号为:8089
如果找不到 idea 的 Run Dashboard 面板上,请查看:
# IDEA2019 如何打开 Run Dashboard 运行仪表面板
2、重新启动 父工程 spring_cloud_demo 下 全部子项目(eureka,product,order,gateway,gateway2)的启动类,进行测试:
1)浏览器地址栏输入(正常访问):
http://localhost:8088/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
2)浏览器地址栏输入(也可以正常访问):
http://localhost:8089/product-service/product/1 就转发到 http://127.0.0.1:9001/product/1
3、安装一个 Nginx,并在安装目录下的 conf 目录下,找到配置文件 nginx.conf 打开并编辑它。
# D:\Program Files\nginx-1.8.1\conf\nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
# 集群配置
upstream gateway {
server 127.0.0.1:8088;
server 127.0.0.1:8089;
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
#location / {
# root html;
# index index.html index.htm;
#}
# 当请求到 127.0.0.1 就转发到 gateway
location / {
proxy_pass http://gateway;
}
# 路由到订单服务
#location /api-product {
# proxy_pass http://127.0.0.1:9001/;
#}
# 路由到商品服务
#location /api-order {
# proxy_pass http://127.0.0.1:9002/;
#}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
4、启动 Nginx 服务,并重新启动 父工程 spring_cloud_demo 下 全部子项目(eureka,product,order,gateway,gateway2)的启动类,进行测试:
浏览器地址栏输入(也能正常访问):
http://localhost/product-service/product/1
就相当于访问转发到
http://localhost:8088/product-service/product/1
http://localhost:8089/product-service/product/1
5、如果在 idea 的 Run Dashboard 面板上,停掉一个 GatewayServerApplication 如:8088
浏览器地址栏输入(也能正常访问):
http://localhost/product-service/product/1
但是,http://localhost:8088/product-service/product/1 就不能正常访问了。
上一节关联链接请点击:
# 从浅入深 学习 SpringCloud 微服务架构(十二)网关限流算法和 SCG 网关 filter 限流。