本博客是基于观看b站里黑马程序员Java微服务架构视频做的一个学习记录,存有不足的地方,敬请见谅。
网关的核心功能特性:
权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
路由和负载均衡:一切请求都必须先经过 gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
在 SpringCloud 中网关的实现包括两种:
Zuul 是基于 Servlet
实现,属于阻塞式编程。而 Spring Cloud Gateway
则是基于 Spring5
中提供的WebFlux
,属于响应式编程的实现,具备更好的性能。
网关的作用:
引入SpringCloudGateway的依赖和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>
创建 application.yml 文件,编写路由配置及nacos地址,内容如下:
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
# filters: ##网关过滤器,用于对谓词中的内容进行判断分析以及处理
# - StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos
我们将符合Path
规则的一切请求,都代理到 uri
参数指定的地址。
上面的例子中,我们将 /user/**
开头的请求,代理到 lb://userservice
,其中 lb
是负载均衡(LoadBalance),根据服务名拉取服务列表,实现负载均衡。
重启网关,访问 http://localhost:10010/user/1 时,符合 /user/**
规则,请求转发到 uri:http://userservice/user/1
其中:路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:
id
,路由标识符,区别于其他 Route。uri
,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。predicate
,断言(谓词)的作用是进行条件判断,只有断言都返回真,才会执行路由。filter
,过滤器用于修改请求和响应信息。@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
网关才是服务访问的入口,所有服务都会在网关层面进行底层映射,所以在访问服务时,要基于服务serivce id(服务名)去查找对应的服务,让请求从网关层进行均衡转发,以平衡服务实例的处理能力。
第一步:项目中添加服务发现依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
第二步:修改其配置文件
server:
port: 9000
spring:
application:
name: sca-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #开启通过服务注册中心的serviceId创建路由
routes:
- id: route01
##uri: http://localhost:8081/
uri: lb://sca-provider # lb为服务前缀(负载均衡单词的缩写),不能随意写
predicates: ###匹配规则
- Path=/nacos/provider/echo/**
filters:
- StripPrefix=1 #转发之前去掉path中第一层路径,例如nacos
其中,lb
指的是从nacos中按照名称获取微服务,并遵循负载均衡策略。
打开gateway日志,代码如下:
logging:
level:
org.springframework.cloud.gateway: debug
第三步:启动服务,进行访问测试,并反复刷新分析
执行流程分析
根据官方的说明,其Gateway具体工作流程,如图所示:
客户端向Spring Cloud Gateway发出请求。 如果Gateway Handler Mapping 通过谓词predicates(predicates)的集合确定请求与路由(Routers)匹配,则将其发送到Gateway Web Handler。
Gateway Web Handler 基于路由配置调用过滤链中的过滤器(也就是所谓的责任链模式)进一步的处理请求。 Filter由虚线分隔的原因是, Filter可以在发送请求之前和之后执行拓展逻辑。
Predicate(断言)又称谓词,用于条件判断,只有断言结果都为真,才会真正的执行路由。断言其本质就是定义路由转发的条件。
再者通常,我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory
读取并处理,转变为路由判断的条件。
例如 Path=/user/**
是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在 Spring Cloud Gateway
还有十几个
SpringCloud Gateway包括一些内置的谓词工厂(所有工厂都直接或间接的实现了RoutePredicateFactory
接口),这些断言或谓词工厂负责创建谓词对象,并通过这些谓词对象判断http请求的合法性。常见谓词工厂如下:
基于Datetime
类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
1) AfterRoutePredicateFactory
:判断请求日期是否晚于指定日期
2) BeforeRoutePredicateFactory
:判断请求日期是否早于指定日期
3) BetweenRoutePredicateFactory
:判断请求日期是否在指定时间段内
-After=2020-12-31T23:59:59.789+08:00[Asia/Shanghai]
当且仅当请求时的时间After配置的时间时,才转发该请求,若请求时的时间不是After配置的时间时,则会返回404 not found。时间值可通过ZonedDateTime.now()
获取。
基于header
的断言工厂HeaderRoutePredicateFactory
判断请求Header是否具有给定名称且值与正则表达式匹配。例如:
-Header=X-Request-Id, \d+
基于Method请求方法的断言工厂,MethodRoutePredicateFactory
接收一个参数,判断请求类型是否跟指定的类型匹配。例如:
-Method=GET
基于Query请求参数的断言工厂,QueryRoutePredicateFactory
:
接收两个参数,请求param和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。例如:
-Query=pageSize,\d+
内置的路由断言工厂应用案例,例如:
server:
port: 9000
spring:
application:
name: sca-gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #开启通过服务中心的serviceId 创建路由的功能
routes:
- id: bd-id
##uri: http://localhost:8081/
uri: lb://sca-provider
predicates: ###匹配规则
- Path=/nacos/provider/echo/**
- Before=2021-01-30T00:00:00.000+08:00
- Method=GET
filters:
- StripPrefix=1 # 转发之前去掉1层路径
当条件不满足时,则无法进行路由转发,会出现404异常。
Filter
)就是在请求传递过程中,对请求和响应做一个处理。Gateway
的Filter从作用范围可分为两种:GatewayFilter
与GlobalFilter
。其中:
Spring提供了31种不同的路由过滤器工厂。
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:
基于AddRequestHeader GatewayFilterFactory,为原始请求添加Header。
例如,为原始请求添加名为 X-Request-Foo ,值为 Bar 的请求头:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-Foo, Bar
基于AddRequestParameter GatewayFilterFactory,为原始请求添加请求参数及值
例如,为原始请求添加名为foo,值为bar的参数,即:foo=bar。
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=foo, bar
基于PrefixPath GatewayFilterFactory,为原始的请求路径添加一个前缀路径
例如,该配置使访问${GATEWAY_URL}/hello 会转发到uri/mypath/hello。
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- PrefixPath=/mypath
基于RequestSize GatewayFilterFactory,设置允许接收最大请求包的大小
配置示例
spring:
cloud:
gateway:
routes:
- id: request_size_route
uri: http://localhost:8080/upload
predicates:
- Path=/upload
filters:
- name: RequestSize
args:
# 单位为字节
maxSize: 5000000
如果请求包大小超过设置的值,则会返回 413 Payload Too Large以及一个errorMessage
在列出几个可能会使用到的过滤器工厂:
Gateway 也是有全局过滤器的,如果要对所有的路由都生效,则可以将过滤器工厂写到 default-filters
下:
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=sign, xn2001.com is eternal # 添加请求头
全局过滤器(GlobalFilter)作用于所有路由, 无需配置。在系统初始化时加载,并作用在每个路由上。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。一般内置的全局过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们 自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。 例如,当客户端第一次请求服务时,服务端对用户进行信息认证(登录), 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证 以后每次请求,客户端都携带认证的token 服务端对token进行解密,判断是否有效。
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样。区别在于 GlobalFilter 的逻辑可以写代码来自定义规则;而 GatewayFilter 通过配置定义,处理逻辑是固定的。
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取username 参数
String username=exchange.getRequest()
.getQueryParams().getFirst("username");
if (!"admin".equals(username)) {
System.out.println("认证失败");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置拦截
return exchange.getResponse().setComplete();
}
//调用chain.filter继续向下游执行
return chain.filter(exchange);
}
// 设置过滤器优先级,值越低优先级越高
// 也可以使用 @Order 注解
@Override
public int getOrder() {
return 0;
}
}
启动Gateway服务,假如在访问的url中不带“user=admin”这个参数,可能会出现异常.
局部过滤器
与全局过滤器
请求进入网关会碰到三类过滤器:DefaultFilter
、当前路由的过滤器
、GlobalFilter
;
请求路由后,会将三者合并到一个过滤器链(集合)中,排序后依次执行每个过滤器.
排序的规则是什么呢?
GlobalFilter
通过实现 Ordered
接口,或者使用 @Order
注解来指定 order 值,由我们自己指定。defaultFilter
的 order
由 Spring
指定,默认是按照声明顺序从1递增。过滤器
的 order
值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。例子
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
方法是先加载defaultFilters
,然后再加载某个route的filters
,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
跨域:域名不一致就是跨域,主要包括:
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
在 Gateway 网关中解决跨域问题还是比较方便的。
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求 allowedOrigins: “*” 允许所有网站
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
网关是所有外部请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们采用Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进行限流。参考网址如下:
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
第一步:添加依赖
在原有spring-cloud-starter-gateway依赖的基础上再添加如下两个依赖,例如:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>
第二步:添加sentinel及路由规则(假如已有则无需设置)
routes:
- id: route01
uri: lb://sca-provider
predicates: ###匹配规则
- Path=/provider/echo/**
sentinel:
transport:
dashboard: localhost:8180 #Sentinel 控制台地址
eager: true #取消Sentinel控制台懒加载,即项目启动即连接
第三步:启动网关项目,检测sentinel控制台的网关菜单。
启动时,添加sentinel的jvm参数,通过此菜单可以让网关服务在sentinel控制台显示不一样的菜单,代码如下。
-Dcsp.sentinel.app.type=1
说明,假如没有发现请求链路,API管理,关闭网关项目,关闭sentinel,然后重启sentinel,重启网关项目.
第四步:在sentinel面板中设置限流策略,如图所示:
第五步:通过url进行访问检测是否实现了限流操作
自定义API分组,是一种更细粒度的限流规则定义,它允许我们利用sentinel提供的API,将请求路径进行分组,然后在组上设置限流规则即可。
第一步:新建API分组,如图所示:
常见的包括:
限流过滤器-计数器算法
固定窗口计数器算法概念如下:
限流过滤器-漏桶算法
漏桶算法说明:
限流过滤器-令牌桶算法
漏桶算法说明: