抱歉,你查看的文章不存在

微服务网关Zuul迁移到Spring Cloud Gateway

本文将会介绍将微服务网关由Zuul迁移到Spring Cloud Gateway。

Spring Cloud Netflix Zuul是由Netflix开源的API网关,在微服务架构下,网关作为对外的门户,实现动态路由、监控、授权、安全、调度等功能。

Zuul基于servlet 2.5(使用3.x),使用阻塞API。 它不支持任何长连接,如websockets。而Gateway建立在Spring Framework 5,Project Reactor和Spring Boot 2之上,使用非阻塞API。 比较完美地支持异步非阻塞编程,先前的Spring系大多是同步阻塞的编程模式,使用thread-per-request处理模型。即使在Spring MVC Controller方法上加@Async注解或返回DeferredResult、Callable类型的结果,其实仍只是把方法的同步调用封装成执行任务放到线程池的任务队列中,还是thread-per-request模型。Gateway 中Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的开发体验。

在一个微服务集成的项目中microservice-integration,我们整合了包括网关、auth权限服务和backend服务。提供了一套微服务架构下,网关服务路由、鉴权和授权认证的项目案例。整个项目的架构图如下:

具体参见:微服务架构中整合网关、权限服务。本文将以该项目中的Zuul网关升级作为示例。

Zuul网关

在该项目中,Zuul网关的主要功能为路由转发、鉴权授权和安全访问等功能。

Zuul中,很容易配置动态路由转发,如:

