目录
1. SpringCloudAlibaba Nacos(服务的注册与发现、配置的动态刷新)
2. SpringCloudAlibaba Sentinel(流量控制)
1. SpringCloudAlibaba Nacos
Nacos(Dynamic Naming and Configuration Service)由Alibaba开发的Java开源项目:服务注册中心(实现服务的注册与发现, 类似SpringCloudEureka功能)和配置中心(实现配置的动态刷新,类似SpringCloudConfig+SpringCloudBus功能)的组合体(以服务为核心)。
Nacos作为服务注册中心经历了十年“双十一”的洪峰考验,具有简单易用、稳定可靠、性能卓越等优点,可以帮助用户更敏捷、容易地构建和管理微服务应用。Nacos支持几乎所有主流类型“服务”的发现、配置和管理:
1. Kubernetes Service
2. gRPC&Dubbo RPC Service
3. Spring Cloud RESTful Service
Nacos的特性
1. 服务发现
Nacos 支持基于 DNS 和 RPC 的服务发现。当服务提供者使用原生 SDK、OpenAPI 或一个独立的 Agent TODO 向 Nacos 注册服务后,服务消费者可以在 Nacos 上通过 DNS TODO 或 HTTP&API 查找、发现服务。
2. 服务健康监测
对服务实时健康检查,阻止请求发送到不健康主机或服务实例上。提供了一个健康检查仪表盘,能够帮助我们根据健康状态管理服务的可用性及流量。
3. 动态配置服务
动态配置服务可以让我们以中心化、外部化和动态化的方式,管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效、敏捷。
配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
Nacos 提供了一个简洁易用的 UI 帮助我们管理所有服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助我们更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
4. 动态 DNS 服务
Nacos 提供了动态 DNS 服务,能够让我们更容易地实现负载均衡、流量控制以及数据中心内网的简单 DNS 解析服务。
Nacos 提供了一些简单的 DNS APIs TODO,可以帮助我们管理服务的关联域名和可用的 IP:PORT 列表。
5. 服务及其元数据管理
Nacos 能让我们从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及 metrics 统计数据。
Nacos的两大组件
与Eureka类似,Nacos也采用CS(Client/Server,客户端/服务器)架构。
1. NacosServer(Nacos服务端)
与EurekaServer不同,NacosServer由阿里巴巴团队使用Java语言开发,只需下载并运行。
NacosServer可以作为服务注册中心,帮助NacosClient实现服务的注册与发现。
NacosServer可以作为配置中心,帮助NacosClient在不重启的情况下,实现配置的动态刷新。
2. NacosClient(Nacos 客户端)
通常指的是微服务架构中的各个服务,由开发者自己搭建,可以使用多种语言编写。
NacosClient通过添加依赖spring-cloud-starter-alibaba-nacos-discovery,在服务注册中心(Nacos Server)中实现服务的注册与发现。
NacosClient通过添加依赖spring-cloud-starter-alibaba-nacos-config,在配置中心(Nacos Server)中实现配置的动态刷新。
从Github下载NacosServer
NacosServer目录说明:
1. bin目录
用于存放Nacos的可执行命令。
2. conf目录
用于存放Nacos配置文件。
3. target目录
用于存放Nacos应用的jar包。
运行NacosServer
1. 终端执行
cd NacosServer的bin目录
Windows:startup.cmd -m standalone 以单机模式启动NacosServer
Linux:sh startup.sh -m standalone
2. 在浏览器中访问http://localhost:8848/nacos登陆页面,输入登录名和密码(默认都是nacos)跳转到NacosServer控制台主页。
- Nacos服务注册中心
共涉及3个角色:
1. 服务注册中心(Register Service)NacosServer
为服务提供者和服务消费者提供服务注册和发现功能。
2. 服务提供者(Provider Service)NacosClient
对外提供服务(将自己提供的服务注册到服务注册中心,以供服务消费者发现和调用)。
3. 服务消费者(Consumer Service)NacosClient
用于消费服务(从服务注册中心获取服务列表,调用所需的服务)。
Nacos实现服务注册与发现的流程如下:
1. 下载NacosServer并运行。
2. 服务提供者NacosClient启动时,会把服务以服务名(spring.application.name)的方式注册到服务注册中心(NacosServer)。
3. 服务消费者NacosClient启动时,也会将自己的服务注册到服务注册中心;并从服务注册中心获取一份服务注册列表信息,该列表中包含了所有注册到服务注册中心上的服务的信息(包括服务提供者和自身的信息);通过HTTP或消息中间件远程调用服务提供者提供的服务。
示例
===》1. 创建spring-cloud-alibaba-demo主项目
1. 修改pom.xml文件
4.0.0
com.sst.cx
spring-cloud-alibaba-demo
0.0.1-SNAPSHOT
pom
org.springframework.boot
spring-boot-starter-parent
2.5.6
8
8
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
2020.0.4
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2021.1
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
===》2. 创建spring-cloud-alibaba-provider-8001 子项目(搭建服务提供者)
1. 修改pom.xml文件
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.properties配置文件(类路径resources目录下)
#端口号
server.port=8001
#服务名
spring.application.name=spring-cloud-alibaba-provider
#Nacos Server 的地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
management.endpoints.web.exposure.include=*
3. 创建UserController.java
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class UserController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/user/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id) {
return "服务访问成功!
服务名:spring-cloud-alibaba-provider
端口号: " + serverPort + "
传入的参数:" + id;
}
}
4. 在主启动类上,添加@EnableDiscoveryClient注解开启Nacos服务发现功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现功能
public class SpringCloudAlibabaProvider8001Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAlibabaProvider8001Application.class, args);
}
}
5. 启动spring-cloud-alibaba-provider-8001,在浏览器中访问http://localhost:8001/user/nacos/1、访问http://localhost:8848/nacos查看“服务管理”下的“服务列表”
===》3. 创建spring-cloud-alibaba-consumer-nacos-8801子项目(搭建服务消费者)
1. 修改pom.xml文件
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-loadbalancer
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.yml配置文件(类路径resources目录下)
server:
port: 8801 #端口号
spring:
application:
name: spring-cloud-alibaba-consumer #服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos server 的地址
#以下配置信息并不是默认配置,而是我们自定义的配置,目的是不在 Controller 内硬编码服务提供者的服务名
service-url:
nacos-user-service: http://spring-cloud-alibaba-provider #服务提供者的服务名
3. 创建ApplicationContextBean.java配置类,添加@LoadBalanced注解与Ribbon进行集成开启负载均衡功能。
package com.sst.cx.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextBean {
@Bean
@LoadBalanced // 与Ribbon集成,并开启负载均衡功能
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
4. 创建UserController_Consumer.java
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class UserController_Consumer {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL; //服务提供者的服务名
@GetMapping("/consumer/user/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id) {
return restTemplate.getForObject(serverURL + "/user/nacos/" + id, String.class);
}
}
5. 在主启动类上,添加@EnableDiscoveryClient注解开启Nacos服务发现功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient // 开启服务注册与发现功能
public class SpringCloudAlibabaConsumerNacos8801Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAlibabaConsumerNacos8801Application.class, args);
}
}
6. 启动spring-cloud-alibaba-consumer-nacos-8801,
在浏览器中访问http://localhost:8001/consumer/user/nacos/1、访问http://localhost:8848/nacos查看“服务管理”下的“服务列表。
- Nacos配置中心
NacosServer还可以作为配置中心,对SpringCloud应用的外部配置进行统一地集中化管理。只需要在应用的pom.xml文件中引入spring-cloud-starter-alibaba-nacos-config即可实现配置的获取与动态刷新。
示例
创建spring-cloud-alibaba-config-client-3377子项目
1. 修改pom.xml文件
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-bootstrap
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建bootstrap.yml配置文件(类路径resources目录下)
server:
port: 3377 #端口号
spring:
application:
name: config-client #服务名
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #Nacos服务注册中心地址
config:
server-addr: 127.0.0.1:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
3. 创建application.yml配置文件(类路径resources目录下)
spring:
profiles:
active: dev #激活 dev 的配置
4. 创建ConfigClientController.java
package com.sst.cx.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String ConfigInfo;
@GetMapping("/config/info")
public String getConfigInfo(){
return ConfigInfo;
}
}
5. 在主启动类上,添加@EnableDiscoveryClient注解开启Nacos服务发现功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudAlibabaNacosConfigClient3377Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAlibabaNacosConfigClient3377Application.class, args);
}
}
6. 启动NacosServer,并在NacosServer控制台的“配置管理”下的“配置列表”中,点击“+”按钮,新建如下配置
Data ID:config-client-dev.yaml
Group:DEFAULT_GROUP
配置格式:YAML
配置内容:config:
info: com.sst.cx
/*
Data ID的完整格式:${prefix}-${spring.profiles.active}.${file-extension}
说明:
1. ${prefix}:默认取值为微服务的服务名,即配置文件中 spring.application.name 的值,可以在配置文件中通过配置 spring.cloud.nacos.config.prefix 来指定。
2. ${spring.profiles.active}:表示当前环境对应的 Profile,例如 dev、test、prod 等。当没有指定环境的 Profile 时,其对应的连接符也将不存在, dataId 的格式变成 ${prefix}.${file-extension}。
3. ${file-extension}:表示配置内容的数据格式,可以在配置文件中通过配置项 spring.cloud.nacos.config.file-extension 来配置,例如 properties 和 yaml。
*/
7. 启动spring-cloud-alibaba-config-client-3377,并使用浏览器访问http://localhost:3377/config/info。
在NacosServer中,将config-client-dev.yaml 中的配置修改info: hello com.sst.cx,使用浏览器再次访问http://localhost:3377/config/info,可以看到发生改变。
- NacosServer集群化部署
实际的项目开发中,一个微服务系统往往由十几,几十个甚至几百个微服务组成。 这些服务若全部注册到同一台NacosServer,就极有可能导致NacosServer因为不堪重负而崩溃,最终导致整个微服务系统瘫痪。解决这个问题最直接的办法就是使用NacosServer集群。
NacosServer的集群化部署有一个十分明显的优点,那就是可以保障系统的高可用性。在集群化部署中,只要不是所有的NacosServer都停止工作,NacosClient就还可以从集群中正常的NacosServer上获取服务信息及配置,而不会导致系统的整体瘫痪,这就是NacosServer集群化部署的高可用性。
示例
1. 将NacosServer的conf目录下的cluster.conf.example文件重命名为cluster.conf,添加
192.168.0.101:3333
192.168.0.101:4444
192.168.0.101:5555
2. 在config目录下的application.properties中,将server.port(端口号)修改为 3333,添加MySQL数据库配置
server.port=3333
###MySQL数据库配置####
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
db.user=root
db.password=12345678
3. 将该NacosServer目录复制到另外两台机器上,并将它们的端口号分别修改为: 4444 和 5555。
4. 下载Nginx,并修改Nginx中conf目录下的nginx.conf的配置
/*
下载Nginx稳定版并解压
cd跳转到该目录下,编译安装
./configure
make
sudo make install
启动
cd /usr/local/nginx/sbin
sudo ./nginx
*/
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream cluster{
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}
server {
listen 1111;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
#root html;
#index index.html index.htm;
proxy_pass http://cluster;
}
}
}
5. 启动集群中所有的NacosServer、Nginx,在浏览器中访问http://localhost:1111/nacos/,若成功访问 NacosServer的控制台,则说明 Nacos 集群部署成功。
6. 将主工程spring-cloud-alibaba-demo下所有子模块配置文件中的 Nacos Server 地址统一修改为:localhost:1111
server-addr: localhost:1111 #集群版 Nacos Server 的地址
7. 重启spring-cloud-alibaba-consumer-nacos-8801,并使用浏览器访问“http://localhost:1111/nacos”,查看“服务管理”下的“服务列表”。
2. SpringCloudAlibaba Sentinel(高可用流量控制组件)
Sentinel由Alibaba开发的开源项目:面向分布式微服务架构的轻量级高可用流量控制组件(以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度帮助用户保护服务的稳定性)。
功能上类似SpringCloudNetfilxHystrix ,但比Hystrix更强大(如:提供了流量控制功能、更完善的实时监控功能等)。
优势:
1. 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的“双十一”大促流量的核心场景,例如秒杀(将突发流量控制在系统可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用服务等。
2. 完备的实时监控:Sentinel 提供了实时监控功能。用户可以在控制台中看到接入应用的单台机器的秒级数据,甚至是 500 台以下规模集群的汇总运行情况。
3. 广泛的开源生态:Sentinel 提供了开箱即用的与其它开源框架或库(例如 Spring Cloud、Apache Dubbo、gRPC、Quarkus)的整合模块。只需在项目中引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。此外,Sentinel 还提供 Java、Go 以及 C++ 等多语言的原生实现。
4. 完善的 SPI 扩展机制:Sentinel 提供简单易、完善的 SPI 扩展接口,我们可以通过实现这些扩展接口快速地定制逻辑,例如定制规则管理、适配动态数据源等。
SPI(全称:Service Provider Interface)是一种服务发现机制。它可以在 ClassPath 路径下的 META-INF/services 文件夹查找文件,并自动加载文件中定义的类。
Sentinel由2部分组成:
1. Sentinel核心库
不依赖任何框架或库,能够运行于 Java 8 及以上的版本的运行时环境中,同时对 Spring Cloud、Dubbo 等微服务框架提供了很好的支持。
Sentinel 核心库不依赖 Sentinel Dashboard,但两者结合使用可以有效的提高效率,让 Sentinel 发挥它最大的作用。
2. Sentinel控制台(Dashboard)
1. 机器自发现(查看机器列表以及健康情况)
收集Sentinel客户端发送的心跳包,判断机器是否在线。
2. 监控(单机和集群聚合)
通过Sentinel客户端暴露的监控API,可以实现秒级的实时监控。
3. 对规则(如:流量控制、熔断降级)进行配置和管理
针对资源定义和推送规则。
5. 簇点链路自发现
6. 鉴权
从Sentinel 1.6.0起,Sentinel控制台引入基本的登录功能,默认用户名和密码都是sentinel。
的重要入口之一。
Sentinel的基本概念
1. 资源
可以是Java应用中的任何内容(如:由应用提供的服务、服务里的方法、一段代码)。
通过Sentinel提供的API来定义一个资源,使其能够被Sentinel保护起来。通常情况下,我们可以使用方法名、URL甚至是服务名来作为资源名来描述某个资源。
2. 规则
围绕资源而设定的规则。Sentinel支持流量控制、熔断降级、系统保护、来源访问控制和热点参数等多种规则,所有这些规则都可以动态实时调整。
Sentinel的使用步骤
1. 在项目中【引入Sentinel依赖】:spring-cloud-starter-alibaba-sentinel 。
Sentinel对大部分的主流框架都进行了适配(如:Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor等)。
2. 【定义资源】:
在项目开发时,只需要考虑这个服务、方法或代码是否需要保护,如果需要保护,就可以将它定义为一个资源。
在Sentinel 控制台的“簇点链路”中,可查看资源的实时统计。
4种方式:
1. 适配主流框架 自动定义资源
Sentinel 对大部分的主流框架都进行了适配,只要引入相关的适配模块(例如 spring-cloud-starter-alibaba-sentinel),Snetinel 就会自动将项目中的服务(包括调用端和服务端)定义为资源,【资源名就是服务的请求路径】。只要再对资源定义一些规则,这些资源就可以享受到Sentinel的保护。
2. 通过SphU类(try-catch风格)手动定义资源。
3. 通过SphO类(if-else风格)手动定义资源。
返回值为false(发生限流)时进行限流之后的逻辑处理。
4. 注解方式(@SentinelResource注解)手动定义资源。
3. 根据实时统计信息,【对资源定义规则】(如:流控规则、熔断规则、热点规则、系统规则、授权规则)。
4. 运行程序,【检验规则】是否生效。
@SentinelResource注解的常用属性 | 说明 | 必填与否 | 使用要求 |
---|---|---|---|
value | 用于指定资源的名称 | 必填 | - |
entryType | entry 类型 | 可选项(默认为 EntryType.OUT) | - |
blockHandler | 服务限流后会抛出 BlockException 异常,而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。简单点说,该属性用于指定服务限流后的后续处理逻辑。 可选项 | 1. blockHandler 函数访问范围需要是 public;2. 返回类型需要与原方法相匹配;3. 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;4. blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandler 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 | |
blockHandlerClass | 若 blockHandler 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 可选项 | 1. 不能单独使用,必须与 blockHandler 属性配合使用;2. 该属性指定的类中的 blockHandler 函数必须为 static 函数,否则无法解析。 | |
fallback | 用于在抛出异常(包括 BlockException)时,提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 可选项 | 1. 返回值类型必须与原函数返回值类型一致;2. 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;3. fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 | |
fallbackClass | 若 fallback 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 可选项 | 1. 不能单独使用,必须与 fallback 或 defaultFallback 属性配合使用;2. 该属性指定的类中的 fallback 函数必须为 static 函数,否则无法解析。 | |
defaultFallback | 默认的 fallback 函数名称,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 可选项 | 1. 返回值类型必须与原函数返回值类型一致;2. 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;3. defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 | |
exceptionsToIgnore | 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 可选项 | - |
注:在 Sentinel 1.6.0 之前,fallback 函数只针对降级异常(DegradeException)进行处理,不能处理业务异常。
流量控制规则
任何系统处理请求的能力都是有限的,但任意时间内到达系统的请求量往往是随机且不可控的,如果在某一个瞬时时刻请求量急剧增,那么系统就很有可能被瞬时的流量高峰冲垮。为了避免此类情况发生,需要根据系统的处理能力对请求流量进行控制,即流量控制(简称:流控)。
流控规则
1. 资源名(流控规则的作用对象)。
2. 阈值(流控阈值)。
3. 阈值类型(流控阈值的类型)
QPS(默认值,表示并发请求数,即每秒钟最多通过的请求数)、并发线程数。
4. 针对来源(流控针对的调用来源)
默认值:default(表示不区分调用来源)。
5. 流控模式(调用关系限流策略)
直接(默认值)、链路、关联。
6. 流控效果
直接拒绝(默认值)、Warm Up、匀速排队。不支持按调用关系限流。
对资源定义流控规则后:
Sentinel会根据这些规则对流量相关的各项指标进行监控,当这些指标当达到或超过流控规则规定的阈值时,Sentinel会对请求的流量进行限制(即“限流”),以避免系统被瞬时的流量高峰冲垮,保障系统的高可用性。
触发限流时,资源会抛出BlockException异常,可以捕捉该异常来自定义被限流后的处理逻辑。
·同一个资源可以创建多条流控规则,Sentinel会遍历这些规则,直到有规则触发限流或者所有规则遍历完毕为止。
熔断降级规则
除了流量控制以外,对调用链路中不稳定资源的熔断降级,也是保障服务高可用的重要措施之一。
在分布式微服务架构中,一个系统往往由多个服务组成,不同服务之间相互调用,组成复杂的调用链路。如果链路上的某一个服务出现故障,那么故障就会沿着调用链路在系统中蔓延,最终导致整个系统瘫痪。Sentinel 提供了熔断降级机制就可以解决这个问题。Sentinel 的熔断将机制会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),暂时切断对这个资源的调用,以避免局部不稳定因素导致整个系统的雪崩。
熔断降级作为服务保护自身的手段,通常在客户端(调用端)进行配置,资源被熔断降级最直接的表现就是抛出 DegradeException 异常。
熔断规则
1. 资源名(熔断规则的作用对象)
2. 熔断策略(慢调用比例---默认、异常比例、异常数策略)
3. 最大RT(请求的最大相应时间,请求的响应时间大于该值则统计为慢调用)
仅限熔断策略为慢调用比例。
4. 熔断时长(熔断开启状态持续的时间,超过该时间熔断器会切换为探测恢复状态HALF-OPEN,单位为s)
5. 最小请求数(熔断触发的最小请求数,默认值:5,请求数小于该值时即使异常比率超出阈值也不会熔断)1.7.0 引入。
6. 统计时长(熔断触发需要统计的时长,默认值:1000ms,单位为ms)1.8.0 引入
7. 比例阈值(分为慢调用比例阈值和异常比例阈值,取值范围[0.0,1.0],即慢调用或异常调用占所有请求的百分比)
仅限熔断策略为:慢调用比例 、异常比例。
8. 异常数(请求或调用发生的异常的数量)
仅限熔断策略为:异常数
Sentinel熔断策略(3种)
1. 慢调用比例(SLOW_REQUEST_RATIO)
选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大响应时间),若请求的响应时间大于该值则统计为慢调用。
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则再次被熔断。
2. 异常比例(ERROR_RATIO)
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目且异常的比例大于阈值,则在接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
3. 异常数(ERROR_COUNT)
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
Sentinel熔断状态(3种)
1. 熔断关闭状态(CLOSED)
处于关闭状态时,请求可以正常调用资源。
满足以下任意条件,Sentinel熔断器进入熔断关闭状态:
1. 全部请求访问成功。
2. 单位统计时长(statIntervalMs)内请求数目小于设置的最小请求数目。
3. 未达到熔断标准,例如服务超时比例、异常数、异常比例未达到阈值。
4. 处于探测恢复状态时,下一个请求访问成功。
2. 熔断开启状态(OPEN)
处于熔断开启状态时,熔断器会一定的时间(规定的熔断时长)内,暂时切断所有请求对该资源的调用,并调用相应的降级逻辑使请求快速失败避免系统崩溃。
满足以下任意条件,Sentinel 熔断器进入熔断开启状态:
1. 单位统计时长内请求数目大于设置的最小请求数目,且已达到熔断标准,例如请求超时比例、异常数、异常比例达到阈值。
2. 处于探测恢复状态时,下一个请求访问失败。
3. 探测恢复状态(HALF-OPEN)
处于探测恢复状态时,Sentinel 熔断器会允许一个请求调用资源。则若接下来的一个请求成功完成(没有错误)则结束熔断,熔断器进入熔断关闭(CLOSED)状态;否则会再次被熔断,熔断器进入熔断开启(OPEN)状态。
在熔断开启一段时间(降级窗口时间或熔断时长,单位为 s)后,Sentinel 熔断器自动会进入探测恢复状态。
Sentinel实现熔断降级过程
1. 在项目中,使用@SentinelResource注解的fallback属性可以为资源指定熔断降级逻辑(方法)。
2. 通过Sentinel控制台或代码定义熔断规则,包括熔断策略、最小请求数、阈值、熔断时长以及统计时长等。
3. 若单位统计时长(statIntervalMs)内,请求数目大于设置的最小请求数目且达到熔断标准(例如请求超时比例、异常数、异常比例达到阈值),Sentinel 熔断器进入熔断开启状态(OPEN)。
4. 处于熔断开启状态时, @SentinelResource 注解的 fallback 属性指定的降级逻辑会临时充当主业务逻辑,而原来的主逻辑则暂时不可用。当有请求访问该资源时,会直接调用降级逻辑使请求快速失败,而不会调用原来的主业务逻辑。
5. 在经过一段时间(在熔断规则中设置的熔断时长)后,熔断器会进入探测恢复状态(HALF-OPEN),此时 Sentinel 会允许一个请求对原来的主业务逻辑进行调用,并监控其调用结果。
6. 若请求调用成功,则熔断器进入熔断关闭状态(CLOSED ),服务原来的主业务逻辑恢复,否则重新进入熔断开启状态(OPEN)。
下载Sentinel控制台
1. 启动Sentinel控制台(Dashboard)
java -jar sentinel-dashboard-xxx.jar
2. 浏览器访问http://localhost:8080/,输入用户名和密码(默认都是 sentinel)
Github下载Sentinel控制台
示例
- 引入Sentinel依赖
创建spring-cloud-alibaba-sentinel-service-8401子项目
1. 修改pom.xml文件
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.csp
sentinel-datasource-nacos
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.yml配置文件(类路径resources目录下)
server:
port: 8401 #端口
spring:
application:
name: sentinel-service #服务名
cloud:
nacos:
discovery:
#Nacos服务注册中心(集群)地址
server-addr: localhost:1111
sentinel:
transport:
#配置 Sentinel dashboard 地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
3. 创建SentinelFlowLimitController.java
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class SentinelFlowLimitController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/testA")
public String testA() {
return "服务访问成功------testA";
}
@GetMapping("/testB")
public String testB() {
return "服务访问成功------testB";
}
}
4. 在主启动类上,添加@EnableDiscoveryClient注解开启Nacos服务发现功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudAlibabaSentinelService8401Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAlibabaSentinelService8401Application.class, args);
}
}
5. 依次启动NacosServer集群、 spring-cloud-alibaba-sentinel-service-8401
在浏览器中访问http://localhost:8401/testA。
访问Sentinel控制台主页,在“首页”下方新增了一个“sentinel-servcie”的菜单,而这正是 spring-cloud-alibaba-sentinel-service-8401 的服务名(spring.application.name),说明 Sentinel 已经监控到这个服务。点击“实时监控”,查看sentinel-service下各请求的实时监控数据。
- 定义资源(通过SphU、SphO、注解 定义资源)
1. 在spring-cloud-alibaba-sentinel-service-8401的SentinelFlowLimitController中,创建
testAbySphU()方法、testBbySphO()方法、testC()方法,分别定义资源:testAbySphU、testBbySphO、testCbyAnnotation。
// 通过 SphU 手动定义资源
public String testAbySphU() {
Entry entry = null;
try {
entry = SphU.entry("testAbySphU");
// 您的业务逻辑 - 开始
log.info("服务访问成功------testA:"+serverPort);
return "服务访问成功------testA:"+serverPort;
// 您的业务逻辑 - 结束
} catch (BlockException e1) {
// 流控逻辑处理 - 开始
log.info("testA 服务被限流");
return "testA 服务被限流";
// 流控逻辑处理 - 结束
} finally {
if (entry != null) {
entry.exit();
}
}
}
// 通过 SphO 手动定义资源
public String testBbySphO() {
if (SphO.entry("testBbySphO")) {
// 务必保证finally会被执行
try {
log.info("服务访问成功------testB:" + serverPort);
return "服务访问成功------testB:" + serverPort;
} finally {
SphO.exit();
}
} else {
// 资源访问阻止,被限流或被降级
// 流控逻辑处理 - 开始
log.info("testB 服务被限流");
return "testB 服务被限流";
// 流控逻辑处理 - 结束
}
}
// 通过注解定义资源
@GetMapping("/testC")
@SentinelResource(value = "testCbyAnnotation")
public String testC() {
log.info("服务访问成功------testC:" + serverPort);
return "服务访问成功------testC:" + serverPort;
}
在testA()方法中直接返回testAbySphU();、在testB()方法中直接返回testBbySphO();
2. 重启 spring-cloud-alibaba-sentinel-service-8401,
在浏览器中访问http://localhost:8401/testA、http://localhost:8401/testB、http://localhost:8401/testC,访问Sentinel控制台主页,点击sentinel-service下的“簇点链路”。
- 流量控制
===》1. 通过Sentinel控制台定义流控规则
1. 在 spring-cloud-alibaba-sentinel-service-8401 下的 SentinelFlowLimitController 中,新增
// 通过 Sentinel 控制台定义流控规则
@GetMapping("/testD")
public String testD() {
log.info("服务访问成功------testD:" + serverPort);
return "服务访问成功------testD:" + serverPort;
}
2. 重启 spring-cloud-alibaba-sentinel-service-8401,
在浏览器中访问http://localhost:8401/testD,点击 sentinel-sevice 下的“簇点链路”,点击“/testD”右侧的“+流控”按钮,在弹出的“新增流控规则”窗口中定义流控规则(单机阀值添2,其他默认),点击新增按钮,跳转到“流控规则”列表。
3. 快速连续(频率大于每秒钟 2 次)访问http://localhost:8401/testD,页面会显示Blocked by Sentinel (flow limiting),说明该服务已被限流。这种提示是Sentinel系统自动生成的,用户体验不好。
4. 在服务代码SentinelFlowLimitController中使用 @SentinelResource注解定义资源名称,并在 blockHandler属性指定一个限流函数,来自定义服务限流信息(展示给用户):
// 通过 Sentinel 控制台定义流控规则
@GetMapping("/testD")
@SentinelResource(value = "testD-resource", blockHandler = "blockHandlerTestD")
public String testD() {
log.info("服务访问成功------testD:" + serverPort);
return "服务访问成功------testD:" + serverPort;
}
// 限流之后的逻辑
public String blockHandlerTestD(BlockException exception) {
log.info(Thread.currentThread().getName() + "TestD服务访问失败! 您已被限流,请稍后重试");
return "TestD服务访问失败! 您已被限流,请稍后重试";
}
使用@SentinelResource注解的blockHandler属性时,需要注意以下事项:
1. blockHandler 函数访问范围需要是 public;
2. 返回类型需要与原方法相匹配;
3. 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;
4. blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandler 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
5. 请务必添加 blockHandler 属性来指定自定义的限流处理方法,若不指定,则会跳转到错误页(用户体验不好)。
5. 重启 spring-cloud-alibaba-sentinel-service-8401,在浏览器中访问Sentinel控制台主页,点击 sentinel-sevice 下的“簇点链路,点击资源“testD-resource”右侧的“+流控”按钮,并在弹出的“新增流控规则”窗口中为这个资源定义流控规则,流控规则内容为 :QPS 的阈值为 2(即每秒最多通过 2 个请求)
6. 快速连续(频率大于每秒钟 2 次)访问http://localhost:8401/testD,结果如下:
TestD服务访问失败! 您已被限流,请稍后重试
===》2. 通过代码定义流控规则
在SentinelFlowLimitController中,首先通过FlowRule的以下属性来定义流控规则,然后调用FlowRuleManager类的loadRules(List)方法。
1. resource(资源名,即流控规则的作用对象)
2. count(限流的阈值)
3. grade(流控阈值的类型:QPS---默认值、并发线程数)
4. limitApp(流控针对的调用来源。默认值:default,表示不区分调用来源)
5. strategy(调用关系限流策略:直接--默认值、链路、关联)
6. controlBehavior(流控效果:直接拒绝--默认值、Warm Up、匀速排队)不支持按调用关系限流。
1. SentinelFlowLimitController 中添加一个 initFlowRules() 方法,为名为 testD-resource 的资源定义流控规则:每秒最多只能通过 2 个请求(即:QPS的阈值为 2)
// 通过代码定义流量控制规则
private static void initFlowRules() {
List rules = new ArrayList<>();
// 定义一个限流规则对象
FlowRule rule = new FlowRule();
// 资源名称
rule.setResource("testD-resource");
// 限流阈值的类型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置 QPS 的阈值为 2
rule.setCount(2);
rules.add(rule);
// 定义限流规则
FlowRuleManager.loadRules(rules);
}
2. 在testD()方法中调用initFlowRules()方法,初始化流控规则:
@GetMapping("/testD")
@SentinelResource(value = "testD-resource", blockHandler = "blockHandlerTestD")
public String testD() {
initFlowRules(); // 调用初始化流控规则的方法
log.info("服务访问成功------testD:" + serverPort);
return "服务访问成功------testD:" + serverPort;
}
3. 重启spring-cloud-alibaba-sentinel-service-8401,
使用浏览器访问“http://localhost:8401/testD
服务访问成功------testD:8401
快速连续(频率大于每秒钟 2 次)访问http://localhost:8401/testD
TestD服务访问失败! 您已被限流,请稍后重试
命令查看资源的实时统计信息
curl http://localhost:8719/cnode?id=testD-resource
idx id thread pass blocked success total aRt 1m-pass 1m-block 1m-all exception
说明:
1. thread: 代表当前处理该资源的并发数;
2. pass: 代表一秒内到来到的请求;
3. blocked: 代表一秒内被流量控制的请求数量;
4. success: 代表一秒内成功处理完的请求;
5. total: 代表到一秒内到来的请求以及被阻止的请求总和;
6. RT: 代表一秒内该资源的平均响应时间;
7. 1m-pass: 则是一分钟内到来的请求;
8. 1m-block: 则是一分钟内被阻止的请求;
9. 1m-all: 则是一分钟内到来的请求和被阻止的请求的总和;
10. exception: 则是一秒内业务本身异常的总和。
- 熔断降级
===》1. 通过Sentinel控制台定义熔断降级规则
创建spring-cloud-alibaba-api子项目
1. 修改pom.xml文件
org.projectlombok
lombok
true
2. 创建User.java
package com.sst.cx.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor // 无参构造函数
@Data // 提供类的get、set、equals、hashCode、canEqual、toString 方法
@Accessors(chain = true)
public class User implements Serializable {
private Integer id;
private String name;
private String password;
}
3. 创建CommonResult.java
package com.sst.cx.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
创建spring-cloud-alibaba-provider-mysql-8003子项目
1. 修改pom.xml文件
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
net.biancheng.c
spring-cloud-alibaba-api
${project.version}
junit
junit
mysql
mysql-connector-java
ch.qos.logback
logback-core
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.0
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.yml配置文件(类路径resources目录下)
server:
port: 8003 #端口
spring:
application:
name: spring-cloud-alibaba-provider-mysql
cloud:
nacos:
discovery:
server-addr: localhost:1111
####### 数据库连接 #######
datasource:
username: root #数据库登陆用户名
password: 12345678 #数据库登陆密码
url: jdbc:mysql://127.0.0.1:3306/test #数据库url
driver-class-name: com.mysql.cj.jdbc.Driver
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
###### MyBatis 配置 #######
mybatis:
# 指定 mapper.xml 的位置
mapper-locations: classpath:mybatis/mapper/*.xml
#扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
type-aliases-package: com.sst.cx.domain
configuration:
#默认开启驼峰命名法,可以不用设置该属性
map-underscore-to-camel-case: true
3. 创建UserMapper.java
package com.sst.cx.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.sst.cx.domain.User;
@Mapper
public interface UserMapper {
// 根据主键获取数据
User selectByPrimaryKey(Integer id);
// 获取表中的全部数据
List getAll();
}
4. 创建UserMapper.xml(resources/mybatis/mapper/目录下)
id, name, password
7. 创建UserService.java
package com.sst.cx.service;
import java.util.List;
import com.sst.cx.domain.User;
public interface UserService {
User get(Integer id);
List selectAll();
}
5. 创建UserServiceImpl.java
package com.sst.cx.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sst.cx.domain.User;
import com.sst.cx.mapper.UserMapper;
import com.sst.cx.service.UserService;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User get(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
@Override
public List selectAll() {
return userMapper.getAll();
}
}
6. 创建UserController.java
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.sst.cx.domain.*;
import com.sst.cx.service.UserService;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/user/get/{id}", method = RequestMethod.GET)
public CommonResult get(@PathVariable("id") int id) {
log.info("端口:" + serverPort + "\t+ user/get/");
try {
TimeUnit.SECONDS.sleep(1);
log.info("休眠 1秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = userService.get(id);
CommonResult result = new CommonResult(200, "from mysql,serverPort: " + serverPort, user);
return result;
}
@RequestMapping(value = "/user/list", method = RequestMethod.GET)
public CommonResult> list() {
log.info("端口:" + serverPort + "\t+ user/list/");
List userList = userService.selectAll();
CommonResult> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, userList);
return result;
}
}
7. 在主启动类上,添加@EnableDiscoveryClient注解开启Nacos服务发现功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudAlibabaProviderMysql8003Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAlibabaProviderMysql8003Application.class, args);
}
}
创建spring-cloud-alibaba-consumer-mysql-8803子项目
1. 修改pom.xml文件
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-loadbalancer
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.sst.cx
spring-cloud-alibaba-api
${project.version}
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.yml配置文件(类路径resources目录下)
server:
port: 8803
spring:
application:
name: spring-cloud-alibaba-consumer-mysql-feign
cloud:
nacos:
discovery:
server-addr: localhost:1111
sentinel:
transport:
dashboard: localhost:8080
port: 8719
# 以下配置信息并不是默认配置,而是我们自定义的配置,目的是不在 Controller 内硬编码 服务提供者的服务名
service-url:
nacos-user-service: http://spring-cloud-alibaba-provider-mysql #消费者要方位的微服务名称
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
3. 创建UserFeignService.java
package com.sst.cx.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.sst.cx.domain.*;
import java.util.List;
@Component
@FeignClient(value = "spring-cloud-alibaba-provider-mysql", fallback = UserFallbackService.class)
public interface UserFeignService {
@RequestMapping(value = "/user/get/{id}", method = RequestMethod.GET)
public CommonResult get(@PathVariable("id") int id);
@RequestMapping(value = "/user/list", method = RequestMethod.GET)
public CommonResult> list();
}
4. 创建UserFallbackService.java
package com.sst.cx.service;
import java.util.List;
import org.springframework.stereotype.Component;
import com.sst.cx.domain.CommonResult;
import com.sst.cx.domain.User;
@Component
public class UserFallbackService implements UserFeignService{
@Override
public CommonResult get(int id) {
System.err.println("--------->>>>服务降级逻辑");
User user = new User(id, "null", "null");
return new CommonResult(444, "服务被降级" , user);
}
@Override
public CommonResult> list() {
System.err.println("--------->>>>服务降级逻辑");
return null;
}
}
5. 创建UserFeignController.java
package com.sst.cx.service.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.util.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import com.sst.cx.domain.*;
import com.sst.cx.service.UserFeignService;
@RestController
@Slf4j
public class UserFeignController {
@Resource
UserFeignService userFeignService;
// 使用@SentinelResource注解的fallback属性指定了一个fallback函数,进行熔断降级的后续处理。
@RequestMapping(value = "consumer/feign/user/get/{id}", method = RequestMethod.GET)
@SentinelResource(value = "fallback", fallback = "handlerFallback")
public CommonResult get(@PathVariable("id") int id) {
monitor();
System.out.println("--------->>>>主业务逻辑");
CommonResult result = userFeignService.get(id);
if (id == 6) {
System.err.println("--------->>>>主业务逻辑,抛出非法参数异常");
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
} else if (result.getData() == null) {
System.err.println("--------->>>>主业务逻辑,抛出空指针异常");
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
@RequestMapping(value = "consumer/feign/user/list", method = RequestMethod.GET)
public CommonResult> list() {
return userFeignService.list();
}
// 处理异常的回退方法(服务降级)
public CommonResult handlerFallback(@PathVariable int id, Throwable e) {
System.err.println("--------->>>>服务降级逻辑");
User user = new User(id, "null", "null");
return new CommonResult(444, "服务被降级!异常信息为:" + e.getMessage(), user);
}
// 自定义事件监听器,监听熔断器状态转换
public void monitor() {
EventObserverRegistry.getInstance().addStateChangeObserver("logging",
(prevState, newState, rule, snapshotValue) -> {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (newState == CircuitBreaker.State.OPEN) {
// 变换至 OPEN state 时会携带触发时的值
System.err.println(String.format("%s -> OPEN at %s, 发送请求次数=%.2f", prevState.name(),
format.format(new Date(TimeUtil.currentTimeMillis())), snapshotValue));
} else {
System.err.println(String.format("%s -> %s at %s", prevState.name(), newState.name(),
format.format(new Date(TimeUtil.currentTimeMillis()))));
}
});
}
}
5. 在主启动类上,添加@EnableDiscoveryClient注解开启Nacos服务发现功能、添加@EnableFeignClients注解开启。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class SpringCloudAlibabaConsumerMysql8803Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudAlibabaConsumerMysql8803Application.class, args);
}
}
6. 依次启动 NacosServer集群、Sentinel控制台、spring-cloud-alibaba-provider-mysql-8003 和 spring-cloud-alibaba-consumer-mysql-8803,
在浏览器中访问http://localhost:8803/consumer/feign/user/get/1,结果如下
{"code":200,"message":"from mysql,serverPort: 8003","data":{"id":1,"name":"zhansan","password":"123456"}}
在浏览器访问“http://localhost:8803/consumer/feign/user/get/7”,结果如下
{"code":444,"message":"服务被降级!异常信息为:NullPointerException,该ID没有对应记录,空指针异常","data":{"id":7,"name":"null","password":"null"}}
7. 访问Sentinel控制台,在“簇点链路”列表中,点击fallback资源的“+降级”按钮,选择异常数,并设置异常数为1,熔断时常(时间窗口)为10s。
当熔断器处于熔断开启状态时,所有的请求都直接交给降级逻辑处理。
熔断器在经历了10秒的熔断时长后,自动切换到了探测恢复状态(HALF-OPEN),并在下一个请求成功的情况下,结束了熔断开启状态,切换到了熔断关闭状态(CLOSED)。
【存疑:无法设置最小请求数、统计时长,1.70版本没有这两选项,未进入熔断状态】
===》2. 通过代码定义熔断规则
首先通过DegradeRule类的以下属性来定义熔断降级规则,然后调用DegradeRuleManager类(Sentinel核心库提供)的loadRules(List rules) 方法。
1. resource(资源名,即规则的作用对象)
2. grade(熔断策略,慢调用比例---默认、异常比例、异常数策略)
3. count(慢调用比例模式下为慢调用临界RT,超出该值计为慢调用;异常比例/异常数模式下为对应的阈值)
4. timeWindow(熔断时长,单位为s)
5. minRequestAmount(熔断触发的最小请求数,默认5,请求数小于该值时即使异常比率超出阈值也不会熔断)1.7.0 引入【注意该属性】
6. statIntervalMs(统计时长,单位为ms,默认1000ms)1.8.0 引入
7. slowRatioThreshold(慢调用比例阈值,仅慢调用比例模式有效)1.8.0 引入
1. 在 spring-cloud-alibaba-consumer-mysql-8803的UserFeignController 中,添加initDegradeRule方法定义熔断规则(30s内当请求数>=100且异常率大于0.7时会进入熔断状态10s),在UserFeignController的get()方法的开始处调用initDegradeRule()方法初始化熔断规则。
// 初始化熔断策略
private static void initDegradeRule() {
List rules = new ArrayList<>();
DegradeRule rule = new DegradeRule("fallback");
// 熔断策略为异常比例
rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
// 异常比例阈值
rule.setCount(0.7);
// 最小请求数
rule.setMinRequestAmount(100);
// 统计时长,单位毫秒
rule.setStatIntervalMs(30000);
// 熔断时长,单位秒
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
2. 重启spring-cloud-alibaba-consumer-mysql-8803,
在浏览器中访问http://localhost:8803/consumer/feign/user/get/1,结果如下
{"code":200,"message":"from mysql,serverPort: 8003","data":{"id":1,"name":"zhansan","password":"123456"}}
访问Sentinel控制主页,点击“降级规则”查看熔断降级规则列表。