目录
- 一、什么是API网关
- 二、基本使用
- 三、谓词
- 四、过滤器-Filter
- 五、使用Gateway实现限流
- 六、使用Gateway实现服务降级
- 七、自定义全局过滤器
- 八、自定义路由过滤器
一、什么是API网关
API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可在网关中提供额外的功能。
总结:网关就是所有项目的一个统一入口。
二、基本使用
1.准备Eureka注册中心
2.准备一个微服务工程
3.搭建Gateway网关微服务
(1)导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
(2)编写配置文件
server:
port: 9999
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 自动发现工具的本地路由规则是:
# 请求路径 - http://网关IP:网关端口/微服务的服务名/要访问的具体地址
# gateway自动解析,把请求地址中的'微服务的服务名'截取,从Eureka Client发现的服务列表中查看,如果有同名服务,则开始转发。
spring:
application:
name: cloud-gateway
cloud: # spring cloud相关配置的常用前缀
gateway: # 网关技术配置前缀
discovery: # 自动发现工具
locator: # 本地逻辑
enabled: true # 开启自动发现工具的本地路由逻辑
lower-case-service-id: true # 把从EurekaServer上发现的服务名称,转换成全小写
(3)编写启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
三、谓词
谓词:当满足条件在进行路由转发。
在Spring Cloud Gateway中谓词实现GatewayPredicate接口。其中类名符合:XXXRoutePredicateFactory,其中XXX就是在配置文件中谓词名称。
所有的谓词都设置在predicates属性中,当设置多个谓词时取逻辑与条件,且一个谓词只能设置一组条件,如果需要有个多条件,添加多个相同谓词。
(1)Path
用于匹配路由地址规则的谓词。
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: false # 关闭自动发现工具的本地路由逻辑
lower-case-service-id: true
routes: # 配置多路由策略的属性,类型是List。配置方案是:回车 + 缩进 + - + 空格。集合中的每个对象的属性,对齐多行配置
- id: application-service # 路由的唯一名称
uri: lb://application-service # 规则满足后,转发到的地址。lb是spring cloud gateway支持的一种协议名
predicates: # 谓词
- Path=/service/** # 路由地址规则
filters: # 过滤器,先使用,后续课程细致讲解。后续案例配置统一,文档中省略
- StripPrefix=1
(2)Query
用于校验请求中是否包含指定的请求参数,同时可也校验请求参数值是否符合要求。
- id: application-service
uri: lb://application-service
predicates: # 谓词
- Path=/service/**
- Query=name # 请求参数必须包含name
下述配置代表,请求中必须包含命名为name和age的参数,且参数值必须已'bjsxt'开头。下述配置中bjsxt.*是一个正则表达式。在正则表达式中点(.)表示匹配任意一个字符。所以当请求参数name=bjsxt或name=bjsxtAdmin等都能满足谓词条件。
如果设定请求中必须包含多个参数及值。则设置多个Query。在此处演示多个相同谓词配置,其他谓词中就不在强调如何配置多个谓词。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Query=name,bjsxt.* # 请求参数必须包含name,请求参数的值必须以 bjsxt 开头
- Query=age # 请求参数必须包含age
(3)Header
用于校验请求中是否包含指定的请求头,同时可也校验请求头数值是否符合要求。配置方式和Query类似。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Query=name,bjsxt.*
- Query=age
- Header=Host,.* # 请求头必须有Host,值为任意字符串
(4)Method
Method表示请求方式。支持多个值,使用逗号分隔,多个值之间为or条件。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Method=GET,POST # 请求方式必须是GET或POST
(5)RemoteAddr
允许访问的客户端地址。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- RemoteAddr=192.168.41.252 # 客户端IP必须是192.168.41.252
(6)Host
匹配请求中Host请求头的值。满足Ant模式(之前在Spring Security中学习过)可以使用:
- ? 匹配一个字符
- * 匹配0个或多个字符
- ** 匹配0个或多个目录
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Host=127.0.0.1:9999 # 请求头Host值必须是127.0.0.1:9999
(7)Cookie
要求请求中包含指定Cookie名和满足特定正则要求的值。
Cookie必须有两个值,第一个Cookie包含的参数名,第二个表示参数对应的值,正则表达式。不支持一个参数写法。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Cookie=name,bjsxt.* # 请求必须包含名称是name,值符合bjsxt开头的cookie。
(8)Before
在指定时间点之前。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Before=2022-10-01T18:00:00.000+08:00[Asia/Shanghai] # 2022-10-01晚18点前可以访问
(9)After
在指定时间点之后。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- After=2020-10-01T08:00:00.000+08:00[Asia/Shanghai] # 2020-10-01早8点后可以访问
(10)Between
请求时必须在设定的时间范围内,才进行路由转发。
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
- Between=2020-10-01T08:00:00.000+08:00[Asia/Shanghai],2022-10-01T18:00:00.000+08:00[Asia/Shanghai] # 2020-10-01早8点后,2022-10-01晚18点前可以访问
(11)Weight
多版本服务发布的时候,偶尔使用。
如v1.0+v1.1两个版本同时发布服务。内容一致,实现机制不同。发布两个不同命名的服务集群。使用Gateway做负载均衡并设置权重。 代表同一个组中URI进行负载均衡。语法:Weight=组名,负载均衡权重 在Eureka中注册两个服务,这个服务(项目)是相同的,应用程序名分别叫做application-service1和application-service2。 Gateway在路由匹配时application-service1将占20%,application-service2将占80%。
- id: application-service1
uri: lb://application-service1
predicates:
- Path=/service/**
- Weight=group1,2
- id: application-service2
uri: lb://application-service2
predicates:
- Path=/service/**
- Weight=group1,8
四、过滤器-Filter
在路由转发到代理服务之前和代理服务返回结果之后额外做的事情。Filter是在路由转发之后,被代理的服务执行前后运行的。只要Filter执行了,说一定满足了谓词条件。 在Spring Cloud Gateway的路由中Filter分为:
- 路由过滤器:框架内置的Filter实现都是路由过滤器,都是GatewayFilter实现类型。本章节所有案例都是路由过滤器。
- 全局过滤器:框架未内置全局过滤器实现,需自定义。全局过滤器需实现接口GlobalFilter。
(1)StripPrefix
跳过路由uri中前几段后发送给下游。
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: false
lower-case-service-id: true
routes:
- id: application-service
uri: lb://application-service
predicates:
- Path=/service/**
filters: # 过滤器
- StripPrefix=1 # 跳过路由uri中前1段后发送给下游。
(2)AddRequestHeader
添加请求头参数,参数名和值之间使用逗号分隔。
filters:
- StripPrefix=1
- AddRequestHeader=company,bjsxt
(3)AddRequestParameter
添加请求表单参数,多个参数需要有多个过滤器。
filters:
- StripPrefix=1
- AddRequestParameter=name,bjsxt
- AddRequestParameter=age,18
(4)AddResponseHeader
添加响应头。
filters:
- StripPrefix=1
- AddResponseHeader=company,bjsxt··
(5)DedupeResponseHeader
对指定响应头去重复。配置语法:DedupeResponseHeader=响应头参数 或 DedupeResponseHeader=响应头参数,strategy。
- 去重策略strategy可选值:
- RETAIN_FIRST :默认值,保留第一个
- RETAIN_LAST 保留最后一个
RETAIN_UNIQUE 保留唯一的,出现重复的属性值,会保留一个。例如有两个My:bbb的属性,最后会只留一个。
filters:
- StripPrefix=1
- DedupeResponseHeader=MyHeader,RETAIN_UNIQUE
(6)CircuitBreaker
实现熔断时使用,支持CircuitBreaker和Hystrix两种。
(7)FallbackHeaders
可以添加降级时的异常信息。
(8)PrefixPath
给符合规则替换后的URI地址添加统一的前缀地址。
(9)RequestRateLimiter
限流过滤器。
(10)RedirectTo
重定向。有两个参数,status和url。其中status应该300系列重定向状态码。
(11)RemoveRequestHeader
删除请求头参数。
(12)RemoveResponseHeader
删除响应头参数。
(13)RemoveRequestParameter
删除请求参数。
(14)RewritePath
重写请求路径。
(15)RewriteResponseHeader
重写响应头参数。
(16)SaveSession
如果项目中使用Spring Security和Spring Session整合时,此属性特别重要。
(17)SecureHeaders
具有权限验证时,建议的头信息内容。
(18)SetPath
功能和StripPrefix有点类似。语法更贴近restful。
- id: setpath_route
uri: https://example.org
predicates:
- Path=/api/{segment}
filters:
- SetPath=/{segment}
(19)SetRequestHeader
替换请求参数头数。不是添加。
(20)SetResponseHeader
替换响应头参数。
(21)SetStatus
设置响应状态码。
(22)Retry
设置重试次数。
(23)RequestSize
请求最大大小。包含maxSize参数,单位包括“KB”或“MB”等。默认为“B”。
(24)ModifyRequestBody
修改请求体内容。
(25)ModifyResponseBody
修改响应体
五、使用Gateway实现限流
令牌桶算法
令牌桶算法可以说是对漏桶算法的一种改进。
在桶中放令牌,请求获取令牌后才能继续执行。如果桶中没有令牌,请求可以选择进行等待或者直接拒绝。
由于桶中令牌是按照一定速率放置的,所以可以一定程度解决突发访问。如果桶中令牌最多有100个,QPS最大为100。
(1)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)新建Key解析器
/**
* 限流过滤器配置必要的类型。
* 令牌桶算法中令牌工厂的组件之一,用于生成一个个与客户端对应的令牌key。 提供一个能对应客户端的数据。
* 如:IP,用户登录名等。
*
* 当前类型对象,需要Spring容器管理。
*/
@Component
public class MyKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
String ip = exchange.getRequest() // 获取请求对象
.getRemoteAddress() // 获取客户端地址对象 InetSocketAddress
.getAddress() // 获取客户端地址对象 InetAddress
.getHostAddress(); // 获取客户端的主机地址(IP或唯一的主机名)
return Mono.just(ip); // 创建返回结果对象
}
}
(3)编写配置文件
server:
port: 9999
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: false
lower-case-service-id: true
routes:
- id: rateLimiter
uri: lb://application-service
predicates:
- Path=/limiter/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
keyResolver: '#{@myKeyResolver}' # 表达式, #{} 从容器找对象, @beanId
# redis-rate-limiter是用于做令牌校验,和令牌生成的类型。gateway框架提供了基于Redis的实现。
redis-rate-limiter.replenishRate: 1 # 每秒令牌生成速率
redis-rate-limiter.burstCapacity: 2 # 令牌桶容量上限
六、使用Gateway实现服务降级
(1)导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
(2)编辑降级逻辑控制器
/**
* 服务降级控制器
*/
@Controller
public class FallbackController {
/**
* 服务降级处理方法。
* 当通过gateway转发请求的服务,不可用时,当前方法执行。返回降级数据。
* @return
*/
@RequestMapping(value = "/fallback", produces = {"text/html; charset=UTF-"})
@ResponseBody
public String fallback(){
return "<div style='color:red; text-align: center'>服务器忙,请稍后重试!</div>";
}
}
(3)配置文件
- id: hystrix
uri: lb://application-service
predicates:
- Path=/hystrix/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallback # 随意定义的名称。相当于@HystrixCommand注解中的commandKey属性。
fallbackUri: forward:/fallback # 如果转发的服务不可用,请求转发到当前系统的哪一个路径上。
七、自定义全局过滤器
编辑全局过滤器
/**
* 自定义全局过滤器。
* 必须实现接口GlobalFilter
* 当前类型的对象,必须被spring容器管理。
* 无须配置,所有路由都生效。
*
* 执行顺序:
* 先执行网关过滤器,后执行全局过滤器
* 多个全局过滤器,执行顺序由Spring boot扫描管理当前对象的顺序决定。
* 每个过滤器,都是完整执行后,才执行下一个过滤器。
*/
@Component
public class MyGlobalFilter implements GlobalFilter {
/**
* 过滤方法。
* 实现上,只有唯一的要求。必须调用方法chain.filter(exchange),并把方法的返回值,返回。
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("前置全局过滤");
Mono<Void> result = chain.filter(exchange);
System.out.println("后置全局过滤");
return result;
}
}
八、自定义路由过滤器
定义针对于Router的Filter。必须经由配置才能生效。 注意:
- 类名必须定义为XxxGatewayFilterFactory。注入到Spring容器后使用时的名称就叫做xxx。
- 类建议继承AbstractGatewayFilterFactory。如果不继承,则必须实现接口GatewayFilterFactory,这种方式开发成本高。
- 所有需要传递进来的参数都配置到当前类的静态内部类Config中。
(1)编辑路由过滤器
/**
* 自定义网关过滤器(路由过滤器),必须经过配置使用才能生效的过滤器。
* 要求当前类型的对象必须被spring容器管理。
* 要求必须实现接口 GatewayFilterFactory, 建议继承AbstractGatewayFilterFactory
*
* 多个网关过滤器执行顺序:
* 按照配置文件中,过滤器的配置顺序,依次运行。每个过滤器完整运行结束后,执行下一个过滤规则。
*/
@Component
public class LoggerFilterGatewayFilterFactory
extends AbstractGatewayFilterFactory<LoggerFilterGatewayFilterFactory.Config> {
/**
* 建议提供个构造方法。一个无参数。一个有参数,参数类型就是当前类型中的Config静态内部类的类对象类型。
* 父类型,可以帮助解析配置文件,并创建Config对象。
*/
public LoggerFilterGatewayFilterFactory(){
this(Config.class);
}
public LoggerFilterGatewayFilterFactory(Class<Config> configClass){
super(configClass);
}
/**
* 如果需要简化配置方案。提供方法shortcutFieldOrder
* 有当前方法,配置文件使用,可以简化配置为 LoggerFilter=abc
* 没有当前方法,配置文件完整编写,内容是:
* name: LoggerFilter
* args:
* remark: abc
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("remark");
}
/**
* 创建网关过滤器的方法。
* @param config 就是配置文件中的内容,就是当前类型中的静态内部类对象。
* @return 一般使用匿名内部类,创建GatewayFilter接口的实现对象。
*/
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
/**
* 过滤方法。要求必须调用chain.filter(exchange),并返回方法的返回结果
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("前置 - 日志过滤器 - config.remark = " + config.getRemark());
Mono<Void> result = chain.filter(exchange);
System.out.println("后置 - 日志过滤器 - config.remark = " + config.getRemark());
return result;
}
};
}
/**
* 定义静态内部类,作为配置对象
* 定义的每个属性,都是用于在配置文件中配置的对应属性。
* 必须提供getter和setter方法。
*/
public static class Config{
private String remark;
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
}
(2)编辑配置文件
- id: logger
uri: lb://application-service
predicates:
- Path=/logger/**
filters:
- StripPrefix=1
- LoggerFilter=simpleTestGatewayFilter
- name: LoggerFilter
args:
remark: fullyTestGatewayFilter