API网关技术实战
在介绍了API网关的相关理论之后,大家可以了解到API网关的作用和优缺点,接下来将为大家介绍API网关在微服务项目中的技术框架和用法实践。
Zuul网关
其实API网关并不是很难的技术,就算没有框架,我们通过原生的Servlet Filter、HttpClient等远程调用方式也能够实现网关的路由和过滤。所以,API网关的精髓在于它解决问题的思路和方式,但框架可以给我们带来很多便利,提高开发效率。下面先来了解一下Spring Cloud大家族中的一款API网关框架:Zuul。
Zuul是使用最广泛的API网关框架之一,由著名的Netflix公司开发,Spring Cloud在Zuul上添加了更高一层的封装,提供请求路由、过滤等多种功能的灵活配置,下面详细介绍Spring Cloud Netflix Zuul框架的具体用法。
1. 路由
首先,我们可以通过https://start.spring.io来创建项目,并且添加Zuul的依赖,或者直接在要开发的API网关项目中添加SpringCloud的依赖及Zuul的依赖,代码如下。
然后,在Spring Boot的启动类上增加@EnableZuulProxy注解即可,代码如下。
关于网关的初始配置完成,Zuul的Spring Cloud Starter会完成实例化相关Bean的工作,接下来看一下在Zuul中如何做路由。
假设我们现在有一个用户服务,端口是8081,提供一个GET方法的接口,请求地址是/users/{id},如果要查询ID1的用户信息,直接访问这个用户服务的接口,URL应该是
GEThttp://localhost:8081/users/1,如果通过Zuul来做路由,只需在application.yml中进行相应的配置即可,代码如下。
如上述代码所示,这里需要声明我们想要的路由规则,首先在zuul.routes下配置一个users的路由规则,规则是请求的路径符合/users/ **就转发给地址http://localhost:8081。例如,这里Zuul的 端 口 是 9000,给 API网关发送一个请求 , 地 址 是
http://localhost:9000/users/1,这个请求就会被Zuul的路由转发给
http://localhost:8081/uesrs/1。
stripPrefix又是什么意思?stripPrefix的默认值是true,即去掉请求的前缀,如我们的路由规则是/users/ **,Zuul会默认请求路径中的/users是路由的规则,而不是真实需要转发的请求路径。例如,我们请求
http://localhost:9000/users/1,如果stripPrefix是true,实际Zuul请求的地址是http://localhost:8081/1,会自动去掉路径中的/uesrs前缀。所以,如果真实服务路径是/uesrs/ **,通过设置stripPrefix为false,Zuul就会直接使用URL加原始请求路径来进行请求的转发。
当然,我们也可以通过让API网关集成注册中心的方式,直接通过服务ID来做路由,这样就不用关心具体的服务地址和端口,做到自动发现服务。
假设现在已经启动Consul作为注册中心,Consul的服务地址是127.0.0.1:8500,用户服务也已经使用注册中心注册了自己的服务,服务ID是user-service(具体服务的注册与发现在第2章已经详细介绍过,这里不再赘述)。作为Spring Cloud的一员,Spring CloudNetflix Zuul使用注册中心的配置和其他服务没有不同,在API网关中创建一个名称为bootstrap.yml的配置文件,其配置内容如下。
然后,修改application.yml文件中的路由规则如下。
这里只需设置Service ID就可以通过注册中心直接路由到对应的服务地址了。当然,因为这里使用了Consul作为注册中心,所以首先要启动一个Consul的实例,如可以使用Docker指令快速启动一个Consul,指令如下。
最后,只需要引入Consul的依赖即可,在build.gradle文件中添加如下内容。
在ZuulApplication上加入@EnableDiscoveryClient注解开启注册功能,代码如下。
启动项目后,可以访问http://localhost:8500来查看服务是否注册成功,如图5.23所示。
2. 服务治理
有人可能会问,既然集成了注册中心,Zuul可以动态地发现服务,如果这个服务部署了多个实例,那么Zuul是否可以做到负载均衡和服务熔断?
通过查看注解@EnableZuulProxy的源码可以知道,Spring CloudNetflix Zuul本身已经集成了Ribbon和Hystrix两个框架,在第2章和第3章中已经详细介绍过,所以Zuul本身已经拥有了负载均衡和服务熔断的能力。
其中需要注意的是,Zuul的负载均衡只支持Service ID的路由方式,如果使用path+Service ID的路由配置,这里不需要写任何代码,Zuul就可以通过内置的RibbonRoutingFilter来实现负载均衡,默认采用Ribbon的轮询方式。
Service ID需要注册中心的支持,如果当前环境没有集成注册中心是否就不能做负载均衡?Ribbon本身的定位是客户端的负载均衡器,并不一定要集成注册中心,如果我们在Zuul中使用URL方式的路由规则,那么该如何做到负载均衡?
这很简单,只需修改少量配置即可,虚拟一个Service ID,然后指定这个Service ID对应的服务地址即可,配置内容如下。
由上述配置可见,我们禁用注册中心,依然配置Service ID为users-service,然后通过指定users-service的listOfServers属性来声明服务对应的地址,多个节点以“,”分隔,这样就可以在不使用注册中心的情况下在Zuul路由时进行负载均衡。
介绍完负载均衡,下面来说服务熔断,之前说过Spring CloudNetflix Zuul本身集成了Hystrix,那么具体如何使用?在第3章介绍过,Hystrix服务熔断的核心思路是能够指定方法的降级(fallback),然后在服务断路时快速地给予调用者反馈,在使用Zuul建立API网关后,请求都由Zuul进行路由到服务端,那么降级应该设置在哪里?在图5.10中,API网关的断路应该针对服务端,所以为了设置服务的降级,Spring Cloud Netflix Zuul提供了相应的接口FallbackProvider,只需实现它,就能设置针对服务级别的熔断策略,代码如下。
其中,getRoute方法需要返回对应的路由规则中配置的ServiceID,这里断路的对象是用户服务,所以返回users-service,当然不使用注册中心也可以做到服务熔断,具体方式和之前介绍的不使用注册中心的负载均衡配置相同,然后在FallbackResponse方法中返回ClientHttpResponse对象作为降级策略,我们可以自由地设置响应的状态信息以及响应头和响应体等内容。
除了服务熔断和降级,Spring Cloud Netflix Zuul还可以配置线程隔离策略。例如,我们可以将Hystrix默认的线程池的隔离策略修改为使用信号量的隔离策略,配置如下。
3. 过滤器
除了路由和服务治理,API网关还有一个重要的功能,即请求的过滤,这里了解一下Zuul中过滤器的用法。在Zuul中,我们可以通过继承Zuul提供的抽象类ZuulFilter来定义一个新的过滤器,代码如下。
其中,filterType()返回过滤器的类型,目前有pre、route、post、error和static共5种类型的过滤器,意义如下。
(1)pre:在路由之前执行。
(2)route:在路由时执行。
(3)post:在路由后执行。
(4)error:在路由发生异常时执行。
(5)static:请求静态资源时执行。
定义了filterType()就等于定义了过滤器的执行时机,那么如果定义了多个pre过滤器,它们的顺序又是怎样的?filterOrder()就是用来定义相同类型的过滤器的执行顺序的,通常如果过滤器没有顺序要求,就可以直接返回0,整型的值越小,对应的过滤器越先执行。
shouldFilter()很好理解,只有返回true,过滤器才会被执行,所以这里可以定义一些规则,或者使用配置文件来灵活地、可插拔地使用过滤器。
run()方法是过滤器的执行方法,想要过滤器完成的事情都会写在run方法中。例如,我们需要给所有路由的返回都加上一个自定义的消息头,那么可以写一个post类型的filter,然后定义run方法,内容如下。
由于run方法没有注入任何参数,因此我们可以通过Zuul提供的RequestContext来获取请求和响应信息,然后修改响应头,加入需要添加的内容。如果想通过过滤器阻止请求的路由,可以使用pre过滤器,然后在run方法中抛出异常即可,代码如下。
如上述代码所示,当请求的令牌认证失败时,可以抛出一个认证异常,这时请求被中断,不过身份认证通常会使用Spring Security来完成,在5.5.3节会介绍Spring Security的基本用法。
除了能够灵活地自定义过滤器,Spring Cloud Netflix Zuul还提供了一些内置的过滤器,如敏感信息头过滤器。我们可以不写代码,通过简单的配置来过滤请求头中的一些敏感信息。通过源码可以得知Spring Cloud Netflix Zuul默认过滤了头部信息中的3种数据:Cookie、Set-Cookie和Authorization。当请求通过Zuul路由到具体的服务时,头部中一旦存在Cookie、Set-Cookie和Authorization的信息,就会被删掉。我们也可以通过配置文件来修改这些配置,内容如下。
这里需要注意的是,新的配置会替换默认的配置,所以如果只想新增加一些敏感头的过滤 , 就要加上Cookie 、 Set-Cookie 和Authorization,代码如下。
关于Zuul的用法就介绍到这里,详细的教程可以查看Spring Cloud Netflix的官方文档,地址为
https://spring.io/projects/spring-cloud-netflix,或者直接访问Zuul在GitHub上的官网,地址是
https://github.com/Netflix/zuul。
Spring Cloud Gateway
Spring Cloud在Netflix Zuul之后又开发了一款新的API网关框架——Spring Cloud Gateway,它是基于Spring 5.x和Spring Boot 2.x所开发的一款支持非阻塞式的API网关框架(不过Zuul从2.x版本开始支持非阻塞的API)。相比Zuul,Spring Cloud Gateway还支持长连接,所以可以支持WebSockets的使用,旨在提供一种简单而有效的方式来路由到API,并为它们提供横切关注点,如安全性、监控/指标和弹性等。
在Spring的官网上列出了Spring Cloud Gateway的功能特性,具体如下。
(1)基于Spring Framework 5、Project Reactor和Spring Boot2.0构建。
(2)能够根据请求的任何属性匹配上路由。
(3)判断条件(Predicates)和过滤器(Filters)可作用于特定的路由。
(4)集成Hystrix断路器。
(5)集成Spring Cloud Discovery Client。
( 6 ) 易于编写的判断条件( Predicates )和过滤器(Filters)。
(7)请求率限制,即限流。
(8)路径重写。
可以看出,Spring Cloud Gateway比Zuul更加灵活,功能更加强大 , 并 且 对 于 Spring Cloud 更加契合,下面介绍 Spring Cloud Gateway的用法。
首先,可以通过工具或https://start.spring.io创建一个新的Gradle项目,加入Spring Cloud Gateway的依赖,build.gradle文件内容如下。
1.路由
假设有一个用户服务,提供一个查询用户的接口,然后和Zuul的用法类似,我们可以在配置文件中快速地增加路由的配置,修改application.yml内容如下。
在上述代码中,我们可以定义多个路由规则,其中id作为路由的唯一标识,uri是原始服务的地址,predicates请求是否匹配的判断条件,配置的条件是Path,即根据请求的路径来判断是否匹配该路由。
除了Path,Spring Cloud Gateway还提供了更加灵活的判断规则。例如,我们可以使用Cookie来进行路由,代码如下。
如上配置就表示只要请求中带有key为test、value为123的Cookie,该请求就会被匹配到这个路由中,当然,还可以进行条件组合,如以下配置。
上述配置就表示请求必须同时满足路径为/users/ **,并且请求中要有test=123和test2=456两组Cookie才会匹配上该路由。
除了Path和Cookie,Spring Cloud Gateway还提供了多种匹配方式。例如,我们可以根据请求头来匹配路由,配置如下。
上述配置就表示请求头中必须包含X-request-id的信息,并且值必须是users-service,这样的请求才会匹配上该路由。此外,还可以根据HTTP的Method来进行路由,如对所有的GET方法进行匹配,具体如下。
或者可以根据请求的参数来进行路由,配置如下。
上述配置就表示请求的URL参数中必须包含a=123的参数才会匹配上该路由,如//localhost: 8080/users/1?a=123。
除了这些匹配方式,Spring Cloud Gateway还提供了一些特别的路由方式,如按照时间的Before、After和Between。这里以Between为例,我们将请求按照时间段的规则进行路由,配置如下。
上述配置就表示时间在2018年1月1日至2018年12月31日的所有请求都将匹配该路由规则。值得注意的是,由于匹配的是客户端发生请求的时间,时间配置需要配置时区。Between表示请求时间在指定的两个时间之间,同理Before和After分别表示请求时间在指定的时间之前和之后,并且只需指定一个时间即可。
此外,Spring Cloud Gateway还提供了基于Host和RemoteAddr的匹配规则,这里不再一一演示,所有的规则都可以进行组合。
2. 过滤
除了路由,Spring Cloud Gateway还提供了很多强大的内置过滤器,而且每个过滤器可以直接配置到路由中。例如,我们可以通过AddRequestHeader和AddResponseHeader来增加请求或响应的头部信息,以AddResponseHeader为例,配置如下。
在请求API网关后,返回的响应头中就可以得到X-test=1的信息。除 了 添 加 头 部 信 息 , 还 可 以 通 过 RemoveRequestHeader和RemoveResponseHeader 来 删 除 请 求 和 响 应 的 头 部 信 息 ,以RemoveRequestHeader为例,配置如下。
上述配置就可以在请求头中删除key为Set-Cookie的头部信息。除了能够修改请求和响应的头部信息,Spring Cloud Gateway还提供了修改请求参数的过滤器,配置如下。
上述配置就可以在请求的路径上加入我们制定的前缀了。例如,请 求 路 径 是 /users/1 , 那 么 实 际 请 求 会 被 路 由 到
http://localhost:8081/myservice/users/1上。
除了上述内置的过滤器,Spring Cloud Gateway还提供了其他的过滤器。例如,用于管理Session的SaveSession过滤器,用于设置状态的SetStatus过滤器,用于重定向的RedirectTo过滤器,等等。
3. 服务治理
除了这些路由级别的过滤器,Spring Cloud Gateway还提供了一些全局的过滤器,如LoadBalancerClient可以帮助我们完成负载均衡的工作。
当然,需要集成注册中心,首先要添加注册中心的相关依赖,在build.gradle中添加如下dependencies。
然后,增加和配置其他注册中心的服务,如增加bootstrap.yml文件,内容如下。
最后,修改路由uri,配置如下。
这里lb://后面就是服务的应用名称,这样Spring Cloud Gateway就能够通过LoadBalancer Client来完成客户端负载均衡的操作。
除了负载均衡,Spring Cloud Gateway还集成了Hystrix,可以完成服务熔断和降级的处理,首先需要添加Spring Cloud Netflix Hystrix的依赖,在build.gradle中添加如下dependencies。
然后,修改application.yml的路由配置,内容如下。
在上述配置中,增加一个Hystrix的过滤器,然后Spring CloudGateway会创建一个HystrixCommand来完成服务熔断和降级的操作,通过参数(args)的配置,可以指定HystrixCommand的name和fallback的uri,配置降级策略为跳转到/users/fallback的地址,所以还需要添加一个降级的服务,在API网关中添加Controller,代码如下。
我们可以将用户服务关闭,测试一下断路是否生效。当关闭用户服务后,再次通过API网关调用用户接口,那么将返回信息usersservice crashed。
除了基础的负载均衡和服务熔断,Spring Cloud Gateway还提供了 基 于 Redis 的 服 务 限 流 的 功 能 , Spring Cloud Gateway 通 过RequestRateLimiter过滤器来完成限流的工作,由于Spring Cloud Gateway是非阻塞的架构,因此需要集成spring-boot-starter-dataredis-reactive 来 连 接 Redis , 在 build.gradle 中 添 加 如 下dependencies。
接下来就可以配置Redis的连接信息,默认是localhost:6379,内容如下。
然后添加RequestRateLimiter过滤器,配置如下。
这里采用的是令牌桶算法(Token Bucket Algorithm),参数
redis-rate-limiter.replenishRate表示允许用户每秒的最大请求数,即令牌桶每秒的填充数率,如设置是10,表示每秒令牌桶最大只能装满10次请求,但是多余的请求不会丢失,而是等待令牌桶中有空闲的空间后再继续执行,
redis-rate-limiter.burstCapacity也是表示 1s 内 允 许 的 最 大 请 求 数 量 , 但 与 replenishRate 的 区 别 是burstCapacity会直接拒绝多余的请求,如burstCapacity设置的值是20,那么一旦请求超过20,多余的请求将被丢弃,不再执行。
当 然 , 限 流 需 要 指 定 一 个 KeyResolver 才 能 正 常 工 作 ,KeyResolver可以理解为限流的维度,如我们根据请求的路径进行限流,配置一个KeyResolver的Bean,内容如下。
在配置文件中,key-resolver: "#{@pathKeyResolver}"指定了Bean的name,即pathKeyResolver,然后相同路径的请求将被限制流速。
关于Spring Cloud Gateway的用法就介绍到这里,可以到Spring Cloud Gateway的官方网站查看更详细的教程。
本文给大家讲解的内容是API网关技术实战
- 下篇文章给大家讲解的是Spring Security;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!