目录
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.2 application.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 总结
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的工作原理图:
反向代理、鉴权、流量控制、熔断、日志监控等等。
微服务中网关处于什么位置?
1 ),基于Spring 5,Spring Boot 2和Project Reactor进行构建。
2),动态路由能够匹配任何请求属性。
3),可以对路由指定Predicate(断言)和Filter(过滤),易于编写Predicate和Filter。
4),集成Hystrix的断路器功能。
5),集成SpringCloud的服务发现功能。
6),请求限流功能。
7),支持路径重写。
路由是构建网关的基本模块,它由ID、目标URL、一系列的断言、过滤器组成,如果断言为true,那么匹配该路由。
参考Java 8 Function Predicate,开发人员可以匹配Http请求中的所有内容(例如请求头、请求参数),如果请求与断言相匹配则进行路由。
过滤器是Spring框架中GatewayFilter的实例,使用过滤器可以实现在请求被路由前或者之后对请求进行修改。
路由转发+执行过滤器链。
Gateway网关路由有两种配置方式:yml文件配置、代码中注入RouteLocator的Bean。
在父工程下新建一个名为cloud-gateway-gateway9527的module,对应的pom依赖如下:
cloud2020
com.bighuan.springcloud
1.0-SNAPSHOT
4.0.0
cloud-gateway-gateway9527
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
com.bighuan.springcloud
cloud-api-commons
${project.version}
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
com.bighuan.springcloud.GatewayMain9527
repackage
application.yml文件配置如下。关于路由配置可以参考官网:id表示路由的ID,没有固定的规则,但是一定要唯一,可以配合服务名来配置;uri表示匹配后提供服务的路由地址;predicates下的Path是为了匹配对应的路径。
关于注册中心的相关配置,都在此前的博客中有相关记录,不再赘述。
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
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-service
client: # 服务提供者的provider注册进eureka的服务列表里
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
主启动类并没有特殊的配置,比较简单。
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}
在yml文件中关于路由的配置那一块,uri和predicates下的Path等配置都是为了指向服务提供者。服务提供者相关的博客可参考此前的博客,为行文方便,只粘贴出对应的controller。
@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
return serverPort;
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult 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);
}
}
分别启动注册中心Eureka7001、服务提供者8001、Gateway9527,浏览器分别访问:
http://127.0.0.1:9527/payment/get/31、http://127.0.0.1:9527/payment/lb
发现都成功返回了对应的数据,前者需要查询数据库,后者则直接返回服务提供者的端口8001。通过Gateway9527,将服务提供者隐藏起来,对外界是不可见的,通过服务网关的路由就可以访问。
代码配置方式可参考官网的配置,如下:
本文配置类代码如下,特别注意的是,需要让此类可以被主启动类扫描到。
//代码配置: 配置路由的第二种方式
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
return routes.route("path_route_bighuan",
r -> r.path("/guonei").uri("http://news.baidu.com/guonei"))
.build();
}
}
同样,分别启动注册中心7001、服务提供者8001(只测试代码配置路由,不需要启动也可以)、Gateway9527,访问http://127.0.0.1:9527/guonei,页面跳转到到了国内百度新闻的页面。
以上的测试中,只有一个服务提供者8001,如果不止一个服务提供者8001,还有服务提供者8002、8003,每个都要在配置文件中配置一次的话,那么是不现实的。动态路由就可以解决这个问题。
默认情况下,Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
动态路由的配置文件为,application-dynamic.yml,配置如下:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
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-service
client: # 服务提供者的provider注册进eureka的服务列表里
register-with-eureka: true
fetch-registry: true
service-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交替出现,说明动态路由配置成功,而且负载均衡也实现了。
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=2020-03-28T17:25:10.065+08:00[Asia/Shanghai]
这种特殊的时间可以通过ZoneDateTime来获取
ZonedDateTime now = ZonedDateTime.now();
// 2020-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后,访问正常。
路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway包括许多内置的GatewayFilter工厂。Spring Cloud Gateway内置了多种路由过滤器,它们都GatewayFilter工厂类产生。(官网gatewayfilter-factories,配置参考官网即可)
Filter的生命周期有两种:pre、post。
在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志输出,流量监控等。(注:这段话来自某大佬博客)
Filter的种类有两种:GatewayFilter、GlobalFilter。
自定义全局GlobalFilter过滤器
自定义全局过滤器,需要实现GlobalFilter、Ordered两个接口。自定义全局过滤器,可以实现全局日志记录、统一网关鉴权等等。
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono 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
*/
@Override
public 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则会被拦截。
虽然写这篇博客花的的时间比较长,但坚持坚持,会有收获的!