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的两个核心组件HandleMapping 和WebHandler。
- 首先HandleMapping会加载路由配置,并调用路由配置中的断言函数进行匹配,如果匹配成功,则使用该路由规则。
- WebHandler会把路由配置中指定的过滤器(GatewayFilter)和全局过滤器(GlobalFilter)组装成过滤器链,然后调用过滤器链进行请求处理。
模拟Gateway手写一个微服务网关
熟悉了Gateway的功能和原理之后,我们可以参考Gateway着手设计并实现我们自己的微服务网关。
架构设计
我们设计的微服务网关也是基于Spring-WebFlux的,我们实现了Spring-WebFlux定义的两个组件接口HandlerMapping和WebHandler,分别是GatewayHandlerMapping 和FilteringWebHandler。
当webflux接收到请求时,会调用我们的GatewayHandlerMapping获取Handler。

GatewayHandlerMapping会组装拦截器链(我们这里跟Gateway不一样,拦截器链的组装放到HandlerMapping来做了),先加入是三个固定的拦截器,分别是LoadBalanceGatewayFilter 、HttpRoutingGatewayFilter 、WriteResponseGatewayFilter,然后通过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。
拦截器链的构建就如我们上面架构设计中描述的那样:
- 加入三个固定拦截器
- 通过SPI机制加载自定义的拦截器,判断是否与本次请求匹配,匹配则放入拦截器链中
- 拦截器排序
- 拦截器链保存到ServerWebExchange 的attributes中

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方法处理流程:
- 首先从url中取到serviceName,比如http://localhost:8080/userService/xxxxx,那么取得的serviceName就是userService。
- 重新组装调用后端微服务的url,比如http://localhost:8080/userService/xxxxx,则重写组装为http://userService/xxxxx。
- 调用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干的事情就是把客户端发送过来的请求信息(请求头、请求体)重新封装一下,然后使用Netty 的HttpClient发送请求给后端微服,再把后端返回的结果重新封装一下返回。

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
