【深入理解SpringCloud微服务】Gateway简介与模拟Gateway手写一个微服务网关

Gateway简介与模拟Gateway手写一个微服务网关

Gateway简介

网关的作用

由于微服务架构下,我们的系统通常被拆分为多个服务,此时缺少一个统一的入口,外部客户端调用将成为一个问题。因此需要网关作为统一入口,让网关帮我们做路由转发

单是微服务网关一般不会作为系统最前端的入口,在微服务网关前面还有一层负载均衡

除了路由转发外,微服务网关还具有认证、鉴权、安全策略、防刷、流控、监控日志等。

Gateway原理

核心概念

Gateway有三个核心概念需要了解:路由(route)、断言(predicates)、过滤器(Filter),它们都是yml配置文件中的配置。

路由(route)

路由是yml配置文件中最基础的部分,它在yml配置文件中就是routes配置,routes配置是数组格式,数组中每个配置项是一个路由配置,每个路由配置包括一个唯一的id、目标URI、一组断言(predicates)、一组过滤器(Filter)。

id用于唯一定位一条路由配置,目标URI表示路由转发的目标地址。

断言(predicates)

断言指的是断言函数,用于与请求中的各种请求信息(比如请求路径或请求头)匹配,如果匹配结果为true,则使用该条路由配置;匹配结果为false则不使用该条路由配置。

过滤器(Filter)

Gateway中的过滤器,Gateway中的所有功能(路由转发、认证、鉴权等等)都在过滤器中实现,Gateway会把所有的过滤器组成一个过滤器链对请求和响应进行处理。

工作原理

Gateway是依赖于spring-webflux的,底层原理如下:

Gateway基于Spring-WebFlux ,实现了WebFlux的两个核心组件HandleMappingWebHandler

  1. 首先HandleMapping会加载路由配置,并调用路由配置中的断言函数进行匹配,如果匹配成功,则使用该路由规则。
  2. WebHandler会把路由配置中指定的过滤器(GatewayFilter)和全局过滤器(GlobalFilter)组装成过滤器链,然后调用过滤器链进行请求处理。

模拟Gateway手写一个微服务网关

熟悉了Gateway的功能和原理之后,我们可以参考Gateway着手设计并实现我们自己的微服务网关。

架构设计

我们设计的微服务网关也是基于Spring-WebFlux的,我们实现了Spring-WebFlux定义的两个组件接口HandlerMapping和WebHandler,分别是GatewayHandlerMappingFilteringWebHandler

当webflux接收到请求时,会调用我们的GatewayHandlerMapping获取Handler。

GatewayHandlerMapping会组装拦截器链(我们这里跟Gateway不一样,拦截器链的组装放到HandlerMapping来做了),先加入是三个固定的拦截器,分别是LoadBalanceGatewayFilterHttpRoutingGatewayFilterWriteResponseGatewayFilter,然后通过SPI机制加载用户自定义的拦截器(如果有的话),然后与请求进行匹配,匹配成功的拦截器也会加入到拦截器链中,然后对拦截器链中的拦截器进行排序。

然后GatewayHandlerMapping返回FilteringWebHandler。WebFlux获取到GatewayHandlerMapping返回的FilteringWebHandler,然后调用FilteringWebHandler对请求进行处理。

FilteringWebHandler会按顺序调用拦截器链中的拦截器进行请求处理。

拦截器链中最后的三个拦截器的作用分别是:

  • LoadBalanceGatewayFilter:负载均衡
  • HttpRoutingGatewayFilter:根据负载均衡得出的结果请求目标后端服务
  • WriteResponseGatewayFilter:后端服务返回的处理结果发送给客户端

源码解析

META-INF/spring.factories

bash 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.huangjunyi1993.simple.microservice.gateway.config.GatewayConfig

GatewayConfig

