服务网关——Gateway
1、zuul说明
官网:https://github.com/Netflix/zuul
zuul是由Netflix公司下的服务网关,由于zuul2尚在开发中,所以我们学习SpringCloud的gateway。
2、SpringCloud Gateway介绍
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/
SpringCloud Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤功能,例如熔断、限流、重试、日志监控等。
SpringCloud1.x的版本中使用了Zuul1.0,目前Zuul1.0进入了维护阶段。SpringCloud Gateway作为SpringCloud生态系统中的网关,目的是替代Zuul,在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0以上最新高新能版本进行集成,仍然使用的是Zuul 1.x非Reactor模式的老版本。
而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
特性
反向代理、鉴权、流量控制、熔断、日志监控等等。SpringCloud Gateway具有如下特性:
- 基于Spring Framework 5, Project Reactor和SpringBoot 2.x进行构建,能够更好地整合到springcloud家族中,系统稳定性强;
- 动态路由:能够匹配任何请求属性;
- 可以对路由指定Predicate(断言)和Filter(过滤器);
- 集成Hystrix的熔断器功能;
- 集成SpringCloud服务发现功能;
- 请求限流功能;
- 支持路径重写等待。
3、Gateway在微服务架构中的位置
4、Spring Cloud Gateway与Zuul对比
在SpringCloud Finchley正式版以前,SpringCloud推荐的网关是Netflix提供的Zuul:
- Zuul 1.x 是一个基于阻塞I/O的API Gateway;
- Zuul 1.x 基于Servlet 2.5使用阻塞架构,它不支持任何长连接(如WebSocket),Zuul的设计模式和Nginx比较像,每次I/O操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx是用C++实现的,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差;但是高并发的分布式系统更推荐使用异步非堵塞的架构。
- Zuul 2.x 理念更先进,想基于Netty非阻塞和支持长连接,但是SpringCloud目前还没有整合。Zuul 2.x 的性能较Zull 1.x有很大提升。根据官方提供的基准测试,SpringCloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
- SpringCloud Gateway基于Spring Framework 5, Project Reactor和SpringBoot 2.x进行构建,使用非阻塞API,还支持WebSocket。
注:
servlet是一个简单的网络IO模型,当请求进入servlet container时会为其绑定一个线程,在并发量不高的情况下这种模型是适用的,但在高并发的情况下线程数量就会大大增加,而线程资源是很昂贵的,严重影响了请求的处理时间,在Servlet3.1之后才有了异步非堵塞的支持。Zuul1.x是基于servlet2.5的一个阻塞式处理模型
而Spring cloud gateway使用了webflux,webflux是一个典型的异步非堵塞框架,它的核心是基于Reactor的相关API实现的。
5、Gateway核心概念
1. 路由 Route
路由是构建网关的基本模块,它由ID、目标URI、一系列的断言和过滤器组成,如果断言为true则匹配路由
2. 断言 Predicate
参考的是Java8的 `java.util.function.Predicate’,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言匹配则进行路由。
3. 过滤器 Filter
Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改。
客户当发送Web请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化的控制,而断言就是这些匹配调教,过滤器可以理解为一个无所不能的拦截器,用来实现这些精细化的控制,有了断言和过滤器,再加上目标的URI,就可以实现一个具体的路由。
6、网关工作流程
客户端向网关提出请求。如果网关处理程序映射确定请求与路由匹配,它将被发送到网关网络处理程序。此处理程序通过特定于请求的筛选器链运行请求。过滤器用虚线分开的原因是过滤器可以在发送代理请求之前和之后运行逻辑。
7、IDEA中配置路由
设定在8005微服务中有如下两个服务,但我们不想暴露8005端口
@Value("${server.port}")
private String serverPort;
@GetMapping("/getPort")
public String getPort() {
return this.serverPort;
}
@GetMapping("/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Long id) {
return myService.paymentInfo_OK(id);
}
需要新建一个模块作为网关,配置路由并转发请求
1.pom
添加了gateway的依赖后不需要添加web的依赖包
<!-- 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>
2.配置文件
1、通过application.yaml
配置端口号,eureka服务提供方配置,以及路由配置
这时可以通过我们配置的9527端口的网关对服务提供方提供的8005端口微服务进行路由处理
server:
port: 9527
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7002.com:7002/eureka
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: eureka-order #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8005 #匹配后提供服务的路由地址
predicates:
- Path=/getPort # 断言,路径相匹配的进行路由
- id: eureka-order2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8005 #匹配后提供服务的路由地址
predicates:
- Path=/hystrix/ok/** # 断言,路径相匹配的进行路由
上述是通过域名+IP来访问微服务,但这样把路由地址写死了,我们可以通过微服务名来实现动态路由,对应的改动如下:
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: eureka-order
uri: lb://eureka-order #匹配后提供服务的路由地址
predicates:
- Path=/getPort
- id: eureka-order2
uri: lb://eureka-order #匹配后提供服务的路由地址
predicates:
- Path=/hystrix/ok/**
2、通过配置类
package org.jun.Config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author junfeng.lin
* @date 2021/2/23 1:09
*/
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//通过localhost:9527/baidu访问到http://baidu.com
routes.route("path_baidu",
r -> r.path("/baidu")
.uri("http://www.baidu.com")).build();
return routes.build();
}
}
3.主启动类
package org.jun;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author junfeng.lin
* @date 2021/2/22 0:10
*/
@SpringBootApplication
@EnableEurekaClient
public class GateApplication9527 {
public static void main(String[] args) {
SpringApplication.run(GateApplication9527.class,args);
}
}
4.测试
先后启动7002端口,8005端口和9527端口,我们可以发现真实的服务和网关的微服务都已经注册到eureka中
原来需要访问8005端口才能访问服务
现在通过访问9527端口便能通过路由访问8005端口的服务,可以不再对外暴露微服务的真实地址,而是统一暴露为网关的地址
8、IDEA中配置过滤器
Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:
GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上。
GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
除此之外还可以自定义过滤器:
package org.jun.Filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author junfeng.lin
* @date 2021/2/22 16:52
*/
@Component
public class MyGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//TODO 进行业务逻辑的判断
String token = exchange.getRequest().getQueryParams().getFirst("access-token");
if (false) {
//当不符合业务逻辑时,返回错误编码
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 加载过滤器的顺序,数字越小优先级越高
* @return
*/
@Override
public int getOrder() {
return 3;
}
}
实现两个接口org.springframework.cloud.gateway.filter.GlobalFilter
和org.springframework.core.Ordered
,前者实现了全局过滤器,而后者规定了过滤器的执行顺序,该顺序数字越小,过滤器越先被执行
还可以通过配置内置Bean来配置过滤器,其中.then()方法执行的是post后置方法
@Bean
@Order(-1)
public GlobalFilter a() {
return (exchange, chain) -> {
log.info("first pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("third post filter");
}));
};
}
@Bean
@Order(0)
public GlobalFilter b() {
return (exchange, chain) -> {
log.info("second pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("second post filter");
}));
};
}
@Bean
@Order(1)
public GlobalFilter c() {
return (exchange, chain) -> {
log.info("third pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("first post filter");
}));
};
}