系列文章目录
1、父工程创建
2、支付模块构建和热部署
3、消费者订单模块
4、服务注册中心-Eureka
5、zookeeper没学习
6、服务注册中心-Consul
7、Eureka、Consul异同
8、服务调用-Ribbon
9、服务调用-OpenFeign
10、服务降级-Hystrix
11、服务降级-Hystrix(二)
12、服务熔断-Hystrix
13、服务网关-Gateway
14-17 在git上做配置中心,没有学习
17、请求链路跟踪-Sleuth
18、Spring Cloud Alibaba-Nacos注册中心与配置中心
19、Spring Cloud Alibaba-Nacos集群和持久化配置
20、Sentinel流控
21、Sentinel熔断降级、热点key限流
22、SentinelResource配置
23、Sentinel 服务熔断与持久化
1. 环境准备
1.1 新建服务提供者模块
新建cloudalibaba-provider-payment9003、cloudalibaba-provider-payment9004,两者相同
1.1.1 POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.chzu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-provider-payment9003</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- commons -->
<dependency>
<groupId>com.chzu</groupId>
<artifactId>cloud-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
1.1.2 YML
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
1.1.3 主启动类
/**
* @author lwd
*/
@SpringBootApplication
@EnableDiscoveryClient
public class NacosPaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(NacosPaymentMain9003.class,args);
}
}
1.1.4 业务类
Controller
/**
* @author lwd
*/
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
//模拟数据库
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static
{
hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, payment);
return result;
}
}
1.2 新建消费者模块
新建cloudalibaba-consumer-nacos-order84
1.2.1 POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.chzu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-consumer-nacos-order84</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- commons -->
<dependency>
<groupId>com.chzu</groupId>
<artifactId>cloud-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
1.2.2 YML
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
1.2.3主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class NacosOrderMain84 {
public static void main(String[] args) {
SpringApplication.run(NacosOrderMain84.class,args);
}
}
1.2.4 业务类
ApplicationContextConfig
@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
OrderController
@RestController
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")//没有配置
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
}
2. @SentinelResource属性配置
2.1 无配置
@RestController
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")//没有配置
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
}
@SentinelResource(value = “fallback”)没有任何配置
访问
http://localhost:84/consumer/fallback/4
http://localhost:84/consumer/fallback/5
两个都是错误的访问,因为没有数据。
当用户访问错误时,返回给给用户Error Page,不友好。
2.2 只配置fallback
更改cloudalibaba-consumer-nacos-order84 的Controller
@RestController
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback")//没有配置
@SentinelResource(value = "fallback", fallback = "handlerFallback") //fallback只负责业务异常
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}
}
@SentinelResource(value = “fallback”, fallback = “handlerFallback”) //fallback只负责业务异常
fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致; 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- fallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的
- Class对象,注意对应的函数必需为 static 函数,否则无法解析。link
访问
http://localhost:84/consumer/fallback/4
返回
{"code":444,"message":"兜底异常handlerFallback,exception内容 IllegalArgumentException,非法参数异常....","data":{"id":4,"serial":"null"}}
http://localhost:84/consumer/fallback/5
返回
{"code":444,"message":"兜底异常handlerFallback,exception内容 NullPointerException,该ID没有对应记录,空指针异常","data":{"id":5,"serial":"null"}}
至少没有了Error Page页面,舒服多了。
2.3 只配置blockHandler
blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException的函数名称,可选项。blockHandler 函数访问范围需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。link
@RestController
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
@SentinelResource(value = “fallback”,blockHandler = “blockHandler”)
它处理的是当请求触发了在Sentinel页面中设置的条件,做出的发生反应。比如,设置当连续的错误访问,返回“445 blockHandler-sentinel限流,无此流水 …”
它不处理运行时异常,访问http://localhost:84/consumer/fallback/5 会返回Error Page。
2.4 配置fallback和blockHandler
@RestController
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback") //没有配置
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//本例是fallback
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}
//本例是blockHandler
public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
@SentinelResource(value = “fallback”,fallback = “handlerFallback”,blockHandler = “blockHandler”)
fallback = “”:当java报出的异常时的处理逻辑
blockHandler = “”:当请求满足Sentinel设置条件时的处理逻辑
测试:
http://localhost:84/consumer/fallback/1
- 访问,返回正常
{"code":200,"message":"from mysql,serverPort: 9003","data":{"id":1,"serial":"28a8c1e3bc2742d8848569891fb42181"}}
- 快速点击触发限流,blockHandler触发
{"code":445,"message":"blockHandler-sentinel限流,无此流水: blockException null","data":{"id":1,"serial":"null"}}
http://localhost:84/consumer/fallback/4
- 访问,fallback 触发
{"code":444,"message":"兜底异常handlerFallback,exception内容 IllegalArgumentException,非法参数异常....","data":{"id":4,"serial":"null"}}
- 快速点击触发限流,blockHandler触发
{"code":445,"message":"blockHandler-sentinel限流,无此流水: blockException null","data":{"id":4,"serial":"null"}}
2.5 配置exceptionsToIgnore
exceptionsToIgnore,忽略指定异常,即这些异常不用兜底方法处理。
....
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
if (id == 4) {
//exceptionsToIgnore属性有IllegalArgumentException.class,
//所以IllegalArgumentException不会跳入指定的兜底程序。
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
...
exceptionsToIgnore = {IllegalArgumentException.class}) 忽略非法参数异常
访问 http://localhost:84/consumer/fallback/4 报出Error Page
访问 http://localhost:84/consumer/fallback/5
{"code":444,"message":"兜底异常handlerFallback,exception内容 NullPointerException,该ID没有对应记录,空指针异常","data":{"id":5,"serial":"null"}}
3. 服务熔断
sentinel整合ribbon+openFeign+fallback
3.1 修改模块
修改cloudalibaba-consumer-nacos-order84模块
3.1.1 POM
添加一个maven依赖
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3.1.2 YML
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
3.1.3 主启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class NacosOrderMain84 {
public static void main(String[] args) {
SpringApplication.run(NacosOrderMain84.class,args);
}
}
3.1.4 业务类
新建PaymentService接口
@Component
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallBackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
新建PaymentFallBackService类,处理降级逻辑
@Component
public class PaymentFallBackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服务降级返回");
}
}
Controller
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
return paymentService.paymentSQL(id);
}
测试:
访问 http://localhost:84/consumer/paymentSQL/1 返回
{"code":200,"message":"from mysql,serverPort: 9004","data":{"id":1,"serial":"28a8c1e3bc2742d8848569891fb42181"}}
此时将服务9003、9004停止,再次访问,84消费侧自动降级,不会被耗死
{"code":444,"message":"服务降级返回","data":null}
4. 熔断框架比较
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔商/信号量隔离 |
熔断降级策略 | 满调用比例、异常比例、异常数 | 基于异常比例 |
动态规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持预热模式匀速器模式、预热排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等 | 简单的监控查看 |
5. Sentinel持久化规则
5.1 有什么作用
一旦我们重启应用,sentinel配置的规则就会消失
在生产环境需要将配置规则进行持久化
5.2 怎么做
将限流配置规则持久化进Nacos保存,我们只要访问8401某个rest地址,在sentinel页面就可以看到
而且只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
5.2.1 修改模块
修改cloudalibaba-sentinel-service8401
5.2.2 POM
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
5.2.3 YML
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
#=============== 添加Nacos数据源配置
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
5.2.4
配置内容解析:
[{
"resource": "fallback",
"IimitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}]
- resource:资源名称;
- limitApp:来源应用;
- grade:阈值类型,0表示线程数, 1表示QPS;
- count:单机阈值;
- strategy:流控模式,0表示直接,1表示关联,2表示链路;
- controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
- clusterMode:是否集群;
刷新sentinelWeb页面发现业务规则
当重启cloudalibaba-consumer-nacos-order84时,发现Sentinel可能没有出现持久化的内容,不要慌,多访问几遍接口,就可以了。