😀😀😀创作不易,各位看官点赞收藏.
文章目录
- Spring Cloud Alibaba 笔记
- 1、Nacos 服务注册和配置中心
- 1.1、Nacos 之下载启动
- 1.2、Nacos 之注册中心
- 1.3、Nacos 之服务发现
- 1.4、Nacos 之配置中心
- 1.5、Nacos 之分类配置
- 1.6、Nacos 之集群配置
- 1.6.1、Nacos 之持久化配置
- 1.6.2、Nacos 之集群配置
- 2、Sentinel 熔断与限流
- 2.1、Sentinel 之基本使用
- 2.2、Sentinel 之流量监控
- 2.3、Sentinel 之服务降级
- 2.4、Sentinel 之热点限流
- 2.5、Sentinel 之系统规则
- 2.6、Sentinel 之`@SentinelResource`注解
- 3、 OpenFeign 远程服务调用
- 3.1、Openfeign 之基本使用
- 3.2、Openfeign 之超时控制
- 3.3、Openfeign 之异常处理
- 4、Gateway 服务网关
- 4.1、Gateway 之网关简介
- 4.2、Gateway 之配置路由
- 4.3、Gateway 之配置断言
- 4.4、Gateway 之配置过滤器
Spring Cloud Alibaba 笔记
Spring Cloud Alibaba 旨在为微服务开发提供一站式解决方案。该项目包括开发分布式应用程序和服务所需的组件,以便开发人员可以使用 Spring Cloud 编程模型轻松开发分布式应用程序。
使用Spring Cloud Alibaba,您只需添加一些注解和配置,您的应用程序就可以使用阿里巴巴的分布式解决方案,并通过阿里巴巴中间件构建您自己的分布式系统。
Spring Cloud 阿里巴巴的特点:
- 流量控制和服务降级:支持WebServlet、WebFlux、OpenFeign、RestTemplate、Dubbo接入限流降级功能。可以在运行时通过控制台实时修改限流和降流规则,还支持对限流和降流Metrics的监控。
- 服务注册和发现:可以注册服务,客户端可以使用 Spring 管理的 bean,自动集成 Ribbon 发现实例。
- 分布式配置:支持分布式系统的外部化配置,配置变化时自动刷新。
- Rpc Service:扩展 Spring Cloud 客户端 RestTemplate 和 OpenFeign 以支持调用 Dubbo RPC 服务。
- 事件驱动:支持构建与共享消息系统连接的高度可扩展的事件驱动微服务。
- 分布式事务:支持高性能、易用的分布式事务解决方案。
- 阿里云对象存储:海量、安全、低成本、高可靠的云存储服务。支持随时随地在任何应用程序中存储和访问任何类型的数据。
- 阿里云SchedulerX:精准、高可靠、高可用的定时作业调度服务,响应时间秒级。
- 阿里云短信:覆盖全球的短信服务,阿里短信提供便捷、高效、智能的通信能力,帮助企业快速联系客户。
1、Nacos 服务注册和配置中心
Nacos: 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
Nacos官网:https://nacos.io/zh-cn/index.html
1.1、Nacos 之下载启动
安装方式有很多,可以去github上去下载对应的压缩包,但是下载慢。也可以去gitee上去找一个别人fork下源码,然后通过maven自己打包(要确定自己电脑上有Java环境和Maven环境)。
git clone https://github.com/alibaba/nacos.git
cd nacos/
# 打包命令
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
# 进入到这个文件下,会发现打包好的文件
cd distribution/target/nacos-server-$version/nacos/bin
Windows启动方式:
# 启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
注意:启动目录路劲不能包含中文,启动后就可以访问:127.0.0.1:8848/nacos/index.html
,出现下面的页面就是启动成功了。
Linux启动:
# Linux/Unix/Mac
sh startup.sh -m standalone
# ubuntu系统
bash startup.sh -m standalone
1.2、Nacos 之注册中心
父spring-cloudalibaba的pom引入依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
本地引入nacos的pom依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
编写yaml文件:
server:
port: 8081
spring:
application:
name: pay-application-01 # 注册名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 # 注册地址
management:
endpoints:
web:
exposure:
include: '*'
主启动类:
@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现
public class PayApplication01 {
public static void main(String[] args) {
SpringApplication.run(PayApplication01.class,args);
}
}
启动服务并注册到Nacos中:
1.3、Nacos 之服务发现
nacos可以进行服务的调用,也可以自己进行负载均衡。将服务消费者注册到nacos中,并使用RestTemplate
进行服务调用。
yaml文件编写:
server:
port: 80
# 服务消费者
spring:
application:
name: consumer-application # 注册名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 # 注册地址
# 消费者将要访问的微服务名称
service-url:
nacos-pay-service-name: pay-application-01
由于Spring Cloud2020.0.1.0之后不再使用netflix,所以不使用Ribbon做负载均衡,采用下面方式进行服务调用。
我们使用LoadBalancerClient作为负载均衡,首先引入依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
@RestController
public class TestController {
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancerClient loadBalancerClient;
@Value("${service-url.nacos-pay-service-name}")
private String SERVICE_NAME;
@GetMapping("/test")
public String test(){
ServiceInstance serviceInstance = loadBalancerClient.choose(SERVICE_NAME);
// 服务调用地址
String path = String.format("http://%s:%s/%s",serviceInstance.getHost(),serviceInstance.getPort(),"test");
System.out.println("request path:" +path);
// 通过RestTemplate调用服务
return restTemplate.getForObject(path,String.class);
}
}
每刷新一次,就会由不同服务提供者进行提供,这就实现了轮询的负载均衡。
1.4、Nacos 之配置中心
Nacos可以作为一个配置中心实时更新项目中的配置文件,这样就可以只需修改一处配置文件使所有服务的配置文件都修改。Nacos在项目初始化时要先从配置中心拉取配置之后,才能保证项目的正常启动。
引入依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 配置中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
编写配置文件:
一般有两种配置文件bootstrap.yaml
和application.yaml
,但是前者优先级高于后者,所以将全局的配置放在bootstrap中,自己配置文件放在application中。(写一个就可以了)
server:
port: 7001
spring:
profiles:
active: dev # 设置成开发环境
application:
name: config-application
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 服务注册地址
config:
server-addr: 127.0.0.1:8848 # 配置中心地址
file-extension: yml # 配置文件后缀名
编写好配置模块后,在Nacos中创建配置文件:
Data ID命名规则:
命名公式 ${spring.cloud.nacos.config.prefix}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
,注意每一个不能省略,中间使用 - 隔开。
- spring.cloud.nacos.config.prefix: 前缀,默认是项目名称,可以通过spring.cloud.nacos.config.prefix修改。
- spring.profiles.active:在bootstrap中配置的环境类型。
- spring.cloud.nacos.config.file-extension:在bootstrap中配置文件扩展名。
可能新版的nacos不支持bootstrap文件,需要导入依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
以上的Data ID就该命名为:config-application-dev.yml
配置文件动态刷新:
@RestController
@RefreshScope // 配置文件动态刷新,当修改nacos中心的配置文件发布后,不用重启项目就会发现配置发生变化了
public class TestController {
@Value("${test.value}")
private String values;
@GetMapping("/test")
public String test(){
return values;
}
}
1.5、Nacos 之分类配置
在分布式开发中存在多环境,通常在实际开发中都会准备不同的开发环境,例如dev开发环境、test测试环境、prod生产环境,如何进行管理这些环境配置保证每个服务读取到正确的配置文件,这就需要Nacos的分类配置。
Namespace、group、Data ID三者之间的关系:
Namespace区分部署环境,Group和Data ID逻辑上区分两个目标对象。相当于Java中的包名、类名。默认的Namespace是public,Group是DEFAULT_GROUP。
Nacos三种分配配置方式:(开发环境、测试环境、生产环境)
- 基于Data ID配置:通过切换项目的开发环境去配置中心拉取不同Data ID的配置文件。(常用)
# 项目切换不同环境
spring:
profiles:
active: dev # 设置成开发环境,对应配置中心Data ID为config-application-dev.yml
# active: test # 设置成测试环境,对应配置中心Data ID为config-application-test.yml
# active: prod # 设置成生产环境,对应配置中心Data ID为config-application-prod.yml
配置中心创建对应的配置文件,是在默认的public的Namespace下,默认的DEFAULT_GROUP分组下,分别切换对应环境就可以实现不能拉取不同环境的配置文件。
- 基于GROUP分组配置:去拉取不同分组下同一个Data ID配置文件。
创建两个相同Data ID的配置文件,但是在不同的分组下。
spring:
profiles:
active: dev # 设置成开发环境,对应配置中心Data ID为config-application-dev.yml
# active: test # 设置成测试环境,对应配置中心Data ID为config-application-test.yml
# active: prod # 设置成生产环境,对应配置中心Data ID为config-application-prod.yml
application:
name: config-application
cloud:
refresh:
enabled: true
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 服务注册地址
config:
server-addr: 127.0.0.1:8848 # 配置中心地址
file-extension: yml # 配置文件后缀名
group: DEV_GROUP # 切换对应分组
- 基于Namespace命名空间:创建不同的命名空间,然后在不同的命名空间去查找对应的配置文件。
创建两个不同的命名空间,并创建两个GROUP和Data ID相同的配置文件,默认有一个public命名空间不能够删除。
在不同分组中创建了两个相同GROPU和Data ID的配置文件,并修改配置文件来切换不同的命名空间。
spring:
profiles:
active: dev # 设置成开发环境,对应配置中心Data ID为config-application-dev.yml
# active: test # 设置成测试环境,对应配置中心Data ID为config-application-test.yml
# active: prod # 设置成生产环境,对应配置中心Data ID为config-application-prod.yml
application:
name: config-application
cloud:
refresh:
enabled: true
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 服务注册地址
config:
server-addr: 127.0.0.1:8848 # 配置中心地址
file-extension: yml # 配置文件后缀名
group: TEST_GROUP # 切换对应分组
namespace: module-dev # 切换不同的Namespace,填创建时的命名空间ID
1.6、Nacos 之集群配置
1.6.1、Nacos 之持久化配置
在0.7版本之前,在单机模式时nacos使用嵌入式数据库(derby)实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力。对于搭建Nacos集群数据库为了保证数据的一致性,也是使用MySQL数据库来存放数据保证数据高可用。
初始化Nacos数据库脚本:在Nacos的conf目录下有一个nacos-mysql.sql脚本,复制后在MySQL来执行。
修改Nacos的数据源:修改conf目录下的
application.properties
文件,在文件最下面添加下面一句话。
# 一定要是mysql
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=1234567
重启Nacos并插入数据观察数据库是否增加数据记录,如果增加数据库就切换成功,这样新增的数据存放在数据库中,重启Nacos数据就不会丢失。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tvtSrau-1692611041818)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20221102112013186.png)]
1.6.2、Nacos 之集群配置
在生产环境中Nacos都已集群的形式存在,这样保证了服务高可用。一般集群模式是客户端请求到代理服务器,然后代理服务器转发到每一台Nacos上。下面介绍使用1台Nginx+3台Nacos+1台MySQL做Nacos集群。
- 在Linux下安装Nacos压缩包并解压,并初始化持久化Sql脚本修改对应数据源,修改每一台Nacos启动端口和开启连接密码(都在application.properties)。
- 修改cluster.conf文件,配置集群组。
- 安装Nginx代理服务器:https://blog.redis.com.cn/install
安装编译环境:
# g++环境
yum -y install gcc automake autoconf libtool make
yum install gcc gcc-c++
# 安装pcre、zlib、openssl
yum install pcre -y
yum install pcre-devel -y
yum install zlib -y
yum install zlib-devel -y
yum install openssl -y
yum install openssl-devel -y
下载tar包,并解压,默认解压后的路径是
/usr/local/nginx
#解压
cd /usr/local
tar -zxvf nginx-1.16.0.tar.gz
#进行configure配置,查看是否报错
cd nginx-1.16.0/
./configure
#编译
make
#安装
make install
#在 /usr/local/nginx目录下,可以看到如下4个目录:
#conf配置文件,html网页文件,logs日志文件,sbin主要二进制程序
修改Nginx配置文件并启动:
#指定配置文件启动
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
- 启动每一台Nacos服务
# 使用内置数据源
sh startup.sh -p embedded
# 使用外置数据源,我们使用这个
sh startup.sh
由于Nacos集群服务器启动需要很大内存,一般普通的云服务跑不起来,可以修改启动命令,将初始化的内存值改小。
避坑端口偏移:Nacos 2.x 新增了 gRPC 协议的通讯端口,在启动时会自动在原有端口 port 的基础上根据偏移量 1000 和 1001 再打开另外两个端口。所以在一台服务器上去跑两个nacos不要使用相邻的端口号和不要使用偏移端口号,同时打开这两个端口。
2、Sentinel 熔断与限流
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
下载地址:https://github.com/alibaba/Sentinel/releases/tag/v1.8.0
java -Dsentinel.dashboard.auth.username=admin -Dsentinel.dashboard.auth.password=admin123 -jar sentinel-dashboard-1.8.1.jar &
启动:下载完成后是一个Jar包,直接通过
java -Dserver.port=7777 -jar sentinel-dashboard-1.8.0.jar
命令启动后就可以直接访问sentinel的控制面板,账号和密码都是sentinel,不指定端口就是默认8080端口。在Centos中运行jar包:
nohup java -Dserver.port=7777 -jar sentinel-dashboard-1.8.0.jar >sentinel.log 2>&1 &
nohup意思是不挂断运行命令,当账户退出或终端关闭时,程序仍然运行
>spring.log代表将命令的输出定向存储到spring.log这个文件中,文件名可以自己定义。
&代表在后台运行
2.1、Sentinel 之基本使用
导入依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改yaml,使用sentinel监控服务:
server:
port: 8081
spring:
profiles:
active: dev
application:
name: sentinel-application-01
cloud:
nacos:
discovery:
server-addr: xxxxxxx:8001
config:
server-addr: xxxxxxxx:8001 # 配置中心地址
file-extension: yml # 配置文件后缀名
sentinel:
transport:
# sentinel的地址
dashboard: 127.0.0.1:8080
# 默认端口号,如果被占用自动+1扫描,直到未被占用端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
启动服务,需要请求服务服务的某个接口,不然sentinel是空白页面。
2.2、Sentinel 之流量监控
簇点链路:访问服务对应的访问路径,也会监控QPS、线程数等数据
流控规则:对某个簇点设置对应的流控规则。
- 资源名:簇点访问路径
- 针对来源:Sentinel可以针对调用者进行限流,默认是default(不分来源),一般填写微服务名称。
- 阈值类型:
- QPS:当api的每秒的请求数量达到阈值时进行限流。
- 线程数:当调用api的线程数达到阈值时进行限流。
- 是否集群:是否在集群中进行限流。
- 流控模式:
- 直接:当api达到限流条件时,直接限流。
- 关联:当关联的api达到限流条件时,就限流自己。
- 链路:只记录链路上的流量(如果api是从某个链路来的流量,当流量达到阈值就进行限流自己)。
- 流控效果:
- 快速失败:直接失败,抛出异常。
- Warm Up:(预热),有一个默认的冷却因子3,当QPS、线程数达到
阈值 = 阈值\3
之前开始预热,会设置一个预热时间,在这个时间阈值有一个缓冲上升直到达到设置阈值。 - 排队等待:匀速排队,设置请求超时时间(毫秒),这时阈值类型必须是QPS,否则无效。
QPS:指每一秒中请求api的次数。
线程数:
指服务中处理请求的线程数。
2.3、Sentinel 之服务降级
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态(调用超时或异常比例升高),会对这个资源的请求进行限制,让请求快速失败,以避免影响到其它资源导致联错误。当服务降级后,在降级时间窗口之内,调用该资源就会自动熔断抛出异常(DegradeException)。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
- 慢调用比例:(响应时间)
- 最大RT:最大响应时间,当一个请求的响应时间超过这个阈值就是一个异常请求(单位:秒),这个服务进入准降级状态。
- 最小请求数:当出现一个异常请求后,在一个单位时间内(默认是1s)会统计请求数,如果超过最小请求数,则统计时间内异常请求数的比例是否操作比例阈值,超过就进行服务降级。
- 熔断时长:当服务发生熔断时,在熔断时长内所有请求自动抛出异常,时长过后服务继续使用,再出现熔断继续抛出异常。
- 比例阈值:单位时间内,异常比例超过这个值就发生熔断。
- 异常比例:
- 比例阈值:当请求出现异常时,在单位时间(1s)就会统计请求数量,如果请求数量超过最小请求数并且异常请求比例超过比例阈值,服务就会进行熔断。(如果熔断后恢复,第一次请求出现异常会继续熔断)
- 异常数:
- 异常数:当请求出现异常,会在单位时间(1s)统计请求数量,如果请求数量大于最小请求数并且异常数量超过异常数,服务就进行降级。
2.4、Sentinel 之热点限流
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
编写一个热点限流接口:
@GetMapping("/hot")
// 作为热点限流的唯一标识,一般写rest接口名
// blockHandler:指定一个限流后的处理方法
@SentinelResource(value = "hot",blockHandler = "paramFlowExceptionHandler")
public String hotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2){
return "success test";
}
public static String paramFlowExceptionHandler(String p1, String p2, BlockException blockException){
return p1+"热点数据被限流";
}
配置热点参数限流:
上面就表示,当访问的接口为url为:127.0.0.1:8081/hot?p1=xxx
时,只要p1这个参数不为空,并且QPS超过阈值就会进行热点限流。如果p1为空就没有限流规则。参数索引就是指定需要进行热点参数的下标位置,从0开始。
参数例外项:当有时候我们希望热点参数为某个值时,对应的QPS的限流不一样,可以通过参数例外项进行控制。
2.5、Sentinel 之系统规则
Sentinel 系统自适应保护从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量,比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的
maxQps * minRt
计算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
2.6、Sentinel 之@SentinelResource
注解
基于资源名的流控、降级、热点:将@SentinelResource的value属性作为资源名,之前使用的rest地址作为资源名。
@GetMapping("/resource/test1")
@SentinelResource(value = "test1",blockHandler = "handlerTest1")
public String test(){
return "test1";
}
public static String handlerTest1(BlockException e){
return "处理test1的sentinel异常";
}
blockHandler属性:
用于处理当满足在sentinel设置的规则进行处理时,自定义处理方法名,设置后不再使用sentine默认的返回值。
- 处理方法必须在同一个类中。
- 处理方法的返回值类型和参数必须与对应接口相同,并且需要再后面新增一个BlockException参数。
- 处理方法必须使用static修饰,不然不会被解析,方法也必须是public修饰。
若希望使用其他类的函数,则可以指定 blockHandlerClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析。但是使用上面方式去处理会发现每一个接口都需要一个自定义处理方法,这样代码膨胀,需要全局去处理。
blockHandler和fallback的理解:
blockHandler:指定一个方法去处理违反了sentinel配置规则的请求,不在使用sentinel默认的处理方式。
fallback:指定一个方法去处理在请求接口时出现了Java异常时,进行异常处理,不在用户页面展示报错信息。
@GetMapping("/resource/test2")
// fallback:指定处理异常的方法
@SentinelResource(value = "test2",fallback = "fallbackHandler")
public String test1(){
int i = 1/0;
return "test1";
}
// 方法名指定、static修饰、返回值类型相同、参数相同并且加一个Throwable、public修饰
public static String fallbackHandler(Throwable e){
return "处理test1的Java异常"+e.getMessage();
}
注意 blockHandler只处理在违背了sentinel配置错误时的异常,fallback只处理Java运行时的异常。
exceptionsToIgnore属性:参数是数组,当出现数组里面的异常时不进行处理
Sentinel规则持久化:默认在Sentinel的规则都是临时的,只要关闭了服务所有的规则就会消失。下面介绍将Sentinel规则持久化进Nacos中。
- 引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 修改pom
3、 OpenFeign 远程服务调用
Feign是一个声明式 Web 服务客户端。 它使编写 Web 服务客户端变得更加容易。 要使用Feign创建一个接口并对其进行注释。 它具有可插拔的注释支持,包括Feign注释和JAX-RS注释。 Feign还支持可插拔编码器和解码器。 Spring Cloud 增加了对 Spring MVC 注释的支持,并支持在 Spring Web 中默认使用相同注释。 Spring Cloud 集成了 Eureka 和 Spring Cloud LoadBalancer,以便在使用 Feign 时提供负载平衡的 http 客户端。HttpMessageConverters
导入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3.1、Openfeign 之基本使用
开启openfeign:使用
@EnableFeignClients
注解开启,basePackages
数组属性:去指明应用程序A在启动的时候需要扫描服务B中的标注了@FeignClient注解的接口的包路径。
@SpringBootApplication
// 扫描com.cj包下所有带有@FeignClient的接口
@EnableFeignClients(basePackages = {"com.cj"})
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
编写接口:
// value:是调用哪个注册到nacos中心服务的服务名称
// contextId:这个是调用注入时是bean的名称
@FeignClient(value = SourceConstants.ORDER_SOURCE)
public interface OrderRemoteService {
// 在这个服务下对应的调用的那个接口
@GetMapping("/order")
R order();
}
编写服务调用:
@RestController
public class ConsumerController {
// 注入编写好的openfeign接口,然后直接可以进行调用方法
@Resource
private OrderRemoteService orderRemoteService;
@GetMapping("/1")
public R consumer(){
return orderRemoteService.order();
}
}
总结:
- Openfeign:是一个面向接口的远程服务调用,并且自带了负载均衡功能。
- 使用:需要将服务注册到nacos中,并且在接口上指定调用服务的名称。
3.2、Openfeign 之超时控制
在时机开发中,一些业务的处理时间可能和Openfeign默认的超时时间存在时间差,导致业务是正常时间完成但是Openfeign会报TimeOutException,所以需要我们自己去控制Openfeign的超时时间。
模拟超时异常:在业务逻辑让线程睡5s
@GetMapping("/timeOut")
public R timeOut(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return R.success("端口获取成功",port);
}
远程调用:
@GetMapping("/2")
public R timeOut(){
return orderRemoteService.timeOut();
}
修改超时时间:
3.3、Openfeign 之异常处理
配置日志:编写一个配置类,设置对应的日志等级。
@Configuration
public class OpenfeignConfig {
@Bean
Logger.Level openFeignLoggerLevel(){
return Logger.Level.FULL;
}
}
- NONE:默认,不显示任何日志。
- BASIC:仅记录请求方法、url、响应状态码、执行时间。
- HEADERS:除BASIC中的内容,还有请求头、响应头信心。
- FULL:所有请求信息。
配置日志:
logging:
level:
# 配置openfeign以什么级别去监控哪个接口
com.jx.*: debug
Openfeign+Sentinel远程服务异常处理:
- 将服务注册进Sentinel中,并开启Openfeign的Sentinel支持。
feign:
sentinel:
enabled: true
client:
config:
default:
# 修改openfeign的超时时间
readTimeout: 5000 # 修改为5s
connectTimeout: 5000
- 增加一个远程调用异常处理类
// 当调用远程服务出异常时,通过这个类进行处理,实现FallbackFactory接口,并重写create方法
@Component
public class OrderRemoteFallbackFactory implements FallbackFactory<OrderRemoteService> {
private static final Logger log = LoggerFactory.getLogger(OrderRemoteFallbackFactory.class);
@Override
public OrderRemoteService create(Throwable cause) {
log.error("order远程服务调用失败:{}", cause.getMessage());
return new OrderRemoteService() {
@Override
public R order() {
// 在这个方法中返回出现异常后的远程调用结果
return R.error("order远程服务调用失败:{}",cause.getMessage());
}
@Override
public R timeOut() {
return R.error("order远程服务调用失败:{}",cause.getMessage());
}
@Override
public R exceptionTest() {
System.out.println("============================================");
return R.error("order远程服务调用失败:{}",cause.getMessage());
}
};
}
}
- 修改远程接口,在
@FeignClient
注解上添加fallbackFactory
属性
@FeignClient(contextId = "orderRemoteService",value = SourceConstants.ORDER_SOURCE,fallbackFactory = OrderRemoteFallbackFactory.class)
public interface OrderRemoteService {
@GetMapping("/order")
R order();
@GetMapping("/timeOut")
R timeOut();
@GetMapping("/exception")
R exceptionTest();
}
- 编写一个测试接口
/exception
@GetMapping("/exception")
public R exceptionTest() throws Exception {
R r = null;
r.put("name","name");
return r;
}
4、Gateway 服务网关
服务网关:在微服务的架构中,所有请求需要先通过网关,再由网关将请求路由转发到对应的每一个服务上。
- 使用网关可以简化客户端工作,这样客户端只需和网关进行交互,不再很麻烦去交互所有的微服务。
- 降低客户端与服务之间的耦合度,修改了服务接口只需要修改网关的配置策略,不用修改客户端。
- 网关可以统一管理请求,可以实现负载均衡、流控、熔断降级、认证等操作。
4.1、Gateway 之网关简介
Spring Cloud Gateway 使用的是Webflux中的reactor-netty响应式编程组件,底层是一个Netty通讯框架。
Webflux是一个异步非阻塞框架,那么Gateway也是一个异步非阻塞模型,对于高并发请求性能很好。它能与 SpringCloud 生态很好兼容,单从流式编程+支持异步上也足以让开发者选择它了
Gateway的三大组件:
- Route路由:路由是构建网关的基本模块,它由ID、目标URL、一系列断言和过滤器组成,如果断言为true则进行路由转发。
- Predicate断言:开发人员可以匹配Http请求的信息,如果请求与断言相匹配则进行路由转发。
- Filter过滤器:使用过滤器,可以在请求的前或后对请求进行修改。
4.2、Gateway 之配置路由
引入网关依赖:
<!-- 引入gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
注意,需要去除掉spring-boot-starter-web
,不然启动会报错。
路由配置:Route 主要由 路由id、目标uri、断言集合和过滤器集合组成
- id: 路由的唯一标识,必须唯一,自己定义。
- uri:请求转发的目标地址。
- order:路由的优先级,数字越小优先级越高。
- predicates:断言数组,就是判断是否满足转发条件,如果断言为true就进行路由转发。
- filters:过滤器,在请求的传递过程中对请求做一些修改。
spring:
profiles:
active: dev
application:
name: cloud-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8001
config:
server-addr: 127.0.0.1:8001
file-extension: yml
gateway:
discovery:
locator:
lowerCaseServiceId: true
enabled: true
routes: # 配置路由
- id: order # id,唯一标识
uri: http://127.0.0.1:8000 # 路由转发的目标地址
# 如果访问地址为 http://127.0.0.1:9000/order/test,他就会路由断言成功,就会转发到http://127.0.0.1:8000/test(去除了一级访问地址)
predicates: # 断言判断,如果满足就进行路由转发
- Path=/order/**
filters:
- StripPrefix=1 # 原始路去除到一级,上面就去除到/order
- id: pay
uri: http://127.0.0.1:9011
predicates:
- Path=/pay/**
filters:
- StripPrefix=1
Gateway动态路由配置:在服务架构中,服务可能是部署到多台服务上的,直接配置服务的url地址不方便,一旦服务ip地址发生变化就需要改配置,所以需要在注册中心去通过服务名称去发现服务并实现负载均衡的路由转发。
- 将Gateway网关注册进Nacos服务中心。
- 修改yaml配置文件
gateway:
discovery:
locator:
# 路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
lowerCaseServiceId: true
# 开启从注册中心动态创建路由功能
enabled: true
routes: # 动态配置路由
- id: order
uri: lb://cj-order # 固定格式:lb://+服务名
predicates:
- Path=/order/**
filters:
- StripPrefix=1
- id: pay
uri: lb://modules-pay-01
predicates:
- Path=/pay/**
filters:
- StripPrefix=1
- 引入依赖:因为带有负载均衡,需要引入
loadbalancer
。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
4.3、Gateway 之配置断言
- id: pay
uri: lb://modules-pay-01
predicates:
# 表示这个路径需要在这个时间之后访问才有效
- After=2022-11-08T17:22:39.520+08:00[Asia/Shanghai]
# 在这个时间之前访问才有效
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
# 在这个时间时间段访问才有效
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2022-11-08T17:22:39.520+08:00[Asia/Shanghai]
# 带有指定cookie名的请求,并且值是指定的(支持正则)
- Cookie=chocolate, ch.p
# 带有指定的Header的请求,并且值是指定的(支持正则)
- Header=X-Request-Id, \d+
# Host带有指定值的,且值是指定的(支持正则)
- Host=**.somehost.org,**.anotherhost.org
# 指定方法的请求才有效果,可以指定多个,用逗号隔开
- Method=GET,POST
# 匹配请求路径
- Path=/pay/**
# 必须参数和正则参数,必须参数请求必须带有指定参数值的参数,正则参数就是满足对应正则的参数即可
- Query=green, gree.
# 请求的远程地址符合指定地址
- RemoteAddr=192.168.1.1/24
# 同一个uri的通过group分组,值表示权重,权重越高在负载均衡的时候分配的流量越大
- Weight=group1, 2
时间格式的获取:
public static void main(String[] args) {
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
// 2022-11-08T16:44:48.004+08:00[Asia/Shanghai]
}
4.4、Gateway 之配置过滤器
Gateway自己的过滤器分为局部过滤和全局过滤,每个过滤器都有pre和post,局部有34个全局有9个。
文档地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters]
自定义局部过滤器:可以自己定义局部过滤器让对应过滤器在对应的路由上使用。
// 配置局部过滤器,需要继承AbstractGatewayFilterFactory,<C>可能后续版本有用
@Component
@Slf4j
public class PartFilter extends AbstractGatewayFilterFactory<Object> {
// 进行过滤操作
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) ->{
// 获取请求
ServerHttpRequest request = exchange.getRequest();
List<String> usernames = request.getQueryParams().get("username");
System.out.println(usernames.toString());
if (usernames==null || usernames.size()==0 || !usernames.get(0).equals("张三")){
ServerHttpResponse response = exchange.getResponse();
log.error("非法用户");
// 直接拦截请求并返回错误信息
return response.setComplete();
}
log.info("合法用户");
// 放行请求
return chain.filter(exchange);
};
}
}
filters:
# 将局部filter设置到对应的路由中,过滤器的名称
- PartFilter
- StripPrefix=1
配置全局过滤器:全局过滤器回去过滤网关中所有的路由,直接注入就可使用,不用其它配置。
@Component
@Slf4j
public class FilterTest implements GlobalFilter, Ordered {
// 自定义全局过滤操作
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();
List<String> usernames = request.getQueryParams().get("username");
System.out.println(usernames.toString());
if (usernames==null || usernames.size()==0 || !usernames.get(0).equals("张三")){
ServerHttpResponse response = exchange.getResponse();
log.error("非法用户");
// 直接拦截请求并返回错误信息
return response.setComplete();
}
log.info("合法用户");
// 放行请求
return chain.filter(exchange);
}
// 设置过滤器的启动优先级,数字越小越先启动
@Override
public int getOrder() {
return 0;
}
}