介绍
zuul是Netflix开发的一套不错的网关,但是也有着一定的缺陷,目前Netflix 对着zuul2.0开始了开发;但是随着Netflix 开始对旗下很多微服务组件开始停更以后,zuul2.0以后也可能面临着停更的风险;在此基础上 spring推出了比较优秀的一款框架 – gateway;其目标是为了替换zuul,成为新一代的网关;
Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。
比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。
搭建环境
-
创建maven工程
-
导入pom依赖
<dependencies>
<!--gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 创建启动器
@SpringBootApplication
@EnableDiscoveryClient//成为注册中心服务
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
- 修改配置文件
server:
port: 9999
spring:
application:
name: gateway-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
cloud:
gateway:
routes:
- id: user-service #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://user-service #匹配后提供服务的路由地址,lb指的是从注册中心中获取对应服务名称的ip地址
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
注:网关可以配置真实的url地址的,但是这样维护比较麻烦,所以一般都是通过注册中心来获取配置信息
- 测试
这里跟zuul的不同,zuul是在端口号后面加上设置的 后缀,在转发的;这里在端口号中直接加你访问的端口号,之后路由匹配规则后转发的
gateway功能
gateway有个强大的几个功能
- Route(路由):这是网关的基本构建模块。它由一个ID,一个目标url,一组断言和一组过滤器定义。如果断言是真,则路由匹配
- Predicate(断言):输入类型是一个ServerWebExchange。我们可以使用它来匹配来自Http请求的任务内容,例如Headers或参数
- Filter(过滤器):Gateway中的filter分为两种类型的Filter,分别是GatewayFilter和Global Filter。这种过滤器Filter将会对请求和响应进行修改处理
路由
路由和断言是gateway的一个特色,路由的转发,会根据匹配到断言设置的,转发,这里gateway的配置有两种,一种是配置文件中配置,一种是代码配置
配置文件配置
server:
port: 10001
spring:
application:
name: gateway-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
cloud:
gateway:
routes:
- id: user-service #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://user-service #匹配后提供服务的路由地址
order: -1
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
在配置文件中配置的话,就是按照我们原来案例中来配置的
代码配置
在代码中配置的话,是利用springboot的配置文件的方法来实现配置
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//第一个 user-service2 对应使用的id
//第二个对应路径
//第三个对应url
routes.route("user-service2", r -> r.path("/ace/**").uri("lb://user-service")).build();
return routes.build();
}
}
断言
在配置路由的时候我们发现了,gateway是根据匹配到的规则来转发的;但是gateway配置的规则不只是有路径,还有其他
配置文件实现
server:
port: 10001
spring:
application:
name: gateway-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
cloud:
gateway:
routes:
- id: user-service #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://user-service #匹配后提供服务的路由地址
order: -1
predicates:
- Path=/user/** #断言,路径相匹配的进行路由
- Host=**.foo.org #通过hsot匹配接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。它通过参数中的主机地址作为匹配规则。
- Cookie=ityouknow, kee #一个是 Cookie name , 一个是正则表达式,路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。--cookie "ityouknow=kee"
- Path=/headers #断言,路径相匹配的进行路由
- Method=GET # 通过请求方式匹配
- Header=X-Request-Id, \d+ # 通过请求头中的参数来匹配 第一个是属性 第二个是属性值
- Query=foo, ba. # 通过请求参数属性匹配 包含 keep 属性并且参数值是以 pu 开头的长度为三位的字符串才会进行匹配和路由。 例如localhost:8080?keep=pub
- Query=baz # 通过请求参数属性匹配 必须有 含有的参数
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai] # 时间配置 在这个时间之后才会进入
- RemoteAddr=192.168.1.1/24 # 根据id地址匹配
注:在代码中实现的话,将r.path()更换为其他的方式匹配即可
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//第一个 user-service2 对应使用的id
//第二个对应路径
//第三个对应url
routes.route("user-service2",
r -> r.path("/ace/**").uri("lb://user-service")).build();
return routes.build();
}
过滤器
- 生命周期
gateway的生命周期比zuul的就简单很多了,跟普通的过滤器相同,两种在
- pre 在业务逻辑之前
- post 在业务逻辑之后
- 种类
种类为为两种
- 单一 GatewayFilter
- 全局 GlobaFilter
- 实现
- 配置文件实现
spring:
application:
name: gateway-application
cloud:
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
gateway:
# 路由配置项,对应 RouteDefinition 数组
routes:
- id: user-service # 路由的编号
uri: lb://user-service # 路由到的目标地址,只能到域名部分 https://blog.csdn.net/dear_little_bear
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/user/**
filters:
- StripPrefix=1
- AddRequestHeader=X-Request-red, blue # 请求头的属性和属性值
- 代码实现
全局拦截器
@Component
@Slf4j
public class MyLogGateWayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
//从上下文中请求头查询到
String username = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
//根据上下文回应
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
//返回拦截器的优先级
public int getOrder() {
return 0;
}
}
单一拦截器
@Component
public class CsdnRequestFilter implements GatewayFilter, Ordered {
@Value("${gateway.csdn.PaaSID}")
private String paaSID;
@Value("${gateway.csdn.PaaSToken}")
private String paaSToken;
@Value("${gateway.csdn.nonce}")
private String nonce;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String timeTamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = SecureUtil.sha256(timeTamp + nonce + paaSToken);
System.out.print("自定义过滤器:timeTamp:"+timeTamp+";signature:"+signature);
//提取应用账户及应用令牌,鉴权
ServerHttpRequest request = exchange.getRequest().mutate()
.header("x-tif-nonce", nonce)
.header("x-tif-signature", signature)
.header("x-tif-paasid", paaSID)
.header("x-tif-timestamp", timeTamp)
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return 1;
}
}
这是继承实现后,需要在代码实现的路由器中配置
return builder.routes()
.route(r -> r.path("/csdn3/**")
.filters(f->f.stripPrefix(1)
.filter(csdnRequestFilter))
.uri(csdnHost)
.order(3))
.build();
总结
Zuul:
使用的是阻塞式的 API,不支持长连接,比如 websockets。
底层是servlet,Zuul处理的是http请求
没有提供异步支持,流控等均由hystrix支持。
依赖包spring-cloud-starter-netflix-zuul。
Gateway:
底层依然是servlet,但使用了webflux,多嵌套了一层框架
依赖spring-boot-starter-webflux和/ spring-cloud-starter-gateway
提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了RedisRateLimiter。
相同点:
1、底层都是servlet
2、两者均是web网关,处理的是http请求
不同点:
1、内部实现:
gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
2、是否支持异步
zuul仅支持同步
gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
3、框架设计的角度
gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
4、性能
WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的 开发 体验。
Zuul 1.x,是一个基于阻塞io的API Gateway。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。
总结
总的来说,在微服务架构,如果使用了Spring Cloud生态的基础组件,则Spring Cloud Gateway相比而言更加具备优势,单从流式编程+支持异步上就足以让开发者选择它了。
对于小型微服务架构或是复杂架构(不仅包括微服务应用还有其他非Spring Cloud服务节点),zuul也是一个不错的选择。