从浅入深 学习 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)配置限流阈值。@PostConstructpublic 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.ymlserver:port: 8088 # 启动端口 命令行注入。
spring:application:name: api-gateway-service #spring应用名, # 注意 FeignClient 不支持名字带下划线redis: # 引入 redishost: localhostpool: 6379database: 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/1filters: # 配置路由过滤器 http://localhost:8088/product-service/product/1 --> http://127.0.0.1:9001/product/1- RewritePath=/product-service/(?<segment>.*), /$\{segment} # 路径重写的过滤器。
eureka: # 配置 Eurekaclient: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)配置限流阈值。@PostConstructpublic 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);}// 自定义限流 处理器方法@PostConstructpublic void initBlockHandlers(){BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {@Overridepublic 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)配置限流阈值。@PostConstructpublic 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);}// 自定义限流 处理器方法@PostConstructpublic void initBlockHandlers(){BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {@Overridepublic 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)对小组配置限流规则。@PostConstructprivate 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 就转发到 gatewaylocation / {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 限流。