做为Netflix Zuul的替代者,Spring Cloud Gateway是一款很是实用的微服务网关,在Spring Cloud微服务架构体系中发挥很是大的做用。本文对Spring Cloud Gateway常见使用场景进行了梳理,但愿对微服务开发人员提供一些帮助。前端
微服务网关SpringCloudGatewayreact
1.概述web
Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway做为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不只提供统一的路由方式,而且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。redis
2.核心概念spring
网关提供API全托管服务,丰富的API管理功能,辅助企业管理大规模的API,以下降管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等贡呢。通常来讲网关对外暴露的URL或者接口信息,咱们统称为路由信息。若是研发过网关中间件或者使用过Zuul的人,会知道网关的核心是Filter以及Filter Chain(Filter责任链)。Sprig Cloud Gateway也具备路由和Filter的概念。下面介绍一下Spring Cloud Gateway中几个重要的概念。后端
如上图所示,Spring cloudGateway发出请求。而后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再经过指定的过滤器链将请求发送到咱们实际的服务执行业务逻辑,而后返回。跨域
快速入门安全
以Spring Boot框架开发为例,启动一个Gateway服务模块(以Consul做为注册中心),一个后端服务模块。client端请求经gateway服务把请求路由到后端服务。服务器
前提条件:架构
1.微服务开发
这里以使用Spring Boot框架开发微服务为例,启动一个服务并注册到Consul。
引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
注册服务到Consul,配置文件配置以下:
spring: application: name: service-consumer cloud: consul: host: 127.0.0.1 port: 8500 discovery: service-name: service-consumer
以下定义RestController,发布HTTP接口。
@RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; @GetMapping(value = "/info") public User info() { return userService.info(); } }
注:此为服务端配置,经Gateway把请求路由转发到该服务上。
2.网关配置
建立一个Gateway服务,引入如下依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
启动类配置以下:
@SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
Spring Cloud Gateway对client端请求起到路由功能,主要配置以下:
server: port: 8098 spring: application: name: service-gateway cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true consul: host: 127.0.0.1 #注册gateway网关到consul port: 8500 discovery: service-name: service-gateway
此时使用http://localhost:8089/service-consumer/user/info访问服务,网关便可对服务进行路由转发,把请求转发到具体后端服务上。此时,url中使用的url前缀service-consumer,是后端服务在Consul注册的服务名称转为小写字母之后的字符串。
最佳实践
01
Gateway网关配置
本文第二部分开发规范中定义了网关进行路由转发的配置,除了上述配置方式还可使用下面的方式进行配置:
gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: service_consumer uri: lb://service-consumer predicates: - Path= /consumer/** filters: - StripPrefix=1
在上面的配置中,配置了一个Path的predicat,将以/consumer/**开头的请求都会转发到uri为lb://service-consumer的地址上,lb://service-consumer(注册中心中服务的名称)即service-consumer服务的负载均衡地址,并用StripPrefix的filter 在转发以前将/consumer去掉。同时将spring.cloud.gateway.discovery.locator.enabled改成false,若是不改的话,以前的http://localhost:8081/service-consumer/user/info这样的请求地址也能正常访问,由于这时为每一个服务建立了2个router。
本文第二部分和本节一共讲述了两种配置方式,两种配置均可以实现请求路由转发的功能。参数spring.cloud.gateway.discovery.locator.enabled为true,代表Gateway开启服务注册和发现的功能,而且Spring Cloud Gateway自动根据服务发现为每个服务建立了一个router,这个router将以服务名开头的请求路径转发到对应的服务。spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写(由于服务注册的时候,向注册中心注册时将服务名转成大写的了)。
gateway: discovery: locator: enabled: true lower-case-service-id: true
02
Gateway跨域访问
Spring Cloud Gateway还针对跨域访问作了设计,可使用如下配置解决跨域访问问题:
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "https://docs.spring.io" allowedMethods: - GET allowHeaders: - Content-Type
在上面的示例中,容许来自https://docs.spring.io的get请...。
03
Gateway 过滤器
Spring Cloud Gateway的filter生命周期不像Zuul那么丰富,它只有两个:“pre”和“post”:
Spring Cloud gateway的filter分为两种:GatewayFilter和Globalfilter。GlobalFilter会应用到全部的路由上,而Gatewayfilter将应用到单个路由或者一个分组的路由上。
利用Gatewayfilter能够修改请求的http的请求或者是响应,或者根据请求或者响应作一些特殊的限制。更多时候能够利用Gatewayfilter作一些具体的路由配置。
下面的配置是AddRequestParameter Gatewayfilter的相关配置。
spring: application: name: service-gateway cloud: gateway: discovery: locator: enabled: true routes: - id: parameter_route uri: http://localhost:8504/user/info filters: - AddRequestParameter=foo, bar predicates: - Method=GET
上述配置中指定了转发的地址,设置全部的GET方法都会自动添加foo=bar,当请求符合上述路由条件时,便可在后端服务上接收到Gateway网关添加的参数。
另外再介绍一种比较经常使用的filter,即StripPrefix gateway filter。
配置以下:
spring: cloud: gateway: routes: - id: stripprefixfilter uri: lb://service-consumer predicates: - Path=/consumer/** filters: - StripPrefix=1
当client端使用http://localhost:8098/consumer/user/info路径进行请求时,若是根据上述进行配置Gateway会将请求转换为http://localhost:8098/service-consumer/user/info。以此做为前端请求的最终目的地。
04
Gateway请求匹配
Gateway网关能够根据不一样的方式进行匹配进而把请求分发到不一样的后端服务上。
经过header进行匹配,把请求分发到不一样的服务上,配置以下:
spring: cloud: gateway: routes: - id: header_route uri: http://baidu.com predicates: - Header=X-Request-Id, \d+
经过curl测试:curl http://localhost:8080 -H "X-Request-Id:666666",返回页面代码证实匹配成功。
若是是以Host进行匹配,配置以下:
spring: cloud: gateway: routes: - id: host_route uri: http://baidu.com predicates: - Host=**.baidu.com
经过curl http://localhost:8098 -H "Host: www.baidu.com"进行测试,返回页面代码即转发成功。
能够经过POST、GET、PUT、DELTE等不一样的方式进行路由:
spring: cloud: gateway: routes: - id: method_route uri: http://baidu.com predicates: - Method=GET
经过 curl http://localhost:8098 进行测试,返回页面代码即表示成功。
上述是单个匹配进行路由,若是把多个匹配合在一块儿进行路由,必须知足全部的路有条件才会进行路由转发。
05
Gateway熔断
Spring Cloud Gateway也能够利用Hystrix的熔断特性,在流量过大时进行服务降级,同时项目中必须加上Hystrix的依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
配置后,Gateway将使用fallbackcmd做为名称生成HystrixCommand对象进行熔断处理。若是想添加熔断后的回调内容,须要添加如下配置:
spring: cloud: gateway: routes: - id: hystrix_route uri: lb://consumer-service predicates: - Path=/consumer/** filters: - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/fallback - StripPrefix=1 hystrix: command: fallbackcmd: execution: isolation: thread: timeoutInMilliseconds: 5000 #超时时间,若不设置超时时间则有可能没法触发熔断
上述配置中给出了熔断以后返回路径,所以,在Gateway服务模块添加/fallback路径,以做为服务熔断时的返回路径。
@RestController public class GatewayController { @RequestMapping(value = "/fallback") public String fallback(){ return "fallback nothing"; } }
fallbackUri: forward:/fallback配置了 fallback 时要会调的路径,当调用 Hystrix 的 fallback 被调用时,请求将转发到/fallback这个 URI,并以此路径的返回值做为返回结果。
06
Gateway重试路由器
经过简单的配置,Spring Cloud Gateway就能够支持请求重试功能。
spring: cloud: gateway: routes: - id: header_route uri: http://localhost:8504/user/info predicates: - Path=/user/** filters: - name: Retry args: retries: 3 status: 503 - StripPrefix=1
Retry GatewayFilter经过四个参数来控制重试机制,参数说明以下:
使用上述配置进行测试,当后台服务不可用时,会在控制台看到请求三次的日志,证实此配置有效。
07
Gateway 限流操做
Spring Cloud Gateway自己集成了限流操做,Gateway限流须要使用Redis,pom文件中添加Redis依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
配置文件中配置以下:
spring: cloud: gateway: routes: - id: rate_limit_route uri: lb://service-consumer predicates: - Path=/user/** filters: - name: RequestRateLimiter args: key-resolver: "#{@hostAddrKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 3 - StripPrefix=1 consul: host: 127.0.0.1 port: 8500 discovery: service-name: service-gateway instance-id: service-gateway-233 redis: host: localhost port: 6379
在上面的配置问价中,配置了Redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器须要配置三个参数:
注意:filter下的name必须是RequestRateLimiter。
Key-resolver参数后面的bean须要本身实现,而后注入到Spring容器中。KeyResolver须要实现resolve方法,好比根据ip进行限流,则须要用hostAddress去判断。实现完KeyResolver以后,须要将这个类的Bean注册到Ioc容器中。还能够根据uri限流,同hostname限流是同样的。例如以ip限流为例,在gateway模块中添加如下实现:
public class HostAddrKeyResolver implements KeyResolver { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); } public HostAddrKeyResolver hostAddrKeyResolver() { return new HostAddrKeyResolver(); } }
把该类注入到spring容器中:
@SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } @Bean public HostAddrKeyResolver hostAddrKeyResolver(){ return new HostAddrKeyResolver(); } }
基于上述配置,能够对请求基于ip的访问进行限流。
08
自定义Gatewayfilter
Spring Cloud Gateway内置了过滤器,可以知足不少场景的需求。固然,也能够自定义过滤器。在Spring Cloud Gateway自定义过滤器,过滤器须要实现GatewayFilter和Ordered这两个接口。
下面的例子实现了Gatewayfilter,它能够以log日志的形式记录每次请求耗费的时间,具体实现以下:
public class RequestTimeFilter implements GatewayFilter, Ordered { private static final Log log = LogFactory.getLog(GatewayFilter.class); private static final String REQUEST_TIME_BEGIN = "requestTimeBegin"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN); if (startTime != null) { log.info("请求路径:"+exchange.getRequest().getURI().getRawPath() + "消耗时间: " + (System.currentTimeMillis() - startTime) + "ms"); } }) ); } @Override public int getOrder() { return 0; } }
上述代码中定义了本身实现的过滤器。Ordered的int getOrder()方法是来给过滤器定优先级的,值越大优先级越低。还有一个filter(ServerWebExchange exchange, GatewayFilterChain chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器。而后再chain.filter()的内部类中的run()方法中至关于"post"过滤器,在此处打印了请求所消耗的时间。
接下来将该过滤器注册到router中,代码以下。
@Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/user/**") .filters(f -> f.filter(new RequestTimeFilter()) .addResponseHeader("X-Response-Default-Foo", "Default-Bar")) .uri("http://localhost:8504/user/info") .order(0) .id("customer_filter_router") ) .build(); }
除了上述代码的方式配置咱们自定义的过滤器的方式以外,也能够在application.yml文件中直接配置,这里再也不赘述。
启动程序,经过curl http://localhost:8098/user/info控制台会打印出请求消耗时间,日志以下:
.... 2019-05-22 15:13:31.221 INFO 19780 --- [ctor-http-nio-4] o.s.cloud.gateway.filter.GatewayFilter : 请求路径:/user/info消耗时间: 54ms ... 2019-05-22 16:46:23.785 INFO 29928 --- [ctor-http-nio-1] o.s.cloud.gateway.filter.GatewayFilter : 请求路径:/user/info3消耗时间: 5ms ....
09
自定义GlobalFilter
Spring Cloud Gateway根据做用范围分为GatewayFilter和GlobalFilter,两者区别以下:
GatewayFilter : 须要经过spring.cloud.routes.filters 配置在具体路由下,只做用在当前路由上或经过spring.cloud.default-filters配置在全局,做用在全部路由上。
GlobalFilter:全局过滤器,不须要在配置文件中配置,做用在全部的路由上,最终经过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不须要配置,系统初始化时加载,并做用在每一个路由上。
在上一小节中定义的是Gatewayfilter,下面实现的是Globalfilter:
public class TokenFilter implements GlobalFilter, Ordered {
Logger logger= LoggerFactory.getLogger( TokenFilter.class ); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (token == null || token.isEmpty()) { logger.info( "token 为空,没法进行访问." ); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; }
}
上述代码实现了Globalfilter,具体逻辑是判断请求中是否含参数token,若是没有,则校验不经过,对全部请求都有效。若是含有token则转发到具体后端服务上,若是没有则校验不经过。
经过curl http://localhost:8098/user/info进行访问,由于路径中不含有参数token,则没法经过校验,打印日志以下:
2019-05-22 15:27:11.078 INFO 5956 --- [ctor-http-nio-1] com.song.gateway.TokenFilter : token 为空,没法进行访问. ...
经过curl http://localhost:8098/user/info?token=123进行访问时,则能够获取到后端服务返回结果。