SA实战 ·《SpringCloud Alibaba实战》第13章-服务网关:项目整合SpringCloud Gateway网关 下

简介: SA实战 ·《SpringCloud Alibaba实战》第13章-服务网关:项目整合SpringCloud Gateway网关

网关整合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,如下所示。

图片.pngimage.gif

如果是在命令行启动网关服务的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菜单,如下所示。

图片.png

在server-gateway菜单下的流控规则子菜单中可以看到网关的流控规则已经注册到Sentinel,如下所示。

图片.png

(6)通过服务网关访问用户微服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001,不断刷新页面,如下所示。

image.gif图片.png

用户微服务返回的原始数据如下所示。

{
  "code": 1001,
  "codeMsg": "接口被限流了"
}

可以看到,通过服务网关不断刷新用户微服务时,触发了服务限流,并返回了自定义的限流结果数据。

(7)通过服务网关访问商品微服务,在浏览器中输入http://localhost:10001/server-product/product/get/1001,不断刷新页面,如下所示。

图片.png

商品微服务返回的原始数据如下所示。

{
  "code": 1001,
  "codeMsg": "接口被限流了"
}

可以看到,通过服务网关不断刷新商品微服务时,触发了服务限流,并返回了自定义的限流结果数据。

(8)通过服务网关访问订单微服务,在浏览器中输入http://localhost:10001/server-order/order/test_sentinel,不断刷新页面,如下所示。

图片.png

可以看到,通过服务网关不断刷新订单微服务时,触发了服务限流,并返回了自定义的限流结果数据。


实现自定义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菜单,如下所示。

image.gif图片.png

此时,由于我们注释了调用以route维度限流的方法,所以,在流控规则里的限流规则为空,如下所示。

image.gif图片.png

在API管理里面会发现我们定义的API分组已经自动注册到Sentinel中了,如下所示。

图片.pngimage.gif

(5)在Sentinel管理界面的流控规则中,新增网关流控规则,如下所示。

图片.pngimage.gif

点击新增网关流控规则后,会弹出新增网关流控规则配置框,按照如下方式为user_api1分组配置限流规则。

image.gif图片.png

点击新增按钮后,按照同样的方式为user_api2分组配置限流规则。

image.gif图片.png

配置完毕后,在流控规则中的限流规则如下所示。

图片.pngimage.gif

(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,如下所示。

图片.pngimage.gif

返回的原始数据如下所示。

{
  "code": 1001,
  "codeMsg": "接口被限流了"
}

说明触发了服务限流,并返回了自定义的限流结果数据。

(8)在浏览器上频繁访问http://localhost:10001/server-user/user/api1/demo2,如下所示。

图片.pngimage.gif

返回的原始数据如下所示。

{
  "code": 1001,
  "codeMsg": "接口被限流了"
}

说明触发了服务限流,并返回了自定义的限流结果数据。

(9)在浏览器上频繁访问http://localhost:10001/server-user/user/api2/demo1,如下所示。

图片.png

返回的原始数据如下所示。

{
  "code": 1001,
  "codeMsg": "接口被限流了"
}

说明触发了服务限流,并返回了自定义的限流结果数据。

(10)在浏览器上频繁访问http://localhost:10001/server-user/user/api2/demo2,如下所示。

图片.png

可以看到,访问http://localhost:10001/server-user/user/api2/demo2时,无论访问的频率多频繁,都不会触发Sentinel限流。

至此,我们就成功在项目中整合了SpringCloud Gateway网关,并通过Sentinel整合SpringCloud Gateway实现了网关的限流操作。


另外,一不小心就写了13章了,小伙伴们你们再不上车就跟不上了!!!

相关文章
|
3月前
|
存储 缓存 负载均衡
Gateway 网关坑我! 被这个404 问题折腾了一年?
小富分享了一个困扰团队一年多的 SpringCloud Gateway 路由 404 问题。通过日志追踪和源码分析,发现是网关在 Nacos 配置更新后未能正确清理旧的路由权重缓存,导致负载均衡时仍使用已删除的路由数据。最终通过监听路由刷新事件并手动更新缓存,成功解决了问题。
765 125
Gateway 网关坑我! 被这个404 问题折腾了一年?
|
3月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
2月前
|
缓存 JSON NoSQL
别再手写过滤器!SpringCloud Gateway 内置30 个,少写 80% 重复代码
小富分享Spring Cloud Gateway内置30+过滤器,涵盖请求、响应、路径、安全等场景,无需重复造轮子。通过配置实现Header处理、限流、重试、熔断等功能,提升网关开发效率,避免代码冗余。
349 1
|
2月前
|
机器学习/深度学习 Kubernetes API
【Azure APIM】自建网关(self-host gateway)收集请求的Header和Body内容到日志中的办法
在Azure API Management中,通过配置trace策略可完整记录API请求的Header和Body信息。在Inbound和Outbound策略中分别使用context.Request/Response.Headers和Body.As&lt;string&gt;方法捕获数据,并写入Trace日志,便于排查与审计。
124 7
|
4月前
|
安全 虚拟化
Omnissa Secure Email Gateway 2.33 - 电子邮件网关
Omnissa Secure Email Gateway 2.33 - 电子邮件网关
109 0
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
361 0
|
6月前
|
缓存 监控 Java
说一说 SpringCloud Gateway 堆外内存溢出排查
我是小假 期待与你的下一次相遇 ~
851 5
|
6月前
|
Java API Nacos
|
12月前
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
1043 69
利用Spring Cloud Gateway Predicate优化微服务路由策略
|
9月前
|
存储 监控 安全
工业物联网关应用:PLC数据通过智能网关上传阿里云实战
本文介绍如何使用智能网关将工厂PLC数据传输至阿里云平台,适合中小企业远程监控设备状态。硬件准备包括三菱FX3U PLC、4G智能网关和24V电源。接线步骤涵盖PLC编程口与网关连接、运行状态检测及天线电源接入。配置过程涉及通讯参数、阿里云对接和数据点映射。PLC程序关键点包括数据上传触发和温度值处理。阿里云平台操作包含实时数据查看、数据可视化和规则引擎设置。最后提供常见故障排查表和安全建议,确保系统稳定运行。
825 1