https://juejin.im/post/5ba8daa56fb9a05cfe486ebf

背景

在之前的文章中,我们介绍过微服务网关Spring Cloud Netflix Zuul,前段时间有两篇文章专门介绍了Spring Cloud的全新项目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配置的其他路由断言:

    @Beanpublic 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、请求头部和请求的参数。在一个路由定义中,可以配置多个断言,采取与或非的关系判断。

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

过滤器

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

全局过滤器

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

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

定义全局过滤器,可以通过在配置文件中,增加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(); } 复制代码

在如上的实现中,初始化好RedisRateLimiterRemoteAddrKeyResolver两个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; } } 复制代码

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

    @Beanpublic 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,如下:

    @Beanpublic 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网关。

欲了解更详细的实现原理和细节,大家可以关注笔者本月底即将出版的《Spring Cloud 微服务架构进阶》,本书中对Spring Cloud Finchley.RELEASE版本的各个主要组件进行原理讲解和实战应用,网关则是基于最新的Spring Cloud Gateway。

本文的源码地址:
GitHub:github.com/keets2012/m… 或者 码云:gitee.com/keets/micro…

作者:aoho
链接:https://juejin.im/post/5ba8daa56fb9a05cfe486ebf
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://www.cnblogs.com/davidwang456/articles/10402519.html

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

  1. 微服务接入oauth2_微服务权限终极解决方案,Spring Cloud Gateway+Oauth2实现统一认证和鉴权!...

    最近发现了一个很好的微服务权限解决方案,可以通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权.此方案为目前最新方案,仅支持Spring Boot 2.2.0.Spring Cloud Hox ...

  2. 微服务网关Zuul和Gateway的区别

    spring-cloud-Gateway是spring-cloud的一个子项目.而zuul则是netflix公司的项目,只是spring将zuul集成在spring-cloud中使用而已. 因为zuu ...

  3. 微服务架构实战第一节 Spring Cloud介绍

    开篇词 为什么你要学习微服务架构? 你好,我是萧然,长期从事分布式系统的构建和优化工作,负责过大型电商以及健康类系统的设计和开发,曾带领团队完成大规模微服务架构建设,在基于 Spring Cloud ...

  4. 微服务为什么一定要选spring cloud?

    作者:董添 李秉谦 || 网易乐得技术团队 来自:http://tech.lede.com/ 现如今微服务架构十分流行,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性.同时,支持微服务的技术栈 ...

  5. 微服务为什么离不开spring cloud?

    转载自  微服务为什么离不开spring cloud? 现如今微服务架构十分流行,而采用微服务构建系统也会带来更清晰的业务划分和可扩展性.同时,支持微服务的技术栈也是多种多样的,本系列文章主要介绍这些 ...

  6. Spring Cloud微服务网关Zuul过滤链和整合OAuth2+JWT入门实战

    一.Spring Cloud Zuul 过滤链 1.1 工作原理 Zuul的核心逻辑是由一系列的Filter来实现的,他们能够在进行HTTP请求或者相应的时候执行相关操作.Zuul Filter的主要 ...

  7. 分布式和微服务区别_深度解析spring cloud分布式微服务的实现

    分布式系统 微服务就是原来臃肿的项目拆分为多个模块互不关联.如:按照子服务拆分.数据库.接口,依次往下就更加细粒度,当然运维也就越来越难受了. 分布式则是偏向与机器将诺大的系统划分为多个模块部署在不同 ...

  8. 漫谈微服务架构:什么是Spring Cloud,为何要选择Spring Cloud

    Spring Cloud是基于Spring Boot的,因此还在使用SpringMVC的同学要先了解Spring Boot.先上一段官话,Spring Cloud是一个基于Spring Boot实现的 ...

  9. 一个简单的微服务项目带你上手spring cloud 全家桶

    最近一个月,断断续续学习了spring cloud的主流微服务模块,然后实践了一个比较容易上手的微服务项目,现在做一个总结. 这个项目是在github上的一个比较经典的spring cloud易上手的 ...

最新文章

  1. Codeforces Round #370 (Div. 2)
  2. 【积淀】半夜突然有点想法
  3. CAS (3) —— Mac下配置CAS客户端经代理访问Tomcat CAS
  4. arcgis java api_Arcgis API For Javascript下载与安装
  5. python创建一个空列表alist_【Python 1-7】Python手把手教程之——详解列表List
  6. virtualbox+vagrant学习-2(command cli)-20-vagrant suspend命令
  7. 江苏机器人竞赛南航_中国青少年机器人竞赛
  8. CKEditor/FCKEditor的使用
  9. 送书|北大出版:R语言数据分析与可视化从入门到精通
  10. 常用的前端跨域的几种方式
  11. python click
  12. C#中枚举类型enum的使用[转载]
  13. 确认过眼神,你是要来百度AI开发者实战营深圳站的人
  14. 浏览器了解(五)资源加载顺序
  15. 如何截图一张完整的表_表格太长怎么截图
  16. linux 繁体转简体,linux2 简体中文转繁体
  17. 黑帽SEO常用作弊手法分析
  18. 操作系统的资源分配系列算法
  19. 火车头采集翻页内容_火车头采集器怎么采集内容页的分页
  20. phalcon蹩脚的面包屑(breadcrumbs)功能

热门文章

  1. 像证券交易员一样思考_2.理解绝招
  2. html页面如何循环遍历,遍历foreach循环中的HTML DOM元素
  3. tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析
  4. java表格标题栏_Java MFixedColumnTable (提供行标题栏的表格)
  5. yum 卸载php及依赖包,yum使用指南-软件卸载、安装、更新、获取软件包
  6. 苹果手机at系统_iOS14系统苹果手机如何设置充满电提示音?
  7. android 之图文混排+GridView
  8. 《基于张量网络的机器学习入门》学习笔记6
  9. 机器学习——线性回归数学推导
  10. python word2vector (三)