目录
1 Gateway是什么
2 能干嘛
3 Gateway特性
4 Gateway与Zuul的区别
5 Gateway三大核心概念
5.1 路由Route
5.2 断言Predicate
5.3 过滤器(Filter)
5.4 Gateway工作流程
6 Gateway代码实操
6.1 基本环境搭建
6.1.1 pom依赖
6.1.2application.yml(yml配置方式配置路由)
6.1.3 主启动类
6.1.4 服务提供者
6.2 Gateway测试
6.2.1 yml方式配置路由测试
6.2.2代码中注入RouteLocator的Bean的方式配置路由测试
6.2.3 动态路由配置
6.2.4 断言Predicate
6.2.5 Route Filter路由过滤器
7 总结
1 Gateway是什么
Gateway是一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注点,例如:安全性,监视/指标和弹性。(官网介绍)
SpringCloud全家桶中有一个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关,但在2.x版本中,Netflix对Zuul的升级工作一直跳票,最终SpringCloud自己研发了Gateway用来替代Zuul。Netflix很多组件(Eureka、Hystrix等)都进入维护阶段,选择Gateway做网关是更有保障的,毕竟是SpringCloud团队自己开发的。
SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是替代Zuul。在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0最新高性能版本进行集成,仍然还是使用zuul1.x非Reactor模式的老版本。为了提升网关的性能,SpringCloud Gateway是基于WebFlex框架实现的,而WebFlex框架底层使用了Reactor模式通信框架Netty。
官网Gateway的工作原理图:
2 能干嘛
反向代理、鉴权、流量控制、熔断、日志监控等等。
微服务中网关处于什么位置?
3 Gateway特性
1 ),基于Spring 5,Spring Boot 2和Project Reactor进行构建。
2),动态路由能够匹配任何请求属性。
3),可以对路由指定Predicate(断言)和Filter(过滤),易于编写Predicate和Filter。
4),集成Hystrix的断路器功能。
5),集成SpringCloud的服务发现功能。
6),请求限流功能。
7),支持路径重写。
4 Gateway与Zuul的区别
5 Gateway三大核心概念
5.1 路由Route
路由是构建网关的基本模块,它由ID、目标URL、一系列的断言、过滤器组成,如果断言为true,那么匹配该路由。
5.2 断言Predicate
参考Java 8 Function Predicate,开发人员可以匹配Http请求中的所有内容(例如请求头、请求参数),如果请求与断言相匹配则进行路由。
5.3 过滤器(Filter)
过滤器是Spring框架中GatewayFilter的实例,使用过滤器可以实现在请求被路由前或者之后对请求进行修改。
5.4 Gateway工作流程
路由转发+执行过滤器链。
6 Gateway代码实操
Gateway网关路由有两种配置方式:yml文件配置、代码中注入RouteLocator的Bean。
6.1 基本环境搭建
6.1.1 pom依赖
在父工程下新建一个名为cloud-gateway-gateway9527的module,对应的pom依赖如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0"xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"><parent><artifactId>cloud</artifactId><groupId>com.bighuan.springcloud</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>cloud-gateway-gateway9527</artifactId><dependencies><!--gateway--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!-- Eureka client--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><!--引入自定义的api通用包,可以使用Payment支付Entity--><dependency><groupId>com.bighuan.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass>com.bighuan.springcloud.GatewayMain9527</mainClass></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
6.1.2application.yml(yml配置方式配置路由)
application.yml文件配置如下。关于路由配置可以参考官网:id表示路由的ID,没有固定的规则,但是一定要唯一,可以配合服务名来配置;uri表示匹配后提供服务的路由地址;predicates下的Path是为了匹配对应的路径。
关于注册中心的相关配置,都在此前的博客中有相关记录,不再赘述。
server:port: 9527spring:application:name: cloud-gatewaycloud:gateway:routes:- id: payment_routh# payment_route 路由的ID,没有固定规则但要求唯一,建议配合服务名uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址predicates:- Path=/payment/get/** # 断言,路径相匹配的进行断言- id: payment_routh2# 路由的ID,没有固定规则但要求唯一,建议配合服务名uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址predicates:- Path=/payment/lb/** # 断言,路径相匹配的进行断言eureka:instance:hostname: cloud-gateway-serviceclient: # 服务提供者的provider注册进eureka的服务列表里register-with-eureka: truefetch-registry: trueservice-url:defaultZone: http://localhost:7001/eureka
6.1.3 主启动类
主启动类并没有特殊的配置,比较简单。
@SpringBootApplication@EnableEurekaClientpublic class GatewayMain9527 {public static void main(String[] args) {SpringApplication.run(GatewayMain9527.class,args);}}
6.1.4 服务提供者
在yml文件中关于路由的配置那一块,uri和predicates下的Path等配置都是为了指向服务提供者。服务提供者相关的博客可参考此前的博客,为行文方便,只粘贴出对应的controller。
@GetMapping(value = "/payment/lb")public String getPaymentLB(){return serverPort;}@GetMapping(value = "/payment/get/{id}")public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {Payment payment = paymentService.getPaymentById(id);log.info("*****查询结果*****:" + payment+",server.port:"+serverPort);if (payment != null) {return new CommonResult(200, "查询成功,serverPort:"+serverPort, payment);} else {return new CommonResult(444, "没有对应记录,查询ID:"+id+","+"serverPort:"+serverPort, null);}}
6.2 Gateway测试
6.2.1 yml方式配置路由测试
分别启动注册中心Eureka7001、服务提供者8001、Gateway9527,浏览器分别访问:
http://127.0.0.1:9527/payment/get/31、http://127.0.0.1:9527/payment/lb
发现都成功返回了对应的数据,前者需要查询数据库,后者则直接返回服务提供者的端口8001。通过Gateway9527,将服务提供者隐藏起来,对外界是不可见的,通过服务网关的路由就可以访问。
6.2.2代码中注入RouteLocator的Bean的方式配置路由测试
代码配置方式可参考官网的配置,如下:
本文配置类代码如下,特别注意的是,需要让此类可以被主启动类扫描到。
//代码配置: 配置路由的第二种方式@Configurationpublic class GatewayConfig {@Beanpublic RouteLocator routes(RouteLocatorBuilder builder) {RouteLocatorBuilder.Builder routes = builder.routes();return routes.route("path_route_bighuan",r -> r.path("/guonei").uri("/guonei")).build();}}
同样,分别启动注册中心7001、服务提供者8001(只测试代码配置路由,不需要启动也可以)、Gateway9527,访问http://127.0.0.1:9527/guonei,页面跳转到到了国内百度新闻的页面。
6.2.3 动态路由配置
以上的测试中,只有一个服务提供者8001,如果不止一个服务提供者8001,还有服务提供者8002、8003,每个都要在配置文件中配置一次的话,那么是不现实的。动态路由就可以解决这个问题。
默认情况下,Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
动态路由的配置文件为,application-dynamic.yml,配置如下:
server:port: 9527spring:application:name: cloud-gatewaycloud:gateway:discovery:locator:enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由routes:- id: payment_routh# payment_route 路由的ID,没有固定规则但要求唯一,建议配合服务名# uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址uri: lb://CLOUD-PAYMENT-SERVICE # 匹配后提供服务的路由地址predicates:- Path=/payment/get/** # 断言,路径相匹配的进行断言- id: payment_routh2# 路由的ID,没有固定规则但要求唯一,建议配合服务名# uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址uri: lb://CLOUD-PAYMENT-SERVICE # 匹配后提供服务的路由地址predicates:- Path=/payment/lb/** # 断言,路径相匹配的进行断言eureka:instance:hostname: cloud-gateway-serviceclient: # 服务提供者的provider注册进eureka的服务列表里register-with-eureka: truefetch-registry: trueservice-url:defaultZone: http://localhost:7001/eureka
说明:
1)spring.cloud.gateway.discovery.locator.enabled设置为true,开启从注册中心动态创建路由的功能,利用微服务名进行路由。
2)uri的协议为lb,表示基于服务注册的负载均衡,即启动Gateway的负载均衡功能,CLOUD-PAYMENT-SERVICE是服务提供者的微服务名。
测试步骤:启动一个注册中心Eureka7001、分别启动服务提供者8001和8002、启动Gateway9527,然后连续访问:http://127.0.0.1:9527/payment/lb,返回结果则为8001、8002交替出现,说明动态路由配置成功,而且负载均衡也实现了。
6.2.4 断言Predicate
SpringCloud Gateway包含许多内置的Route Predicate工厂,所有这些Predicate都与HTTP请求的不同属性相匹配,多个Predicate工厂可以进行组合。可以理解为Predicate就是 Sql语句中Where后的条件,可以有多个条件进行条件限制。
SpringCloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route对象。官网可以看到,至少内置了11中Route Predicate Factory。其中,第8种Path Route Predicate Factory前文已经接触过了,主要用来匹配路径。
在application-dynamic.yml的payment_routh2的predicates下增加after配置,表示需要在该时间之后才能成功匹配路由匹配。
- After=-03-28T17:25:10.065+08:00[Asia/Shanghai]
这种特殊的时间可以通过ZoneDateTime来获取
ZonedDateTime now = ZonedDateTime.now();// -03-28T16:51:32.977+08:00[Asia/Shanghai]System.out.println(now);
分别启动项目,在设置的after时间之前访问,会报错。
当时间过了after时间之后,就可以正常访问了。
Before、Between Route Predicate Factory的配置方式类似。
还是在application-dynamic.yml的payment_routh2的predicates下增加cookie配置。
- Cookie=username,bighuan
分别启动项目,一开始请求时不带cookie,直接返回404;带上cookie后,访问正常。
6.2.5 Route Filter路由过滤器
路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway包括许多内置的GatewayFilter工厂。Spring Cloud Gateway内置了多种路由过滤器,它们都GatewayFilter工厂类产生。(官网gatewayfilter-factories,配置参考官网即可)
Filter的生命周期有两种:pre、post。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志输出,流量监控等。(注:这段话来自某大佬博客)
Filter的种类有两种:GatewayFilter、GlobalFilter。
自定义全局GlobalFilter过滤器
自定义全局过滤器,需要实现GlobalFilter、Ordered两个接口。自定义全局过滤器,可以实现全局日志记录、统一网关鉴权等等。
@Component@Slf4jpublic class MyLogGateWayFilter implements GlobalFilter,Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("******come in MyLogGateWayFilter:"+new Date());String uname=exchange.getRequest().getQueryParams().getFirst("uname");if(uname == null){log.info("*****用户名为空,是非法用户,o(╥﹏╥)o");exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);return exchange.getResponse().setComplete();}return chain.filter(exchange);}/*** 加载过滤器的优先级,值越小,优先级越高* @return*/@Overridepublic int getOrder() {return 0;}}
测试:使用application.yml启动Gateway9527,访问http://127.0.0.1:9527/payment/lb?uname=bighuan可以通过获得结果,http://127.0.0.1:9527/payment/lb则会被拦截。
7 总结
虽然写这篇博客花的的时间比较长,但坚持坚持,会有收获的!
如果觉得《微服务学习之Gateway服务网关【Hoxton.SR1版】》对你有帮助,请点赞、收藏,并留下你的观点哦!