大家好,我是悟空。本文为大家带来微服务框架中一个非常重要的组件:API网关。前言在PassJava项目中,我使用了SpringCloudGateway作为API网关。客户端的所有请求都会先经过网关,再转发给成员微服务、主题微服务等。例如API网关和成员微服务对应的访问地址如下:API网关地址:http://localhost:8060会员微服务地址:http://localhost:14000客户端请求通过API网关访问,然后网关转发给会员微服务,客户端不需要知道会员微服务的地址。本文将以PassJava为例进行讲解。PassJava开源地址:https://github.com/Jackson0714/PassJava-Platform为什么需要API网关?在服务架构中,往往会有多个微服务。这些微服务可能部署在不同的机器上,一个微服务可能扩展成多个相同的微服务,形成一个微服务集群。微服务架构接入示例图在这种情况下,会存在以下问题:如果需要增加认证功能,则需要修改每个微服务。如果需要流量控制,每个微服务都需要改造。对于跨域问题,需要对各个微服务进行改造。存在安全问题,每个微服务都需要将自己的Endpoint暴露给客户端。Endpoint是服务的访问地址+端口。比如下面的地址:http://order.passjava.cn:8000灰度发布和动态路由需要修改每个微服务。这个问题的痛点是每个微服务只有一个入口。有没有办法统一入口?解决这个问题的方法是在客户端和服务器之间增加一个中间人。中间人只有一个入口,中间人就是网关。还有一个详细的问题:多个微服务如何通信?这就需要使用远程调用组件,比如OpenFeign。但是,服务之间的调用需要知道彼此的端点。如果一个服务有多个微服务,则需要通过负载均衡组件来分配流量。Endpoint不是暴露在微服务之间吗?这没有问题。毕竟只有后台服务知道,外界是不知道的。为了帮助大家更方便的理??解网关的作用,这里做一个网关、客户端、微服务的三方调用。网关对话网关:客户端你好,你现在只能和我通信,我可以把你原本想发的流量转发给微服务。微服务处理完了,把结果返回给我,我给你。客户:你没有做出改变,是吗?API网关:我可能会加一些请求头,做鉴权、授权、限流等。客户端:微服务自己做不行吗?API网关:但是每个微服务都要自己添加,很麻烦,交给我吧。微服务:网关您好,您会为我保密我的地址,对吗?API网关:当然,我给客户端显示的是我自己的地址。客户端不需要知道你的地址,只需要知道哪个API是你的,剩下的交给你。API网关选择对比业内知名网关:SpringCloudGateway、NetflixZuul、Nginx、Kong、AlibabaTengine。作为SpringCloud全家桶中的一个组件,当然选择了SpringCloudGateway。起初SpringCloud推荐的网关是NetflixZuul1.x,后来停止维护,后来有了Zuul2.0,但是因为开发延迟严重,SpringCloud官方开发了SpringCloudGateway网关组件来替代祖尔网关。所以本文我们只讲解SpringCloudGateway网关组件。SpringCloudGateway的工作流程Gateway的工作流程如下图所示:①路由判断;client的请求到达gateway后,首先会经过GatewayHandlerMapping处理,这里会做断言(Predicate)判断,看符合哪条路由规则,这个route-map后端的服务。②请求过滤:然后请求到达GatewayWebHandler。里面有很多过滤器,组成一个过滤器链(FilterChain)。这些过滤器可以对请求进行拦截和修改,比如添加请求头、参数校验等,有点像净化污水。然后将请求转发到实际的后端服务。这些过滤器在逻辑上可以称为Pre-Filters,Pre可以理解为“之前...”。③服务处理:后台服务处理请求。④Response过滤:后端处理完结果后,再次处理返回给Gateway的过滤器。逻辑上可以称为Post-Filters,Post可以理解为“after...”。⑤响应返回:响应过滤后返回给客户端。总结:首先通过匹配规则找到合适的路由,就可以将客户端的请求映射到具体的服务上。然后请求被过滤器处理并转发给特定的服务。服务处理完成后,再次被过滤器处理,最后返回给客户端。SpringCloudGateway的Predicate这个词听起来极其深奥,它是一个编程术语,我们在生活中根本用不到。说白了就是对一个表达式进行if判断,结果是真还是假,如果为真就做这件事,否则就做那件事。在Gateway中,如果客户端发送的请求满足断言的条件,就会映射到指定的路由器,转发给指定的服务进行处理。断言配置的示例如下。配置了两条路由规则,还有一个谓词断言配置。当请求url中包含api/thirdparty时,匹配第一条路由route_thirdparty。(代码示例来自我的开源项目PassJava)。断言配置接下来我们看一下Route路由与Predicate断言的对应关系:断言与路由对应关系示意图一对多:一个路由规则可以包含多个断言。如上图所示,路由Route1配置了三个谓词。同时满足:如果路由规则中有多个断言,需要同时满足才能匹配。如上图所示,路由Route2配置了两个断言。客户端发送的请求必须同时满足这两个断言才能匹配路由Route2。第一次匹配成功:如果一个请求可以匹配多条路由,则映射第一个成功的路由。如上图所示,客户端发送的请求满足Route3和Route4的断言,但是Route3的配置在配置文件中较早,所以只会匹配Route3。常见的Predicate断言配置如下所示。假设匹配路由成功,转发到http://localhost:9001。普通Predicate断言配置代码演示下面演示Gateway中通过断言匹配路由的例子。新建一个Maven项目,引入Gateway依赖。org.springframework.cloudspring-cloud-starter-gateway新建一个application.yml文件,添加Gateway路由规则。spring:cloud:gateway:routes:-id:route_qquri:http://www.qq.com谓词:-Query=url,qq-id:route_baiduuri:http://www.baidu.com谓词:-Query=url,baiduserver:port:8060第一条路由规则:断言是Query=url,qq,意思是当请求路径包含url=qq时,会跳转到http://www.qq的第二条路由规则.com:当请求路径包含url=baidu时,跳转到http://www.baidu.comSpringCloudGateway动态路由在微服务架构中,我们不会直接通过IP+端口访问微服务,而是通过服务名访问.如下图,在微服务中添加注册中心,多个微服务将自己注册到注册中心,这样注册中心就保存了服务名和IP+端口的映射关系。将微服务注册到注册中心接下来我们看看加入Gateway后请求是如何转发的。客户端先向Nginx发送请求,再转发给网关。网关通过断言匹配到路由后,将请求转发到指定的uri。这个uri可以配置为微服务的名字,比如passjava-member。那么这个服务名应该转发到哪个IP地址和端口呢?这取决于注册表的注册表。网关从注册表中拉取注册表,可以知道服务名对应的具体IP+端口。如果一个服务部署了多台机器,也可以通过负载均衡来转发请求。原理如下图所示:网关+注册中心对应的配置是uri字段如下:uri:lb://passjava-question,表示请求转发到passjava-question微服务,支持负载均衡。lb是单词负载平衡的首字母缩写词。那么什么是动态路由呢?当passjava-question服务中增加了微服务,或者IP地址发生变化时,Gateway可以感知到,但是不需要更新配置。这里的动态是指微服务集群的数量、IP、端口是动态变化的。代码示例案例:调用OSS第三方服务,上传文件到OSS。(基于PassJava项目)前提:前端页面配置的统一访问路径为网关地址:http://localhost:8060/api/,OSS服务对应地址为http://本地主机:14000。预期结果:将前端请求http://localhost:8060/api/thirdparty/v1/admin/oss/getPolicy转发给OSS服务。http://localhost:14000/thirdparty/v1/admin/oss/getPolicy配置gateway:spring:cloud:gateway:routes:-id:route_thirdparty#第三方微服务路由规则uri:lb://passjava-thirdparty#load平衡,将请求转发到注册中心predicates中注册的passjava-thirdparty服务:#Assertion-Path=/api/thirdparty/**#如果前端请求路径包含api/thirdparty,应用这条路由规则filters:#filter-RewritePath=/api/(?.*),/$\{segment}#将跳转路径中包含的api替换为空测试,上传文件成功。接下来我们来看Gateway中非常重要和核心的功能:过滤器。SpringCloudGateway的过滤网关,顾名思义,就是网络中的一个检查点,可以统一对请求和响应进行一些操作。Filter的分类FilterFilter根据请求和响应可以分为两种类型:Pre型和Post型。预类型:在请求转发给微服务之前,对请求进行拦截和修改,如参数验证、权限验证、流量监控、日志输出、协议转换等。Post类型:微服务处理完请求后,返回响应给网关,网关可以再次处理,比如修改响应内容或header,日志输出,流量监控等。另一种分类是根据过滤器作用域Filter:GlobalFilter:全局过滤器,应用于所有路由的过滤器。本地过滤器GatewayFilter:本地过滤器,应用于单个路由或一组路由的过滤器。标记为红色的表示更常用的过滤器。组织了一个包含27个内置GatewayFilter过滤器的列表。具体如何使用,这里举例说明,如果URL匹配成功,去掉URL中的“api”。filters:#filter-RewritePath=/api/(?.*),/$\{segment}#将跳转路径中包含的“api”替换为空当然我们也可以自定义过滤器,这个文章不展开。全局过滤器组织一个全局过滤器表。具体使用请参考官方文档。官方文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filters全局过滤器最常见的用途是负载均衡。配置如下:spring:cloud:gateway:routes:-id:route_member#第三方微服务路由规则uri:lb://passjava-member#负载均衡,转发请求到注册在passjava-member服务谓词中registrationcenter:#Assertion-Path=/api/member/**#如果前端请求路径中包含api/member,应用这条路由规则filters:#filter-RewritePath=/api/(?.*),/$\{segment}#将跳转路径中包含的api替换为空。这里有一个关键字lb,它使用了全局过滤器LoadBalancerClientFilter。当匹配到这条路由后,请求就会转发给passjava-member服务,它支持负载均衡转发,就是先把passjava-member解析成实际微服务的host和端口,再转发给实际微服务。为了实现简单的token认证,在使用Gateway进行登录认证时,我们通常需要自定义一个用于登录认证的过滤器。例如,客户端登录时,将用户名和密码发送给网关。网关转发给认证服务器后,如果账号密码正确,则获得一个JWTtoken。然后,当客户端访问应用服务时,它首先向网关发送请求。网关统一JWT认证,如果JWT满足条件,则将请求转发给应用服务。原理如下图所示,红框内的部分就是我后面要演示的部分。案例演示下面是一个简单的认证例子。当客户端携带token访问会员服务时,网关首先会验证token的合法性,验证规则如下:当请求头中包含token,且token=admin时,认证通过。验证通过后,请求将转发给会员服务。代码示例首先定义了一个全局过滤器来验证令牌的合法性。@ComponentpublicclassGlobalLoginFilterimplementsGlobalFilter,Ordered{@OverridepublicMonofilter(ServerWebExchangeexchange,GatewayFilterChainchain){ServerHttpRequestrequest=exchange.getRequest();Stringtoken=request.getHeaders().tokFirst)("if(!StringUtils.isEmpty(token)){if("admin".equals(token)){returnchain.filter(exchange);}}exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);returnexchange.getResponse().setComplete();}@OverridepublicintgetOrder(){return0;}}测试token不正确的场景,先测试加入token=123inheader,响应结果为401Unauthorized,无权限。测试token正确场景。然后测试在header中添加token=admin,正常返回响应数据。