SpringCloudAlibaba组件集成
Nacos服务注册与发现
1.Nacos认识与安装
1.1.什么是Nacos
Nacos和Eureka有着相同的能力,甚至更为强大,作为Dubbo 生态系统中重要的注册中心实现。官方对它有如下定义:
Nacos致力于帮助您发现,配置和管理微服务。它提供了一组简单有用的功能,使您能够实现动态服务发现,服务配置,服务元数据和流量管理。
Nacos使构建,交付和管理微服务平台变得更容易,更快捷。它是通过微服务或云原生方法支持以服务为中心的现代应用程序体系结构的基础架构。
Nacos不仅是服务发现组件,同时也是一个配置管理组件,也就是说它不仅可以用来取代Eureka作为注册中心, 也可以用来取代Spring Cloud Config 做配置统一管理。
1.2.Nacos服务端安装
官方提供了Nacos的服务端供我们下载使用,我们启动Nacos后将我们的微服务注册进入Nacos即可。
下载地址:https://github.com/alibaba/nacos/releases
启动Nacos:解压后,
- windows执行bin目录下的startup命令 :startup.cmd -m standalone
- linux 执行 :sh startup.sh -m standalone
访问Nacos,端口8848:http://127.0.0.1:8848/nacos/index.html ,用户名和密码都是:nacos
2.项目结构搭建
2.1.服务调用流程
这里要演示的案例是两个服务的通信,用户服务(user-server)作为服务提供者需要编写接口返回User实体对象,订单服务(order-server)作为消费者需要调用用户服务获取User实体对象,浏览器调用订单服务,订单服务调用用户服务或到User实体后返回给容器,用户和订单都注册到Nacos中,如下:
注意:这里的订单服务和用户服务都用到了User实体,所以为了让User实体共用,我们为User实体抽取了一个公共的user-common模块,用户服务和订单服务都去依赖这个模块即可使用User实体。
2.2.项目结构搭建
我们根据上面的图例来搭建项目环境,这里使用多模块方式演示,搭建父工程,提供者服务,消费者服务,以及公共的user-common模块,结构如下:
java
springcloudalibaba-parent
pom.xml
springcloudalibaba-order-common //公共的order实体,服务调用传输对象
springcloudalibaba-order-server-10020 //提供者服务
springcloudalibaba-user-server-10010 //消费者服务
父工程搭建
搭建父工程springcloudalibaba-parent并管理相关依赖,Spring boot版本为2.1.3.RELEASE,Spring Cloud 版本为Greenwich.SR1,Alibaba版本为2.1.0.RELEASE ,父工程的pom如下:
xml
<!--公共的一些配置-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--SpringBoot-->
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<!--SpringCloud-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--导入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
3.服务注册到Nacos
3.1.导入依赖
修改springcloudalibaba-user-server-1010导入服务发现依赖
xml
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入WEB依赖是为了方便后面写Controller-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.2.主配置类
创建配置类加上@EnableDiscoveryClient注解开启服务发现功能,代码如下
java
//服务注册与发现
@SpringBootApplication
@EnableDiscoveryClient
public class UserServerApplication1010 {
public static void main(String[] args) {
SpringApplication.run(UserServerApplication1010.class) ;
}
}
3.3.配置文件
配置文件主要配置端口,服务名,已经nacos注册中心地址
yml
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #注册中心地址
4.服务通信
服务通信可以使用Ribbon,OpenFeign,甚至Dubbo,使用方式和在SpirngCloudNetflix中没有任何区别。
Nacos配置管理
1.Nacos配置中心
1.1.概述
Nacos作为Spring Cloud Alibaba的一个重要组件,它不仅可以用作服务注册与发现,也可以用来替代Spring Cloud Config作为统一配置文件管理,而且他的使用更为简单和人性化。
1.2.Nacos添加配置
第一步:打开Nacos监控面板 - 进入配置列表 -点击 "+" 图标添加配置 如下:
第二步:填写Data ID,选择YAML,编辑配置文件内容
这里定义了一个名字为application-user-dev.yaml的配置,使用的是YAML格式。
-
Data ID
: 非常重要,可以看做是配置的文件的名字,在程序中拉取配置文件的时候需要指定Data ID。 -
Group
: 分组,默认是 DEFAULT_GROUP , 可以针对不同的项目指定不同的配置组。
2.客户端接入配置中心(重要)
2.1.导入依赖
修改工程 springcloudalibaba-user-server-1010 ,添加配置中心依赖nacos-config。
xml
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.2.编写Controller
下面的Controller用来做配置刷新测试,temp.notify
对应了配置文件中的配置项目。@RefreshScope
注解是用来做配置自动刷新。那么当我们修改了Nacos中的配置文件,Controller中读取到的配置temp.notify
将会自动变化。
java
@RefreshScope //刷新配置
@RestController
public class UserController {
@Value("${temp.notify}")
private String notify;
@GetMapping("/user/{id}")
public User getById(@PathVariable Long id){
System.out.println("测试配置notify="notify);
return new User(id,"zs:"+id, "我是zs");
}
}
2.3.修改yml配置
注意,将原来的配置文件修改成bootstrap.yml,然后增加如下内容:
yml
server:
port: 1010
spring:
profiles:
active: dev
application:
name: user-server
cloud:
nacos:
discovery:
server-addr: localhost:8848 #注册中心
config:
server-addr: localhost:8848 #配置中心
file-extension: yaml # 配置文件的后缀名
prefix: application-user #配置前缀 ,默认使用sring.application.name
group: DEFAULT_GROUP #默认分组
#如何查找配置文件:application-user + dev + yaml=application-user-dev.yaml 正好和Nacos配置的DataId一致
提示:客户端是如何从Nacos中找到配置文件的呢?
spring.cloud.nacos.config.server-addr
:配置了Nacos的地址spring.cloud.nacos.config.file-extension
:指定了配置文件的格式为YAML,默认是properties,spring.cloud.nacos.config.prefix
:配置前缀,如果不配置前缀默认会把 服务名即spring.application.name
的值作为前缀spring.cloud.nacos.config.group
:分组名,默认是DEFAULT_GROUP对应了Nacos配置中的Groupspring.profiles.active
:配置了环境为dev .该配置可以实现多配置多环境管理
根据如上配置,那么config客户端会将:前缀+环境+后缀 拼接成的文件名"application-user-dev.yaml
" 去Nacos上查找是否有对应Data ID的配置文件。
2.4.测试
启动Nacos,启动 springcloudalibaba-user-server-1010 工程 , 修改Nacos中的配置文件内容,然后访问 http://localhost:1010/user/11
,观察控制台打印的 "notify"的值会发生变化。
2.5.注意细节
- 云端配置文件的后缀应该是 yaml而不是yml
- 客户端配置需要指定:spring.profiles.active=dev 环境名
- 客户端配置 :前缀 + 环境名 + 后缀应该和云端配置文件的DataId一致
3.命名空间
命名空间可以用来隔离不同项目的配置文件,在Nacos中配置了命名空间后,那么Java客户端需要指定命名空间后才能拉取到该命名空间下的配置文件。
xml
server:
port: 1010
spring:
profiles:
active: dev
application:
name: user-server
cloud:
nacos:
discovery:
server-addr: localhost:8848 #注册中心
config:
server-addr: localhost:8848 #配置中心
file-extension: yaml #配置文件格式
prefix: application-user #配置前缀 ,默认使用sring.application.name
group: DEFAULT_GROUP #默认分组
namespace: 8ef8c1e5-6d20-4efc-80c8-2b2c05541fa3 #命名空间的ID
对应配置而言,可以通过namespace,group,dataid的不同区分不同环境!
- 区分项目 namespace
- 区分环境 group(采纳)/dataid(上个项目)
对于服务发现而言,不同组不能相互调用。
- 区分项目 namespace
- 区分环境:开发环境(dev-group) 测试环境,线上环境 通过组来区分就欧克
Sentienl限流
Sentinel Server服务端
Sentinel 提供了现成的服务端供我们使用,点我下载地址,下载之后通过命令行启动
shell
java -jar -Dserver.port=1111 sentinel-dashboard-1.6.0.jar
注意:只有1.6.0及以上版本才有这个登录页面。默认用户名和密码都是sentinel
。对于用户登录的相关配置可以在启动命令中增加下面的参数来进行配置:
-
-Dsentinel.dashboard.auth.username=sentinel
: 用于指定控制台的登录用户名为 sentinel; -
-Dsentinel.dashboard.auth.password=123456
: 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel -
-Dserver.servlet.session.timeout=7200
: 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟; -
-Dserver.port=1111
:配置端口
Sentinel 客户端接入
1.导入依赖
修改用户服务 springcloudalibaba-user-server-1010 ,加入sentinel依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置Sentinel
修改yml配置,添加senticel服务控制台地址
yml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:1111
3.资源限流
Sentinel为我们提供了 @SentinelResource
注解标记需要限流的资源。 修改UserController,代码如下:
java
@GetMapping("/user/{id}")
//限流降级
@SentinelResource(value="user",blockHandler="exceptionHandler")
public User getById(@PathVariable Long id){
System.out.println(notify);
return new User(id,"zs:"+id, "我是zs");
}
// 限流与阻塞处理 : 参数要和 被降级的方法参数一样
public User exceptionHandler(@PathVariable Long id, BlockException ex) {
ex.printStackTrace();
System.out.println("限流了...");
return new User(-1L,"限流了","限流了");
}
提示:这里通过@SentinelResource
的value属性为资源取名为 "user" ,后续我们可以根据该资源名来进行限流。
同时这里通过 blockHandler
属性我配置了一个限流降级方法,即当"user"资源触发限流了会调用blockHandler
指向的降级方法返回托底数据,不至于抛出默认的限流异常信息给客户端(一串英文用户也看不懂) ,需要注意的是:降级方法要和被限流的方法参数一致,然后加上 BlockException异常对象。
当然,也可以通过 blockHandlerClass 属性把降级方法写在一个专门的类中,里面方法必须是static,如:
java
@SentinelResource(value="user",blockHandler="exceptionHandler"
,blockHandlerClass=ExceptionUtil.Class)
降级类
java
public final class ExceptionUtil {
public static User exceptionHandler(Long id ,lockException ex) {
//...
}
}
Sentinel设置限流策略
启动应用 springcloudalibaba-user-server-1010 ,然后通过浏览器访问 http://localhost:1010/user/11
,然后登录Sentinel控制台,在"实时监控"列表中可以看到资源的相关监控信息的
在 "族点链路" 列表中可以看到资源的调用链 ,并且可以通过"流控"按钮设置流控规则
也可以在"流量控制"菜单中我们可以针对资源进行限流规则的设置。如下:
这里我添加了一个流控规则,资源名对应客户端 @SentinelResource(value="user"..
注解的资源,通过QPS限流(每秒请求数量),阈值是 1 ,意思是"user"这个资源每秒只能有1个请求进来,多余的请求会触发限流,返回降级数据。
限流测试
通过浏览器频发访问 "user"资源,当QPS大于1就会触发限流,效果如下:
Sentinel流控模式
直接
Sentinel默认的流控处理就是【直接->快速失败】,QPS(query peer seconds)达到阈值,当前资源直接失败。
关联-你关联的到达一定并发,限制自己
关联的资源达到某个阈值,限流自己,如:限流的资源是/user/delete
,关联的资源是/user/list
,当/user/list
达到阈值,限流user/delete
, 举例: 支付并发太高,可以限制下单的流量 下单关联支付,支付并发过高,限制下单
链路-你调用的到达一定并发,限制自己
限流线路调用链路的入口,如 /user/list
资源中 调用了 /dept/list
,对/dept/list
添加限流,当/dept/list
达到阈值,其实限流的是/user/list
,因为他是访问的入口
Sentinel流控效果
快速失败
快速失败:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)是默认的流控方式,当流量达到阀值直接返回异常,QPS达到任何规则阈值后,后续请求就会立即拒绝,并抛出FlowException 异常。简单理解:并发太高,直接请求拒绝
Warm Up预热
Warm Up预热:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,根据codeFactor(默认3)的值,从(阀值/codeFactor)为初始阀值,经过预热时长,才到达设置的QPS的阀值,即预热/冷启动方式。简单理解:慢慢的增大处理并发的能力
提示:初始的QPS阈值为 100 / 3 =33 ,10秒后 QPS阈值达到 100.
当系统长期并发不高,流量突然增加可能会直接把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值的上限,给系统一个预热的时间,避免冷系统被压垮。
排队等待
排队等待(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER),忽然增加的请求并发量达到了限流阈值后续请求会被拒绝,有时候我们可能更希望后续的请求可以加入队列进行排队,慢慢执行,而不是直接拒绝请求,这种方式后严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法,这种方式主要用于处理间隔性突发的流量,例如消息队列。 简单理解:突发流量处理不过来,让请求排队。
热点限流
还有一种特殊的动态限流规则,用于限制动态的热点资源 , 比如对同一个用户的请求频率做限定,比如对参数进行限定,比如对参数的值做限定(比如对商品ID为1的资源做限流)。
参数限流
参数限流就是 对资源的参数进行限流,我们来编写一个方法,接受两个参数:p1,和p2并设置好限流降级。
java
//限流降级
@SentinelResource(value="/parameterLimit",blockHandler="parameterLimitHandler")
@GetMapping(value="/parameterLimit")
public String parameterLimit(@RequestParam("p1") String p1 ,@RequestParam("p2") String p2){
return "parameterLimit方法调用成功...";
}
// 限流与阻塞处理
public String parameterLimitHandler(@RequestParam("p1") String p1 ,@RequestParam("p2") String p2,BlockException ex) {
return "限流了...";
}
配置热点规则 , 对第一个参数限流 , 当第一个参数超过了1的QPS就熔断降级。
参数值限流
对某一个参数的值满足某种条件的时候就进行限流,如下配置
意思是第一个参数的值为 haha 的时候限流阈值为10 , 超过 QPS > 10的并发就限流。
举例:应用场景比如说商品名称为"华为p40"的商品并发特别高,我们可以针对参数商品名为"华为p40"的商品进行限流。
系统规则
配置全局限流规则
系统规则可以看做是总的限流策略,所有的只要都要受到系统规则的限制。
Gateway使用Sentinel限流
Spring Cloud Gateway 作为微服务的网关,它是微服务的访问入口,当请求的流量洪峰到来我们可以在Gateway网关层通过Sentinel对请求进行流控,把好第一道关。
导入依赖
这里我们需要导入sentinel基础依赖和 sentinel-gateway 整合依赖
xml
<!-- 限流和gataway使用-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置Sentinel地址
修改yml配置,增加Sentinel服务地址
yml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:1111
配置限流规则
启动Gateway,登录sentinel控制台,对url资源进行流控限制,配置方式和前面的配置方式一样,
限流算法
Spring Cloud Alibaba Sentinel底层采用了以下几种流行的限流算法:
- 直接计数(Direct Count):基于固定时间窗口的计数器算法,根据单位时间内的请求数量进行限流。这是最简单直接的一种算法,但不支持动态参数调整。
- 滑动窗口(Sliding Window):通过滑动时间窗口内的请求数量来判断是否进行限流,支持按时间窗口和请求个数进行配置。Sentinel使用双向队列存储时间窗口内的请求记录,并根据请求的时间戳进行滑动。
- 令牌桶(Token Bucket):基于令牌桶算法,使用令牌桶中的令牌数量来控制单位时间内的请求速率。Sentinel底层实现了一个简化版的令牌桶算法,可以动态调整令牌生成速率和桶容量。
- 漏桶(Leaky Bucket):基于漏桶算法,通过固定速率的流出速度来控制单位时间内的请求速率。Sentinel底层实现了一个简化版的漏桶算法,可以动态调整漏桶的流出速率和桶容量。
此外,Sentinel还支持系统自适应限流算法,该算法会根据系统的负载情况自动调整限流策略,例如根据平均响应时间、QPS等指标进行动态调整。
以上是Spring Cloud Alibaba Sentinel底层常用的限流算法。可以根据具体场景和需求选择适合的算法进行配置。
Sentinel熔断
资源熔断降级
修改springcloudalibaba-user-1010工程,修改UserController ,通过@SentinelResource
注解的fallback
属性指定降级方法。
java
// 限流降级
public User exceptionHandler(@PathVariable Long id, BlockException ex) {
ex.printStackTrace();
System.out.println("限流了...");
return new User(-1L,"限流了","限流了");
}
// 熔断降级,参数和返回值与源方法一致
public User getByIdfallback(@PathVariable Long id){
System.out.println(notify);
return new User(id,"zs:"+id, "熔断托底了");
}
@GetMapping("/user/{id}")
//限流降级
@SentinelResource(value="user",blockHandler="exceptionHandler",fallback = "getByIdfallback")
public User getById(@PathVariable Long id){
int i = 1 / 0; //方法异常,触发熔断
return new User(id,"zs:"+id, "我是zs");
}
提示:方法中通过 int i = 1 / 0;
模拟异常,然后会熔断触发降级调用降级方法 。 通过 fallback
属性指定熔断的降级方法 ,熔断方法参数也要和被熔断方法参数一致。
注意:这里可以通过 @SentinelResource
注解的 exceptionsToTrace
属性忽略异常,即针对某个异常不熔断
2.2.配置降级策略
在Sentinel控制台,在族点链路 菜单中找到"user "资源,然后点击"降级 "按钮添加降级策略,如下:
这里的降级策略为"RT",大概意思是:如果并发数大于5 (QPS > 5) ,然后平均响应时间大于200毫秒,那么接下来的2秒钟之内对该资源的请求会被熔断降级。
2.3.测试熔断
启动springcloudalibaba-user-1010工程,访问 http://localhost:1010/user/2
,浏览器返回:
这里已经返回了托底数据,其实是因为"user"资源方法中抛出了异常,触发了熔断降级。
3.熔断规则
资源在什么情况下会触发熔断降级?调用异常,达到流控,调用超时 都会触发熔断降级,在上面的案例中我们看到资源的降级策略有 RT,异常比例,异常数三种方式,我们可以通过这三种方式来定义资源是否稳定,决定是否要进行熔断降级。
3.1.平均响应RT
平均响应时间 (DEGRADE_GRADE_RT):当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。如果接下来 1s 内持续进入 5 个请求(即 QPS >= 5),它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
总结一下:RT其实就是平均相应时间太长资源熔断。
3.2.异常比例
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):每秒请求量 > 5 ,当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
总结一下:异常比例就是按照资源的请求失败率来熔断。
3.3.异常数
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
总结一下:异常数就是按照 一分钟的异常的数量 来熔断。
3.4.慢调用比例
熔断策略慢调用比例是以慢调用数量的比例作为阈值,首先需要设置最大 RT(即最大的响应时间,用于鉴定是否是慢调用),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数大于设置的最小请求数,并且慢调用的比例大于比例阈值,则接下来的请求会自动熔断,熔断时间为设置的熔断时长。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若在HALF-OPEN状态下有一个请求响应时间小于 最大RT 则结束熔断,否则继续熔断。
4.openFeign整合Sentinel熔断
Spring Cloud Alibaba是Spring Cloud的一个子项目,OpenFeign是Spring Cloud的客户端负载均衡器,使用Spring Cloud Alibaba依然可以很方便的集成OpenFeign,如果要使用OpenFeign作为服务客户端负载均衡,那么我们需要考虑OpenFeign开启Sentinel进行服务熔断降级。
4.1.开启Sentinel
OpenFeign与Sentinel组件集成除了引**入sentinel-starter
**依赖关系之外,还需要在属性文件中启用Sentinel支持:feign.sentinel.enabled=true
yaml
feign:
sentinel:
enabled: true #熔断
4.2.给Feign接口降级
这里跟Feign开启Hystrix降级一样,还是可以使用fallback属性
java
@FeignClient(value = "user-server",fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
User getById(@PathVariable Long id);
}
4.3.编写降级类
java
@Component
public class UserClientFallback implements UserClient {
@Override
public User getById(Long id) {
return new User(-1L,"无此用户","无此用户");
}
}
服务网关SpringCloud Gateway
Spring Cloud Gataway的特点
在Spring Cloud官方定义了SpringCloud Gateway 的如下特点:
- 基于 Spring 5,Project Reactor , Spring Boot 2.0
- 默认集成 Spring Cloud DiscoveryClient
- 默认集成 Hystrix 断路器
- Predicates(断言) 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters
- 支持动态路由、限流、路径重写
Spring Cloud Gataway的核心概念
Spring Cloud Gataway有几个核心组成:
- Filter(过滤器):
Spring Cloud Gateway的Filter和Zuul的过滤器类似,可以在请求发出前后进行一些业务上的处理 ,这里分为两种类型的Filter,分别是Gateway Filter网关filter和Global Filter全局Filter, 他们的区别在后续会讲到。
- Route(路由):
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。说白了就是把url请求路由到对应的资源(服务),或者说是一个请求过来Gateway应该怎么把这个请求转发给下游的微服务,转发给谁。
- Predicate(断言):
这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。简单理解就是处理HTTP请求的匹配规则,在什么样的请情况下才能命中资源继续访问。
Spring Cloud Gateway的工作方式
Spring Cloud Gateway 的工作原理跟 Zuul 的差不多,最大的区别就是 Gateway 的 Filter 只有 pre 和 post 两种,下面是官方的执行流程图:
客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序通过特定于请求的过滤器链来运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有"前置"过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行"后"过滤器逻辑。
Spring Cloud Gataway入门
工程名:springcloud-gateway-server-1110 ,导入gataway基础依赖
xml
<dependencies>
<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>
<!--拉取配置中心-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
2.2.主配置类
java
//服务注册与发现
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServerApplication1110 {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication1110 .class);
}
}
2.3.yml配置
yml
server:
port: 7010
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #注册中心地址
namespace: 0f800d23-c0dd-4eb8-b0c0-1de010bab3ef
group: sb
gateway:
discovery:
locator:
enabled: false #false 不开放服务名访问方式
lower-case-service-id: true #服务名小写
routes:
- id: xxxx #指定服务名
uri: lb://order-service #去注册中心找这个服务名
predicates: #断言,匹配访问的路径
- Path=/services/order/** #服务访问路径
filters:
- StripPrefix=2 #请求转发的时候会去掉 /user访问路径
- id: yyyy #指定服务名
uri: lb://user-service #去注册中心找这个服务名
predicates: #断言,匹配访问的路径
- Path=/services/user/** #服务访问路径
filters:
- StripPrefix=2 #请求转发的时候会去掉 /user访问路径
这里除了要注册到nacos以外,还需要配置Gataway的路由
spring.cloud.gateway.discovery.locator.enabled=false
: 不开放服务名访问方式spring.cloud.gateway.discovery.locator.lower-case-service-id: true
忽略服务名大小写,大写小写都可以匹配spring.cloud.gateway.routes.id
: 指定了路由的服务名,可以自己定义spring.cloud.gateway.routes.uri=lb://user-server
: 去注册中心找服务,采用负载均衡的方式请求。其实就是找要调用的服务。spring.cloud.gateway.routes.predicates
: 断言,这里使用的Path=/user/**,即匹配访问的路径如果匹配/user/就可以将请求路由(分发)到user-server这个服务上。spring.cloud.gateway.routes.filters
:这里使用StripPrefix=1主要是处理前缀 /user ,访问目标服务的时候会去掉前缀访问。这个需要根据url情况来定义。
Predicate断言工厂
什么是断言工厂
什么是断言工程,在Spring Cloud Gateway官方文档有如下解释:
Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础架构的一部分进行匹配。Spring Cloud Gateway包括许多内置的路由断言工厂。所有这些断言都与HTTP请求的不同属性匹配。您可以将多个路由断言工厂与逻辑and语句结合使用。
这里不难理解,其实断言工厂就是用来判断http请求的匹配方式。比如我们再上面案例中配置的:"Path=/user/**
" ,就是使用的是 "Path Route Predicate Factory
" 路径匹配工厂,意思是http请求的资源地址必须是 /user 才会被匹配到对应的路由,然后继续执行对应的服务获取资源。
在Spring Cloud Gateway中,针对不同的场景内置的路由断言工厂,比如
Query Route Predicate Factory
:根据查询参数来做路由匹配Path Route Predicate Factory
: 更加路径来匹配RemoteAddr Route Predicate Factory
:根据ip来做路由匹配Header Route Predicate Factory
:根据请求头中的参数来路由匹配Host Route Predicate Factory
:根据主机名来进行路由匹配Method Route Predicate Factory
:根据方法来路由匹配Cookie Route Predicate Factory
:根据cookie中的属性值来匹配Before Route Predicate Factory
:指定时间之间才能匹配After Route Predicate Factory
: 指定时间之前才能匹配Weight Route Predicate Factory
: 根据权重把流量分发到不同的主机
Gateway 的 Filter 过滤器
Gateway filter
针对单个路由的Filter, 它允许以某种方式修改HTTP请求或HTTP响应。过滤器可以作用在某些特定的请求路径上。Gateway内置了很多的GatewayFilter工厂。如果要使用这些Filter只需要在配置文件配置GatewayFilter Factory的名称。下面拿一个内置的Gateway Filter举例:
AddRequestHeader GatewayFilter Factory
该Filter是Gateway内置的,它的作用是在请求头加上指定的属性。配置如下:
yml
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
在spring.cloud.gateway.routes.filters
配置项配置了一个AddRequestHeader
,他是"AddRequestHeader GatewayFilter Factory
"的名称,意思是在请求头中添加一个"X-Request-red
"的属性,值为blue
。
其他的Filter可以去看 AbstractGatewayFilterFactory 的实现类。
自定义Gateway Filter-局部过滤器
在Spring Cloud Gateway自定义过滤器,过滤器需要实现GatewayFilter和Ordered这两个接口。我们下面来演示自定义filter计算请求的耗时。
java
public class RequestTimeFilter implements GatewayFilter, Ordered {
private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String COUNT_Start_TIME = "countStartTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//开始时间
exchange.getAttributes().put(COUNT_Start_TIME, System.currentTimeMillis());
//执行完成之后
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
//开始时间
Long startTime = exchange.getAttribute(COUNT_Start_TIME);
//结束时间
Long endTime=(System.currentTimeMillis() - startTime);
if (startTime != null) {
log.info(exchange.getRequest().getURI().getRawPath() + ": " + endTime + "ms");
}
})
);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
提示: getOrder返回filter的优先级,越大的值优先级越低 , 在filterI方法中计算了请求的开始时间和结束时间
最后我们还需要把该Filter配置在对应的路由上,配置如下:
java
@Configuration
public class FilterConfig {
//配置TokenCheckFilter作用于那个访问规则上
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/user/**") // 只有/user/**的请求才会经过RequestTimeFilter
.filters(f -> f.stripPrefix(1)
.filter(new RequestTimeFilter())
.addResponseHeader("X-Response-test", "test"))
.uri("lb://user-server")
.order(0)
.id("test-RequestTimeFilter")
)
.build();
}
}
提示:这里将 RequestTimeFilter 添加到 "/user/**"这里路由上,当请求包含/user就会触发Filter的执行。
自定义GlobalFilter(重点)-全局过滤器
GlobalFilter:全局过滤器,不需要在配置文件中配置 ,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
这里我们模拟了一个登陆检查的Filter.
java
@Component
public class TokenFilter implements GlobalFilter , Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//从请求头中获取token
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> strings = headers.get("token");
//如果没有拦截
if (strings==null|| strings.size()<1){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//否则放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -200;
}
}
如果请求参数中没有 authToken ,就返回没有权限的状态。
Gateway跨域配置
所谓的跨域是因为浏览器的同源(同一个域)策略限制,其实就是同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互 ,在前后端分离的项目架构中就会出现跨域问题,因为Gateway 网关是微服务的访问入口,所以我们只需要在Gateway配置跨域即可:官方文档
yml
spring:
cloud:
globalcors: #跨域配置
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io" #允许的站点
allowedMethods: #允许的请求方式
- GET
- POST
- DELETE
- PUT
- HEAD
- CONNECT
- TRACE
- OPTIONS
allowHeaders: #允许的请求头
- Content-Type
提示:运行跨域访问的站点:https://docs.spring.io ,同时把常见的请求方式都开放。
Gateway超时
超时配置在微服务调用和数据读取的时候显得尤为重要,下面演示Gateway中的全局超时设置:
yml
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
指定路由超时配置:
yml
spring:
cloud:
gateway:
routes:
- id: per_route_timeouts
uri: https://example.org
predicates:
- name: Path
args:
pattern: /delay/{timeout}
metadata:
response-timeout: 200
connect-timeout: 200