java 复制代码
@Configuration
@EnableConfigurationProperties({GatewayProperties.class})
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class GatewayConfig {

    @Bean
    public GatewayHandlerMapping gatewayHandlerMapping() {
        return new GatewayHandlerMapping();
    }

    @Bean
    public FilteringWebHandler filteringWebHandler() {
        return new FilteringWebHandler();
    }

    @Bean
    public LoadBalanceGatewayFilter loadBalanceGatewayFilter() {
        return new LoadBalanceGatewayFilter();
    }

    @Bean
    public HttpRoutingGatewayFilter httpGatewayFilter() {
        return new HttpRoutingGatewayFilter();
    }

    @Bean
    public WriteResponseGatewayFilter writeResponseGatewayFilter() {
        return new WriteResponseGatewayFilter();
    }

}

我们通过SpringBoot的自动装配机制加载我们的配置类GatewayConfig,通过GatewayConfig向Spring容器注册我们的核心类型:GatewayHandlerMapping、FilteringWebHandler以及三个固定的拦截器。

GatewayHandlerMapping

java 复制代码
/**
 * @author huangjunyi
 * @date 2024/1/5 18:09
 * @desc
 */
public class GatewayHandlerMapping extends AbstractHandlerMapping {

    ...

    public GatewayHandlerMapping() {
        setOrder(1);
    }

    @Override
    protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
        // 1、通过SPI加载并收集与当前请求匹配的所有filter,保存到ServerWebExchange
        buildFilterChain(exchange);

        // 2、返回FilteringWebHandler
        return Mono.just(webHandler);
    }

    private void buildFilterChain(ServerWebExchange exchange) {
    	// 组装拦截器链
        List<GatewayFilter> filterList = new ArrayList<>();

		// 加入三个固定拦截器
        filterList.add(loadBalanceGatewayFilter);
        filterList.add(httpRoutingGatewayFilter);
        filterList.add(writeResponseGatewayFilter);

		// 通过SPI机制加载用户自定义的拦截器
        ServiceLoader<GatewayFilter> gatewayFilters = ServiceLoader.load(GatewayFilter.class);
        for (GatewayFilter gatewayFilter : gatewayFilters) {
        	// 判断是否与本次请求匹配
            if (gatewayFilter.match(exchange)) {
                filterList.add(gatewayFilter);
            }
        }

		// 拦截器排序
        filterList.sort(Comparator.comparing(GatewayFilter::order));
	
		// 拦截器链保存到ServerWebExchange的attributes中
        exchange.getAttributes().put(FILTER_LIST, filterList);
    }
}

GatewayHandlerMapping继承了AbstractHandlerMapping 并实现了它的抽象方法getHandlerInternal(ServerWebExchange),getHandlerInternal(ServerWebExchange)方法中首先会构建拦截器链,然后返回FilteringWebHandler。

拦截器链的构建就如我们上面架构设计中描述的那样:

  1. 加入三个固定拦截器
  2. 通过SPI机制加载自定义的拦截器,判断是否与本次请求匹配,匹配则放入拦截器链中
  3. 拦截器排序
  4. 拦截器链保存到ServerWebExchangeattributes

FilteringWebHandler

java 复制代码
/**
 * @author huangjunyi
 * @date 2024/1/5 19:23
 * @desc
 */
public class FilteringWebHandler implements WebHandler {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        // 从ServerWebExchange的attributes中获取过滤器链
        List<GatewayFilter> filterList = (List<GatewayFilter>) exchange.getAttributes().get(GatewayHandlerMapping.FILTER_LIST);
        Mono<Void> mono = Mono.empty();
        // 遍历并执行过滤器
        for (GatewayFilter gatewayFilter : filterList) {
            mono = gatewayFilter.filter(exchange, mono);
        }
        return mono;
    }
}

FilteringWebHandler实现了WebFlux定义的WebHandler 接口并实现了它的handle(ServerWebExchange) 方法。handle方法中从ServerWebExchange的attributes中获取过滤器链,然后遍历并执行过滤器。

GatewayFilter

