在微服务架构中,一个大的系统被拆分成为了多个小的服务提供出来,每一个服务他们自成体系,每一个服务可以拥有自己的数据库,可以使用不同的技术框架和不同的语言进行开发,每一个服务都有自己的服务地址,服务之间通常以提供 Rest Api 风格的接口来相互调用。
客户端在一次请求中,可能需要调用多个服务接口才能完成一个业务需求,如果让客户端直接与各个微服务进行通信,可能会存在如下问题:
在微服务架构中使用网关就可以解决以上问题。
网关介绍:
API 网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。
API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。
微服务网关是微服务架构中的一个关键的角色,用来保护、增强和控制对于微服务的访问,微服务网关是一个处于应用程序或服务之前的系统,用来管理授权、访问控制和流量限制等,这样微服务就会被微服务网关保护起来,对所有的调用者透明。因此,隐藏在微服务网关后面的业务系统就可以更加专注于业务本身。
微服务网关主要作用:
Spring Cloud Gateway 是 Spring官方基于Spring 5.0,Spring Boot 2.0和 Project Reactor 等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway 作为 Spring Cloud生态系中的网关,目标是替代 zuul ,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
Nignx 是一个高性能的Http和反向代理web服务器,特点是占用内存少,并发能力强,用C语言写的。
作用:
总结:gateway是前端工程到后台服务器之间的一个对内网关,nginx是用户到前端工程的网关对外网关。Nginx在其中扮演的角色:反向代理,负载均衡。gateway在其中扮演的角色:统一鉴权,监控,路由。
Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
Predicate(断言):参考的是Java8的 java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
Filter(过滤):指的是Spring框架中Gateway Filter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
一个 web 请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制(过滤器)。
predicate (断言) 就是路由的匹配条件,过滤器 filter,可以理解为一个无所不能的拦截器,可以在执行前后来执行自定义的逻辑,再加上目标 uri,就可以实现一个具体的路由。
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter 在 “pre” 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在 “post” 类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
创建新模块,引入Gateway 核心依赖。Gateway 作为一个独立的服务,也需要注册到注册中心,同时也需要去注册中心获取服务列表,这里使用 nacos 作注册中心。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
在配置文件进行gateway配置
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
discovery:
locator:
#开启默认动态路由规则
enabled: true
经过上面的配置,gateway已经具备了默认的动态路由规则。
默认动态路由规则格式
http://Gateway网关的域名 : Gateway网关的端口 / 微服务的名称(大写)+ 微服务的服务路径
如
http://localhost:10010/SERVICE-GOODS/goods/findByGid?gid=1
等价于
http://service-goods/goods/findByGid?gid=1
在配置文件中配置自定义路由
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
discovery:
locator:
#开启默认动态路由规则
enabled: true
#自定义路由映射,可以配置多条路由规则,一个路由是由id、uri、predicates、filters组成
routes:
- id: user-service # 路由标示,必须唯一,建议为服务名
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
#filters:
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
#filters:
可以在 routes 下配置多条路由规则,一条路由是由 id,uri,predicates,filters 组成,其中 id 用来唯一标识一个路由,uri 是路由匹配后要访问的地址,predicates(断言)匹配路由的规则,filters 当路由匹配后,使用过滤器可以对请求和响应进行精细化的控制。
其中 uri 可以是固定的服务地址,如 http://localhost:8001。也可以使用 lb://服务名称。gateway会去注册中心获取服务列表,然后访问的时候对服务进行负载均衡。
默认动态路由规则和自定义路由规则可以同时存在,如果要禁用默认动态路由规则,只需要把 gateway.discovery.locator.enabled的值修改为 false即可生效。
代码配置自定义路由规则
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
//可以使用通配符,例如:/provider/product/**
.route("user-service", r -> r.path("/user/**").uri("lb://userservice"))
.route("order-service", r -> r.path("/order/**").uri("lb://orderservice"))
.build();
}
}
断言作用:匹配路由的条件,如果请求与断言相匹配则进行路由,如果不匹配直接 404
路由断言工厂 Route Predicate Factory:在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory 读取并处理,转变为路由判断的条件。
After 路由谓词工厂接受一个参数,一个日期时间(它是一个java ZonedDateTime)。此谓词匹配在指定日期时间之后发生的请求。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
Before路由谓词工厂接受一个参数,一个日期时间(它是一个java ZonedDateTime)。此谓词匹配在指定日期时间之前发生的请求。
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
Between 路由谓词工厂接受两个参数datetime1和datetime2,它们是java ZonedDateTime对象。此谓词匹配在datetime1之后和datetime2之前发生的请求。datetime2参数必须在datetime1之后。
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie路由谓词工厂接受两个参数,Cookie名称和 regexp (一个Java正则表达式)。此谓词匹配具有给定名称且其值与正则表达式匹配的cookie。
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
Header 路由谓词工厂接受两个参数:Header和regexp(一个Java正则表达式)。此谓词与具有给定名称且其值与正则表达式匹配的标头匹配。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
Host 路由谓词工厂接受一组参数,一组匹配的域名列表,多个域名之间使用逗号分隔,该模式是一种 Ant 风格的模式,以 . 分隔符为分隔符。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
如果请求的主机头值为 www.somehost.org 或 beta.somehost.org 或 www.anotherhost.org,则此路由匹配。
Method 路由谓词工厂接受一个方法参数,该参数是一个或多个参数: 请求的方式(POST,GET)。只有指定方式的请求才可以匹配成功。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
Path 路由谓词工厂接受两个参数: 路径模式列表和一个名为matchTrailingSlash的可选标志(默认为true)。
matchTrailingSlash 属性来为路由添加斜杠(/)作为路径的结尾。matchTrailingSlash属性只影响路由中路径的结尾,而不会影响其他部分的路径。
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
如果请求路径为/ red/1 或 /red/1/ 或 /red/blue 或 /blue/green,则此路由匹配。如果matchTrailingSlash 设置为false,那么请求路径 /red/1/ 将不匹配。
Query 路由谓词工厂接受两个参数:一个必需的参数和一个可选的regexp(它是一个Java正则表达式)。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
如果请求中包含 green 查询参数,则匹配上述路由。
http://localhost:10010/order-service/order/findAll?green=1
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
如果请求参数中包含red参数,并且red参数必须符合逗号后边的正则表达式的值,匹配成功则匹配上述路由。
http://localhost:10010/order-service/order/findAll?red=green
RemoteAddr 路由谓词工厂接受一个源列表(最小大小为1),它们是 CIDR 表示法(IPv4 或 IPv6)字符串,例如192.168.0.1/16(其中192.168.0.1是 IP 地址和16子网掩码)。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
如果请求的远端地址为192.168.1.1 ~ 192.168.1.255,则此路由匹配。
Weight路由谓词工厂接受两个参数:group和Weight (int型)。权重按组计算。
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
这条路线会将 80% 的流量转发到weighthigh.org,将 20% 的流量转发到weightlow.org。
XForwarded Remote Addr路由谓词工厂接受一个源列表(最小大小为1),这些源是 CIDR 表示法(IPv4或IPv6)字符串,例如192.168.0.1/16(其中192.168.0.1是IP地址,16是子网掩码)。
该路由谓词允许基于HTTP头 X-Forwarded-For 过滤请求。
可以与反向代理一起使用,例如负载均衡或web应用程序防火墙,其中只有当请求来自这些反向代理使用的受信任IP地址列表时才允许请求。
spring:
cloud:
gateway:
routes:
- id: xforwarded_remoteaddr_route
uri: https://example.org
predicates:
- XForwardedRemoteAddr=192.168.1.1/24
如果X-Forwarded-For报头包含192.168.1.1 ~ 192.168.1.255,则此路由匹配。
Spring Cloud Gateway 根据作用范围划分为 GatewayFilter(路由过滤器) 和 GlobalFilter(全局过滤器)。
路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器的作用域是特定的路由。Spring Cloud Gateway包括许多内置的GatewayFilter工厂。
全局过滤器:全局过滤器和路由过滤器区别,就是全局过滤器针对于所有路由进行设置的过滤规则,实际开发当中很少会针对于某一个路由使用Filter,大部分都会采用全局过滤器。
从生命周期划分,可以分为:
Pre型过滤器:用于在请求被转发到业务服务之前进行处理,可以用于执行参数校验、权限校验等操作。
Post型过滤器:用于在收到业务服务的响应之后进行处理,可以用于执行响应内容、响应头的修改等操作。
用于拦截并链式处理Web请求,可以实现横切与应用无关的需求,比如:鉴权、限流、日志输出等。
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应,可以限定作用在某些特定请求路径上。Spring Cloud Gateway包含许多内置的网关过滤器工厂,包括头部过滤器、路径过滤器、Hystrix过滤器和重写请求URL的过滤器,还有参数和状态码等其他类型的过滤器。
AddRequestHeader GatewayFilter 工厂接受一个名称和值参数。
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
将X-Request-red:blue报头添加到所有匹配请求的下游请求报头中。
// 取出报文头中的参数
String header = request.getHeader("X-Request-red");
AddResponseHeader GatewayFilter 工厂接受一个名称和值参数。
spring:
cloud:
gateway:
routes:
- id: add_response_header_route
uri: https://example.org
filters:
- AddResponseHeader=X-Response-Red, Blue
将X-Response-Red:Blue报头添加到所有匹配请求的下游响应报头中。
// 取出报文头中的参数
String header = response.getHeader("X-Request-red");
AddRequestParameter GatewayFilter 工厂接受一个名称和值参数。
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=red, blue
将为所有匹配请求的下游请求的查询字符串添加 red=blue。
Default Filters 对所有路由都生效的过滤器
要添加过滤器并将其应用于所有路由,可以使用spring.cloud.gateway.default-filters。
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
在 default-filters 下添加的过滤器,将会对所有路由都生效。
更多的内置路由过滤器,请查看官网。Gateway文档地址
Gateway全局过滤器(Global Filters)是一种可以对所有进入网关的请求和微服务响应进行处理过滤的机制。它不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain能够识别的过滤器。
自定义全局过滤器
自定义全局过滤器过滤器需要实现两个接口:GatewayFilter、Ordered(也可以使用 @Order 注解)。
代码示例,验证 authorization 参数来决定是否放行。
// @Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
// exchange 请求上下文,里面可以获取Request、Response等信息
// chain 用来把请求委托给下一个过滤器
@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);
// 5.2.拦截请求,当前请求结束
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
过滤器执行顺序
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加 @Order注解来指定order值。
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
跨域:在浏览器中,当 JavaScript 需要向一个不同的域名或端口发送AJAX请求时,就会发生跨域请求。
解决方案:CORS,CORS是一个缩写,全称为Cross-Origin Resource Sharing,它是一种用于Web应用程序的安全机制,用于在浏览器和Web服务器之间允许跨域请求。CORS是为了解决Web应用程序中不同源之间的安全问题而提出的。通过使用CORS,Web应用程序可以安全地从一个源向另一个源发送跨域请求,从而避免了安全漏洞和攻击。
CORS 配置
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
# 允许跨域的源(网站域名/ip),设置*为全部
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
# 允许跨域请求里的head字段,设置*为全部
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
# 跨域允许的有效期
maxAge: 360000 # 这次跨域检测的有效期