看完就会的Spring Cloud Gateway

作者 : 开心源码 本文共12941个字,预计阅读时间需要33分钟 发布时间: 2022-05-14 共188人阅读

推荐观看:新一代微服务实战教程,揭秘SpringCloud Alibaba是如何应对淘宝双十一丨Sentinel、Nacos、RocketMQ、Hystrix、Nginx

阿里黄俊:深入Hotspot源码与Tamcat、Redis源码,多维分析高性能架构设计实战

当一个系统拆分成微服务后,会产生的问题与处理方案:服务如何发现与管理Nacos注册中心实战,服务与服务如何通信Ribbon, Feign实战

今天我们就来聊一聊另一个问题:用户端如何访问?

在单体架构时,我们的系统只有一个入口,前台人员调用起来十分的简单。

但是当我们拆分为一个微服务系统后,每个服务都有属于自己ip和端口号,我们不可能跟前台说:诶,调用这个接口的时候你就使用这个地址哈。

前台:

既然这样不行的话,那我们能不能利用已有的知识想一个处理方案呢?

不是真的能用的处理方案

其实我们很容易的就能想到,我们的服务是具有互相发现及通信的能力的,那么,我们是不是可以搞一个相似统一入口(网关)样的服务,前台只请求这个服务,由这个服务去调用真实服务的Feign接口。

举个例子:

  • 商品服务的获取商品接口:localhost:8080/get/goods
  • 订单服务的下订单接口:localhost:8081/order

现在有个网关服务, 里面有两个接口:localhost:5555/get/goods, localhost:5555/order

前台调用获取商品接口时,访问:localhost:5555/get/goods,而后网关服务调用商品服务的Feign接口

下单时:访问:localhost:5555/order,而后网关服务调用订单服务的Feign接口

小结一下:

这个方案能否处理了服务入口统一的问题:处理了

能用吗:能用,但不是完全能用

由于这样会有一个问题,服务写的每一个接口,都需要给出一个Feign接口,给我们的网关服务调用。

真正的处理方案

Spring Cloud为我们提供了一个处理方案:Spring Cloud Gateway

Spring Cloud Gateway提供了一个建立在Spring生态系统之上的API网关,能够简单而有效的方式来路由到API,并基于 Filter 的方式提供少量功能,如:安全、监控。

Spring Cloud Gateway是由Spring Boot 2.x、Spring WebFlux和Reactor实现的,需要Spring Boot和Spring Webflux提供的Netty运行环境。它不能在传统的Servlet容器中工作,也不能在以WAR形式构建时工作。

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

概念

Route(路由):网关的基本构件,它由一个ID、一个目的地URI、一个断言集合和一个过滤器集合定义。假如集合断言为真,则路由被匹配。

Predicate(断言):Java 8断言函数。参数类型是Spring Framework ServerWebExchange。可以让开发者在HTTP请求中的任何内容上进行匹配,比方头文件或者参数。

Filter(过滤):由特定的工厂构建的GatewayFilter的实例,与传统的Filter一样,能够请求前后对请求就行解决。

工作原理

用户端向Spring Cloud Gateway发出请求。假如Gateway解决程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web解决程序。这个解决程序通过一个特定于该请求的过滤器链来运行该请求。

过滤器可以在代理商请求发送之前和之后运行pre和post逻辑。

简单使用

准备

预先准备一个服务,用来测试路由

我这里准备了个一个商品服务,并提供了一个接口:http://localhost:8082/goods/get-goods

现在,开始编写网关服务

引入依赖

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

编写配置

bootstrap.yaml

server:  port: 5555spring:  application:    name: my-gateway  cloud:    nacos:      discovery:        server-addr: 127.0.0.1:8848        namespace: public        username: nacos        password: nacoslogging:  level:    org.springframework.cloud.gateway: info    com.alibaba.nacos.client.naming: warn

application.yaml

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/goods/**

启动类

package com.my.micro.service.gateway;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @author Zijian Liao * @since 1.0.0 */@SpringBootApplicationpublic class GatewayApplication {    public static void main(String[] args) {        SpringApplication.run(GatewayApplication.class, args);    }}

测试

启动服务,并访问:http://localhost:5555/goods/get-goods

可以看到,服务成功被路由了

一个简单的网关服务就这样完成了,小伙伴看完过有没有对网关的概念更加深刻呢?

断言

在上面的例子中,我们就用到了一个断言工厂:Path

在Spring Cloud Gateway中,所有的断言工厂都是继承于AbstractRoutePredicateFactory, 并且命名规则为:XxxRoutePredicateFactory, 比方Path的类名为:PathRoutePredicateFactory

