Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。 Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Spring Cloud Gateway Diagram
客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。此处理程序运行通过特定于请求的过滤器链发送请求。滤波器被虚线划分的原因是滤波器可以在发送代理请求之前或之后执行逻辑。执行所有“预”过滤器逻辑,然后进行代理请求。在发出代理请求之后,执行“post”过滤器逻辑。
引入gateway依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
修改后的pom文件
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.li</groupId> <artifactId>SpringCloudLearn</artifactId> <version>1.0-SNAPSHOT</version> <relativePath>../</relativePath> </parent> <artifactId>gateway-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>gateway-server</name> <description>Demo project for Spring Boot</description>
<dependencies> <!--gateway 网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
启动类加入注解@EnableEurekaClient向服务中心注册
package com.li.gatewayserver;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.gateway.route.RouteLocator;import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.context.annotation.Bean;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpMethod;import org.springframework.http.HttpStatus;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.web.cors.reactive.CorsUtils;import org.springframework.web.server.ServerWebExchange;import org.springframework.web.server.WebFilter;import org.springframework.web.server.WebFilterChain;import reactor.core.publisher.Mono;
@EnableEurekaClient@SpringBootApplicationpublic class GatewayServerApplication {
public static void main(String[] args) { SpringApplication.run(GatewayServerApplication.class, args); }}
application.yml配置文件
server: port: 8768eureka: port: 8761spring: application: name: gateway-server eureka: client: service-url: defaultZone: http://${eureka.instance.hostname}:${eureka.port}/eureka/ redis: host: 127.0.0.1 port: 6379 cloud: gateway: routes: # 请求路径匹配 - id: path_route uri: http://www.lhdyx.cn predicates: - Path=/lhd/** # 请求地址携带lhd的,则转发 # 加上StripPrefix=1,否则转发到后端服务时会带上前缀 filters: - StripPrefix=1
配置说明:
配置了一个 id 为 path_route的路由规则,当访问地址 http://localhost:8768/lhd 时会自动转发到地址:http://www.lhdyx.cn 启动服务发现报错
Description:
Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration
经查阅资料
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>
与
<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>
有冲突 为了快速启动 我先将父pom文件的web依赖去掉 后面用上再加上 继续启动程序 打开浏览器访问 http://localhost:8768/lhd 页面展示为我的博客地址 并且没有带上/lhd前缀
/** * 代码实现路由跳转 * 配置了一个id为p_route的路由 当访问地址http://localhost:8768/lhdblog时会 * 自动转发到地址:http://www.lhdyx.cn 和上面的转发效果一样 * * @param builder * @return */ @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("p_route", r -> r.path("/lhdblog") .uri("http://www.lhdyx.cn")) .build(); }
配置了一个 id 为 proute 的路由,当访问地址http://localhost:8768/lhdblog时会自动转发到地址:http://www.lhdyx.cn/lhdblog和上面的转发效果一样
在实际项目使用中可以将 uri 指向对外提供服务的项目地址,统一对外输出接口, 上面两个示例中 uri 都是指向了我的个人网站,在实际项目使用中可以将 uri 指向对外提供服务的项目地址
这里简单介绍了以路径规则的路由转发
Spring Cloud Gateway 的功能很强大,我们仅仅通过 Predicates 的设计就可以看出来,前面我们只是使用了 predicates 进行了简单的条件匹配,其实 Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。
Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础结构的一部分进行匹配。Spring Cloud Gateway包含许多内置的Route Predicate工厂。所有这些谓词都匹配HTTP请求的不同属性, 这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。add--与、or--或、negate--非
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。网上有一张图总结了 Spring Cloud 内置的几种 Predicate 的实现。
以下是Spring Cloud GateWay 内置几种 Predicate 的使用
# 请求路径匹配 - id: path_route uri: http://www.lhdyx.cn predicates: - Path=/lhd/** # 请求地址携带lhd的,则转发 # 加上StripPrefix=1,否则转发到后端服务时会带上前缀 filters: - StripPrefix=1 # 请求参数匹配 - id: query_route uri: http://www.lhdyx.cn predicates: - Query=lhd # 请求参数含有lhd filters: - StripPrefix=1 # Cookie 匹配 - id: cookie_route uri: http://www.lhdyx.cn predicates: - Cookie=www.lhdyx.cn, lhd # 如果携带cookie,参数名为www.lhdyx.cn,值为lhd,则转发 filters: - StripPrefix=1 # 时间匹配 - id: after_route uri: http://www.lhdyx.cn predicates: - After=2019-04-02T06:06:06+08:00[Asia/Shanghai] # 此路线与2019年4月2日之后的所有要求相匹配 filters: - StripPrefix=1 # Header 属性匹配 - id: header_route uri: http://www.lhdyx.cn predicates: - Header=request, \d+ # 如果请求头含有request,且为数字,则转发 filters: - StripPrefix=1 # 请求方法匹配 post get等 - id: method_route uri: http://www.lhdyx.cn predicates: - Method=/get # 请求方法过滤 filters: - StripPrefix=1 # ip匹配 - id: ip_route uri: http://www.lhdyx.cn predicates: - RemoteAddr=192.168.1.101 # ip地址 filters: - StripPrefix=1 # Host 匹配 - id: host_route uri: http://www.lhdyx.cn predicates: - Host=**.lhdyx.cn filters: - StripPrefix=1
这里不多介绍了 大家可以尝试下
官方文档 https://cloud.spring.io/spring-cloud-gateway/spring-cloud-gateway.html
配置文件
配置了一个Path 的predict,将以/client/**开头的请求都会转发到uri为lb://eureka-client的地址上,lb://eureka-client即eureka-client服务的负载均衡地址
配置完成后 启动之前的eureka-client服务 并查看注册情况
打开浏览器访问: http://localhost:8768/client/hello?name=lhd 浏览器返回以下的响应
说明已经成功转发到服务上
只要实现GlobalFilter, Ordered接口即可加 GlobalFilter 全局过滤器,打印每次请求的url
package com.li.gatewayserver.config;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;
/** * @Classname RequestFilter * @Description 全局过滤器 * @Author 李号东 lihaodongmail@163.com * @Date 2019-04-02 22:50 * @Version 1.0 */@Componentpublic class MyFilter implements GlobalFilter, Ordered {
//执行逻辑 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String uri = request.getURI().toString(); //打印每次请求的url System.out.println(" uri : " + uri); return chain.filter(exchange); }
//执行顺序 @Override public int getOrder() { return 1; }}
重启gateway-server服务 打开浏览器访问: http://localhost:8768/client/hello?name=lhd
控制台打印出url 说明过滤器生效
集成 Hystrix 熔断降级,引用hystrix依赖,在filters下加入熔断降级配置,设置降级后返回的路由 引入依赖
<!-- 断路器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
application.yml配置
新建一个HystrixController 的RestController,内容如下:
package com.li.gatewayserver.controller;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;import java.util.Map;
/** * @Classname HystrixController * @Description 熔断调用地址 * @Author 李号东 lihaodongmail@163.com * @Date 2019-04-02 23:05 * @Version 1.0 */@RestControllerpublic class HystrixController {
@RequestMapping("/defaultfallback") public Map<String,String> defaultfallback(){ System.out.println("降级操作..."); Map<String,String> map = new HashMap<>(); map.put("resultCode","fail"); map.put("resultMessage","服务异常"); map.put("resultObj","null"); return map; }}
关闭eureka-client服务 重启gateway-server服务, 然后浏览器访问: http://localhost:8768/client/hello?name=lhd 响应如下:
说明熔断器成功
Spring Cloud Gateway默认集成了Redis限流,可以对不同服务做不同维度的限流,如:IP限流、用户限流 、接口限流 本文演示的是 IP限流 ,先添加redis依赖,添加KeyResolver,再添加配置,需启动redis
引入依赖
<!-- 限流 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
通过KeyResolver来指定限流的Key,新建一个 RateLimiterConfig 类,IP限流代码如下:
package com.li.gatewayserver.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import reactor.core.publisher.Mono;
/** * @Classname RateLimiterConfig * @Description 限流方法 * @Author 李号东 lihaodongmail@163.com * @Date 2019-04-02 23:09 * @Version 1.0 */@Configurationpublic class RateLimiterConfig { @Bean(value = "remoteAddrKeyResolver") public KeyResolver remoteAddrKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); }}
application.yml下添加限流配置:
配置说明:
配置完成后 启动redis 和 eureka-client服务 重启gateway-server服务
测试网关是否做到了限流,使用 jmeter 测试工具 ,jmeter 阿帕奇的压测工具 自行百度 这里不做介绍
测试配置如下,100个线程,2秒内,循环2次,总共200个请求,请求地址:http://localhost:8768/client/hello?name=lhd
启动测试工具 结果如下
还可以使用用户限流、接口限流 用户限流,使用这种方式限流,请求路径中必须携带userId参数
@BeanKeyResolver userKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));}
接口限流,获取请求地址的uri作为限流key
@BeanKeyResolver apiKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getPath().value());}
在真实场景中,限流数的调整需要依赖配置中心,当网站做活动时,动态调整限流数,新服务上线时,通过配置中心做动态路由等
后面有时间继续介绍
有时候前端需要支持跨域访问, 这里简单配置允许所有域名访问.
/** * 跨域配置 * @return */ @Bean public WebFilter corsFilter() { return (ServerWebExchange ctx, WebFilterChain chain) -> { ServerHttpRequest request = ctx.getRequest(); if (!CorsUtils.isCorsRequest(request)) { return chain.filter(ctx); }
HttpHeaders requestHeaders = request.getHeaders(); ServerHttpResponse response = ctx.getResponse(); HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); HttpHeaders headers = response.getHeaders(); headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders()); if (requestMethod != null) { headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); } headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "all"); headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600"); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } return chain.filter(ctx); }; }
注入bean即可
Gateway介绍就到这里大家去试试吧 功能比较强大 源码下载: https://github.com/LiHaodong888/SpringCloudLearn