Zuul网关存在的问题:
性能问题
Zuul1x 版本本质上就是一个同步Servlet,采用多线程阻塞模型进行请求转发。简单讲,每来一个请求,Servlet容器要为该请求分配一个线程专门负责处理这个请求,直到响应返回客户端这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被阻塞,阻塞期间线程资源被占用,不能干其它事情。我们知道Servlet容器线程池的大小是有限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成容器无法接受新的请求。
不支持任何长连接,如 websocket
Zuul网关的替换方案:
Zuul2.x版本
SpringCloud Gateway
Gateway简介:
Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式,统一访问接口。Spring Cloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix ZUUL,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。
核心概念:
1. 路由(route) 路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
2. 断言(predicates) Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自Http Request中的任何信息,比如请求头和参数等。
3. 过滤器(filter) 一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。
入门案例:
1.创建新模块gateway_server导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
注意 SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容。引入的限流组件是hystrix。redis底层不再使用jedis,而是lettuce。
2.配置启动类
@SpringBootApplication public class GatewayServerApplication { public static void main(String[] args) { SpringApplication.run(GatewayServerApplication.class, args); } }
3.创建 application.yml 配置文件
server:
port: 8080
spring:
application:
name: gateway-server #服务名称
##路由配置
cloud:
gateway:
routes:
- id: service-product #自定义的路由 ID,保持唯一
uri: http://127.0.0.1:9011 #目标服务地址
#order: 1 #路由优先级,数字越小优先级越高
#路由条件(断言),Predicate 接收一个输入参数,返回一个布尔值结果。
#该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
predicates:
- Path=/product/**
4.启动测试,访问 http://localhost:8080/product/1
路由规则:
Spring Cloud Gateway 的功能很强大,前面我们只是使用了 predicates 进行了简单的条件匹配,其实Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。
#路由断言之后匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://xxxx.com
predicates:
- After=xxxxx
#路由断言之前匹配
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://xxxxxx.com
predicates:
- Before=xxxxxxx
#路由断言之间
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://xxxx.com
predicates:
- Between=xxxx,xxxx
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://xxxx.com
predicates:
- Cookie=chocolate, ch.p
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配d+
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://xxxx.com
predicates:
- Header=X-Request-Id, d+
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://xxxx.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
#路由断言Method匹配,匹配的是请求的HTTP方法
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://xxxx.com
predicates:
- Method=GET
#路由断言匹配,{segment}为可变参数
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://xxxx.com
predicates:
- Path=/foo/{segment},/bar/{segment}
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含foo,并且foo的值匹配ba.)
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://xxxx.com
predicates:
- Query=baz 或 Query=foo,ba.
#路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位
数即255.255.255.0
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
View Code
动态路由(面向服务的路由):
和zuul网关类似,在SpringCloud GateWay中也支持动态路由:即自动的从注册中心中获取服务列表并访问。
1.添加注册中心依赖
在工程的pom文件中添加注册中心的客户端依赖(这里以Eureka为例)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2.配置动态路由
修改 application.yml 配置文件,添加eureka注册中心的相关配置,并修改访问映射的URL为服务名称
##配置eureka eureka: client: serviceUrl: defaultZone: http://127.0.0.1:9000/eureka/ instance: preferIpAddress: true
##路由配置 cloud: gateway: routes: - id: service-product #自定义的路由 ID,保持唯一 #uri: http://127.0.0.1:9011 #目标服务地址 uri: lb://service-product predicates: - Path=/product/**
uri : uri以 lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
3.重写转发路径:
在SpringCloud Gateway中,路由转发不同于 Zuul 截取 /** 来拼接URL,而是直接将匹配的路由 path 直接拼接到映射路径 URI 之后(http://127.0.0.1:8080/product/1 –> http://127.0.0.1:9011/product/1),上面例子直接拼接上去即可访问是刻意为之,在微服务开发中往往没有那么便利,就需要通过RewritePath机制来进行路径重写。
(1) 案例改造:
修改 application.yml ,将匹配路径改为 /service-product /**
(2) 添加RewritePath重写转发路径
##路由配置 cloud: gateway: routes: - id: service-product #自定义的路由 ID,保持唯一 #uri: http://127.0.0.1:9011 #目标服务地址 uri: lb://service-product predicates: - Path=/service-product/** filters: #- RewritePath=/service-product/(?<segment>.*), /${segment} #方式1 - StripPrefix=1 #方式2:在请求转发之前去掉一层路径
4.配置自动根据服务名称进行路由转发:此规则和 Zuul 网关默认规则一样
##路由配置 cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true #微服务名称以小写形式呈现
过滤器:
Spring Cloud Gateway除了具备请求路由功能之外,也支持对请求的过滤。和Zuul网关类似,也是通过过滤器的形式来实现的。
过滤器的生命周期:
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:“pre” 和 “post”。
PRE : 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST :这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
局部过滤器:
局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。
gateway内置了很多局部过滤器,使用方式:
全局过滤器:
全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway 定义了Global Filter接口,用户可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能,并且全局过滤器也是使用比较多的过滤器。
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的
自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“access-token”,如果不包含请求参数“access-token”则不转发路由,否则执行正常的逻辑。
/** * 自定义一个全局过滤器 * 实现 globalfilter , ordered接口 */ @Component public class LoginFilter implements GlobalFilter,Ordered { /** * 执行过滤器中的业务逻辑 * 对请求参数中的access-token进行判断 * 如果存在此参数:代表已经认证成功 * 如果不存在此参数 : 认证失败. * ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext) */ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("执行了自定义的全局过滤器"); // 1.获取请求参数access-token String token = exchange.getRequest().getQueryParams().getFirst("access-token"); // 2.判断是否存在 if(token == null) { //3.如果不存在 : 认证失败 System.out.println("没有登录"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); // 请求结束 } //4.如果存在,继续执行 return chain.filter(exchange); //继续向下执行 } /** * 指定过滤器的执行顺序 , 返回值越小,执行优先级越高 */ public int getOrder() { return 0; } }
网关限流:
常见的限流算法:
1.计数器
计数器限流算法是最简单的一种限流实现方式。其本质是通过维护一个单位时间内的计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间已经过去,再将计数器重置为零
2.漏桶算法(保护别人)
漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。
为了更好的控制流量,漏桶算法需要通过两个变量进行控制:一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。
3.令牌桶算法(保护自己)
令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
基于Filter的限流:
SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。
环境搭建:
1.导入 redis的依赖(redis的reactive依赖)和监控依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-actuator</artifactId> </dependency>
2. 修改application.yml配置文件,加入限流的配置
spring:
...省略
##路由配置 cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true #微服务名称以小写形式呈现 filters: - name: RequestRateLimiter args: #使用SpEL从容器中获取对象 key-resolver: '#{@pathKeyResolver}' #令牌桶每秒填充平均速率 redis-rate-limiter.replenishRate: 1 #令牌桶的总容量 redis-rate-limiter.burstCapacity: 3
key-resolver ,用于限流的键的解析器的 Bean 对象名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
添加redis的信息:
spring: ...省略 ##配置redis redis: host: localhost port: 6379 database: 0 password: root
3.配置KeyResolver
为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。
@Configuration public class KeyResolverConfiguration { /** * 基于请求路径的限流 */ @Bean public KeyResolver pathKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getPath().toString() ); } /** * 基于请求ip地址的限流 */ @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") ); } /** * 基于用户的限流 */ @Bean public KeyResolver userKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getQueryParams().getFirst("user") ); } }
Spring Cloud Gateway目前提供的限流还是相对比较简单的,在实际中我们的限流策略会有很多种情况,比如:
对不同接口的限流
被限流后的友好提示
基于Sentinel的限流:
Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
另外也可以使用 Sentinel 控制台图形界面方式定义限流规则,更加方便。