该项目提供了一个用于在 Spring WebFlux 之上构建 API 网关的库。Spring Cloud Gateway
旨在提供一种简单而有效的方法来路由到 API,并为它们提供横切关注点,例如:安全性、监控/指标和弹性
Spring Cloud Gateway 特性:
(1)Filter(过滤器):
和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。
(2)Route(路由):
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
(3)Predicate(断言):
这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。
spring:
cloud:
gateway:
filter:
remove-hop-by-hop:
headers: #默认的移除request中的header
- aaa
- bbb
# httpclient:
# connect-timeout: 1000 # 转发后端服务超时时间
# response-timeout: 3s # 转发后端服务响应超时时间
# discovery:
# locator:
# enabled: true #开启服务注册和发现
# lower-case-service-id: true #注册名转为小写
routes: #路由配置
- id: route_1 #直接跳转到指定域名下 http://10.2.11.6:8808/gw/1 > http://10.2.11.6:8800/gw/1
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/gw/** #匹配路径
- id: route_2 #去除前缀跳转 http://10.2.11.6:8808/sp/hi/1 > http://10.2.11.6:8800/hi/1
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/sp/** #匹配路径
- Method=GET #指定方式
filters:
- StripPrefix=1 #去除第一个级前缀
- id: route_3 # 增加前缀 http://10.2.11.6:8808/pp/1 > http://10.2.11.6:8800/hi/1
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/pp/** #匹配路径
filters:
- StripPrefix=1 #去除第一个级前缀
- PrefixPath=/hi #
- id: route_4 # 重写前缀 http://10.2.11.6:8808/rp/1 > http://10.2.11.6:8800/hi/1
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/rp/** #匹配路径
filters:
- RewritePath=/rp, /hi
- id: route_5 # 动态重写前缀 http://10.2.11.6:8808/rp1 > http://10.2.11.6:8800/hi/rp1
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/rp1/** #匹配路径
filters:
- RewritePath=(?>^/), /hi$\{oldPath}
- id: route_6 # 指定跳转路径 http://10.2.11.6:8808/setpath > http://10.2.11.6:8800/hi/1
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/setpath/** #匹配路径
filters:
- SetPath=/hi/1
- id: route_7 # 设置路径,同时使用时,根据前后顺序进行设置,改变顺序结果不同 http://10.2.11.6:8808/allpath > http://10.2.11.6:8800/hi/a
uri: http://10.2.11.6:8800 #目标路径
predicates: #谓词匹配
- Path=/allpath/** #匹配路径
filters:
- PrefixPath=/b # http://10.2.11.6:8808/allpath > http://10.2.11.6:8800/b/allpath
- SetPath=/a # http://10.2.11.6:8800/b/allpath > http://10.2.11.6:8800/a
- RewritePath=(?>^/), /hi$\{oldPath} # http://10.2.11.6:8800/b/a > http://10.2.11.6:8800/hi/a
- id: route_8 # 通过eureka访问 http://10.2.11.6:8808/eureka/lb/1 > http://10.2.11.6:8800/lb/1
uri: lb://lizz-eureka-provider #目标路径
predicates: #谓词匹配
- Path=/eureka/** #匹配路径
# metadata:
# connect-timeout: 200 #响应时间 ,当前2.2.0版本暂不支持
# response-timeout: 1000 #链接时间,当前2.2.0版本暂不支持
filters:
- StripPrefix=1 #去除第一个级前缀
- name : RequestSize
args:
maxSize: 500000 #限制请求数据大小,byte,比较的长度String contentLength = request.getHeaders().getFirst("content-length");
- name: Retry #重试过滤器,重试调用lizz-eureka-provider的次数
args:
retries: 1 #重试次数 默认3次
statuses: BAD_GATEWAY #HttpStatus 什么情况下重试,可以是错误码,如:500,502
series: # HttpStatus.重试系列 默认 SERVER_ERROR-5xx,SUCCESSFUL-2xx ,空为异常补重试,与statuses并行使用
exceptions: #异常重试 默认IOException and TimeoutException,空为异常补重试
methods: GET,POST # HttpMethod 重试的方法 默认 GET GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
backoff: #延时重试
firstBackoff: 10ms #首次延时次数
maxBackoff: 50ms # 最大延迟后停止
factor: 2 #重试因子 下一次重试延时为firstBackoff*2*n次
basedOnPreviousValue: false #为true, backoff 计算公式为 prevBackoff * factor.
- id: route_9 # 流量限制
uri: lb://lizz-eureka-provider #目标路径
predicates: #谓词匹配
- Path=/iprate/** #匹配路径
filters:
- StripPrefix=1 #去除第一个级前缀
- name: RequestRateLimiter
args:
redis-rate-limiter: #过期时间=burstCapacity/replenishRate*2s
replenishRate: 3 #每次补充数量
burstCapacity: 1000 #突发容量
requestedTokens: 1 #每次请求消耗几个令牌,可以控制不同频率,默认1
key-resolver: "#{@ipKeyResolver}" # ipKeyResolver 自定义限流柜子
# rate-limiter: "#{@myRateLimiter}" # 使用自定义限流规则
- id: route_10 # 修改header
uri: lb://lizz-eureka-provider #目标路径
predicates: #谓词匹配
- Path=/userrate/** #匹配路径
filters:
- AddRequestHeader=X-Request-Foo,Bar #增加转发请求的header 格式:key,value
- AddRequestParameter=foo,bar #增加转发请求的参数
- AddResponseHeader=X-Request-Foo,Bar #增加返回数据的header
- RemoveRequestHeader=X-Request-Foo #移除转发请求的header
- RemoveResponseHeader=X-Request-Foo #移除返回数据的header
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
server:
port: 8899
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: gateway
uri: http://172.16.100.24:8087
predicates:
- Path=/consumer/**
main:
web-application-type: reactive
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//是否发送 Cookie
config.setAllowCredentials(true);
//放行哪些请求方式
config.addAllowedMethod("*");
//放行哪些原始请求头部信息
config.addAllowedHeader("*");
//暴露哪些头部信息
config.addExposedHeader("*");
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//TODO 自定义认证逻辑(token认证或者接口认证)
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
不经过网关调用
在高并发的系统中,防止大量请求使服务器过载,进行一些限流操作
常见的限流算法
计数器算法为最简答的限流算法,其实现原理是为维护一个单位时间内的计数器。在单位时间内,开始计数器为0,每次通过一个请求计数器+1。如果单位时间内 计数器的数量大于了预先设定的阈值,则在此刻到单位时间的最后一刻范围内的请求都将被拒绝。单位时间结束计数器归零,重新开始计数。
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行
实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。
以redis 方式实现令牌桶算法
maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置
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: '#{@hostAddrKeyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
application:
name: gateway-limiter
redis:
host: localhost
port: 6379
在上面的配置文件,指定程序的端口为8081,配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:
KeyResolver需要实现resolve方法,比如根据Hostname进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。
public class HostAddrKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
@Bean
public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}
根据uri去限流,这时KeyResolver代码如下
public class UriKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getURI().getPath());
}
}
@Bean
public UriKeyResolver uriKeyResolver() {
return new UriKeyResolver();
}
也可以以用户的维度去限流:
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}