zuul:
 ribbon:
 eager-load:
 enabled: true #zuul饥饿加载
 host:
 maxTotalConnections: 200
 maxPerRouteConnections: 20
 routes:
 user:
 path: /user/**
 ignoredPatterns: /consul
 serviceId: user
 sensitiveHeaders: Cookie,Set-Cookie

默认情况下,Zuul在请求路由时,会过滤HTTP请求头信息中的一些敏感信息,这里我们不过多介绍。

网关中还配置了请求的鉴权,结合Auth服务,通过Zuul自带的Pre过滤器可以实现该功能。当然还可以利用Post过滤器对请求结果进行适配和修改等操作。

除此之外,还可以配置限流过滤器和断路器,下文中将会增加实现这部分功能。

迁移到Spring Cloud Gateway

笔者新建了一个gateway-enhanced的项目,因为变化很大,不适合在之前的gateway项目基础上修改。实现的主要功能如下:路由转发、权重路由、断路器、限流、鉴权和黑白名单等。本文基于主要实现如下的三方面功能:

  • 路由断言
  • 过滤器(包括全局过滤器,如断路器、限流等)
  • 全局鉴权
  • 路由配置
  • CORS

依赖

本文采用的Spring Cloud Gateway版本为2.0.0.RELEASE。增加的主要依赖如下,具体的细节可以参见Github上的项目。

 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-gateway</artifactId>
 <!--<version>2.0.1.RELEASE</version>-->
 </dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-gateway-webflux</artifactId>
 </dependency>
 </dependencies>
 
 <dependencyManagement>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Finchley.RELEASE</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 </dependencies>
 </dependencyManagement> 

路由断言

Spring Cloud Gateway对于路由断言、过滤器和路由的定义,同时支持配置文件的shortcut和Fluent API。我们将以在本项目中实际使用的功能进行讲解。

路由断言在网关进行转发请求之前进行判断路由的具体服务,通常可以根据请求的路径、请求体、请求方式(GET/POST)、请求地址、请求时间、请求的HOST等信息。我们主要用到的是基于请求路径的方式,如下:

spring:
 cloud:
 gateway:
 routes:
 - id: service_to_web
 uri: lb://authdemo
 predicates:
 - Path=/demo/**

我们定义了一个名为service_to_web的路由,将请求路径以/demo/**的请求都转发到authdemo服务实例。

我们在本项目中路由断言的需求并不复杂,下面介绍通过Fluent API配置的其他路由断言:

 @Bean
 public RouteLocator routeLocator(RouteLocatorBuilder builder) {
 return builder.routes()
 .route(r -> r.host("**.changeuri.org").and().header("X-Next-Url")
 .uri("http://blueskykong.com"))
 .route(r -> r.host("**.changeuri.org").and().query("url")
 .uri("http://blueskykong.com"))
 .build();
 }

在如上的路由定义中,我们配置了以及请求HOST、请求头部和请求的参数。在一个路由定义中,可以配置多个断言,采取与或非的关系判断。

以上增加的配置仅作为扩展,读者可以根据自己的需要进行配置相应的断言。

过滤器

过滤器分为全局过滤器和局部过滤器。我们通过实现GlobalFilter、GatewayFilter接口,自定义过滤器。

全局过滤器

本项目中,我们配置了如下的全局过滤器:

  • 基于令牌桶的限流过滤器
  • 基于漏桶算法的限流过滤器
  • 全局断路器
  • 全局鉴权过滤器

定义全局过滤器,可以通过在配置文件中,增加spring.cloud.gateway.default-filters,或者实现GlobalFilter接口。

基于令牌桶的限流过滤器

随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token,如果桶已经满了就不再加了。每个请求来临时,会拿走一个 Token,如果没有 Token 可拿了,就阻塞或者拒绝服务。

令牌桶的另外一个好处是可以方便的改变速度。一旦需要提高速率,则按需提高放入桶中的令牌的速率。一般会定时(比如 100 毫秒)往桶中增加一定数量的令牌,有些变种算法则实时的计算应该增加的令牌的数量。

在Spring Cloud Gateway中提供了默认的实现,我们需要引入redis的依赖:

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
 </dependency>

并进行如下的配置:

spring:
 redis:
 host: localhost
 password: pwd
 port: 6378
 cloud:
 default-filters:
 - name: RequestRateLimiter
 args:
 key-resolver: "#{@remoteAddrKeyResolver}"
 rate-limiter: "#{@customRateLimiter}" # token

注意到,在配置中使用了两个SpEL表达式,分别定义限流键和限流的配置。因此,我们需要在实现中增加如下的配置:

 @Bean(name = "customRateLimiter")
 public RedisRateLimiter myRateLimiter(GatewayLimitProperties gatewayLimitProperties) {
 GatewayLimitProperties.RedisRate redisRate = gatewayLimitProperties.getRedisRate();
 if (Objects.isNull(redisRate)) {
 throw new ServerException(ErrorCodes.PROPERTY_NOT_INITIAL);
 }
 return new RedisRateLimiter(redisRate.getReplenishRate(), redisRate.getBurstCapacity());
 }
 
 @Bean(name = RemoteAddrKeyResolver.BEAN_NAME)
 public RemoteAddrKeyResolver remoteAddrKeyResolver() {
 return new RemoteAddrKeyResolver();
 }

在如上的实现中,初始化好RedisRateLimiter和RemoteAddrKeyResolver两个Bean实例,RedisRateLimiter是定义在Gateway中的redis限流属性;而RemoteAddrKeyResolver使我们自定义的,基于请求的地址作为限流键。如下为该限流键的定义:

public class RemoteAddrKeyResolver implements KeyResolver {
 private static final Logger LOGGER = LoggerFactory.getLogger(RemoteAddrKeyResolver.class);
 public static final String BEAN_NAME = "remoteAddrKeyResolver";
 @Override
 public Mono<String> resolve(ServerWebExchange exchange) {
 LOGGER.debug("token limit for ip: {} ", exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
 }
}

RemoteAddrKeyResolver实现了KeyResolver接口,覆写其中定义的接口,返回值为请求中的地址。

如上,即实现了基于令牌桶算法的链路过滤器,具体细节不再展开。

基于漏桶算法的限流过滤器

漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。

这部分实现读者参见GitHub项目以及文末配套的书,此处略过。

全局断路器

关于Hystrix断路器,是一种服务容错的保护措施。断路器本身是一种开关装置,用于在电路上保护线路过载,当线路中有发生短路状况时,断路器能够及时的切断故障电路,防止发生过载、起火等情况。

微服务架构中,断路器模式的作用也是类似的,当某个服务单元发生故障之后,通过断路器的故障监控,直接切断原来的主逻辑调用。关于断路器的更多资料和Hystrix实现原理,读者可以参考文末配套的书。

这里需要引入spring-cloud-starter-netflix-hystrix依赖:

 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 <optional>true</optional>
 </dependency>

并增加如下的配置:

 default-filters:
 - name: Hystrix
 args:
 name: fallbackcmd
 fallbackUri: forward:/fallbackcontroller

如上的配置,将会使用HystrixCommand打包剩余的过滤器,并命名为fallbackcmd,我们还配置了可选的参数fallbackUri,降级逻辑被调用,请求将会被转发到URI为/fallbackcontroller的控制器处理。定义降级处理如下:

 @RequestMapping(value = "/fallbackcontroller")
 public Map<String, String> fallBackController() {
 Map<String, String> res = new HashMap();
 res.put("code", "-100");
 res.put("data", "service not available");
 return res;
 }

全局鉴权过滤器

我们通过自定义一个全局过滤器实现,对请求合法性的鉴权。具体功能不再赘述了,通过实现GlobalFilter接口,区别的是Webflux传入的是ServerWebExchange,通过判断是不是外部接口(外部接口不需要登录鉴权),执行之前实现的处理逻辑。

public class AuthorizationFilter implements GlobalFilter, Ordered {
	//....
 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
 ServerHttpRequest request = exchange.getRequest();
 if (predicate(exchange)) {
 request = headerEnhanceFilter.doFilter(request);
 String accessToken = extractHeaderToken(request);
 customRemoteTokenServices.loadAuthentication(accessToken);
 LOGGER.info("success auth token and permission!");
 }
 return chain.filter(exchange);
 }
	//提出头部的token
 protected String extractHeaderToken(ServerHttpRequest request) {
 List<String> headers = request.getHeaders().get("Authorization");
 if (Objects.nonNull(headers) && headers.size() > 0) { // typically there is only one (most servers enforce that)
 String value = headers.get(0);
 if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
 String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
 // Add this here for the auth details later. Would be better to change the signature of this method.
 int commaIndex = authHeaderValue.indexOf(',');
 if (commaIndex > 0) {
 authHeaderValue = authHeaderValue.substring(0, commaIndex);
 }
 return authHeaderValue;
 }
 }
 return null;
 }
}

定义好全局过滤器之后,只需要配置一下即可:

 @Bean
 public AuthorizationFilter authorizationFilter(CustomRemoteTokenServices customRemoteTokenServices,
 HeaderEnhanceFilter headerEnhanceFilter,
 PermitAllUrlProperties permitAllUrlProperties) {
 return new AuthorizationFilter(customRemoteTokenServices, headerEnhanceFilter, permitAllUrlProperties);
 }

局部过滤器

我们常用的局部过滤器有增减请求和相应头部、增减请求的路径等多种过滤器。我们这里用到的是去除请求的指定前缀,这部分前缀只是用户网关进行路由判断,在转发到具体服务时,需要去除前缀:

 - id: service_to_user
 uri: lb://user
 order: 8000
 predicates:
 - Path=/user/**
 filters:
 - AddRequestHeader=X-Request-Foo, Bar
 - StripPrefix=1

还可以通过Fluent API,如下:

 @Bean
 public RouteLocator retryRouteLocator(RouteLocatorBuilder builder) {
 return builder.routes()
 .route("retry_java", r -> r.path("/test/**")
 .filters(f -> f.stripPrefix(1)
 .retry(config -> config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)))
 .uri("lb://user"))
 .build();
 }

除了设置前缀过滤器外,我们还设置了重试过滤器,可以参见:Spring Cloud Gateway中的过滤器工厂:重试过滤器

路由配置

路由定义在上面的示例中已经有列出,可以通过配置文件和定义RouteLocator的对象。这里需要注意的是,配置中的uri属性,可以是具体的服务地址(IP+端口号),也可以是通过服务发现加上负载均衡定义的:lb://user,表示转发到user的服务实例。当然这需要我们进行一些配置。

引入服务发现的依赖:

 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-consul-discovery</artifactId>
 </dependency>

网关中开启spring.cloud.gateway.discovery.locator.enabled=true即可。

CORS配置

在Spring 5 Webflux中,配置CORS,可以通过自定义WebFilter实现:

 private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN";
 private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
 private static final String ALLOWED_ORIGIN = "*";
 private static final String MAX_AGE = "3600";
 @Bean
 public WebFilter corsFilter() {
 return (ServerWebExchange ctx, WebFilterChain chain) -> {
 ServerHttpRequest request = ctx.getRequest();
 if (CorsUtils.isCorsRequest(request)) {
 ServerHttpResponse response = ctx.getResponse();
 HttpHeaders headers = response.getHeaders();
 headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
 headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
 headers.add("Access-Control-Max-Age", MAX_AGE);
 headers.add("Access-Control-Allow-Headers",ALLOWED_HEADERS);
 if (request.getMethod() == HttpMethod.OPTIONS) {
 response.setStatusCode(HttpStatus.OK);
 return Mono.empty();
 }
 }
 return chain.filter(ctx);
 };
 }

上述代码实现比较简单,读者根据实际的需要配置ALLOWED_ORIGIN等参数。

总结

在高并发和潜在的高延迟场景下,网关要实现高性能高吞吐量的一个基本要求是全链路异步,不要阻塞线程。Zuul网关采用同步阻塞模式不符合要求。

Spring Cloud Gateway基于Webflux,比较完美地支持异步非阻塞编程,很多功能实现起来比较方便。Spring5必须使用java 8,函数式编程就是java8重要的特点之一,而WebFlux支持函数式编程来定义路由端点处理请求。

通过如上的实现,我们将网关从Zuul迁移到了Spring Cloud Gateway。在Gateway中定义了丰富的路由断言和过滤器,通过配置文件或者Fluent API可以直接调用和使用,非常方便。在性能上,也是胜于之前的Zuul网关。

加群即可获取Java工程化、高性能及分布式、高性能、高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限及领取相关资料,群号:835638062 点击链接加入群聊【Java高级架构】:https://jq.qq.com/?_wv=1027&k=5S3kL3v

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

Java架构解析

2 篇文章21 人订阅

相关文章

来自专栏乐沙弥的世界

基于Linux (RHEL 5.5) 安装Oracle 10g RAC

    本文所描述的是在Red Hat 5.5下使用vmware server 来安装Oracle 10g RAC(OCFS + ASM),本文假定你的RHEL...

1463
来自专栏大闲人柴毛毛

手把手0基础项目实战(三)——教你开发一套电商平台的安全框架

写在最前 本文是《手把手项目实战系列》的第三篇文章,预告一下,整个系列会介绍如下内容: 《手把手0基础项目实战(一)——教你搭建一套可自动化构建的微服务框架(S...

4646
来自专栏芋道源码1024

IntelliJ IDEA JDK 8 性能调优

IntelliJ IDEA 问题描述问题原因解决方法调优后观察为什么要选择用户`idea.vmoptions`文件

1872
来自专栏微信公众号:Java团长

Java Web现代化开发:Spring Boot + Mybatis + Redis二级缓存

Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一。Mybatis是一个十分轻量好用的ORM框架。Red...

2712
来自专栏程序猿DD

Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】

前言 在上一篇《服务容错保护(hystrix断路器)》的介绍中,我们提到断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的...

2117
来自专栏青青天空树

springboot配置读写分离

  近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离。这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大都是抄来抄去,,...

2413
来自专栏Gaussic

使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理 顶

访问GitHub下载最新源码:https://github.com/gaussic/SpringMVCDemo

2572
来自专栏耕耘实录

Linux(Centos7.4和RHEL7.4)环境下基于chrony的NTP服务器的构建

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

1171
来自专栏轻扬小栈

ubuntu debian 百度网盘的Python客户端 bypy

9094
来自专栏Java帮帮-微信公众号-技术文章全总结

02.WebService_使用三要素

02.WebService_使用三要素 一、Java中WebService规范 JAVA 中共有三种WebService 规范,分别是JAX-WS、J...

3716

扫码关注云+社区

领取腾讯云代金券