Gateway网关
1、微服务网关的基本介绍
不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以维护,如果让客户端直接与各个微服务通讯,可能会有很多问题:
- 客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
- 在某些场景下存在跨域请求的问题
- 加大身份认证的难度,每个微服务需要独立认证
因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层。所有的外部请求都会先经过微服务网关,客户端只需要与网关交互,只知道一个网关地址即可。
网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过网关这一层。也就是说,API 的实现方更多地只需要考虑业务逻辑,而安全、性能、监控可以交由网关来完成,这样既提高业务灵活性又不缺安全性。
微服务网关是一个服务器,是系统对外的唯一入口。网关封装了系统内部架构,为外部客户端提供一个定制的 API。API 网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP 的访问 API。服务端通过 API-GW 注册和管理服务。
1.1、使用微服务网关的优点
网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。
使用网关的优点:
- 安全 。只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
- 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数。
- 易于统一授权。
微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。
1.2、常见的网关实现方式
常见的实现微服务网关的技术有以下:
- Nginx。 使用Nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用。
- zuul。Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。Netflix开源,功能丰富,使用JAVA开发,易于二次开发;需要运行在web容器中,如Tomcat。但是缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx;
- spring cloud gateway。gateway 是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。
2、搭建gateway网关服务
创建一个以 springboot 作为模板的 gateway 模块,引入nacos服务发现和gateway依赖,如下:
<!--nacos服务注册发现依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--网关gateway依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
然后在配置文件 application.yml 中配置服务基本信息、nacos地址、路由等即可。
路由配置包括:
- 路由id:路由的唯一标示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言(predicates):判断路由的规则
- 路由过滤器(filters):对请求或响应做处理
如下:
server: port: 10010 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 # Nacos地址 gateway: routes: - id: user-service # 路由标示,必须唯一 # uri: http://127.0.0.1:8081 # 路由的目标地址。也可以通过http的写成固定地址,但不推荐使用 uri: lb://userservice # 路由的目标地址。lb就是负载均衡的意思,后面跟服务名称 predicates: # 路由断言,判断请求是否符合规则 - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合 - id: order-service uri: lb://orderservice predicates: - Path=/order/**
启动该服务,可以在 nacos 注册中心中查看到该服务,如下:
然后外部服务即可直接通过访问网关来访问微服务,而无需直接访问微服务的地址,如下通过直接访问 http://localhost:10010/order/101 即可访问到 order-service 的服务,网关会自动将匹配到的路由转发至对应的服务中,并且会自动实现负载均衡。
2.1、路由断言工厂(Route Predicate Factory)
我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory(断言工厂) 读取并处理,转变为路由判断的条件。例如 Path=/user/** 是按照路径匹配,这个规则是由rg.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个。
Spring提供了11种基本的Predicate工厂,如下:
示例:
spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 # Nacos地址 gateway: routes: - id: order-service uri: lb://orderservice predicates: - Path=/order/** - After=2022-04-20T15:35:08.721398+08:00[Asia/Shanghai]
上面的路由断言意思是必须匹配 /order/ 路径,并且只有在 2022-04-20 日期后才能匹配成功正常访问,必须同时满足条件才能正常访问到服务,否则无法访问,接口会报 404。
2.2、路由过滤器工厂(GatewayFilter)
GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。
Spring提供了31种不同的路由过滤器工厂。例如:
示例,比如我们通过路由过滤器来配置所有进入某个服务,比如 userservice 服务的请求都自动添加一个请求头:X-Request-red=blue。只需在配置 gateway 服务的配置文件中添加以下配置即可,如下:
spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 # Nacos地址 gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 过滤器 - AddRequestHeader=X-Request-red, blue # 添加请求头
由此,所有通过 gateway 网关发往 userservice 的请求都会自动添加 X-Request-red=blue 请求头,在 userservice 服务中可以通过参数获取到该请求头数据。
上面的过滤器配置是针对了某个具体的服务,如果要对所有的路由都生效,则可以将过滤器工厂写到 default 下。格式如下:
spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 # Nacos地址 gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** default-filters: # 默认过滤器,会对所有的路由请求都生效 - AddRequestHeader=X-Request-red, blue # 添加请求头
可参考官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
2.3、全局过滤器(GlobalFilter)
全局过滤器(GlobalFilter)与 GatewayFilter 的 default-filters 的作用类似,也是处理一切进入网关的请求和微服务响应,对所有路由都生效。区别在于 GatewayFilter 通过配置定义,只能处理一些比较简单的逻辑,而 GlobalFilter 的逻辑通过代码实现,可以自定义复杂逻辑。
要想实现全局过滤器需要实现 GlobalFilter 接口:
public interface GlobalFilter { /** * 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理 * * @param exchange 请求上下文,里面可以获取Request、Response等信息 * @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束 */ Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); }
示例,我们定义一个全局过滤器,拦截所有的请求,判断请求的参数中是否有 authorization,并且参数值是否为admin,如果是则放行,否则拦截该请求。
我们在 gateway 服务中创建一个类,通过该类实现 GlobalFilter 接口即可,如下:
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(0) @Component public class AuthorizeFilter implements GlobalFilter{ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取请求参数 ServerHttpRequest request = exchange.getRequest(); MultiValueMap<String, String> params = request.getQueryParams(); // 2.获取参数中的 authorization 参数 String auth = params.getFirst("authorization"); // 3.判断参数值是否等于 admin if ("admin".equals(auth)) { // 4.是,放行 return chain.filter(exchange); } // 5.否,拦截 // 5.1.设置状态码 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); //这里设置了HttpStatus.UNAUTHORIZED状态码,即401,如果请求的参数不满足时将会返回401 // 5.2.拦截请求 return exchange.getResponse().setComplete(); } }
2.4、过滤器的执行顺序(defaultFilter、路由过滤器、GlobalFilter)
请求在进入网关后会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。在请求匹配到路由后,会将 当前路由过滤器、DefaultFilter、GlobalFilter,合并到同一个过滤器链(集合)中,通过比较它们之间的 order 值大小进行排序,值越小越先执行,以此来依次执行每个过滤器。
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值;但是路由过滤器和 defaultFilter 的 order 则由 Spring 自动指定,默认是按照声明顺序从1递增,比如 defaultFilter 下有多个过滤器,则从上至下默认从1开始递增,如果 filters 下定义了多个过滤器,同样从上至下默认从1开始递增
-
当不同类型之间的过滤器的 order 值大小一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。
3、服务网关解决跨域问题
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。网关处理跨域采用的是 CORS 方案,只需要简单配置即可实现,如下:
spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 # Nacos地址 gateway: # gateway的全局跨域请求配置 globalcors: add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题 corsConfigurations: '[/**]': # 匹配的路由 allowedHeaders: "*" # 允许在请求中携带的头信息 allowedOrigins: "*" # 允许哪些网站的跨域请求。也可以通过数组的方式来只指定一些特定的网站 allowCredentials: true # 是否允许携带cookie allowedMethods: "*" # 允许的跨域ajax的请求方式。也可以通过数组的方式来只指定一些特定的请求方式 maxAge: 360000 # 跨域检测的有效期