这里要介绍的是Spring
出的spring-cloud-starter-gateway
开始
先了解SpringCloud Gateway
的相关概念:
- Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
- Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
- Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。
作者:MacroZheng
先看看路由的断言有哪些:
- The After Route Predicate Factory:匹配在指定日期时间之后发生的请求
- The Before Route Predicate Factory:匹配在指定日期时间之前发生的请求
- The Between Route Predicate Factory:匹配在指定日期时间端之内发生的请求
- The Cookie Route Predicate Factory:cookie路由
- The Header Route Predicate Factory:header路由
- The Host Route Predicate Factory:主机名地址路由
- The Method Route Predicate Factory:请求方法路由
- The Path Route Predicate Factory:请求uri路由
- The Query Route Predicate Factory:请求参数路由
- The RemoteAddr Route Predicate Factory:ip请求路由
- The Weight Route Predicate Factory:权重路由
路由
了解这些内容后直接看一个示例:
pom.xml
先引入依赖(2.2.6.RELEASE
和Hoxton.SR3
):<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
建议网关的配置都写配置在
yml
配置文件上,不用java
代码写,这样可以更方便的通过配置中心动态修改网关配置spring: cloud: gateway: routes: - id: order-service uri: 'http://192.168.1.233:9300' predicates: - Path=/order-service/**
routers
下面来配置所有的路由规则id
表示一个唯一的值uri
表示路由转发的地址predicates
断言的配置,就是上面那些
这里交代一下:我已经启动了一个order-service
监听在9300
端口了,这个服务有一个路由是order/create
这里使用的是Path
断言,当请求的路径是/order-service/**
(**
表示任意内容)时,则把请求转发到http://192.168.1.233:9300
即:
http://192.168.203.233:9500/order-service/order/create
->
http://192.168.203.233:9300/order-service/order/create
拦截器
SpringCloud Gateway
的全部filter
都在文档上有,想要概览一遍可以看看官网文档
前缀分离
现在你应该会发现一个问题,我们uri
上的order-servidce
只是用来区分请求不同的服务的,真正的请求uri
是order/create
,怎么转发请求的时候也给带上了这个前缀呢?那么想要解决这个问题的话一起来看看Spring
网关自带的filter
吧
想要实现上面的需求,把一个请求的几个前缀给去掉,可以使用The StripPrefix GatewayFilter Factory
,这个拦截器很简单,用于剥离请求uri的层级,我们在上方的yml里新增一些配置,感受一下变化:
spring:
cloud:
gateway:
routes:
- id: order-service
uri: 'http://192.168.1.233:9300'
predicates:
- Path=/order-service/**
# 新增配置
filters:
- StripPrefix=1
我们新增了一个filters
配置,这是一个数组
然后下面我们配置了一个叫StripPrefix
的filter
,这个就是上面说到的StripPrefix GatewayFilter
。设置的值为1
,则说明我们会剥离一层的uri
,最终的请求转发情况如下:
http://192.168.203.233:9500/order-service/order/create
->
http://192.168.203.233:9300/order/create
断路器
这样就完美的达到了我们的要求,是不是很开心,但是这还并没有完哦。
好好想想,在分布式系统中,网关作为流量的入口,因此会有大量的请求进入网关,向其他服务发起调用,其他服务不可避免的会出现调用失败(超时、异常),超时会造成网关堆积的请求过多,而失败则需要快速返回失败给客户端,想要实现这个要求,就必须在网关上做熔断、降级操作
这时候该断路器上场了,同样的SpringCloud Gateway
也提供了这么一个Spring Cloud CircuitBreaker GatewayFilter Factory
拦截器。这个拦截器需要有断路器的实现(impl)来配合,这里用的是SpringCloud
新的断路器Resilience4J Circuit Breakers
,想要用这个断路器还需要一些新的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
这是circuitbreaker
的resilience4j
实现
新增配置:
自定义一个断路器(可不做):
@Configuration public class MyCircuitBreakerConfig { private static final String SLOW_NAME = "SLOW"; @Bean public Customizer<ReactiveResilience4JCircuitBreakerFactory> slowCusomtizer() { return factory -> { factory.configure(builder -> builder .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(2)).build()) .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()), SLOW_NAME); }; } }
可以看到我们这个自定义的断路器只是改了一下超时时间为
2s
(默认1s
),并且给定id
叫SLOW
,其他的保持默认配置一下断路器的快速返回的内容是什么
@RestController @RequestMapping("/fallback") public class FallbackController { @RequestMapping("/order") public String fallback() { return "this request is break"; } }
这是一个标准的
Spring
接口重新回到
yml
配置
spring:
cloud:
gateway:
routes:
- id: order-service
uri: 'http://192.168.1.233:9300'
predicates:
- Path=/order-service/**
filters:
- StripPrefix=1
# 新增配置
- name: CircuitBreaker
args:
name: SLOW
fallbackUri: 'forward:/fallback/order'
- 第一个
name
表示的使用的filter
是什么,这里当然是CircuitBreaker
断路器 args.name
表示使用的断路器的id
是什么,我这里使用了上面配置的id
。实测如果这个id
的断路器配置不存在的话,会使用默认配置args.fallbackUri
则是当发生熔断时跳转的url
请求地址,就是上面定义的地址
这样就配置好了断路器,接下来测试一下是否成功。
- 先把
order-service
这个服务关掉 - 访问网关的
http://192.168.203.233:9500/order-service/order/create
- 等待
2s
直接返回this request is break
(或者你定义的响应)则表示成功
请求限流
限流使用到了redis
,这里加入spring-boot-starter-data-redis-reactive
pom.xml
新增:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
看官网介绍是需要一个key
生成的方法,用于标识一个请求,后续的里程碑版本会提供一些可直接使用的key
生成类,但是现在没有,我们尝试自己来写一个
java配置代码如下:
@Configuration
public class MyLimitConfig {
@Bean
KeyResolver limitKeyResolver() {
return exchange -> Mono.just(
Optional.ofNullable(exchange.getRequest().getRemoteAddress()).
orElse(InetSocketAddress.createUnresolved("127.0.0.1", 80)).
getHostName()
);
}
}
这里单纯的用一个ip地址
来表示一个请求
然后是yml的配置:
spring:
redis:
host: 192.168.1.104
port: 6379
password: pw123456
database: 0
timeout: 10S
cloud:
gateway:
routes:
- id: order-service
uri: 'http://192.168.1.233:9300'
predicates:
- Path=/order-service/**
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: SLOW
fallbackUri: 'forward:/fallback/order'
# 新增
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 2
redis-rate-limiter.requestedTokens: 1
key-resolver: '#{@limitKeyResolver}'
可以看到增加了一个RequestRateLimiter
拦截器,另外Spring
这里的限流算法用的是令牌桶算法
,不了解可以网络自己搜索了解一下
四个参数:
- redis-rate-limiter.replenishRate:表示的是每秒生成的令牌数量
- redis-rate-limiter.burstCapacity:表示的是令牌桶的大小
- redis-rate-limiter.requestedTokens:表示一个请求消耗几个令牌
- key-resolver:生成key的方法,使用的是
SpringEL
表达式
这样配置完成后,当超过请求限制,比如同一秒不同的ip请求3次的话,第三次会返回429
的状态。
结束
Spring
的网关在对比zuul
的话,zuul1
使用的是同步io
,zuul2
则和springcloud gateway
一样使用的是netty
。另外spring
的网关有很多开箱即用的router
,filter
。我觉得以后Spring
的网关应该会成为主流。