sentinel组件学习
sentinel学习
- 服务雪崩
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程。
sentinel容错机制
常见的容错机制有超时机制、服务限流、隔离、服务熔断
- 超时机制
在不做任何处理的情况下,服务提供者不可用回导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源较快,一定程度上可以抑制资源耗尽的问题。 - 服务限流
某个服务达到QPS设定最大值则抛异常。 - 服务熔断
- 服务降级
有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fellback(回退),回调,返回一个缺省值。例如:(备用接口/缓存/mock数据),这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。(服务熔断之后进入客户端降级方法)
使用代码方式进行QPS流控-流控规则初体验
- 引入sentinel相关包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--sentinel核心库-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--sentinel注解方式-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--sentinel整合dashbaord控制台-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies>
- 代码实现
package com.sentinel.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* @author : zhouzhiqiang
* @date : 2024/6/5 23:25
* @description :
*/
@Slf4j
@RestController
@RequestMapping("/hello")
public class HelloController {
private static final String RESOURCE_NAME="hello";
private static final String USER_RESOURCE_NAME="user";
private static final String DEGRADE_RESOURCE_NAME="degrade";
@RequestMapping("/helloSentinel")
public String hello(){
Entry entry=null;
try {
entry= SphU.entry(RESOURCE_NAME);
String str="hello world";
log.info("======="+str+"=======");
return str;
} catch (BlockException e) {
log.info("block");
return "被限流了!";
}catch (Exception e){
Tracer.traceEntry(e,entry);
}finally {
if (entry != null) {
entry.exit();
}
}
return null;
}
@PostConstruct
public static void initFlowRules(){
//流控规则
List<FlowRule> rules=new ArrayList<>();
//流控
FlowRule rule = new FlowRule();
//为哪个资源进行流量控制
rule.setResource(RESOURCE_NAME);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rules.add(rule);
//加载流控规则
FlowRuleManager.loadRules(rules);
}
}
使用@SentinelResource注解进行流控
要使用这个注解需要引入相关包和配置SentinelResourceAspect的bean
- 引入包
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
- 配置bean
@Bean
public SentinelResourceAspect sentinelResourceAspect(){
return new SentinelResourceAspect();
}
- 使用注解方式
value:定义资源(接口名称)
blockHandler :设置流控降级后的处理方法,默认该方法,必须声明在一个类中
fallback:接口中出现异常了,就可以交给fellback指定的处理方法
@PostConstruct
public static void initFlowRules(){
//流控规则
List<FlowRule> rules=new ArrayList<>();
//流控
FlowRule rule = new FlowRule();
//为哪个资源进行流量控制
rule.setResource(USER_RESOURCE_NAME);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rules.add(rule);
//加载流控规则
FlowRuleManager.loadRules(rules);
}
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME,blockHandler = "blockHandlerForGetUser",fallback = )
public User getUser(String id){
return new User("zzq");
}
/**
* 注意:
* 1、一定是public
* 2、返回值一定得和原方法返回值一致,包含方法的参数
* 3、可以在参数的最后添加BlockException,可以区分是什么规则的处理方法
* @param id
* @param e
* @return
*/
//降级方法
public User blockHandlerForGetUser(String id,BlockException e){
e.printStackTrace();
return new User("被流控了!");
}
通过代码方式设置降级规则-降级规则初体验
@RestController
@RequestMapping("/test")
public class TestController {
private static final String DEGRADE_RESOURCE_NAME="degrade";
@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME,entryType = EntryType.IN,blockHandler = "blockHandlerForJob")
public User degrade(String id){
int i=1;
int sum=i/0;
return new User("成功");
}
public User blockHandlerForJob(String id, BlockException e){
e.printStackTrace();
return new User("触发降级规则");
}
@PostConstruct
public void initDegradeRule(){
List<DegradeRule> rules=new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(DEGRADE_RESOURCE_NAME);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
//触发熔断异常数
rule.setCount(2);
//触发熔断的最小请求数
rule.setMinRequestAmount(2);
//统计时长:单位ms 1分钟
rule.setStatIntervalMs(60*1000);
//一分钟内:执行了两次 ,出现两次异常 就会触发熔断
/**
* 熔断持续时间,单位秒。一旦触发了熔断,再次请求接口则直接调用降级方法。
* 10秒后,---半开状态,恢复接口调用。如果再次请求,则会熔断,不再根据熔断规则进入熔断,而是直接进入熔断
*/
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
流控规则一般设置在服务生产方,而降级规则一般设置在服务消费方
sentinel控制台部署
下载dashboard--https://github.com/alibaba/Sentinel/releases
运行jar包:java -jar sentinel-dashboard-1.8.0.jar。运行完毕进行访问,默认端口号8080。用户名和密码默认sentinel
客户端整合服务端
客户端需要引入Transport模块来与sentinel控制台进行通信
<!--sentinel整合dashbaord控制台-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
- 配置启动
客户端启动时需要加入JVM参数-Dcsp.sentinel.dashboard.server=192.168.1.15:8080,指定控制台地址和端口
springcloud整合sentinel
- 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- yaml配置文件
server:
port: 8081
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 192.168.184.15:8080
port: 8719
client-ip: 192.168.184.1
在虚拟机上部署dashboard的时候,主机和虚拟机一直网络不通,解决方法:
1、关闭本机和虚拟机的防火墙
2、本机不动,配置虚拟机ip,网段和本地vmnet8保持一致,网关查看虚拟网络编辑器
ONBOOT="yes"
IPADDR="192.168.184.15"
NETMASK="255.255.255.0"
GATEWAY="192.168.184.2"
DNS1="8.8.8.8"
DNS2="8.8.4.4"
3、最重要的一步,设置网络连接模式为net模式
原因:解释: 1:当我们安装VMware Workstation后,在宿主机(物理电脑)上会多出两个网卡,VMNet1、VMNet8。 2:vmnet1是为host-only方式服务的,vmnet8是为NAT方式服务的
QPS流控规则
添加流控规则:QPS为2,超过2触发流控降级
- 代码中指定自定义降级方法,dashboard定义流控规则,代码自定义降级方法
/**
* 流控测试接口
* @return
*/
@RequestMapping("/flow")
@SentinelResource(value = "flow",blockHandler = "blockHandlerForFlow")
public String flow(){
return "正常访问";
}
public String blockHandlerForFlow(BlockException e){
e.printStackTrace();
System.out.println("触发流控,快速失败");
return "触发流控,快速失败";
}
并发线程数-流控规则
- dashboard配置
- 代码
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "blockHandlerForFlow")
public String flowThread(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "正常访问";
}
public String blockHandlerForFlow(BlockException e){
e.printStackTrace();
System.out.println("触发流控,快速失败");
return "触发流控,快速失败";
}
- 测试
开两个浏览器模拟两个线程
- 测试结果
BlockExceptionHandler统一异常处理,此时可以不加@SentinelResource注解
- 代码
@Data
public class Result <T>{
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result error(Integer code,String msg){
return new Result(code,msg);
}
}
@Component
@Slf4j
public class MyBlockExceptionHandler implements BlockExceptionHandler {
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
//规则的详细信息
log.info("BlockExceptionHandler BlockException=============="+e.getRule());
Result r = null;
if (e instanceof FlowException) {
r=Result.error(100,"接口被限流了!");
}else if (e instanceof DegradeException){
r=Result.error(101,"服务降级了!");
}else if (e instanceof ParamFlowException){
r=Result.error(102,"热点参数限流了!");
}else if (e instanceof SystemBlockException){
r=Result.error(103,"触发系统保护规则!");
}else if (e instanceof AuthorityException){
r=Result.error(104,"授权规则不通过!");
}
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(),r);
}
}
- 测试代码
此时在dashboar的为这个接口设置流控规则后,可以看到设置的同一异常起效果了
@RequestMapping("/exceptionHandler")
public String exceptionHandler(){
return "正常访问";
}
关联流控模式
流控模式有三种直接、关联、链路
关联模式:当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写的速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢。
- dashboard设置
当生成订单QPS>=2的时候,查询订单接口就被限流
- 代码
@RequestMapping("/add")
public String add(){
System.out.println("下单成功");
return "生成订单";
}
@RequestMapping("/get")
public String get(){
return "查询订单";
}
- 生成接口和查询接口测试
生成接口和查询接口不好一起测试,生成接口使用apifox进行压测,查询接口手动点
- 测试结果
查询接口触发流控规则
- 或者使用JMeter去做压测
链路流控模式
可以对资源进行流控
- 代码
public interface IUserService {
User getUser();
}
对getUser添加@SentinelResource注解
@Service
public class IUserServiceImpl implements IUserService{
@SentinelResource(value = "getUser",blockHandler = "blockHandlerForUser")
public User getUser() {
User user = new User();
user.setId("001");
user.setName("针对业务方法进行流控");
return user;
}
public User blockHandlerForUser(BlockException e){
e.printStackTrace();
User user = new User();
user.setId("001");
user.setName("ddddd");
return new User();
}
}
test1和test2调用getUser(),当getUser(),达到QPS2的时候,对test1或者test2做流控
@RestController
@RequestMapping("/link")
public class LinkController {
@Autowired
private IUserService userService;
@RequestMapping("/test1")
public User test1(){
return userService.getUser();
}
@RequestMapping("/test2")
public User test2(){
return userService.getUser();
}
}
- 配置文件配置
web-context-unify: false # 默认将调用链路收敛了
server:
port: 8081
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 192.168.184.15:8080
port: 8719
client-ip: 192.168.184.1
web-context-unify: false # 默认将调用链路收敛了
- dashboar配置
- 测试结果
当test1qps达到2的时候触发流控
流控效果介绍
流控效果有快速失败、预热、排队等待。预热:适用于激增流量的情况
预热流控效果
warm up:预热冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过“冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
- dashboard配置
qps最大为10,10秒内达到10
排队等待
激增流量:长时间处于低水平,某个时间段处于高水平,可以使用预热的方式进行处理。
脉冲流量:一段时间处于低水平某个时刻到达高水平,然后又变为低水平,循环往复
匀速排队:严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过
- 测试
设置流控效果为快速失败,单机QPS阈值为5,使用JMeter测试,一秒执行10次循环5次
测试结果:可以看到一秒内10个QPS5个通过,5个拒绝
- 使用排队等待流控效果
dashboard配置:单机阈值5,超时时间5秒。比如有10个线程,每次进去5个,其他5个等待。执行完一个进去一个,如果5个线程在5秒内没有执行完则直接拒绝
- 测试结果
所有请求都执行成功
熔断降级规则
慢调用比例:慢调用占调用总数的比例
- dashboard配置
最大RT:单位毫秒,不能超过1秒,超过1秒则触发降级。比例阈值:慢调用占请求次数的比例。最小请求数:配合比例阈值,10次请求有一次触发慢调用则触发降级
- 使用Jmetter进行测试
- 测试结果
触发降级
sentinel-整合openfeign降级
nacos服务注册必须是一个web项目
按此步骤完成微服务搭建后,发现服务并没有注册到nacos注册中心,在官方文档查看springboot和springcloud版本也并没有冲突,尝试引入nacos管理依赖后还是不行,最后想到要在nacos注册中心注册的微服务应该必须是一个web项目,而在springboot环境下开发web项目需要引入springMvc的相关启动依赖,加上后发现服务成功注册到了nacos注册中心
- 在消费端引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 消费端配置yml
特别加上:server: port: 8084 spring: application: name: product-service cloud: nacos: username: zzq password: 1w2e3r4! server-addr: 192.168.184.15:8848 discovery: namespace: 31a14d31-cda4-4a37-a4ba-7717aaddd97d feign: sentinel: enabled: true
server:
port: 8084
spring:
application:
name: product-service
cloud:
nacos:
username: zzq
password: 1w2e3r4!
server-addr: 192.168.184.15:8848
discovery:
namespace: 31a14d31-cda4-4a37-a4ba-7717aaddd97d
feign:
sentinel:
enabled: true
消费端feing调用
- feing
@FeignClient(value = "stock-service",path = "/person",fallback = IPersonServiceFallback.class)
public interface IPersonService {
@RequestMapping("/getPerson")
public Person getPerson();
}
必须要有一个feingclint的实现类,并且注入进spring
@Component
public class IPersonServiceFallback implements IPersonService {
public Person getPerson() {
Person person = new Person();
person.setId("001");
person.setName("未找到用户");
return person;
}
}
消费端调用服务端
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private IPersonService personService;
@RequestMapping("/getProduct")
public String getProduct(){
Person person = personService.getPerson();
return "调用成功:"+person.getName();
}
}
- 测试结果
调用异常执行降级方法
热点参数流控
场景:接口参数对某些值的访问QPS比其他高。典型的电商系统中,商品查询接口,某些商品的访问QPS要远远高于其他商品,比如商品ID为1的商品,访问请求远远高于其他商品。
- dashboard配置
sentinel规则持久化
结合nacos配置中心使用推模式进行规则的持久化
基于Nacos配置中心控制台实现推送
- 引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>