GatewayFilter是我们定义的过滤器接口。

java 复制代码
/**
 * 过滤器
 * @author huangjunyi
 * @date 2024/1/5 18:01
 * @desc
 */
public interface GatewayFilter {

    /**
     * 过滤器排序
     * @return
     */
    int order();

    /**
     * 过滤器逻辑
     * @param exchange
     * @return
     */
    Mono<Void> filter(ServerWebExchange exchange, Mono<Void> mono);

    /**
     * 判断当前过滤器是否与当前请求匹配
     * @param exchange
     * @return
     */
    boolean match(ServerWebExchange exchange);

}

GatewayFilter#order() 方法用于排序,GatewayFilter#match(ServerWebExchange) 方法用于判断当前过滤器是否与当前请求匹配,GatewayFilter#filter(ServerWebExchange, Mono<Void>) 方法是过滤器的处理逻辑。

LoadBalanceGatewayFilter
java 复制代码
/**
 * 负责均衡过滤器
 * @author huangjunyi
 * @date 2024/1/5 19:25
 * @desc
 */
public class LoadBalanceGatewayFilter implements GatewayFilter {

    public static final String RECONSTRUCTED_URL = "reconstructedUrl";

    @Autowired
    private LoadBalanceClient loadBalanceClient;

    @Override
    public int order() {
        return Integer.MAX_VALUE - 2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, Mono<Void> mono) {
        // 1、从url中取到serviceName,
        // 比如http://localhost:8080/userService/xxxxx,则取到userService
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().toString();
        String serviceName;
        boolean startsWithHttps = url.startsWith("https");
        String temp = url.replace(startsWithHttps ? "https://" : "http://", "");
        int firstSeparatorIndex = temp.indexOf("/");
        temp = temp.substring(firstSeparatorIndex + 1);
        serviceName = temp.contains("/") ? temp.substring(0, temp.indexOf("/")) : temp;

        // 2、重新组装调用后端微服务的url,
        // 比如http://localhost:8080/userService/xxxxx,则重新组装为http://userService/xxxxx
        url = (startsWithHttps ? "https://" : "http://") + temp;

        // 3、调用loadBalanceClient重写url,里面会进行负载均衡,根据负载均衡结果重写url,保存到重写后的url到ServerWebExchange中
        String reconstructedUrl = loadBalanceClient.reconstructUrl(serviceName, url);
        exchange.getAttributes().put(RECONSTRUCTED_URL, reconstructedUrl);
        return Mono.empty();
    }

    @Override
    public boolean match(ServerWebExchange exchange) {
        return true;
    }

}

LoadBalanceGatewayFilter的filter方法处理流程:

  1. 首先从url中取到serviceName,比如http://localhost:8080/userService/xxxxx,那么取得的serviceName就是userService。
  2. 重新组装调用后端微服务的url,比如http://localhost:8080/userService/xxxxx,则重写组装为http://userService/xxxxx。
  3. 调用loadBalanceClient 重写url,里面会进行负载均衡 ,根据负载均衡结果重写url ,保存到重写后的url到ServerWebExchange中。
HttpRoutingGatewayFilter
java 复制代码
/**
 * 向后端微服务发起http请求
 * @author huangjunyi
 * @date 2024/1/5 19:26
 * @desc
 */
public class HttpRoutingGatewayFilter implements GatewayFilter {

    public static final String CONNECTION = "connection";

