SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway
一、前言
1、SpringCloudGateway是SpringCloud新推出的网关框架,比较于上一代Zuul,功能和性能有很大的提升。Zuul1.x采用的是阻塞多线程方式,也就是一个线程处理一个连接请求,高并发情况下性能较差,即使是Zuul2.x虽然做到了非阻塞,但是面对连续跳票,看起来Zuul要被抛弃了。取而代之的是SpringCloudGateway,SpringCloudGateway是基于Webflux,是一个非阻塞异步的框架,性能上有很大提升,而且包含了Zuul的所有功能,可以从Zuul无缝切换到SpringCloudGateway
2、SpringCloud环境版本:Greenwich.RELEASE
3、SpringBoot环境版本:2.1.3.RELEASE
二、环境搭建
1、在父工程下新建一个网关模块
2、引入SpringCloudGateway需要的POM,记得引入actuator组件,否则服务发现中心会认为服务不在线,导致网关无法路由到服务,并且加入熔断组件Hystrix
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 健康检查 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
3、主函数很简单,加入必要的注解,比如服务发现等
@SpringBootApplication @EnableDiscoveryClient public class GatewayCarfacApplication { public static void main(String[] args) { SpringApplication.run(GatewayCarfacApplication.class, args); } }
4、进行SpringCloudGateway的配置,配置可以使用Java代码进行配置或者yml配置,这里使用yml进行配置
spring: cloud: gateway: discovery: locator: enabled: true # 设置可以路由到其他服务 routes: # 可以配置多个路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id> predicates: - Path=/media/** # 路由规则 filters: - StripPrefix=1 # 不填则无法路由到其他服务 - AddRequestHeader=X-Request-Foo, Bar - name: Hystrix # 添加熔断 args: name: fallbackcmd fallbackUri: forward:/test/fallback # 熔断跳转地址 # 熔断超时时间 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000
5、上面我们配置了熔断的配置,一旦发生熔断就会跳转到/test/fallback这个地址,下面我们实现一下这个接口,这里简单的返回了error,我们可以自定义处理熔断的逻辑
@Controller @RequestMapping(value = "/test") public class TestController { @RequestMapping(value = "/fallback", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<String> fallback() { return new ResponseEntity<>("error.", HttpStatus.OK); } }
6、网关配置好后,再启动一个其他的服务模块,记得添加引入actuator,否则无法被路由到
<!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 健康检查 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
7、编写一个HTTP接口,来测试网关的调用,为了检测熔断的效果,这里通过参数来控制接口的响应时间
@Controller @RequestMapping(value = "/test") public class TestController { @Value("${server.port}") private String port; @RequestMapping(value = "/get/{time}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<String> get(@PathVariable("time") long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return new ResponseEntity<>(port + " get ok.", HttpStatus.OK); } }
8、在同一注册中心下启动网关和服务,根据我们配置的路由规则/media/**,我们可以这样调用http://127.0.0.1:8501/media/get/100,可以看到正确的路由到了服务
9、我们将响应时间改成5秒http://127.0.0.1:8501/media/get/5000,超高熔断的检测时间,可以发现接口熔断,并且跳转到了指定的链接
三、高级特性---断言
1、以上我们实现了SpringCloudGateway的基本使用办法,可以应付大部分应用场景了,我们同时可以细粒度的去改造路由,就使用到了断言Predict,如果不满足我们设置的断言条件,则无法被路由
2、设置断言,我们只需要在predicates下面加入配置即可,要注意如果设置了多个断言,则请求必须满足所有断言才可以被正确路由到
spring: cloud: gateway: routes: # 可以配置多个路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id> predicates: # 加入断言
3、常用的一些断言,还有很多其他断言就不一一列举了,当然最常用的可能是Path断言,我们需要通过路径来指定路由
(1)- Header=<key>, <value>(必须有指定的HTTP Header才能路由)
(2)- Cookie=<key>, <value>(必须有指定的Cookie才能路由)
(3)- Host=aaa.bbb.com(请求域名必须是aaa.bbb.com才能路由)
(4)- Method=GET(请求方式必须是Get请求才能路由)
四、高级特性---过滤器
1、通过设置网关的过滤器,我们可以在用户访问的入口增加一些处理,比如鉴权、接口监控等,下面介绍一些常用的过滤器
2、常用的过滤器
(1)- AddRequestHeader=<key>, <value>(增加自定义HTTP请求头)
(2)- SetStatus=401(设置响应的HTTP错误码)
(3)- RedirectTo=302, http://acme.org(重定向到指定链接)
3、自定义过滤器,可以通过代码定制更加灵活的过滤器,下面实现一个简单的接口耗时统计的过滤器,思路:设置两个过滤器,一个前置过滤器用来收集接口开始调用的时间,一个后置过滤器来将结束时间减去开始时间得到接口的耗时时间,并打印出来
(1)前置过滤器工厂,将开始时间写到attributes里面
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> { public PreGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { exchange.getAttributes().put("requestTime", System.currentTimeMillis()); return chain.filter(exchange); }; } public static class Config { } }
(2)后置过滤器工厂,获取attribute里面的开始时间,并用当前时间减去,得到耗时时间,打印
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config> { public PostGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { return chain.filter(exchange).then(Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute("requestTime"); long time = System.currentTimeMillis() - startTime; System.out.println("接口耗时时间(ms):"+time); })); }; } public static class Config { } }
(3)接下来将两个过滤器注入到Spring里面
@Configuration public class FilterConfig { @Bean public PreGatewayFilterFactory preGatewayFilterFactory() { return new PreGatewayFilterFactory(); } @Bean public PostGatewayFilterFactory postGatewayFilterFactory() { return new PostGatewayFilterFactory(); } }
(4)前往application.yml进行过滤器的配置,在filter属性下配置上我们的自定义过滤器,根据框架的约定引用过滤器:比如我们的过滤器名称是PreGatewayFilterFactory,那我们引用的名称就是去掉GatewayFilterFactory这个后缀,也就是Pre,具体配置如下
spring: cloud: gateway: discovery: locator: enabled: true # 设置可以路由到其他服务 routes: # 可以配置多个路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id> predicates: - Path=/media/** # 路由规则 filters: - StripPrefix=1 # 不填则无法路由到其他服务 - Pre # 自定义过滤器 - Post # 自定义过滤器
(5)接下来我们调用接口,可以看到耗时时间打在公屏上
五、网关限流
1、我们可以在网关层面做限流的功能,防止高并发时把服务器搞崩,或者应对一些网络攻击等情况
2、SpringCloudGateway为我们提供了一个很方便使用的令牌桶限流,思路:我们设置一个固定大小的令牌桶,如果令牌桶不满,则根据一定的频率向桶里放入令牌,每当有客户端的请求发来,会先从令牌桶里面取令牌,取到了则继续执行,取不到请求则会被拒绝,这样就达到了限流的作用,我们可以灵活地调整令牌补充速率和令牌桶大小,来细粒度的控制限流,我们使用redis来存储令牌,下面是一个简单的demo
(1)引入redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
(2)接下来自定义限流的维度,我们可以从域名、uri等维度进行限流
public class HostAddrKeyResolver implements KeyResolver { @Override public Mono<String> resolve(ServerWebExchange exchange) { // 域名维度限流 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); // uri维度限流 // return Mono.just(exchange.getRequest().getURI().getPath()); } }
(3)将解析器注入到Spring
@Configuration public class TokenLimitConfig { @Bean public HostAddrKeyResolver hostAddrKeyResolver() { return new HostAddrKeyResolver(); } }
(4)接下来配置限流器的配置,将我们编写的解析器引用,并且配置令牌桶的属性,这里我配置的是,每秒补充一个令牌,令牌桶的大小为3,最后配置redis的参数,用起来非常简单
spring: cloud: gateway: discovery: locator: enabled: true # 设置可以路由到其他服务 routes: # 可以配置多个路由 - id: service-media-1 # 路由id唯一 uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id> predicates: - Path=/media/** # 路由规则 filters: - StripPrefix=1 # 不填则无法路由到其他服务 - name: RequestRateLimiter # 配置限流器 args: key-resolver: '#{@hostAddrKeyResolver}' # 自定义限流过滤器 redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率 redis-rate-limiter.burstCapacity: 3 # 令牌桶总容量 redis: host: 127.0.0.1 port: 4000 password: 123456
(5)配置好后,使用JMeter来压测一下接口,可以发现某些请求返回429错误码,被限流