这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容:
①SpringCloud 实战:引入Eureka组件,完善服务治理
②SpringCloud 实战:引入Feign组件,发起服务间调用
③SpringCloud 实战:使用 Ribbon 客户端负载均衡
④SpringCloud 实战:引入Hystrix组件,分布式系统容错
⑤SpringCloud 实战:引入Zuul组件,开启网关路由html
Spring Cloud Gateway 是 Spring Cloud 的一个子项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。java
Spring Cloud Gateway 做为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不只提供统一的路由方式,而且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。react
Spring Cloud Gateway 具备以下特性:web
新建jlw-gateway
项目正则表达式
引入gateway依赖redis
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
加入Eureka注册中心算法
开启服务注册和发现spring
# gateway 服务端口 server: port: 9000 spring: cloud: gateway: discovery: locator: # 启用DiscoveryClient网关集成的标志 enabled: true # 服务小写匹配 lower-case-service-id: true
配置完上面,Gateway 就能够自动根据服务发现为每一个服务建立router了,而后将已服务名开头的请求路径转发到对应的服务。api
pom中引入spring-boot-starter-actuator相关依赖,而后配置文件添加以下代码,开启gateway相关的端点。跨域
management: endpoints: web: exposure: #应该包含的端点ID,所有:* include: 'gateway'
重启项目,访问http://localhost:9000/actuator/gateway/routes就能够查看到配置的路由信息了
更多gateway路由信息接口展现以下图:
Gateway 提供了两种不一样的方式用于配置路由:一种是经过yml文件来配置,另外一种是经过Java Bean来配置
①使用配置文件
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2
字段含义解释:
lb://eureka-provider
意思是请求要转发到注册中心的eureka-provider
服务上。②使用Java Bean配置
配置RouteLocator
对象,代码示例以下:
@Bean public RouteLocator customRoutes(RouteLocatorBuilder builder){ return builder.routes() // 请求网关路径包含 /api/ec/** 的都会被路由到eureka-client .route("eureka-client",r->r.path("/api/ec/**") .filters(f->f.stripPrefix(2)) .uri("lb://eureka-client")) // 能够配置多个route .route("eureka-client2",r->r.path("/api/ec2/**") .filters(f->f.stripPrefix(2)) .uri("lb://eureka-client")) .build(); }
以上配置后,经过http://localhost:9000/api/ec/sayHello 或者http://localhost:9000/api/ec2/sayHello 都会被路由到eureka-client
服务。
Spring Cloud Gateway 内置了不少 Predicates 工厂(能够经过访问路径/actuator/gateway/routepredicates
查看),这些 Predicates 工厂经过不一样的 HTTP 请求参数来匹配,多个 Predicates 工厂能够组合使用。
按照其功能能够大体分为如下几种不一样 Predicate
在指定时间以后
的请求会匹配该路由:AfterRoutePredicateFactory
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - After=2019-10-10T00:00:00+08:00[Asia/Shanghai]
在指定时间以前
的请求会匹配该路由:BeforeRoutePredicateFactory
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Before=2019-10-10T00:00:00+08:00[Asia/Shanghai]
在指定时间区间
内的请求会匹配该路由:BetweenRoutePredicateFactory
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Between=2019-10-01T00:00:00+08:00[Asia/Shanghai], 2019-10-10T00:00:00+08:00[Asia/Shanghai]
Cookie Route Predicate 能够接收两个参数,一个是 Cookie name,一个是正则表达式。示例以下:
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Cookie=name, jinglingwang.cn
Header Route Predicate 和 Cookie Route Predicate 同样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式,示例以下:
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Header=name, jinglingwang.cn
该模式接收一个参数:主机列表,示例以下:
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Host=**.jinglingwang.cn,**.jinglingwang.com
能够经过是 POST、GET、PUT、DELETE 等不一样的请求方式来进行路由,示例代码以下:
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Method=GET,POST
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - Path=/api/rc/**
该模式有两个参数:一个必需的param和一个可选的regexp(Java正则表达式),示例以下
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - QUERY=name,jingling*
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client predicates: - RemoteAddr=192.168.1.1/24 #192.168.1.1是IP 24是子网掩码
若是请求的远程地址是192.168.1.10,则此路由匹配。
该模式有两个参数:group和Weight(一个int值),示例以下:
spring: cloud: gateway: routes: - id: weight_high uri: http://localhost:8201 predicates: - Weight=group1, 8 - id: weight_low uri: http://localhost:8202 predicates: - Weight=group1, 2
以上表示有80%的请求会被路由到localhost:8201,20%会被路由到localhost:8202
路由过滤器容许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器只能指定路由进行使用。Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
添加请求Header过滤器
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client filters: - AddRequestHeader=source, jinglingwang.cn
上面的示例会为全部匹配的请求向下游请求时在Header中添加source=jinglingwang.cn
添加请求参数过滤器
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client filters: - AddRequestParameter=source, jinglingwang.cn
上面的示例会把source=jinglingwang.cn
添加到下游的请求参数中
添加响应头过滤器
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client filters: - AddResponseHeader=source, jinglingwang.cn
上面的示例会把source=jinglingwang.cn
添加到全部匹配请求的下游响应头中。
剔除重复的响应头过滤器
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client filters: - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
DedupeResponseHeader过滤器也可接收可选策略参数,可接收参数值包括:RETAIN_FIRST (默认值,保留第一个值), RETAIN_LAST(保留最后一个值), and RETAIN_UNIQUE(保留全部惟一值,以它们第一次出现的顺序保留)。
开启Hystrix断路器功能的过滤器
要开启断路器功能,咱们须要在pom.xml中添加Hystrix的相关依赖:spring-cloud-starter-netflix-hystrix
而后添加相关服务降级的处理类:
@RestController public class FallbackController{ @GetMapping("/fallback") public Object fallback() { Map<String,Object> result = new HashMap<>(3); result.put("data","jinglingwang.cn"); result.put("message","Get request fallback!"); result.put("code",500); return result; } }
添加配置
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2 - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/fallback
启用resilience4j断路器的过滤器
要启用Spring Cloud断路器过滤器,须要引入依赖spring-cloud-starter-circuitbreaker-reactor-resilience4j
spring: cloud: gateway: routes: - id: ribbon-client uri: lb://ribbon-client filters: - CircuitBreaker=myCircuitBreaker
还能够接受一个可选的fallbackUri参数:
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2 - name: CircuitBreaker args: name: myCircuitBreaker fallbackUri: forward:/fallback
关闭eureka-provider服务,访问http://localhost:9000/api/ep/hello接口,出现降级处理信息
上面的配置也能够用JAVA Bean的方式配置:
@Bean public RouteLocator customRoutes(RouteLocatorBuilder builder){ return builder.routes() .route("eureka-provider", r -> r.path("/api/ep/**") .filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback"))) .uri("lb://eureka-provider")) .build(); }
6.1 根据状态码使断路器跳闸
根据返回的状态码,决定断路器是否要跳闸
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2 - name: CircuitBreaker args: name: myCircuitBreaker fallbackUri: forward:/fallback statusCodes: - 500 - 'NOT_FOUND'
或者JAVA Bean配置:
@Bean public RouteLocator customRoutes(RouteLocatorBuilder builder){ return builder.routes() .route("eureka-provider", r -> r.path("/api/ep/**") .filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500"))) .uri("lb://eureka-provider")) .build(); }
其中NOT_FOUND
是HttpStatus枚举的String表示形式
增长路径的过滤器
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - PrefixPath=/mypath
把/mypath
做为全部匹配请求路径的前缀
去掉路径前缀的过滤器
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2
以上配置会忽略两位路径path,当访问网关API /api/ep/hello
时,向eureka-provider发起/hello
请求
用于限流的过滤器
RequestRateLimiter 过滤器能够用于限流,RateLimiter实现来肯定是否容许继续当前请求。 若是不是,则返回HTTP 429—太多请求(默认)的状态。
该过滤器采用可选的keyResolver
参数和速率限制器特定的参数,keyResolver是一个实现KeyResolver接口的bean。在配置中,使用SpEL按名称引用bean。#{@myKeyResolver}是引用名为myKeyResolver的bean的SpEL表达式。
使用 Redis RateLimiter
引入redis依赖,配置好redis。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
添加配置,使用的算法是令牌桶算法。
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 #容许用户每秒处理多少个请求,而不丢弃任何请求。这是令牌桶的填充速率 redis-rate-limiter.burstCapacity: 20 #一个用户在一秒钟内容许作的最大请求数。这是令牌桶能够容纳的令牌数。将该值设置为零会阻止全部请求。 redis-rate-limiter.requestedTokens: 1 #一个请求花费多少令牌
经过在“replenishRate”和“burstCapacity”中设置相同的值来实现稳定的速率。经过将burstCapacity设置为高于replenishRate,能够容许临时爆发。
配置KeyResolver
JAVA 代码:
@Configuration public class RedisRateLimiterConfig{ @Bean KeyResolver userKeyResolver() { // 根据请求参数中的phone进行限流 return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("phone")); } @Bean @Primary public KeyResolver ipKeyResolver() { // 根据访问IP进行限流 return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); } }
配置RequestRateLimiter
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 #容许用户每秒处理多少个请求,而不丢弃任何请求。这是令牌桶的填充速率 redis-rate-limiter.burstCapacity: 2 #一个用户在一秒钟内容许作的最大请求数。这是令牌桶能够容纳的令牌数。将该值设置为零会阻止全部请求。 redis-rate-limiter.requestedTokens: 1 #一个请求花费多少令牌 key-resolver: "#{@ipKeyResolver}"
屡次请求,会返回状态码为429的错误
用于重定向的过滤器
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2 - RedirectTo=302, https://jinglingwang.cn #302 重定向到https://jinglingwang.cn
效果图以下:
用于重试的过滤器
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2 - name: Retry args: retries: 3 statuses: BAD_GATEWAY methods: GET,POST backoff: firstBackoff: 10ms maxBackoff: 50ms factor: 2 basedOnPreviousValue: false
参数解释:
用于限制请求大小的过滤器
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** filters: - StripPrefix=2 - name: RequestSize args: maxSize: 5MB
当请求大小大于容许的限制时,网关会限制请求到达下游服务。maxSize参数后跟一个可选的数据单位,如“KB节”或“MB”,默认是“B”。
上面的配置若是超过限制会出现如下提示:
errorMessage:Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB
咱们能够为全部路由配置Http超时(响应和链接),而且为每一个特定路由配置单独的超时时间
全局的超时时间配置:
spring: cloud: gateway: httpclient: connect-timeout: 1000 response-timeout: 5s
特定路由配置超时时间
spring: cloud: gateway: routes: - id: eureka-provider uri: lb://eureka-provider predicates: - Path=/api/ep/** metadata: response-timeout: 2000 connect-timeout: 1000
或者使用JAVA Bean的方式配置:
@Bean public RouteLocator customRoutes(RouteLocatorBuilder builder){ return builder.routes() .route("eureka-provider", r -> r.path("/api/ep/**") .filters(f->f.stripPrefix(2) .requestRateLimiter(rate->rate.setKeyResolver(ipKeyResolver)) .circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500").addStatusCode("NOT_FOUND"))) .uri("lb://eureka-provider") .metadata(RESPONSE_TIMEOUT_ATTR, 200) .metadata(CONNECT_TIMEOUT_ATTR, 200)) .build(); }
spring: cloud: gateway: globalcors: # 全局跨域配置 cors-configurations: '[/**]': allowedOrigins: "jinglingwang.cn" allowedMethods: - GET add-to-simple-url-handler-mapping: true
上面的配置,对于全部Get请求,容许来自jinglingwang.cn
的跨域请求。
经过一个名为AbstractRoutePredicateFactory
的抽象类来进行扩展,示例代码:
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> { public MyRoutePredicateFactory(){ super(Config.class); } @Override public Predicate<ServerWebExchange> apply(Config config){ return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { String host = exchange.getRequest().getHeaders().getFirst("Host"); return "jinglingwang.cn".equalsIgnoreCase(host); } @Override public String toString() { return String.format("host: name=%s ", config.host); } }; } public static class Config { //自定义过滤器的配置属性 @NotEmpty private String host; } }
要写GatewayFilter,必须实现GatewayFilterFactory,能够经过扩展一个名为AbstractGatewayFilterFactory的抽象类来进行。
@Component public class AddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddHeaderGatewayFilterFactory.Config>{ public AddHeaderGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config){ return (exchange, chain) -> { // 若是要构建“前置”过滤器,则须要在调用chain.filter以前处理 ServerHttpRequest request = exchange.getRequest().mutate() .header("source", "jinglingwang.cn").build(); //使用构建器来处理请求 return chain.filter(exchange.mutate().request(request).build()); }; } public static class Config { //Put the configuration properties for your filter here } }
要查看全部与 Spring Cloud 网关相关的配置属性列表,请参见附录