本文小新为大家带来 微服务网关Gateway组件 相关知识,具体内容包括微服务网关Gateway组件
(包括:Gateway核心概念
,Gateway工作原理
),Spring Cloud Gateway环境搭建
,路由断言工厂(Route Predicate Factories)配置
,过滤器工厂( Gateway Filter Factories)配置
,全局过滤器(Global Filters)配置
,Gateway跨域配置(CORS Configuration)
,Gateway整合sentinel流控降级
,网关高可用
等进行详尽介绍~
不积跬步,无以至千里;不积小流,无以成江海。每天进步一点点,在成为强者的路上,小新与大家共同成长!
博主主页:小新要变强 的主页
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)
↩️本文上接:Spring Cloud Alibaba全家桶(九)——分布式事务组件Seata
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?
如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去用。
这样的架构,会存在着诸多的问题:
上面的这些问题可以借助API网关来解决:
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示:
我们也可以观察下,我们现在的整体架构图:
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
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 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
其他的网关组件:
在SpringCloud微服务体系中,有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway。
网上很多地方都说Zuul是阻塞的,Gateway是非阻塞的,这么说是不严谨的,准确的讲Zuul1.x是阻塞的,而在2.x的版本中,Zuul也是基于Netty,也是非阻塞的,如果一定要说性能,其实这个真没多大差距。
而官方出过一个测试项目,创建了一个benchmark的测试项目:spring-cloud-gateway-bench,其中对比了以下网管组件:
Spring Cloud Gateway官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
Spring Cloud Gateway 功能特征:
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
执行流程大体如下:
<!‐‐ gateway网关 ‐‐>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring‐cloud‐starter‐gatewayartifactId>
dependency>
注意: 会和spring-webmvc的依赖冲突,需要排除spring-webmvc。
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: lb://alibaba-order-seata #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 order-service服务名
#断言规则 用于路由规则的匹配
predicates:
‐ Path=/product‐serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
‐ StripPrefix=1 # 转发之前去掉1层路径.
现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从注册中心获取此地址。
(1)引入依赖
<!‐‐ nacos服务注册与发现 ‐‐>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring‐cloud‐starter‐alibaba‐nacos‐discoveryartifactId>
dependency>
(2)编写yml配置文件
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: lb://alibaba-order-seata #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 order-service服务名
#断言规则 用于路由规则的匹配
predicates:
‐ Path=/product‐serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
‐ StripPrefix=1 # 转发之前去掉1层路径.
简写: 去掉关于路由的配置,自动寻找服务
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
# gateway的配置
gateway:
discovery:
locator:
enabled: true #是否启动自动识别nacos服务
(3)测试
这时候,就发现只要按照网关地址/微服务/接口的格式去访问,就可以得到成功响应。
参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
作用: 当请求gateway的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发, 如果匹配失败就返回404
类型: 内置,自定义
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:
此类型的断言根据时间做判断,主要有三个:
ZonedDateTime.now():
‐ After=2019‐12‐31T23:59:59.789+08:00[Asia/Shanghai]
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
‐ RemoteAddr=192.168.1.1/24
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。
‐Cookie=chocolate, ch.
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。
‐Header=X‐Request‐Id, \d+
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
‐Host=**.testhost.org
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
‐Method=GET
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
‐Path=/foo/{segment}
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
‐Query=baz, ba.
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。
routes:
‐ id: weight_route1
uri: host1
predicates:
‐ Path=/product/**
‐ Weight=group3, 1
‐ id: weight_route2
uri: host2
predicates:
‐ Path=/product/**
‐ Weight= group3, 9
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
注意: 命名需要以 RoutePredicateFactory 结尾
@Component
public class CheckAuthRoutePredicateFactory
extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
@Override
public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
if(config.getName().equals("xushu")){
return true;
}
return false;
}
};
}
// 用于接收配置文件中 断言的信息
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
yml中配置:
server:
port: 8088
spring:
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: http://localhost:8020 #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 order-service服务名
#断言规则 用于路由规则的匹配
#测试:http://localhost:8888/order/findOrderByUserId/1
predicates:
- Path=/order/**
- CheckAuth=xushu2
Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等。
参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true 的 属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正 则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
spring:
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: http://localhost:8020 #需要转发的地址
#配置过滤器工厂
filters:
‐ AddRequestHeader=X‐Request‐color, red #添加请求头
测试:http://localhost:8888/order/testgateway
@GetMapping("/testgateway")
public String testGateway(HttpServletRequest request) throws Exception {
log.info("gateWay获取请求头X‐Request‐color:" +request.getHeader("X‐Request‐color"));
return "success";
}
@GetMapping("/testgateway2")
public String testGateway(@RequestHeader("X‐Request‐color") String color) throws Exception {
log.info("gateWay获取请求头X‐Request‐color:"+color);
return "success";
}
spring:
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂在这里插入代码片
filters:
‐ AddRequestParameter=color, blue # 添加请求参数
测试:http://localhost:8888/order/testgateway3
@GetMapping("/testgateway3")
public String testGateway3(@RequestParam("color") String color) throws Exception {
log.info("gateWay获取请求参数color:"+color);
return "success";
}
spring:
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂在这里插入代码片
filters:
‐ PrefixPath=/mall‐order # 添加前缀 对应微服务需要配置context‐path
mall-order中需要配置:
server:
servlet:
context‐path: /mall‐order
测试:http://localhost:8888/order/findOrderByUserId/1====》 http://localhost:8020/mallorder/order/findOrderByUserId/1
spring:
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂在这里插入代码片
filters:
‐ RedirectTo=302, https://www.baidu.com/ #重定向到百度
测试:http://localhost:8888/order/findOrderByUserId/1
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) ‐> {
log.info("调用CheckAuthGatewayFilterFactory===" + config.getName() + ":" + config.getValue());
return chain.filter(exchange);
};
}
}
配置自定义的过滤器工厂:
spring:
cloud:
# gateway的配置
gateway:
#路由规则
routes:
- id: order_route # 路由的唯一标识,路由到order
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂在这里插入代码片
filters:
‐ CheckAuth=fox,男
测试:
局部过滤器和全局过滤器区别:
GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。
LoadBalancerClientFilter 会查看exchange的属性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉ServerWebExchangeUtils. GATEWAY_REQUEST_URL_ATTR 的内容。
其实就是用来整合负载均衡器Ribbon的:
spring:
cloud:
gateway:
routes:
- id: order_route
uri: lb://mall‐order
predicates:
‐ Path=/order/**
@Component
public class LogFilter implements GlobalFilter {
Logger log= LoggerFactory.getLogger(this.getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
(1)通过yml配置的方式
通过yml配置的方式参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
spring:
cloud:
gateway:
# 跨域配置
globalcors:
cors-configurations:
'[/**]': # 允许跨域访问的资源
allowedOrigins: "*" #跨域允许来源
allowedMethods:
- GET
- POST
(2)通过java配置的方式:
@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);
}
}
网关作为内部系统外的一层屏障, 对内起到一定的保护作用, 限流便是其中之一。网关层的限流可以简单地针对不同路由进行限流,也可针对业务的接口进行限流,或者根据接口的特征分组限流。
参考文档:https://github.com/alibaba/Sentinel/wiki/网关限流
(1)添加依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring‐cloud‐alibaba‐sentinel‐gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring‐cloud‐starter‐alibaba‐sentinelartifactId>
dependency>
(2)添加配置
sentinel:
transport:
# 添加sentinel的控制台地址
dashboard: 127.0.0.1:8080
Sentinel 1.6.3 引入了网关流控控制台的支持,用户可以直接在 Sentinel 控制台上查看 API Gateway 实时的 route 和自定义 API 分组监控,管理网关规则和 API 分组配置。
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
自定义异常方式:
(1)通过yml
spring:cloud.sentinel.scg.fallback.mode = response
spring.cloud.sentinel.scg.fallback.response‐body = '{"code":403,"mes":"限流了"}'
(2)通过GatewayCallbackManager
@Configuration
public class GatewayConfig {
@PostConstruct
public void init(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable throwable) {
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue("降级了!"));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
用户可以通过 GatewayRuleManager.loadRules(rules) 手动加载网关规则
GatewayConfiguration中添加。
@PostConstruct
public void doInit() {
//初始化网关限流规则
initGatewayRules();
//自定义限流异常处理器
initBlockRequestHandler();
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
//resource:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
//count:限流阈值
//intervalSec:统计时间窗口,单位是秒,默认是 1 秒。
rules.add(new GatewayFlowRule("order_route")
.setCount(2)
.setIntervalSec(1));
rules.add(new GatewayFlowRule("user_service_api")
.setCount(2)
.setIntervalSec(1)
);
// 加载网关规则
GatewayRuleManager.loadRules(rules);
}
private void initBlockRequestHandler() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
HashMap<String, String> result = new HashMap<>();
result.put("code",String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
result.put("msg", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
};
//设置自定义异常处理器
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
为了保证 Gateway 的高可用性,可以同时启动多个 Gateway 实例进行负载,在 Gateway 的上游使用 Nginx 或者 F5 进行负载转发以达到高可用。
↪️本文下接:XXXX
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~