Spring Cloud Alibaba 系列之 Gateway(网关)

一、什么是网关

网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul1.0。相比 Zuul 来说,Spring Cloud  Gateway 提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。 它不能在传统的 servlet 容器中工作,也不能构 建成 war 包 。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。

一、Gateway 介绍

2.1 核心概念

1.路由:网关的基本构建组成,表示一个具体的路由信息载体。它由 ID,目标 URI,谓词集合和过滤器集合定义

2.谓词/断言:Java 8 函数谓词,输入类型是 Spring Framework ServerWebExchange,可以匹配 HTTP 请求中的所有内容,例如请求头或参数

3.过滤器:使用特定工厂构造的 Spring Framework GatewayFilter 实例,可以在发送给下游请求之前或之后修改请求和响应

2.2 执行流程


 执行流程大体如下:

  1. Gateway Client 向 Gateway Server 发送请求
  2. 请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文
  3. 然后网关的上下文会传递到 DispatcherHandler,它负责将请求分发给 RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用
  5. 如果过断言成功,由 FilteringWebHandler 创建过滤器链并调用
  6. 请求会一次经过 PreFilter -> 微服务 -> PostFilter 的方法,最终返回响应

三、环境搭建

为了更好的理解上边提到核心概念,我们现用简单的实战案例演示

项目名称端口描述
gateway-test-pom 项目,父工厂
user-service9001用户微服务,服务注册到 nacos
gateway-service9002网关微服务,服务注册到 nacos

注意:搭建项目启动前,必须先开启 Nacos 服务。

3.1 搭建 gateway-test 项目

该工程为 pom 项目,只需要添加如下依赖:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!-- spring cloud alibaba 依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring cloud 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>


        </dependencies>
    </dependencyManagement>

3.2 搭建 user-service 项目

该项目为用户微服务,模拟提供用户相关接口。

1.添加依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
 
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
 
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
 
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. 配置文件(application.yml):


server:
  port: 9001
 
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos

3.业务类:

@Data
@AllArgsConstructor
public class User {
 
    private Integer id;
 
    private String name;
}
@RestController
@RequestMapping("/user")
public class UserController {
 
    private static Map<Integer, User> userMap;
 
    static {
        userMap = new HashMap<>();
        userMap.put(1, new User(1, "张三"));
        userMap.put(2, new User(2, "李四"));
        userMap.put(3, new User(3, "王五"));
    }
 
    @RequestMapping("/findById/{id}")
    public User findById(@PathVariable("id") Integer id) {
        // 为了测试方便,用此方式模拟用户查询
        return userMap.get(id);
    }
}

4.启动类

@SpringBootApplication
@EnableDiscoveryClient
public class UserApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

启动用户微服务,浏览器输入:localhost:9001/user/findById/1,结果如下图:

   nacos添加了一个服务

 页面如图:

 用户微服务正常

3.3 搭建 gateway-service 项目

该服务提供网关功能,核心就是配置路由规则

1.添加依赖:

<dependencies>
    <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>
</dependencies>

2.配置文件(application.yml):

server:
  port: 9090
 
spring:
  application:
    name: gateway-service
 
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
    gateway:
      discovery:
        locator:
          enabled: true # gateway 可以从 nacos 发现微服务

我们暂不配置路由规则

3.启动类:

@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

启动网关项目,我们试着通过网关请求用户微服务接口。

请求规则:网关地址/微服务应用名/接口

我们在浏览器输入:localhost:9090/user-service/user/findById/2,结果如下图:

nacos如下:

 页面如下:

 请求成功,网关项目搭建完成。

使用路由规则:

server:
  port: 9090
 
