网关整合Sentinel限流
Sentinel从1.6.0版本开始,提供了SpringCloud Gateway的适配模块,并且可以提供两种资源维度的限流,一种是route维度;另一种是自定义API分组维度。
- route维度:对application.yml文件中配置的
spring.cloud.gateway.routes.id
限流,并且资源名为spring.cloud.gateway.routes.id
对应的值。 - 自定义API分组维度:利用Sentinel提供的API接口来自定义API分组,并且对这些API分组进行限流。
实现route维度限流
(1)在服务网关shop-gateway模块的pom.xml文件中添加如下依赖。
<dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> </dependency> </dependencies>
(2)在服务网关shop-gateway模块中新建io.binghe.shop.config
包,并在包下新建GatewayConfig类。基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
GatewayConfig类的源代码如下所示。
/** * @author binghe (公众号:冰河技术) * @version 1.0.0 * @description 网关配置类 */ @Configuration public class GatewayConfig { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; @Value("${spring.cloud.gateway.discovery.locator.route-id-prefix}") private String routeIdPrefix; public GatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } /** * 初始化一个限流的过滤器 */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); } @PostConstruct public void init() { this.initGatewayRules(); this.initBlockHandlers(); } /** * 配置初始化的限流参数 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); /** * Sentinel整合SpringCloud Gateway使用的API类型为Route ID类型,也就是基于route维度时, * 由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下: * 生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。 * ${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称 * 其中,${spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀 * * 为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果, * 而无需登录Setinel管理界面手动配置限流规则,可以将 * resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称 * * 当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路, * 然后手动配置限流规则。 **/ // //用户微服务网关 // rules.add(this.getGatewayFlowRule("user-gateway")); // //商品微服务网关 // rules.add(this.getGatewayFlowRule("product-gateway")); // //订单微服务网关 // rules.add(this.getGatewayFlowRule("order-gateway")); //用户微服务网关 rules.add(this.getGatewayFlowRule(getResource("server-user"))); //商品微服务网关 rules.add(this.getGatewayFlowRule(getResource("server-product"))); //订单微服务网关 rules.add(this.getGatewayFlowRule(getResource("server-order"))); //加载规则 GatewayRuleManager.loadRules(rules); } private String getResource(String targetServiceName){ if (routeIdPrefix == null){ routeIdPrefix = ""; } return routeIdPrefix.concat(targetServiceName); } private GatewayFlowRule getGatewayFlowRule(String resource){ //传入资源名称生成GatewayFlowRule GatewayFlowRule gatewayFlowRule = new GatewayFlowRule(resource); //限流阈值 gatewayFlowRule.setCount(1); //统计的时间窗口,单位为 gatewayFlowRule.setIntervalSec(1); return gatewayFlowRule; } /** * 配置限流的异常处理器 */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } /** * 自定义限流异常页面 */ private void initBlockHandlers() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map map = new HashMap<>(); map.put("code", 1001); map.put("codeMsg", "接口被限流了"); return ServerResponse.status(HttpStatus.OK). contentType(MediaType.APPLICATION_JSON_UTF8). body(BodyInserters.fromObject(map)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
GatewayConfig类的源代码看上去比较多,但是都是一些非常简单的方法,冰河在这里就不再赘述了。
这里有个需要特别注意的地方:
Sentinel1.8.4整合SpringCloud Gateway使用的API类型为Route ID类型时,也就是基于route维度时,由于Sentinel为SpringCloud Gateway网关生成的API名称规则如下:
生成的规则为:${spring.cloud.gateway.discovery.locator.route-id-prefix}后面直接加上目标微服务的名称,如下所示。
{spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称。其中,${spring.cloud.gateway.discovery.locator.route-id-prefix}是在yml文件中配置的访问前缀。
为了让通过服务网关访问目标微服务链接后,请求链路中生成的API名称与流控规则中生成的API名称一致,以达到启动项目即可实现访问链接的限流效果,而无需登录Setinel管理界面手动配置限流规则,可以将生成GatewayFlowRule对象的resource参数设置为${spring.cloud.gateway.discovery.locator.route-id-prefix}目标微服务的名称
当然,如果不按照上述配置,也可以在项目启动后,通过服务网关访问目标微服务链接后,在Sentinel管理界面的请求链路中找到对应的API名称所代表的请求链路,然后手动配置限流规则。
(3)将服务网关shop-gateway模块的application.yml文件备份一份名称为application-nacos-simple.yml的文件,并将application.yml文件的内容修改成如下所示。
server: port: 10001 spring: application: name: server-gateway main: allow-bean-definition-overriding: true cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: port: 7777 dashboard: 127.0.0.1:8888 web-context-unify: false eager: true gateway: globalcors: cors-configurations: '[/**]': allowedOrigins: "*" allowedMethods: "*" allowCredentials: true allowedHeaders: "*" discovery: locator: enabled: true route-id-prefix: gateway-
其中:
spring.cloud.sentinel.eager
表示程序启动时,流控规则是否立即注册到Sentinel,配置为true表示立即注册到Sentinel。spring.cloud.gateway.discovery.locator.route-id-prefix
:生成流控规则API名称的前缀。
(4)在IDEA中配置启动服务网关shop-gateway模块的参数-Dcsp.sentinel.app.type=1
,如下所示。
如果是在命令行启动网关服务的Jar包,则可以使用如下命令。
java -Dcsp.sentinel.app.type=1 shop-gateway.jar
或者在启动类io.binghe.shop.GatewayStarter
的main()方法中添加一行System.setProperty("csp.sentinel.app.type", "1");
代码,如下所示。
/** * @author binghe (公众号:冰河技术) * @version 1.0.0 * @description 服务网关启动类 */ @SpringBootApplication @EnableDiscoveryClient public class GatewayStarter { public static void main(String[] args){ System.setProperty("csp.sentinel.app.type", "1"); SpringApplication.run(GatewayStarter.class, args); } }
(5)分别启动用户微服务、商品微服务、订单微服务和服务网关,启动后会在Sentinel管理界面左侧菜单栏中看到server-gateway菜单,如下所示。
在server-gateway菜单下的流控规则子菜单中可以看到网关的流控规则已经注册到Sentinel,如下所示。
(6)通过服务网关访问用户微服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001
,不断刷新页面,如下所示。
用户微服务返回的原始数据如下所示。
{ "code": 1001, "codeMsg": "接口被限流了" }
可以看到,通过服务网关不断刷新用户微服务时,触发了服务限流,并返回了自定义的限流结果数据。
(7)通过服务网关访问商品微服务,在浏览器中输入http://localhost:10001/server-product/product/get/1001
,不断刷新页面,如下所示。
商品微服务返回的原始数据如下所示。
{ "code": 1001, "codeMsg": "接口被限流了" }
可以看到,通过服务网关不断刷新商品微服务时,触发了服务限流,并返回了自定义的限流结果数据。
(8)通过服务网关访问订单微服务,在浏览器中输入http://localhost:10001/server-order/order/test_sentinel
,不断刷新页面,如下所示。
可以看到,通过服务网关不断刷新订单微服务时,触发了服务限流,并返回了自定义的限流结果数据。
实现自定义API分组维度限流
前面,我们实现了route维度的限流,接下来,我们再基于Sentinel与SpringCloud gateway实现自定义API分组维度的限流。
(1)在服务网关shop-gateway模块的io.binghe.shop.config.GatewayConfig
配置类中新增initCustomizedApis()方法,初始化API管理的信息,源码如下所示。
private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("user_api1") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // 以/server-user/user/api1 开头的请求 add(new ApiPathPredicateItem().setPattern("/server-user/user/api1/**"). setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("user_api2") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // 以/server-user/user/api2/demo1 完成的url路径匹配 add(new ApiPathPredicateItem().setPattern("/server-user/user/api2/demo1")); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); }
上述代码中,配置了两个API分组,每个API分组的规则如下。
- user_api1分组:匹配以
/product-serv/product/api1
开头的所有请求。 - user_api2分组:精确匹配
/server-user/user/api2/demo1
。
(2)在服务网关shop-gateway模块的io.binghe.shop.config.GatewayConfig
配置类中init()方法中调用initCustomizedApis()方法,为了避免route维度的限流对自定义API分组维度的限流产生影响,这里,同时在init()方法中注释掉调用initGatewayRules()方法,修改后的init()方法的代码如下所示。
@PostConstruct public void init() { //this.initGatewayRules(); this.initBlockHandlers(); this.initCustomizedApis(); }
(3)在用户微服务shop-user的io.binghe.shop.user.controller.UserController
类中新增四个测试接口,源码如下所示。
@GetMapping(value = "/api1/demo1") public String api1Demo1(){ log.info("访问了api1Demo1接口"); return "api1Demo1"; } @GetMapping(value = "/api1/demo2") public String api1Demo2(){ log.info("访问了api1Demo2接口"); return "api1Demo2"; } @GetMapping(value = "/api2/demo1") public String api2Demo1(){ log.info("访问了api2Demo1接口"); return "api2Demo1"; } @GetMapping(value = "/api2/demo2") public String api2Demo2(){ log.info("访问了api2Demo2接口"); return "api2Demo2"; }
(4)分别启动用户微服务、商品微服务、订单微服务和服务网关,启动后会在Sentinel管理界面左侧菜单栏中看到server-gateway菜单,如下所示。
此时,由于我们注释了调用以route维度限流的方法,所以,在流控规则里的限流规则为空,如下所示。
在API管理里面会发现我们定义的API分组已经自动注册到Sentinel中了,如下所示。
(5)在Sentinel管理界面的流控规则中,新增网关流控规则,如下所示。
点击新增网关流控规则后,会弹出新增网关流控规则配置框,按照如下方式为user_api1分组配置限流规则。
点击新增按钮后,按照同样的方式为user_api2分组配置限流规则。
配置完毕后,在流控规则中的限流规则如下所示。
(6)预期的测试结果如下。
- 当频繁访问
http://localhost:10001/server-user/user/api1/demo1
时会被限流。 - 当频繁访问
http://localhost:10001/server-user/user/api1/demo2
时会被限流。 - 当频繁访问
http://localhost:10001/server-user/user/api2/demo1
时会被限流。 - 当频繁访问
http://localhost:10001/server-user/user/api2/demo2
时不会被限流。
注意:只有最后一个不会被限流。
(7)在浏览器上频繁访问http://localhost:10001/server-user/user/api1/demo1
,如下所示。
返回的原始数据如下所示。
{ "code": 1001, "codeMsg": "接口被限流了" }
说明触发了服务限流,并返回了自定义的限流结果数据。
(8)在浏览器上频繁访问http://localhost:10001/server-user/user/api1/demo2
,如下所示。
返回的原始数据如下所示。
{ "code": 1001, "codeMsg": "接口被限流了" }
说明触发了服务限流,并返回了自定义的限流结果数据。
(9)在浏览器上频繁访问http://localhost:10001/server-user/user/api2/demo1
,如下所示。
返回的原始数据如下所示。
{ "code": 1001, "codeMsg": "接口被限流了" }
说明触发了服务限流,并返回了自定义的限流结果数据。
(10)在浏览器上频繁访问http://localhost:10001/server-user/user/api2/demo2
,如下所示。
可以看到,访问http://localhost:10001/server-user/user/api2/demo2
时,无论访问的频率多频繁,都不会触发Sentinel限流。
至此,我们就成功在项目中整合了SpringCloud Gateway网关,并通过Sentinel整合SpringCloud Gateway实现了网关的限流操作。
另外,一不小心就写了13章了,小伙伴们你们再不上车就跟不上了!!!