Gateway
Zuul的IO模型
Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
大家知道,servlet由servlet container进行生命周期管理。container启动时构造servlet对象并调用servlet init()进行初始化;container关闭时调用servlet destory()销毁servlet;container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()。
弊端:servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的,但是一旦并发上升,线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单的业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。
所以Springcloud Zuul 是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet),并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端。虽然Zuul 2.0开始,使用了Netty,并且已经有了大规模Zuul 2.0集群部署的成熟案例,但是,Springcloud官方已经没有集成改版本的计划了。
Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Gateway
使用了高性能的通信框架Netty
SpringCloud官方,对SpringCloud Gateway 特征介绍如下:
(1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
(2)集成 Hystrix 断路器
(3)集成 Spring Cloud DiscoveryClient
(4)Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters
(5)具备一些网关的高级功能:动态路由、限流、路径重写
从以上的特征来说,和Zuul的特征差别不大。SpringCloud Gateway和Zuul主要的区别,还是在底层的通信框架上。
简单说明一下上文中的三个术语:
(1)Filter(过滤器):
和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。
(2)Route(路由):
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
(3)Predicate(断言):
这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。
配置文件
gateway:
routes: #路由转发配置
- id: user-service # 路由标识,代表为哪个服务进行路由,必须唯一
uri: lb://userservice # 根据服务名称去注册中心查找服务器地址 自动做负载均衡
predicates: # 判断请求是否符合规则
- Path=/user/** # 判断路径是否是以/user开头,如果是则转发请求到上面配置的uri上面
filters: # 局部过滤器
- AddRequestHeader=aaa,bbb
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters: # 所有请求只要是经过这个网关都会被自动添加这个请求头
- AddRequestHeader=aaa,bbb # 使用网关提供的默认过滤器 给请求添加一个头的键值对
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]': # 代表要拦截哪些路径进行跨域设置 /** 所有路径都进行跨域的设置
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:7788"
- 域名
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
# 局部过滤器和全局过滤器 网关会根据你的声明顺序自动+1
# 如果三种过滤器他们的order值是一样的怎么办
# defaultFilter > 路由过滤器 > GlobalFilter的顺序执行
属性详解
routes
-
id:我们自定义的路由 ID,保持唯一
uri:目标服务地址
predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
uri:目标服务地址
uri: https://blog.csdn.net 则路由到该地址
uri: lb://seckill-provider lb则从注册中心中查找服务
predicates:转发规则
假设 转发uri都设定为http://localhost:9023
规则 | 实例 | 说明 |
---|---|---|
Path | - Path=/gate/,/rule/ | ## 当请求的路径为gate、rule开头的时,转发到http://localhost:9023服务器上 |
Query | - Query=keep,- Query=keep, pu. | 总带有 keep 参数即会匹配路由,后者同时要求keep属性的 值必须以pu开头且3位 |
Before | - Before=2017-01-20T17:42:47.789-07:00[America/Denver] | 在某个时间之前的请求才会被转发到 http://localhost:9023服务器上 |
After | - After=2017-01-20T17:42:47.789-07:00[America/Denver] | 在某个时间之后的请求才会被转发 |
Between | - Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver] | 在某个时间段之间的才会被转发 |
Cookie | - Cookie=chocolate, ch.p | 名为chocolate的表单或者满足正则ch.p的表单才会被匹配到进行请求转发 |
Header | - Header=X-Request-Id, \d+ | 携带参数X-Request-Id或者满足\d+的请求头才会匹配 |
Host | - Host=www.hd123.com | 当主机名为www.hd123.com的时候直接转发到http://localhost:9023服务器上 |
Method | - Method=GET | 只有GET方法才会匹配转发请求,还可以限定POST、PUT等请求方式 |
RemoteAddr | - RemoteAddr=192.168.1.1/24 | (其中 192.168.0.1 是 IP 地址,24是子网掩码)如果请求的远程地址是 192.168.1.10,则此路由将匹配。 |
Filter:过滤器规则
过滤规则 | 实例 | 说明 |
---|---|---|
PrefixPath | - PrefixPath=/app | 在请求路径前加上app |
RewritePath | - RewritePath=/test, /app/test | 访问localhost:9022/test,请求会转发到localhost:8001/app/test |
SetPath | SetPath=/app/{path} | 通过模板设置路径,转发的规则时会在路径前增加app,{path}表示原请求路径 |
RedirectTo | - RedirectTo=302, https://acme.org | 重定向:包括重定向的返回码和地址 |
RemoveRequestHeader | 去掉某个请求头信息 | |
RemoveResponseHeader | - RemoveResponseHeader=X-Request-Foo | 去掉某个回执头信息 |
RemoveRequestParameter | - RemoveRequestParameter=red | 去掉某个请求参数 |
SetRequestHeader | - SetRequestHeader=X-Request-Red, Blue | 设置请求头信息 |
SetStatus | - SetStatus=401 | 设置回执状态码 |
StripPrefix | - StripPrefix=2 | 请求/name/blue/red会转发到/red |
RequestSize | - name: RequestSize args: maxSize: 5000000 | 超过5M请求会返回413错误 |
Default-filters | default-filters: - AddResponseHeader=X-Response-Default-Red, Default-Blue - PrefixPath=/httpbin | 对所有请求添加过滤器 |
过滤器Hystrix,作用是通过Hystrix进行熔断降级
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackCmdA
fallbackUri: forward:/fallbackA
hystrix.command.fallbackCmdA.execution.isolation.thread.timeoutInMilliseconds: 5000
@RestController
public class FallbackController {
@GetMapping("/fallbackA")
public Response fallbackA() {
Response response = new Response();
response.setCode("100");
response.setMessage("服务暂时不可用");
return response;
}
}
当上游的请求,进入了Hystrix熔断降级机制时,就会调用fallbackUri配置的降级地址。需要注意的是,还需要单独设置Hystrix的commandKey的超时时间
高级配置
1.分布式限流
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现上述三种过滤器。但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用在Redis内的通过执行Lua脚本实现了令牌桶的方式。
配置文件:
server:
port: 8081
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://httpbin.org:80/get
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@userKeyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
application:
name: cloud-gateway
redis:
host: localhost
port: 6379
database: 0
在上面的配置文件,指定程序的端口为8081,配置了redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:
burstCapacity,令牌桶总容量。
replenishRate,令牌桶每秒填充平均速率。
key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用SpEL表达式根据#{@beanName}从Spring 容器中获取Bean对象。
几种常用限流策略
// 这里根据用户ID限流,请求路径中必须携带userId参数
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
// 如果需要根据IP限流,定义的获取限流Key的bean为
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
// 如果需要根据接口的URI进行限流,则需要获取请求地址的uri作为限流key,定义的Bean对象为
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
其他:
Redis中使用Lua的好处
- 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延
- 原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
- 复用。客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
2.健康检查配置
admin-client、actuator健康检查配置,为之后的功能提供支持,加入以下maven依赖和配置
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
------------------------------------------------------------------------------------------------------------
配置文件
spring:
application:
name: mas-cloud-gateway
boot:
admin:
client:
### 本地搭建的admin-server
url: http://localhost:8011
eureka:
client:
registerWithEureka: true
fetchRegistry: true
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:6887/eureka/
enabled: true
feign:
sentinel:
enabled: true
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
若转发的目标地址为微服务中组件,不为具体ip:port形式的,应写成lb://mas-openapi-service形式,目标地址会从注册中心直接拉取
3.统一配置跨域请求
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- GET
- POST
- DELETE
- PUT
- OPTION
过滤器
全局过滤器:实现 GlobalFilter 和 Ordered,重写相关方法,加入到spring容器管理即可,无需配置,全局过滤器对所有的路由都有效。order值越小,优先级越高。
局部过滤器:
- 1 需要实现GatewayFilter, Ordered,实现相关的方法
- 2 加入到过滤器工厂,并且注册到spring容器中。
- 3、在配置文件中进行配置,如果不配置则不启用此过滤器规则。
局部过滤器和全局过滤器 网关会根据你的声明顺序自动+1
如果三种过滤器他们的order值是一样的怎么办
defaultFilter > 路由过滤器 > GlobalFilter的顺序执行
example
- id: qhse-dwq
uri: http://192.168.1.132:9001
predicates:
- Path=/admin/qhse/**
- Header= dev,dwq
filters:
- RewritePath=/admin/qhse/(?<segment>.*),/$\{segment}
其中filters也可以用代替
filters:
- StripPrefix=2