spring:
  application:
    name: gateway-service
 
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
    gateway:
      discovery:
        locator:
          enabled: true # gateway 可以从 nacos 发现微服务
      routes:
        - id: user_service_route  # 路由 id,确定唯一加尔肯
          uri: lb://user-service  # lb 表示从 nacos 中按照名称获取微服务,并遵循负载均衡策略,user-service 对应用户微服务应用名
          predicates:
            - Path=/user-api/**  # 使用断言
          filters:
            - StripPrefix=1       # 使用过滤器 

其中:

  • id: 路由标识符,区别于其他 Route
  • uri:路由指向的目的地 uri,即客户端请求最终被转发到的微服务
  • predicate:断言,用于条件判断,只有断言都返回真,才会真正的执行路由
  • filter:过滤器用于修改请求和响应信息

添加 routes 相关配置,重启网关项目,请求用户微服务接口。

请求规则:网关地址/断言配置的 Path 路径/接口

我们在浏览器输入:localhost:9090/user-api/user/findById/3,结果如下图:

 

路由规则生效。

简单的使用了路由规则,下文将具体介绍路由规则的使用方式。

 四、断言

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。

SpringCloud Gateway 的断言通过继承 AbstractRoutePredicateFactory 类实现,因此我们可以根据自己的需求自定义断言。

当然,开发团队已为使用者提供了一些内置断言工厂,在开发中已足够使用,请继续阅读下文。

4.1 内置断言

Spring Cloud Gateway 包括 11 种内置的断言工厂,所有这些断言都与 HTTP 请求的不同属性匹配。

补充:断言可以同时使用

  1. AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
  2. BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
  3. BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内

上边三个断言工厂都是根据时间判断。使用方式如下:

案例一(晚于指定日期)

cloud:
    gateway:
      routes:
        - id: order_route    #路由的唯一标识
          uri: lb://order-seata
          predicates:
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]

案例二(早于指定日期)

cloud:
    gateway:
      routes:
        - id: order_route    #路由的唯一标识
          uri: lb://order-seata
          predicates:
            - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

案例三(指定日期之间)

cloud:
    gateway:
      routes:
        - id: order_route    #路由的唯一标识
          uri: lb://order-seata
          predicates:
            - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

4.2、基于远程地址

RemoteAddrRoutePredicateFactory: 接收一个ip地址段,判断请求主机是否在地址端中

- RemoteAddr=192.168.1.1/24

其中,192.168.0.1 是 IP 地址,而 24 是子网掩码。当请求的远程地址为该值时,匹配路由。

4.3、基于Cookie

CookieRoutePredicateFactory: 接收两个参数,cookie名字和一个正则表达式,判断请求cookie是否具有给定名称且与正则表达式匹配

其中,chocolate为 cookie 名称,ch.p为 cookie 值。

- Cookie=chocolate, ch.p

 4.4、基于Header

HeaderRoutePredicateFactory: 接收两个参数,标题名称和一个正则表达式,判断请求Header是否具有给定名称且与正则表达式匹配。

其中,X-Request-Id 为 header 名称,\d+ 为正则表达式,表示数字。

- Header=X-Request-Id, \d+

4.5、基于Method请求方法

MethodRoutePredicateFactory: 接收一个参数,判断请求类型是否跟指定的类型匹配

如果请求方法是 GET 或 POST,则此路由匹配

- Method=GET,POST

 4.6、基于Path匹配请求路径,

PathRoutePredicateFactory:接收一个参数,判断请求的 URI 部分是否满足路径规则。

predicates:
    - Path=/user-api/**

这个就是我们在上边配置的断言,请求是 /user-api/ 开头,则路由到用户微服务上。

4.7、WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
            - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
            - Weight=group1, 2

配置多组路由规则时使用。路由会将约 80% 的流量转发至 weighthigh.org,并将约 20% 的流量转发至 weightlow.org。

4.8、QueryRoutePredicateFactory:接收两个参数,请求 param 和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

predicates:
    - Query=cardId, \d+

请求包含名称为 cardId 的参数,且参数值为数字,则匹配路由。

4.9、HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的 Host 是否满足匹配规则。

predicates:
    - Host=**.somehost.org,**.anotherhost.org

支持 URI 模板变量(例如{sub} .myhost.org),如果请求的主机标头的值为w w w.somehost.org或 beta.somehost.org 或 w w w.anotherhost.org,则此路由匹配

4.10 自定义断言

当内置的断言不满足我们的业务需求时,我们可以自定义断言工厂。

比如,我们需要判断请求 url 中传过来的 age 值在 18~60 范围才可正常路由。

1. 配置断言:

predicates:
    - Age=18, 60

2. 我们需要创建一个类继承 AbstractRoutePredicateFactory 类:

注意:自定义类名有格式要求-> 断言名称 + RoutePredicateFactory。此处断言名称为 Age,对应配置文件中的 Age。

自定义断言工厂需要继承AbstractRoutePredicateFactory类,重写apply方法的逻辑,在apply方法中可以通过exchange.getRequest()拿到ServerHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
 
    public AgeRoutePredicateFactory() {
        super(AgeRoutePredicateFactory.Config.class);
    }
 
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("minAge", "maxAge");
    }
 
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                // 判断逻辑
                String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
                if (ageStr == null || ageStr.length() == 0) {
                    return false;
                }
 
                int age = Integer.parseInt(ageStr);
                return age > config.getMinAge() && age < config.getMaxAge();
            }
        };
    }
 
    @Data
    static class Config {
        private int minAge;
        private int maxAge;
    }
}

要求:

必须spring组件bean
类必须加上RoutePredicateFactory作为结尾
继承AbstractRoutePredicateFactory
必须声明静态内部类,声明属性来接受 配置文件中的信息
需要结合shortcutFieldOrder来进行绑定
通过apply进行逻辑编写,true就是匹配成功,false就是匹配失败

五、过滤器

路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。

在 Gateway 中, Filter 的生命周期只有两个: “pre” 和 “post”。

  1. PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等
  2. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

根据 Filter 的作用范围可以分成两种:GatewayFilter 与 GlobalFilter

  • GatewayFilter:应用到单个路由或者一个分组的路由上

  • GlobalFilter:应用到所有的路由上。

5.1 局部过滤器

局部过滤器是针对单个路由的过滤器。

Spring Cloud Gateway 也提供了 31 种局部的内置 GatewayFilter 工厂。

由于数量较多,笔者只列举部分内置局部过滤器进行展示。

 使用方式:

spring:
    gateway:
      discovery:
        locator:
          enabled: true # gateway 可以从 nacos 发现微服务
      routes:
        - id: user_service_route  
          uri: lb://user-service  
          predicates:
            - Path=/user-api/**  # 使用断言
          filters:
            - StripPrefix=1 
            - SetStatus=2000  # 修改返回状态

同样地,当内置的局部过滤器不符合我们的业务需求时,我们也可以自定义过滤器。

比如:我们需要在调用/路由一个接口之前打印一下日志。

1. 配置局部过滤器

filters:
    - Log=true

2. 创建一个类继承 AbstractGatewayFilterFactory 类:

注意:自定义类名有格式要求-> 过滤器名称 + GatewayFilterFactory。此处过滤器名称为 Log,对应配置文件中的 Log。

@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
 
 
    public LogGatewayFilterFactory() {
        super(LogGatewayFilterFactory.Config.class);
    }
 
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("open");
    }
 
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if (config.open) {
                    // 过滤器逻辑处理
                    System.out.println("====开启日志====");
                }
 
                return chain.filter(exchange);
            }
        };
    }
 
    @Data
    static class Config {
        private boolean open;
    }
}

3. 保存,重启网关项目,测试结果如下:

 5.2 全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

同样地,框架也内置了一些全局过滤器,它们都实现 GlobalFilter 和 Ordered 接口。有兴趣的读者可以自行查看 GlobalFilter 的实现类或浏览下文提供的官方文档获取详细信息。

这里我们主要演示自定义全局过滤器。

比如:我们在接受请求时需要验证 token。

由于是全局过滤器,因此无需修改配置文件,需要定义类实现 GlobalFilter 和 Ordered 接口。
 

@Component
public class TokenGlobalFilter implements GlobalFilter, Ordered {
 
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (token == null || token.length() == 0 || !token.equals("123456")) {
            System.out.println("鉴权失败");
            ServerHttpResponse response = exchange.getResponse();
 
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
 
            // 鉴权失败,返回的数据结构
            Map<String, Object> map = new HashMap<>();
            map.put("code", HttpStatus.UNAUTHORIZED.value());
            map.put("message", HttpStatus.UNAUTHORIZED.getReasonPhrase());
 
            DataBuffer buffer = response.bufferFactory().wrap(new ObjectMapper().writeValueAsBytes(map));
            return response.writeWith(Flux.just(buffer));
        }
 
        return chain.filter(exchange);
    }
 
    @Override
    public int getOrder() {
        return 0;
    }
}

保存,重启网关项目,测试结果如下:

 token 验证失败,返回 401,鉴权失败的提示;token 验证成功,返回接口结果。

六、 路由失败处理

当请求路由地址不匹配或断言为 false 时,Gateway 会默认返回 Whitelabel Error Page 错误页面,这种错误提示不符合我们业务需求。

1. 我们可以自定义返回一个较为友好的错误提示,需要创建一个类继承 DefaultErrorWebExceptionHandler 类,重写其方法:
 

public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
 
    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes,
                                      ResourceProperties resourceProperties,
                                      ErrorProperties errorProperties,
                                      ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }
 
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
 
    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
        Map<String, Object> errorMap = getErrorAttributes(request, includeStackTrace);
        int status = Integer.valueOf(errorMap.get("status").toString());
        Map<String, Object> response = this.response(status, errorMap.get("error").toString(), errorMap);
        return ServerResponse.status(status).contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(response));
    }
 
    // 我们希望返回的数据结构
    public static Map<String, Object> response(int status, String errorMessage, Map<String, Object> errorMap) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", status);
        map.put("message", errorMessage);
        map.put("data", errorMap);
        return map;
    }
}

2. 配置 Bean 实例:

@Configuration
public class GatewayConfiguration {
 
    private final ServerProperties serverProperties;
 
    private final ApplicationContext applicationContext;
 
    private final ResourceProperties resourceProperties;
 
    private final List<ViewResolver> viewResolvers;
 
    private final ServerCodecConfigurer serverCodecConfigurer;
 
    public GatewayConfiguration(ServerProperties serverProperties,
                                ApplicationContext applicationContext,
                                ResourceProperties resourceProperties,
                                ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
 
 
    @Bean("myErrorWebExceptionHandler")
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler myErrorWebExceptionHandler(ErrorAttributes errorAttributes) {
 
        MyErrorWebExceptionHandler exceptionHandler = new MyErrorWebExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
 
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

3. 保存后重启网关项目,请求一个错误的接口地址,结果如下:

 七、跨域问题

针对 PC 端的页面请求,如果项目前后端分离,则请求会出现跨域请求问题。为什么呢?接着看。

URL 由协议、域名、端口和路径组成,如果两个 URL 的协议、域名和端口相同,则表示它们同源,否则反之。

浏览器提供同源策略,限制了来自不同源的 document 或脚本,对当前 document 读取或设置某些属性。其目的是为了保证用户信息的安全,防止恶意的网站窃取数据。

下面笔者演示跨域问题,编写一个简单页面:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <button id="sendBtn">发送请求</button>
    <script src="jquery.min.js"></script>
    <script type="text/javascript">
        $(function() {
            $("#sendBtn").on("click", function() {
                $.ajax({
                    type: "GET",
                    url: "http://localhost:9090/user-api/user/findById/3?token=123456",
                    success: function(resp) {
                        console.log(resp);
                    }
                })
            });
        });
    </script>
</body>
</html>

启动一个服务容器(笔者采用 sublime 的插件),分配了 10800 端口,请求结果如下:

由于请求端的端口与网关端口不一致,不是同源,因此出现跨域问题。

解决方案有两种,如下:

方式一:修改配置文件

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
              allowedOrigins: "*"
              allowedMethods: "*"
              allowedHeaders: "*"

方式二:配置 CorsWebFilter 过滤器

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

八、整合 Sentinel

网关作为微服务,我们也可以对其进行限流和降级操作

注意:配置前记得启动 Sentinel 控制台。

8.1 基础整合

1. 添加依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
    
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
复制代码

2. 修改配置文件,连接 Sentinel 控制台:

spring:
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8081
复制代码

3. 配置 Sentinel Filter 实例

@Configuration
public class GatewayConfiguration {
 
    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}
复制代码

最后,重启网关微服务,在 Sentinel 控制台查看或配置规则即可

8.2 异常处理器

在 Sentinel 控制台配置规后,服务出现限流或降级时,我们需要服务端返回友好的异常信息,而不是一个简单的错误页面。

我们需要配置 BlockRequestHandler 实例。

@Configuration
public class GatewayConfiguration {
 
    @Bean
    @Order(-1)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
 
    @Bean(name = "myBlockRequestHandler")
    public BlockRequestHandler myBlockRequestHandler() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @SneakyThrows
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
 
                Result result;
                if (throwable instanceof FlowException) {
                    result = Result.builder().code(100).msg("接口限流了").build();
 
                } else if (throwable instanceof DegradeException) {
                    result = Result.builder().code(101).msg("服务降级了").build();
 
                } else if (throwable instanceof ParamFlowException) {
                    result = Result.builder().code(102).msg("热点参数限流了").build();
 
                } else if (throwable instanceof SystemBlockException) {
                    result = Result.builder().code(103).msg("触发系统保护规则").build();
 
                } else if (throwable instanceof AuthorityException) {
                    result = Result.builder().code(104).msg("授权规则不通过").build();
                } else {
                    result = Result.builder().code(105).msg("sentinel 未知异常").build();
                }
 
                return ServerResponse.status(HttpStatus.BAD_GATEWAY)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue(new ObjectMapper().writeValueAsString(result)));
            }
        };
        return blockRequestHandler;
    }
 
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(BlockRequestHandler myBlockRequestHandler) {
    
        //重定向bloack处理
        //GatewayCallbackManager.setBlockHandler(new RedirectBlockRequestHandler("https://www.extlight.com"));
        
        //自定义bloack处理
        GatewayCallbackManager.setBlockHandler(myBlockRequestHandler);
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }
}
@Data
@Builder
public class Result {
 
    private int code;
 
    private String msg;
}

注意:当多个 Bean 上都配置 @Order 注解时,要多留意 order 值,否则接口请求后达不到预期效果

Spring Cloud Alibaba是一个基于Spring Cloud的一组开源框架和组件,旨在为微服务架构提供解决方案。Sentinel GatewaySpring Cloud Alibaba中的一部分,是一个高性能的API网关,用于管理和保护微服务的访问权限。 2022年的Sentinel Gateway将在当前的功能基础上进行进一步的优化和增强。首先,它将提供更加灵活和强大的流量控制和熔断降级机制,以便更好地应对高并发和流量高峰情况。这将更好地保护后端微服务的稳定性和可靠性,确保系统的正常运行。 其次,Sentinel Gateway将提供更好的安全防护能力,通过集成常见的安全防护机制,如黑名单、白名单、IP过滤等,保护系统免受恶意攻击和非法访问。这将提升整个微服务架构的安全性,保护敏感数据和业务逻辑的安全性。 此外,Sentinel Gateway还将提供更加强大和灵活的路由配置功能,允许用户根据具体需求和策略进行动态路由转发。这将使得微服务架构在面对复杂的网络环境和多样化的服务调用场景时能够更加灵活和高效地进行消息传递和数据交换。 最后,Sentinel Gateway将进一步完善其监控和统计功能,提供更加全面和准确的系统运行状态和性能指标数据。这将帮助用户更好地理解和掌握系统的运行状况,及时发现和解决潜在的问题,提升整个微服务架构的可管理性和可维护性。 综上所述,2022年的Sentinel Gateway将在流量控制、熔断降级、安全防护、动态路由、监控统计等方面进行进一步的优化和增强,为微服务架构提供更加强大和可靠的API网关解决方案。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值