文章目录
一、官网
zuul官网:Netflix/zuul
gateway官网:Spring Cloud Gateway
二、GateWay介绍
SpingCloud Gateway 是 SpingCloud 的一个全新项目,基于Spring 5 + Spring Boot 2.0 和 Project Reactor 等技术开发的网关,微服务架构提供一种简单有效的统一的API路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud2.0以上版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,仍然还是使用Zuul 1.X非Reactor模式的老版本。而为了提升网关的性能,SpingCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
SpringCloud Gateway的目标是提供统一的路由方式,且基于Filter链的方式提供了网关基本的功能,例如安全,监控/指标,限流。
Spring Cloud Gatew的特性:
- 基于Spring 5 + Spring Boot 2.0 和 Project Reactor 构建,本是一家,兼容性自然杠杠的。
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Hystrix的断路器功能
- 集成SpringCloud 服务发现功能
- 易于编写的Predicate(断言)和Filter(过滤器)
- 请求限流功能
- 支持路径重写
- …
三、 SpringCloud Gateway 与Zuul的区别
- Zuul 1.x,是一个基于阻塞 IO 的API
- Zuul 1.x 基于Servlet 2.5使用阻塞架构,它不支持任何长连接(如WebSocket)。Zuul的设计模式和Nginx比较像,每次 IO 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。
- Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但 SpringCloud 目前还没有整合。Zuul 2.x的性能较Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试,SpringCloud Gateway 的RPS(每秒请求数)是Zuul的1.6倍。
- SpringCloud Gateway 建立在Spring Framework 5、Project Reactor 和 Spring Boot 2之上,使用非阻塞API。
- Spring Cloud Gateway 还支持WebSocket,并且与Spring 紧密集成拥有更好的开发体验。
四、三大核心
Route(路由):是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
Predicate(断言):参考的是Java8的java.util.function.Predicate,允许开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
总结:
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是我们的匹配条件;而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了 。
五、 Gateway工作流程
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到Gateway Web Handler。
Handler 再等通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。
Filter 在 pre 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;在 post 类型的过滤器中可以做响应内容、响应头的修改、日志输出、流量监控等有这非常中要的作用。
核心逻辑就是路由转发 + 执行过滤器链。
六、路由Route
Route 主要由 路由id、目标uri、断言集合和过滤器集合组成,那我们简单看看这些属性到底有什么作用。
(1)id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)
(2)uri:请求最终被转发到的目标地址
(3)order: 路由优先级,数字越小,优先级越高
(4)predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中
(5)filters:过滤器数组,在请求传递过程中,对请求做一些修改
1、通过yml文件配置路由
- 创建maven工程
- pom文件
注意:这里不能引入web 的包,会和 gateway 冲突。
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--boot web actuator 引入会有冲突-->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>-->
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入自定义的api通用包,可使用Payment支付Entity-->
<dependency>
<groupId>org.example</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>
- application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
fetch-registry: true
register-with-eureka: true
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
# defaultZone: http://eureka7002.com:7002/eureka/
# 单机版eureka
defaultZone: http://eureka7001.com:7001/eureka/
- 主启动类
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}
- 运行与测试
启动三个项目:
测试:
- http://localhost:9527/payment/lb
- http://localhost:9527/payment/get/3
能正常访问就代表成功了。原来访问的端口是8001,现在外面包装了一层9527。
2、通过代码实现配置路由
- 新建配置类
@Configuration
public class GateWayConfig
{
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
- 测试
请求http://localhost:9527/guonei
3、动态路由
上面我们只路由到了服务的一个端口,但实际环境中每个服务应该是集群模式而不是单节点。所以我们应该通过服务名称来路由,而不是端口号。
- 修改yml配置文件
- 重启后测试
启动8002服务并访问:http://localhost:9527/payment/lb
访问成功,并实现了负载均衡:
七、Predicates 断言
断言,指断然言之,十分肯定地说。非真即假,非 True 即 False。只有为True时才会放行。
Spring Cloud Gateway 内置的Predict如下:
也可以看官网:Route Predicate Factories
这里我们举两个例子:
1、After 时间在这之后才能执行
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
- After=2020-11-10T20:30:07.417+08:00[Asia/Shanghai] #断言,时间在这之后才能执行
若不知道如何写这个时间格式,可以写个测试方法打印看下。
import java.time.ZonedDateTime;
public class Test1 {
public static void main(String[] args) {
System.out.println(ZonedDateTime.now());
}
}
若是将时间改为明年,访问lb就会报错
2、权重断言
spring:
cloud:
gateway:
# 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
routes:
# 路由标识,要求唯一,名称任意
- id: gateway-provider_1
# 请求最终被转发到的目标地址
uri: http://localhost:9024
# 设置断言
predicates:
# Path Route Predicate Factory 断言,满足 /gateway/provider/** 路径的请求都会被路由到 http://localhost:9024 这个uri中
- Path=/gateway/provider/**
# Weight Route Predicate Factory 断言,同一分组按照权重进行分配流量,这里分配了80%
# 第一个group1是分组名,第二个参数是权重
- Weight=group1, 8
# 配置过滤器(局部)
filters:
# StripPrefix:去除原始请求路径中的前1级路径,即/gateway
- StripPrefix=1
- id: gateway-provider_2
uri: http://localhost:9025
# 设置断言
predicates:
- Path=/gateway/provider/**
# Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
- Weight=group1, 2
# 配置过滤器(局部)
filters:
# StripPrefix:去除原始请求路径中的前1级路径,即/gateway
- StripPrefix=1
Spring Cloud Gateway 中的断言命名都是有规范的,格式:“xxx + RoutePredicateFactory”,比如权重断言 WeightRoutePredicateFactory,那么配置时直接取前面的 “Weight”。
如果路由转发匹配到了两个或以上,则是的按照配置先后顺序转发,上面都配置了路径:“ Path=/gateway/provider/** ”,如果没有配置权重,则肯定是先转发到 “http://localhost:9024”,但是既然配置配置了权重并且相同的分组,则按照权重比例进行分配流量。
八、Filter 过滤
使用过滤器,可以在请求被路由钱或者之后对请求进行修改。
1、生命周期
- PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
2、类型
- GatewayFilter 局部过滤 :应用到单个路由或者一个分组的路由上(需要在配置文件中配置)
官网: GatewayFilter Factories- GlobalFilter 全局过滤:应用到所有的路由上(无需配置,全局生效)
官网:Global Filters- 自定义过滤器(第3小节里写)
3、自定义全局过滤器
我们一般情况下都是编写自定义过滤器:
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{
@Override
public 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("*******用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder()
{
return 0;
}
}
项目重启后测试:
- http://localhost:9527/payment/lb
- http://localhost:9527/payment/lb?uname=‘111’