那么,Spring Cloud Gateway给我们内置了哪些断言工厂呢?

文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

以下展现我觉得常用的断言工厂,更多的内容还请小伙伴自己查看文档

After

匹配在某个时间(ZonedDateTime)后的请求

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/goods/**            # 匹配23:05分后的请求            - After=2021-08-08T23:05:13.605+08:00[Asia/Shanghai]

我们在23:03进行测试

访问失败了

Before

匹配在某个时间(ZonedDateTime)前的请求

与After类似,不再演示

Between

匹配在某个时间段(ZonedDateTime)的请求

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/goods/**            # 匹配23:05-23:10的请求            - Between=2021-08-08T23:05:13.605+08:00[Asia/Shanghai],2021-08-08T23:10:13.605+08:00[Asia/Shanghai]

Host

匹配某个Host的请求

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/goods/**            #配置host为192.168.1.105请求            - Host=192.168.1.105

注意,测试时需要将端口号改为80

尝试使用127.0.0.1发起调用

改为192.168.1.105进行调用

RemoteAddr

匹配指定的远程源地址

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/goods/**            #配置RemoteAddr为192.168.1网段的地址            - RemoteAddr=192.168.1.1/24

测试

启用内网穿透测试

访问失败了

过滤器

关于过滤器这块我举个例子,更多的内容请小伙伴自己查阅文档

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

举一个用的比较多的过滤器:

StripPrefix

顾名思义,除去前缀的过滤器,将匹配的请求的前缀去除,将去除后的请求转发给下游服务

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/api/goods/**          filters:            # 1表示去除一个前缀            - StripPrefix=1

组合来看,意思是当用户端发起请求:http://localhost:5555/api/goods/get-goods, 匹配该路由,而后将第一个前缀api去除,而后转发给商品服务,转发的路径为:/goods/get-goods

测试

自己设置断言工厂

上面提到过:所有的断言工厂都是继承于AbstractRoutePredicateFactory, 并且命名规则为:XxxRoutePredicateFactory, 比方Path的类名为:PathRoutePredicateFactory

我们现在就来尝试实现一个自己设置的请求头断言工厂吧

编写代码

package com.my.micro.service.gateway.filter;import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import java.util.Arrays;import java.util.Collections;import java.util.List;import java.util.function.Predicate;/** * @author Zijian Liao * @since 1.0.0 */@Componentpublic class MyHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHeaderRoutePredicateFactory.Config> {    /**     * Header key.     */    public static final String HEADER_KEY = "header";    /**     * Regexp key.     */    public static final String REGEXP_KEY = "regexp";    public MyHeaderRoutePredicateFactory() {        super(MyHeaderRoutePredicateFactory.Config.class);    }    @Override    public List<String> shortcutFieldOrder() {        return Arrays.asList(HEADER_KEY, REGEXP_KEY);    }    @Override    public Predicate<ServerWebExchange> apply(MyHeaderRoutePredicateFactory.Config config) {        return new GatewayPredicate() {            @Override            public boolean test(ServerWebExchange exchange) {                // 获取请求头                List<String> values = exchange.getRequest().getHeaders()                        .getOrDefault(config.header, Collections.emptyList());                if (values.isEmpty()) {                    return false;                }                // 判断请求头中的值能否与配置匹配                return values.stream()                        .anyMatch(value -> value.matches(config.regexp));            }            @Override            public String toString() {                return String.format("Header: %s=%s ", config.header, config.regexp);            }        };    }    public static class Config {        private String header;        private String regexp;        public String getHeader() {            return header;        }        public void setHeader(String header) {            this.header = header;        }        public String getRegexp() {            return regexp;        }        public void setRegexp(String regexp) {            this.regexp = regexp;        }    }}

编写配置

spring:  cloud:    gateway:      # 路由配置      routes:        # 路由id, 保证唯一性        - id: my-goods          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名          uri: lb://my-goods          # 断言          predicates:            # 匹配goods开头的请求            - Path=/api/goods/**            # 匹配header为name=aljian的请求            - MyHeader=name,ajian          filters:            # 1表示去除一个前缀            - StripPrefix=1

测试

直接在浏览器中访问

改用postman访问

自己设置过滤器

自己设置过滤器的方式与自己设置断言工厂的方式大致相同,所以过滤器继承于AbstractGatewayFilterFactory或者者AbstractNameValueGatewayFilterFactory, 命名规则为XxxGatewayFilterFactory

比方内置的增加请求头过滤器

public class AddRequestHeaderGatewayFilterFactory        extends AbstractNameValueGatewayFilterFactory {    @Override    public GatewayFilter apply(NameValueConfig config) {        return new GatewayFilter() {            @Override            public Mono<Void> filter(ServerWebExchange exchange,                    GatewayFilterChain chain) {        // 获取到需要增加的header value                String value = ServerWebExchangeUtils.expand(exchange, config.getValue());                // 将header增加到request中        ServerHttpRequest request = exchange.getRequest().mutate()                        .header(config.getName(), value).build();                // 重新构建出一个exchange                return chain.filter(exchange.mutate().request(request).build());            }            @Override            public String toString() {                return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this)                        .append(config.getName(), config.getValue()).toString();            }        };    }}

全局过滤器

以上内容都是针对于每一个router,Spring Cloud Gateway提供了一个针对所有router的全局过滤器

实现方式如下

package com.my.micro.service.gateway.filter;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;/** * @author Zijian Liao * @since 1.0.0 */@Slf4j@Componentpublic class MyGlobalFilter implements GlobalFilter {    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        String path = exchange.getRequest().getURI().getPath();        log.info("进入全局过滤器,请求路径为:{}", path);        // 编写任何你想要实现的逻辑,比方权限校验        return chain.filter(exchange);    }}

测试

自己设置异常解决器

小伙伴应该发现了,在遇到错误时,Spring Cloud Gateway返回给用户端的异常并不优雅,所以我们需要自己设置异常解决

编写自己设置异常解决器

package com.my.micro.service.gateway.exception;import com.my.micro.service.gateway.result.BaseResult;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.autoconfigure.web.ErrorProperties;import org.springframework.boot.autoconfigure.web.ResourceProperties;import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;import org.springframework.boot.web.reactive.error.ErrorAttributes;import org.springframework.context.ApplicationContext;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.lang.NonNull;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.RequestPredicates;import org.springframework.web.reactive.function.server.RouterFunction;import org.springframework.web.reactive.function.server.RouterFunctions;import org.springframework.web.reactive.function.server.ServerRequest;import org.springframework.web.reactive.function.server.ServerResponse;import reactor.core.publisher.Mono;/** * @author Zijian Liao */@Slf4jpublic class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {    /**     * Create a new {@code DefaultErrorWebExceptionHandler} instance.     *     * @param errorAttributes    the error attributes     * @param resourceProperties the resources configuration properties     * @param errorProperties    the error configuration properties     * @param applicationContext the current application context     */    public JsonExceptionHandler(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);    }    @NonNull    @Override    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {        Throwable throwable = getError(request);        return ServerResponse.status(HttpStatus.OK)                .contentType(MediaType.APPLICATION_JSON)                .body(BodyInserters.fromValue(this.handleError(throwable)));    }    private BaseResult<Void> handleError(Throwable throwable){        return BaseResult.failure(throwable.getMessage());    }}

BaseResult

package com.my.micro.service.gateway.result;import lombok.Data;/** * @author Zijian Liao * @since 1.0.0 */@Datapublic class BaseResult<T> {    private Integer code;    private String message;    public BaseResult(Integer code, String message){        this.code = code;        this.message = message;    }    public static <T> BaseResult<T> failure(String  message){        return new BaseResult<>(-1, message);    }}

编写配置类

package com.my.micro.service.gateway.exception;import org.springframework.beans.factory.ObjectProvider;import org.springframework.boot.autoconfigure.web.ResourceProperties;import org.springframework.boot.autoconfigure.web.ServerProperties;import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;import org.springframework.boot.web.reactive.error.ErrorAttributes;import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;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 java.util.stream.Collectors;/** * @author Zijian Liao * @since 1.0.0 */@Configurationpublic class ExceptionConfiguration {    @Primary    @Bean    @Order(Ordered.HIGHEST_PRECEDENCE)    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties, ResourceProperties resourceProperties,                                                             ObjectProvider<ViewResolver> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer,                                                             ApplicationContext applicationContext) {        DefaultErrorWebExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,                resourceProperties, serverProperties.getError(), applicationContext);        exceptionHandler.setViewResolvers(viewResolversProvider.orderedStream().collect(Collectors.toList()));        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());        return exceptionHandler;    }}

测试

小结

本编详情了关于微服务架构中——用户端如何访问的处理方案:Spring Cloud Gateway

其中详情了Gateway的三个核心概念:Route,Predicate,Filter。并演示了如何配置及使用他们,还讲解了如何自己设置Predicate和Filter。

最后详情了Spring Cloud Gateway的全局过滤器,以及如何实现自己设置异常解决。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 看完就会的Spring Cloud Gateway

发表回复