    @Override
    public int order() {
        return Integer.MAX_VALUE - 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, Mono<Void> mono) {
    	// 取得LoadBalanceGatewayFilter负载均衡重写后的url
        String url = (String) exchange.getAttributes().get(LoadBalanceGatewayFilter.RECONSTRUCTED_URL);
        // 取得ServerWebExchange中的请求头信息
        Map<String, String> headerMap = exchange.getRequest().getHeaders().toSingleValueMap();
        // 请求方法(GET、POST)
        final io.netty.handler.codec.http.HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
        // 取得ServerWebExchange中的请求体信息
        Flux<DataBuffer> body = exchange.getRequest().getBody();
        // 响应对象
        ServerHttpResponse response = exchange.getResponse();
		// 使用Netty的HttpClient重新组装请求信息并发送
        return HttpClient.create()
        		// 请求头
                .headers(headers -> {
                    for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                        headers.add(entry.getKey(), entry.getValue());
                    }
                })
                // 请求方法
                .request(method)
                // 请求url
                .uri(url)
                // 发送请求
                .send((req, nettyOutbound) -> nettyOutbound.send(body.map(this::getByteBuf)))
                // 处理后端的返回结果
                .responseConnection((res, connection) -> {
					// 状态码保存到response对象
                    HttpStatus status = HttpStatus.resolve(res.status().code());
                    if (status != null) {
                        response.setStatusCode(status);
                    }
					// 响应头保存到response对象
                    HttpHeaders headers = new HttpHeaders();
                    res.responseHeaders().forEach(
                            entry -> headers.add(entry.getKey(), entry.getValue()));
                    response.getHeaders().putAll(headers);
					
                    exchange.getAttributes().put(CONNECTION, connection);
                    return Mono.just(res);
                }).then();
    }

    @Override
    public boolean match(ServerWebExchange exchange) {
        return true;
    }

}

HttpRoutingGatewayFilter干的事情就是把客户端发送过来的请求信息(请求头、请求体)重新封装一下,然后使用NettyHttpClient发送请求给后端微服,再把后端返回的结果重新封装一下返回。

WriteResponseGatewayFilter
java 复制代码
/**
 * 返回结果给客户端
 * @author huangjunyi
 * @date 2024/1/9 19:46
 * @desc
 */
public class WriteResponseGatewayFilter implements GatewayFilter {
    @Override
    public int order() {
        return Integer.MAX_VALUE;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, Mono<Void> mono) {
        return mono.then(writeResponse(exchange));
    }

    private Mono<Void> writeResponse(ServerWebExchange exchange) {
        return Mono.defer(() -> {
        	// 取得ServerWebExchange中的响应对象
            ServerHttpResponse response = exchange.getResponse();
            Connection connection = exchange.getAttribute(HttpRoutingGatewayFilter.CONNECTION);
            // 取得HttpRoutingGatewayFilter中的后端返回结果,做一下转换
            Flux<DataBuffer> responseBody = connection
                    .inbound()
                    .receive()
                    .retain()
                    .map(byteBuf -> wrap(byteBuf, response));
			// 响应结果写回给客户端
            return response.writeWith(responseBody);
        });
    }

    @Override
    public boolean match(ServerWebExchange exchange) {
        return true;
    }
}

WriteResponseGatewayFilter的作用就是把将HttpRoutingGatewayFilter中取得的后端返回结果,做一下转换,然后写回客户端。

整体流程图:

gitee仓库地址simple-microservice-gateway

相关推荐
用户2190326527352 小时前
别再到处try-catch了!SpringBoot全局异常处理这样设计
java·spring boot·后端
梁同学与Android2 小时前
Android ---【经验篇】阿里云 CentOS 服务器环境搭建 + SpringBoot项目部署(二)
android·spring boot·后端
我是小妖怪,潇洒又自在2 小时前
springcloud alibaba(十)分布式事务
分布式·spring cloud·wpf
用户2190326527352 小时前
SpringBoot自动配置:为什么你的应用能“开箱即用
java·spring boot·后端
汤姆Tom2 小时前
前端转战后端:JavaScript 与 Java 对照学习指南(第五篇 —— 面向对象:类、接口与多态)
java·前端·后端
巴塞罗那的风2 小时前
从蓝图到执行:智能体中的“战略家思维
开发语言·后端·ai·语言模型·golang
喵了几个咪2 小时前
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:kratos-bootstrap 入门教程(类比 Spring Boot)
spring boot·后端·微服务·golang·bootstrap