上一篇文章《微服务分布式组件—Seata》
当设计多个微服务之后,会出现诸多的问题:
上面的这些问题我们可以通过 API
网关来解决
所谓的 API 网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、授权、监控、路由转发等等
网关作为流量入口,常用的功能包括路由转发、权限校验、限流等
SpringCloud Gateway 是 SpringCloud 官方推出的第二代网关框架,定位于取代 Neflix Zuul。相比 Zuul 来说,SpringCloud Gateway 提供更优秀的性能,更强大的功能
SpringCloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包
SpringCloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等
很多地方说 Zuul 是阻塞的,Gateway 是非阻塞的,这么说不严谨。准确地讲,Zuul 1.x 版本是阻塞的,而在 Zuul 2.x 也是基于 Netty,是非阻塞的
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter
SpringCloud Gateway 功能特性
路由是网关中最基础的部分,路由信息包括一个 ID、一个目的 URL、一组 Filter 组成。如果断言为真,则说明请求的 URL 和配置的路由匹配
Java8 中的断言函数,SpringCloud Gateway中的断言函数类型是 Spring5.0 框架中的 ServerWebExchange。断言函数允许开发者去定义匹配 Http request 中的任何信息,比如请求头和参数等
SpringCloud Gateway 中的 Gateway Filter 和 Global Filter。Filter 可以对请求和响应进行处理
1)创建一个springcloud-gateway-8088模块
2)导入依赖pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
dependencies>
3)配置application.yml
文件
server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: http://localhost:80 # 需要转发的地址
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer_serv/**
# http://localhost:8088/consumer_serv/consumer/dept/msg 路由到
# http://localhost:80/consumer_serv/consumer/dept/msg
filters:
# http://localhost:80/consumer/dept/msg
- StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
4)测试
1)修改application.yml
server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
discovery:
locator:
enabled: true # 是否启动自动识别nacos服务
# 配置 Nacos
nacos:
discovery:
# nacos 服务注册地址
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
username: nacos
password: nacos
2)启动springcloud-gateway-8088
推荐使用如下配置的application.yml
,这样能够更灵活,配置更具体
server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer_serv/**
# http://localhost:8088/consumer_serv/consumer/dept/msg 路由到
# http://localhost:80/consumer_serv/consumer/dept/msg
filters:
# http://localhost:80/consumer/dept/msg
- StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
# 配置 Nacos
nacos:
discovery:
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
username: nacos
password: nacos
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
作用:当请求 gateway 的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,如果匹配失败就返回404
类型:内置
SpringCloud Gateway 断言工厂
SpringCloud Gate 包括许多内置的断言工厂,所有这些断言都与 HTTP 请求的不同属性匹配
System.out.println(ZonedDateTime.now()); # 获取区域时间
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
AfterRoutePredicateFactory
- After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# 访问20201-05-13...之后
- After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# filters:
# # http://localhost:80/consumer/dept/msg
# - StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
# 配置 Nacos
nacos:
discovery:
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
username: nacos
password: nacos
接口测试工具:ApiPost
下载客户端地址
The RemoteAddr Route Predicate Factory:接收一个 IP 地址,判断请求主机地址是否在地址段中
- RemoteAddr=192.168.1.1/24
The Cookie Route Predicate Factory:接收两个参数,cookie 名字和一个人正则表达式,判断请求
- Cookie=chocolate, ch.p
The Header Route Predicate Factory:接收两个参数,标题名称和正则表达式,判断请求 Header 是否具有给定名称且值与正则表达式匹配
- Header=X-Request-Id,\d+
由于设置响应头不好操作,我们使用ApiPost工具
spring:
cloud:
gateway:
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
predicates:
- Path=/consumer/**
# 访问20201-05-13...之后
- After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# 匹配一个或多个数字的正则表达式
- Header=X-Request-Id,\d+
The Host Route Predicate Factory:接收一个参数,主机名模式,判断请求的 Host 是否满足匹配规则
- Host=**.somehost.org,**.anotherhost.org
The Method Route Predicate Factory:接受一个参数,判断请求类型是否跟指定的类型匹配
- Method=GET,POST
spring:
cloud:
gateway:
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
predicates:
- Path=/consumer/**
# 访问20201-05-13...之后
- After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# 匹配一个或多个数字的正则表达式
- Header=X-Request-Id,\d+
# 只允许是GET请求的接口
- Method=GET
The Path Route Predicate Factory:接受一个参数,判断请求的 URI 部分是否满足路径规则
- Path=/red/{segment},/blue/{segment}
The Query Route Predicate Factory:接收两个参数,请求 param 和正则表达式,判断请求是否具有给定名称且值与正则表达式匹配
- Query=green
spring:
cloud:
gateway:
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
predicates:
- Path=/consumer/**
# 访问20201-05-13...之后
- After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# 匹配一个或多个数字的正则表达式
- Header=X-Request-Id,\d+
# 只允许是GET请求的接口
- Method=GET
# 必须要有一个"name"的参数,可以选择该参数的指定的值
- Query=name,vinjcent|totoro
The Weight Route Predicate Factory:接收到 [组名,权重],然后对于同一个组内的路由按照权重转发
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Path=/product/**
# 代表在group1组中,10次访问有8次路由到 https://weighthigh.org服务下的/product/** 地址
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Path=/product/**
# 10次访问有2次路由到 https://weightlow.org服务下的/product/** 地址
- Weight=group1, 2
自定义理由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply() 方法。在 apply() 1方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息
实现条件:
测试
DivCheckAuthRoutePredicateFactory.class
,必须以xxxRoutePredicateFactory结尾package com.vinjcent.springcloud.config;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* 自定义路由断言工厂
* 如果匹配的"name"不是vinjcent
* 那么就会返回404
*/
@Component
public class DivCheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<DivCheckAuthRoutePredicateFactory.Config> {
public DivCheckAuthRoutePredicateFactory() {
super(DivCheckAuthRoutePredicateFactory.Config.class);
}
// 绑定 config()
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
public Predicate<ServerWebExchange> apply(DivCheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
if (config.getName().equals("vinjcent")){
return true;
}
return false;
}
};
}
// 用于接收配置文件中 断言的信息
@Validated
public static class Config {
private String name;
public Config(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
application.yml
Gateway 内置了很多的过滤器工厂,我们可以通过一些过滤器工厂进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等
33个过滤器
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
过滤工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader |
为原始请求添加 Header | Header 的名称及值 |
AddRequestParameter |
为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader |
为原始响应添加 Header | Header 的名称及值 |
DedupeResponseHeader |
剔除响应头中重复的值 | 需要去重的 Header 名称及去重策略 |
CircuitBreaker | 为路由引入 Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders |
为 fallback Uri 的请求头添加具体的异常信息 | Header 的名称 |
MapRequestHeader |
创建一个新的命名标头 ( ),并从传入的 http 请求中从 toHeader 现有的命名标头 ( ) 中提取值 | Header 的名称及值,使用","隔开 |
PrefixPath |
为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader |
为请求添加一个 preserveHostHeader = true 的属性,路由过滤器会检查该属性以决定是否要发送原始的 Host | 无 |
RequestRateLimiter |
用于请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptKey、emptyKeyStatus |
RedirectTo |
将原始请求重定向到指定的 URL | http状态码及重定向的 url |
RemoveRequestHeader |
为原始请求删除某个 Header | Header 名称 |
RemoveResponseHeader |
为原始响应删除某个 Header | Header 名称 |
RemoveRequestParameter |
为原始请求删除查询参数的名称 | - RemoveRequestParameter=red |
RequestHeaderSize |
请求头(包括键和值)允许的最大数据大小 | - RequestHeaderSize=1000B,如果大于1000字节,会发送 431 状态 |
RewritePath |
重写原始的某给请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteLocationResponseHeader |
||
RewriteResponseHeader |
重写原始响应中的某个 Header | Header 名称,值得正则表达式,重写后的值 |
SaveSession |
再转发请求之前,强制执行 WebSession::save 操作 | 无 |
SecureHeaders |
为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath |
修改原始的请求路径 | 修改后的路径 |
SetRequestHeader |
修改原始请求中某个 Header 的值 | Header名称,修改后的值 |
SetResponseHeader |
修改原始响应中某个 Header 的值 | Header名称,修改后的值 |
SetStatus |
修改原始相应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix |
用于截断原始请求的路径 | 使用数字表示要截断的路径数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize |
设置允许接收最大请求包的大小,如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为 5M |
SetRequestHostHeader |
设置覆盖主机的请求头 Header | host: 主机头名称 |
Modify a Request Body | 在转发之前修改原始请求体内容 | 修改后的请求体内容 |
Modify a Response Body | 修改原始响应体的内容 | 修改后的响应内容 |
Token Relay | - TokenRelay= | |
CacheRequestBody |
缓存请求体 | - name: CacheRequestBody |
Default Filters |
AddRequestHeader 测试
/consumer/header
接口@RestController
@RequestMapping("/consumer")
@SuppressWarnings("all")
public class DeptController {
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@GetMapping("/dept/msg")
public String getMessage() {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/msg", String.class);
}
@GetMapping("/header")
public String getHeader(@RequestHeader("X-Request-color") String color) {
return color;
}
}
applicatiom.yml
文件server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# - DivCheckAuth=vinjcent
# # 访问20201-05-13...之后
# - After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# # 匹配一个或多个数字的正则表达式
# - Header=X-Request-Id,\d+
# # 只允许是GET请求的接口
# - Method=GET
# # 必须要有一个"name"的参数,可以选择该参数的指定的值
# - Query=name,vinjcent|totoro
filters:
- AddRequestHeader=X-Request-color, blue # 添加请求头
# # http://localhost:80/consumer/dept/msg
# - StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
# 配置 Nacos
......
PrefixPath 测试
application.yml
文件server:
port: 80
servlet:
context-path: /vinjcent
spring:
application:
name: springcloud-consumer-dept
# 配置nacos
cloud:
nacos:
# nacos 服务地址
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
discovery:
username: nacos # nacos用户名
password: nacos
namespace: public # 分隔开发环境和测试环境使用
# 调用的服务提供方
springcloud-provider-dept:
ribbon:
# 复制NacosRule类的引用路径
NFLoadBalancerRuleClassName: com.vincjent.ribbon.MyRibbon
......
application.yml
文件server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# - DivCheckAuth=vinjcent
# # 访问20201-05-13...之后
# - After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# # 匹配一个或多个数字的正则表达式
# - Header=X-Request-Id,\d+
# # 只允许是GET请求的接口
# - Method=GET
# # 必须要有一个"name"的参数,可以选择该参数的指定的值
# - Query=name,vinjcent|totoro
filters:
- AddRequestHeader=X-Request-color, blue # 添加请求头
- PrefixPath=/vinjcent # 添加前缀,对应微服务需要配置context-path
RedirectTo 测试
application.yml
文件server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# - DivCheckAuth=vinjcent
# # 访问20201-05-13...之后
# - After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# # 匹配一个或多个数字的正则表达式
# - Header=X-Request-Id,\d+
# # 只允许是GET请求的接口
# - Method=GET
# # 必须要有一个"name"的参数,可以选择该参数的指定的值
# - Query=name,vinjcent|totoro
filters:
- AddRequestHeader=X-Request-color, blue # 添加请求头
- PrefixPath=/vinjcent # 添加前缀,对应微服务需要配置context-path
- RedirectTo=302,https://www.baidu.com/ # 302错误重定向到百度
DivFilterGatewayFilterFactory.class
类,注意后缀必须是 xxxGatewayFilterFactoryimport org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class DivFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<DivFilterGatewayFilterFactory.Config> {
public DivFilterGatewayFilterFactory() {
super(DivFilterGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("value");
}
public GatewayFilter apply(DivFilterGatewayFilterFactory.Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String name = exchange.getRequest().getQueryParams().getFirst("name");
if (StringUtils.isNotBlank(name)){
if (config.getValue().equals(name)){
return chain.filter(exchange);
}else {
// 返回404
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().setComplete();
}
}
// 正常请求
return chain.filter(exchange);
}
};
}
public static class Config {
String value;
public Config() {
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Config(String value) {
this.value = value;
}
}
}
application.yml
文件server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# - DivCheckAuth=vinjcent
# # 访问20201-05-13...之后
# - After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# # 匹配一个或多个数字的正则表达式
# - Header=X-Request-Id,\d+
# # 只允许是GET请求的接口
# - Method=GET
# # 必须要有一个"name"的参数,可以选择该参数的指定的值
# - Query=name,vinjcent|totoro
filters:
- DivFilter=vinjcent #指定过滤参数可以为null或参数值为"vinjcent"
# - AddRequestHeader=X-Request-color, blue # 添加请求头
# - PrefixPath=/vinjcent # 添加前缀,对应微服务需要配置context-path
# - RedirectTo=302,https://www.baidu.com/ # 302错误重定向到百度
# # http://localhost:80/consumer/dept/msg
# - StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
# 配置 Nacos
......
启动springcloud-gateway-8088
访问http://localhost:8088/consumer/dept/msg?name=hhh
局部过滤器和全局过滤器区别:
GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过,GlobalFilter 会作用于所有路由
LoadBalancerClientFilter
LoadBalancerClientFilter 会查看exchange
的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个RUI),如果该值的 scheme 是 lb,比如:lb://myservice,它将会使用 SpringCloud 的 LoadBalancerClient 来将 myservice 解析成实际的 host 和port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容
自定义全局过滤器
DivGlobalFilter.class
并实现GlobalFilter接口重写filter方法@Component
public class DivGlobalFilter 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);
}
}
Reactor Netty 访问日志
要启动 Reactor Netty 访问日志,请设置 -Dreactor.netty.http.server.accessLogEnabled=true
它必须是 Java 系统属性,而不是 SpringBoot 属性配置
可以将日志记录系统配置为具有单独的访问日志文件。以下示例创建一个 Logback 配置
<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
<file>access_log.logfile>
<encoder>
<pattern>%msg%npattern>
encoder>
appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async">appender-ref>
logger>
启动springcloud-gateway-8088并访问
访问http://localhost:8088/consumer/dept/msg
Gateway 跨域配置(CORS Configuration)
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET
使用配置文件方式
main.html
位置于pom.xml同一等级目录下DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>单选页面title>
<head>
<meta charset="utf-8">
<title>Bootstrap 实例 - 悬停表格title>
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js">script>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js">script>
head>
<style>
.btn-block{
border: darkgray 2px solid
}
.toStore{
margin-top: 20px;
}
.toStore a{
text-decoration: none;
}
style>
<body>
<p class="toStore"><a href="/toStore">点击跳转库存页面a>p>
<form action="/choose" onsubmit="">
<table class="table table-hover">
<caption>你心中的偶像是?caption>
<thead>
<tr>
<th>名称th>
<th>单选框th>
tr>
thead>
<tbody>
<tr>
<td>Tanmaytd>
<td><input type="radio" value="Tanmay" name="idol" checked>td>
tr>
<tr>
<td>Sachintd>
<td><input type="radio" value="Sachin" name="idol">td>
tr>
<tr>
<td>Umatd>
<td><input type="radio" value="Uma" name="idol">td>
tr>
<tr>
<td>Totorotd>
<td><input type="radio" value="Totoro" name="idol">td>
tr>
<tr>
<td>Vinjcenttd>
<td><input type="radio" value="Vinjcent" name="idol">td>
tr>
tbody>
table>
<p>
<div class="input-group">
<span class="input-group-addon">年龄span>
<input type="text" id="age" name="age" class="form-control" placeholder="Say something...">
div>
p>
<p>
<input type="button" class="btn btn-default btn-lg btn-block" value="点击跨域请求" onclick="getMsg()" />
p>
form>
body>
<script>
function getMsg(){
$.get('http://localhost:8088/consumer/dept/msg',function (data){
alert(data);
});
}
script>
html>
application.yml
文件spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 允许跨域访问的资源
allowedOrigins: "*" # 跨域允许的来源,如www.vinjcent.com
allowedMethods: # 允许请请求的类型
- GET
- POST
通过代码实现
CORSConfiguration.class
package com.vinjcent.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CORSConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
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/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81
1)在springcloud-gateway-8088模块下
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloudartifactId>
<groupId>com.vinjcentgroupId>
<version>0.0.1-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>gatewayartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
dependencies>
project>
application.yml
server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
# 配置 Nacos
nacos:
discovery:
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
username: nacos
password: nacos
# 配置Sentinel
sentinel:
transport:
dashboard: 127.0.0.1:8858 # Sentinel控制台地址
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# - DivCheckAuth=vinjcent
# # 访问20201-05-13...之后
# - After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# # 匹配一个或多个数字的正则表达式
# - Header=X-Request-Id,\d+
# # 只允许是GET请求的接口
# - Method=GET
# # 必须要有一个"name"的参数,可以选择该参数的指定的值
# - Query=name,vinjcent|totoro
# filters:
# - DivFilter=vinjcent
# - AddRequestHeader=X-Request-color, blue # 添加请求头
# - PrefixPath=/vinjcent # 添加前缀,对应微服务需要配置context-path
# - RedirectTo=302,https://www.baidu.com/ # 302错误重定向到百度
# # http://localhost:80/consumer/dept/msg
# - StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
# 跨域配置
# globalcors:
# cors-configurations:
# '[/**]': # 允许跨域访问的资源
# allowedOrigins: "*" # 跨域允许的来源,如www.vinjcent.com
# allowedMethods:
# - GET
# - POST
sentinel-dashboard
服务Burst size(最大容许数量)
Client IP(客户端IP)
Header(请求头)
URL 参数
API 分组
降级规则
并同时在页面中访问出现异常的接口http://localhost:8088/consumer/exception
自定义异常处理(使用 Java 配置)
GatewayConfig.class
类import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class GatewayConfig {
@PostConstruct
public void init() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
// 异常输出
System.out.println(throwable);
Map<String, String> map = new HashMap<>();
map.put("code",HttpStatus.TOO_MANY_REQUESTS.toString());
map.put("message","限流了!!!");
// 设置响应状态码
return ServerResponse.status(HttpStatus.OK)
// 设置相应类型
.contentType(MediaType.APPLICATION_JSON)
// 设置相应内容
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
自定义异常处理(使用 application.yml 配置)
server:
port: 8088
spring:
application:
# 服务名称
name: api-gateway
cloud:
# 配置 Nacos
nacos:
discovery:
server-addr: 192.168.159.100:7070,192.168.159.100:7080,192.168.159.100:7090
username: nacos
password: nacos
# 配置Sentinel
sentinel:
transport:
dashboard: 127.0.0.1:8858 # Sentinel控制台地址
# 配置自定义异常
scg:
fallback:
mode: response
response-body: "{code:'404',message:'服务出错啦!'}"
inetutils:
# 配置忽略的网卡信息
ignored-interfaces: 'VMware Virtual Ethernet Adapter for VMnet1,VMware Virtual Ethernet Adapter for VMnet8'
# gateway 配置
gateway:
# 路由规则
routes:
- id: consumer_route # 路由的唯一标识,路由到consumer
uri: lb://springcloud-consumer-dept # 从nacos中按照服务名获取微服务 lb: 使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/consumer/**
# - DivCheckAuth=vinjcent
# # 访问20201-05-13...之后
# - After=2021-05-13T13:14:57.028+08:00[Asia/Shanghai]
# # 匹配一个或多个数字的正则表达式
# - Header=X-Request-Id,\d+
# # 只允许是GET请求的接口
# - Method=GET
# # 必须要有一个"name"的参数,可以选择该参数的指定的值
# - Query=name,vinjcent|totoro
# filters:
# - DivFilter=vinjcent
# - AddRequestHeader=X-Request-color, blue # 添加请求头
# - PrefixPath=/vinjcent # 添加前缀,对应微服务需要配置context-path
# - RedirectTo=302,https://www.baidu.com/ # 302错误重定向到百度
# # http://localhost:80/consumer/dept/msg
# - StripPrefix=1 # 转发之前,过滤掉第一层前缀 "consumer_serv"
# 跨域配置
# globalcors:
# cors-configurations:
# '[/**]': # 允许跨域访问的资源
# allowedOrigins: "*" # 跨域允许的来源,如www.vinjcent.com
# allowedMethods:
# - GET
# - POST
使用代码配置断言、异常、访问接口
同样也可以使用代码的方式配置规则,接口为 GatewayxxxRule
package com.vinjcent.springcloud.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Configuration
public class GatewayConfig {
@PostConstruct
public void init() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
System.out.println(throwable);
Map<String, String> map = new HashMap<>();
map.put("code",HttpStatus.TOO_MANY_REQUESTS.toString());
map.put("message","限流了!!!");
// 设置响应状态码
return ServerResponse.status(HttpStatus.OK)
// 设置相应类型
.contentType(MediaType.APPLICATION_JSON)
// 设置相应内容
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
@PostConstruct
public void initRules() {
// 初始化自定义的API
initCustomizedApis();
// 初始化网关的流控规则
initGatewayRules();
// 自定义限流异常处理器
initBlockRequestHandler();
}
private void initCustomizedApis() {
HashSet<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api = new ApiDefinition("user_service_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/user/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// resource: 资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称
// count: 限流阈值
// intervalSec: 统计时间窗口,单位是秒,默认是1s
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 serverWebExchange, Throwable throwable) {
HashMap<String, String> result = new HashMap<>();
result.put("code",String.valueOf(HttpStatus.TOO_MANY_REQUESTS));
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 进行负载转发以达到高可用
下一篇文章《SkyWalking 微服务链路追踪》