theme: channing-cyan
网关
网关,对内,是内部服务访问外部网站的出口;对外,是外部网站访问内部资源的入口。
网关就像一个管家,作为内外服务的一道屏障,可以很好的保护内部服务资源的安全,流量监控、统一认证、避免直接暴露内部服务信息等。
网关工作原理
可以把网关看作一道屏障,外部请求先经过网关,然后由网关转发至后端服务,后端服务处理后,将响应结果经网关返回给调用方:
流量统一收口到网关,我们可以利用网关做很多通用的事情,比如:
- 权限认证、解析
- 指标收集
- ...
日益成熟的 web 市场,现成的网关组件有很多,比如常见的 Spring Cloud Gateway、Netflix 的 zull 网关等。
网关的基本设计思路:
- 路由:能够将请求转发至对应的服务,这是网关的核心能力,转发规则可以多种多样
- 过滤器:网关需要易于扩展的 filter、可插拔式的,这里就会用到常见的责任链模式。
本文主要讲解 web 服务请求过程中经常使用的网关 zull 相关原理和实践。
zull 网关
基本配置
一份常见的 zull 网关配置:
server:
port: 8080
spring:
application:
name: zuul-gateway
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
prefix: /api
ignored-services: '*'
routes:
service1:
path: /service1/**
serviceId: service1
stripPrefix: false
service2:
path: /service2/**
serviceId: service2
stripPrefix: false
设置了所有路由的前缀为/api,然后我们设置了 ignored-services为'*'
,这意味着 Zuul 将不会自动创建 Eureka 服务的路由。
- 然后定义了两个路由规则,所有以 /service1/ 开头的请求都会被路由到 service1 服务,所有以 /service2/ 开头的请求都会被路由到 service2 服务。
- stripPrefix 设置为 false 意味着在路由请求时,不会去掉路径前缀。
- retryable 设置为 true 意味着当请求失败时,Zuul 会尝试重新发送请求。
默认规则
如果你没有在 Zuul 的配置中明确指定要转发的服务,那么 Zuul 默认会将所有从 Eureka 服务注册中心注册的服务都创建路由规则。
例如,如果你有一个名为 service1 的服务在 Eureka 注册中心注册,那么 Zuul 默认会创建一个如下的路由规则:
zuul:
routes:
service1:
path: /service1/**
serviceId: service1
这意味着所有以 /service1/ 开头的请求都会被路由到 service1 服务。
如果你不希望 Zuul 自动创建路由规则,你可以在配置中添加 ignored-services: '*'
,这会告诉 Zuul 忽略所有服务:
zuul:
ignored-services: '*'
然后,你可以手动添加你希望Zuul转发的服务。例如:
zuul:
routes:
service1:
path: /my-service1/**
serviceId: service1
在这个例子中,所有以 /my-service1/ 开头的请求都会被路由到 service1 服务。
路径模式
在 Zuul 的配置中,可以为每个服务定义一个或多个路径模式,Zuul 会将匹配这些模式的请求转发到相应的服务。
例如,以下配置将所有以 /service1/ 开头的请求转发到 service1 服务,将所有以 /service2/ 开头的请求转发到 service2 服务:
zuul:
routes:
service1:
path: /service1/**
serviceId: service1
service2:
path: /service2/**
serviceId: service2
你也可以为同一个服务定义多个路径模式,例如:
zuul:
routes:
service1:
path: /service1/**,/api/service1/**
serviceId: service1
在这个例子中,所有以 /service1/或/api/service1/ 开头的请求都会被转发到 service1 服务。
按 URL 转发
在一些特殊的场景,你可能想要隐藏后端接口,需要在网关按接口维度
做映射,比如:前端接口:/front/getUser
-> 后端接口:/user-service/baseInfo
Zull 没有做到这样细粒度,不过它的 Filter 机制很方便扩展。
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import javax.servlet.http.HttpServletRequest;
public class DynamicRouteFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre"; // 在请求被路由之前调用
}
@Override
public int filterOrder() {
return 0; // filter执行顺序,通过数字指定
}
@Override
public boolean shouldFilter() {
// 这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// ...
Map<String, Object> routeMap;
String uri = requestContext.getRequest().getRequestURI();
// ...
if(routeMap.contains(uri)) {
ctx.setSendZuulResponse(true); // 对请求进行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
} else {
ctx.setSendZuulResponse(false); // 不对其进行路由
ctx.setResponseStatusCode(401); // 返回错误码
ctx.setResponseBody("IP address is not allowed"); // 返回错误内容
ctx.set("isSuccess", false);
}
return null;
}
}
在以上 routeMap 中,你可以自定义前后端接口映射配置,然后在此处判断当前请求是否配置了映射关系,进而决定是转发还是响应错误码。
ZuulFilter 接口定义了 Zuul 过滤器的基本结构,包含两个方法:
- shouldFilter():这个方法决定了是否需要执行 run() 方法。如果返回 true,则执行run() 方法;如果返回 false,则不执行。
- run():这是过滤器的核心方法,当 shouldFilter() 返回 true 时,该方法会被调用。这个方法可以包含过滤器的具体逻辑,例如安全验证、限流控制等。如果在执行过程中发生错误,会抛出 ZuulException 异常。
生命周期
以一条请求为例,先看看一条网关的全生命周期:
- 预过滤(Pre Filter) :这是请求在进入 Zuul 路由之前执行的过滤,通常用于实现身份验证、记录调试信息、决定是否需要对请求进行路由等。
- 路由(Routing Filter) :在这个阶段,Zuul 会根据设定的规则将请求路由到对应的服务实例。
- 后过滤(Post Filter) :这是在路由到微服务后执行的过滤器,这个过滤器将会对返回的数据进行处理,比如添加 HTTP Header、收集统计和指标信息、将响应输出到客户端等。
- 错误处理(Error Filter) :在整个生命周期中任何阶段发生错误时都会进入该过滤器,该过滤器用于统一处理请求过程中出现的异常。
// Pre Filter
public class SimplePreFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
return null;
}
}
// Post Filter
public class SimplePostFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("Response Status : " + ctx.getResponseStatusCode());
return null;
}
}
// Error Filter
public class SimpleErrorFilter extends ZuulFilter {
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("Error occurred, request to " + request.getRequestURL().toString());
return null;
}
}
以上例子中,定义了三个过滤器:预过滤器、后过滤器和错误过滤器。
- 预过滤器记录了请求的HTTP方法和URL
- 后过滤器记录了响应的状态码
- 错误过滤器记录了发生错误的请求URL。
以上便是 zull 网关的核心原理以及实践,欢迎交流探讨!