Sentinel介绍与使用
一、什么是Sentinel
Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。
Sentinel 是面向微服务的轻量级流量控制框架,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel可以作为Hystrix的替代品,为系统提供服务熔断和服务降级的功能。
- 服务熔断:根据保险丝的熔断是一个原理,当调用目标服务大量超时和失败,这时候应该熔断掉该服务的调用,从而快速释放资源,这段时间所有对其调用都是快速返回,保证整体服务系统的稳定
- 服务降级:针对核心业务服务的压力剧增,根据当前业务场景和流量对其他非核心服务进行降级处理,可以进行限流,快速返回等处理,释放资源保证核心任务的正常运行。
官网:https://github.com/alibaba/Sentinel/wiki
二、代码示例
下面跟随官网中提供的Quick Start Demo,去看看如何使用Sentinel实现限流和降级。
本文代码见:github代码
注意:
- JDK >= 1.7;
- Sentinel版本为1.7.0;
引入Sentinel jar包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.0</version>
</dependency>
1、限流
关于限流的使用和介绍见:Sentinel流量控制
流量控制(Flow Control),原理是监控应用流量的QPS或并发线程数等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用高可用性。
下面写个简单的示例,看看如何使用Sentinel实现限流。
Sentinel的限流原理
- Sentinel以Bucket(桶)为单位记录一个时间窗口内的请求总数、异常总数、总耗时等指标数据。
- 而一个Bucket可以是记录一秒内的数据,也可以是10毫秒内的数据,我们称这个时间窗口为Bucket的统计单位,由使用者自定义。
所以Sentinel是基于滑动窗口算法来实现的。
设置Sentinel的阈值指标:
线程数模式
线程数的模式采用信号隔离的方式来防止线程池被占用。
用于防止线程池被占用,一般有两种方式:
- 线程池隔离:为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢,这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。
- 信号隔离:sentinel采用的是信号隔离的方案,简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝。
所以业务处理是多线程的情况下使用线程数模式。
Sentinel采用信号隔离的方式,通过并发线程数模式,并结合基于响应时间的熔断降级模式,可以在不稳定的平均相应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统,避免慢调用引起依赖雪崩的现象。
QPS模式
QPS即每秒查询率,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
QPS模式适合单读线程情况(如servlet请求),这种模式下提供了三种更加精确的流控方式:
- 直接拒绝 :直接失败
- Warm Up: 即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值,通常用于秒杀系统。
- 匀速排队:设置一个等待时间, 匀速处理请求,保证服务的均匀性,不能处理QPS>1000的场景。
Sentinel流控模式
- 直接模式: 接口达到限流条件时,开启限流
- 关联模式: 当关联的资源达到限流条件时,开启限流
- 链路模式:当从某个接口过来的资源达到限流条件时,开启限流
基于调用关系的流量控制
ContextUtil.enter(resourceName, origin) 方法中的origin 参数标明了调用身份。这些信息会在ClusterBuilderSlot 中统计。
流量规则中的limitApp 字段用于根据调用来源进行流量控制。该字段的值有以下三种选择,分别对应不同的场景:
- default :表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则出发限流。
- {some_origin_name} : 表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如NodeA 配置了一条针对调用者caller1 的规则,那么当且仅当来自caller1 对 NodeA 的请求才会触发流量控制。
- other :表示针对除{some_origin_name} 以外的其余调用方的流量进行流量控制。例如:资源NodeA 配置了一条针对调用者caller1 的限流规则,同时又配置了一条调用者为other 的规则,那么任意来自非caller1 对NodeA 的调用,都不能超过other这条规则定义的阈值。
首先写个简单的订单查询接口,用于后续接口限流示例:
@Component
public class OrderQueryService {
public String queryOrderInfo(String orderId) {
System.out.println("获取订单信息:" + orderId);
return "return OrderInfo :" + orderId;
}
}
@Controller
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderQueryService orderQueryService;
@RequestMapping("/getOrder")
@ResponseBody
public String queryOrder1(@RequestParam("orderId") String orderId) {
return orderQueryService.queryOrderInfo(orderId);
}
}
正常情况下,调用OrderController中订单查询接口,会返回订单信息,如何控制接口访问的QPS在2以下呢?Sentienl限流提供了以下实现方式:
Sentienl如何使用 。
首先需要定义限流规则,比如对哪个接口进行限流,限制的QPS为多少,限制调用方app是什么等:
//初始化一个qps的限流规则
@PostConstruct
public void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
// QPS控制在2以内
rule1.setCount(2);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
//流控针对的调用来源,若为 default 则不区分调用来源
rule1.setLimitApp("default");
//采用默认快速失败/直接拒绝策略
rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
// 表示每一个请求的最长等待时间20s 匀速排队 下使用
//rule1.setMaxQueueingTimeMs(20 * 1000);
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
限流实现方式有多种,本文只列出常见两种:
1)限流实现方式一: 抛出异常的方式定义资源
此方式对代码侵入性较高,需要在接口调用的地方通过try-catch风格的API对代码进行包装:
/**
* 限流实现方式一: 抛出异常的方式定义资源
*
* @param orderId
* @return
*/
@RequestMapping("/getOrder1")
@ResponseBody
public String queryOrder2(@RequestParam("orderId") String orderId) {
Entry entry = null;
// 资源名
String resourceName = KEY;
try {
// entry可以理解成入口登记
entry = SphU.entry(resourceName);
// 被保护的逻辑, 这里为订单查询接口
return orderQueryService.queryOrderInfo(orderId);
} catch (BlockException blockException) {
// 接口被限流的时候, 会进入到这里
log.warn("---getOrder1接口被限流了---, exception: ", blockException);
return "接口限流, 返回空";
} finally {
// SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常
if (entry != null) {
entry.exit();
}
}
}
测试,当QPS > 2时,接口返回:
接口限流, 返回空
2)限流实现方式二: 注解方式定义资源
上述通过try-catch风格的API可以实现限流,但是对代码侵入性太高,推荐使用注解的方式来实现。下文若不做注明,默认都会采用注解的方式实现。
关于注解的使用见:Sentinel注解使用
首先需要引入支持注解的jar包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>${sentinel.version}</version>
</dependency>
Sentinel切面类配置:
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
在接口OrderQueryService中,使用注解实现订单查询接口的限流:
/**
* 订单查询接口, 使用Sentinel注解实现限流
*
* @param orderId
* @return
*/
@SentinelResource(value = "getOrderInfo", blockHandler = "handleFlowQpsException",
fallback = "queryOrderInfo2Fallback")
public String queryOrderInfo2(String orderId) {
// 模拟接口运行时抛出代码异常
if ("000".equals(orderId)) {
throw new RuntimeException();
}
System.out.println("获取订单信息:" + orderId);
return "return OrderInfo :" + orderId;
}
/**
* 订单查询接口抛出限流或降级时的处理逻辑
*
* 注意: 方法参数、返回值要与原函数保持一致
* @return
*/
public String handleFlowQpsException(String orderId, BlockException e) {
e.printStackTrace();
return "handleFlowQpsException for queryOrderInfo2: " + orderId;
}
/**
* 订单查询接口运行时抛出的异常提供fallback处理
*
* 注意: 方法参数、返回值要与原函数保持一致
* @return
*/
public String queryOrderInfo2Fallback(String orderId, Throwable e) {
return "fallback queryOrderInfo2: " + orderId;
}
- blockHandler = "handleFlowQpsException"用来处理Sentinel 限流/熔断等错误;
- fallback = "queryOrderInfo2Fallback"用来处理接口中业务代码所有异常(如业务代码异常、sentinel限流熔断异常等);
注:以上两种处理方法中方法名、参数都需与受保护的函数保持一致。
测试 –
2、熔断降级
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。
由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
@Component
@Slf4j
public class GoodsQueryService {
private static final String KEY = "queryGoodsInfo2";
/**
* 模拟商品查询接口
*
* @param spuId
* @return
*/
@SentinelResource(value = KEY, blockHandler = "blockHandlerMethod", fallback = "queryGoodsInfoFallback")
public String queryGoodsInfo(String spuId) {
// 模拟调用服务出现异常
if ("0".equals(spuId)) {
throw new RuntimeException();
}
return "query goodsinfo success, " + spuId;
}
public String blockHandlerMethod(String spuId, BlockException e) {
log.warn("queryGoodsInfo222 blockHandler", e.toString());
return "queryGoodsInfo error, blockHandlerMethod res: " + spuId;
}
public String queryGoodsInfoFallback(String spuId, Throwable e) {
log.warn("queryGoodsInfo222 fallback", e.toString());
return "queryGoodsInfo error, return fallback res: " + spuId;
}
@PostConstruct
public void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// 80s内调用接口出现异常次数超过5的时候, 进行熔断
rule.setCount(5);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setTimeWindow(80);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
// 测试类
@Controller
@RequestMapping("goods")
public class GoodsController {
@Autowired
private GoodsQueryService goodsQueryService;
@RequestMapping("/queryGoodsInfo")
@ResponseBody
public String queryGoodsInfo(@RequestParam("spuId") String spuId) {
String res = goodsQueryService.queryGoodsInfo(spuId);
return res;
}
}
三、控制台的使用
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
主要功能有:
- 查看机器列表以及健康情况;
- 监控;
- 规则管理和推送;
- 鉴权;
1、启动Sentinel控制台
启动控制台
2、客户端接入(Spring Boot项目接入控制台)
启动了控制台模块后,控制台页面都是空的,需要接入客户端。
(1)首先导入与控制台通信的jar包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${sentinel.version}</version>
</dependency>
(2)配置 JVM 启动参数:
-Dproject.name=sentinel-demo -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 -Dcsp.sentinel.api.port=8719
启动应用。
Sentinel的限流原理
四、springcloud sentinel整合
1、添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
注意 之前添加的sentinel 的依赖不要添加了,注释他们
2、修改配置
#sentinel的控制台地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
#sentinel开启饿汉模式
spring.cloud.sentinel.eager=true
#sentinel与我们服务之间交互的端口
spring.cloud.sentinel.transport.port=8719
#开启resttemplate对sentinel的支持
resttemplate.sentinel.enabled=true
#feign开启OKhttp
feign.okhttp.enabled=true
#开启feign对sentinel的支持
feign.sentinel.enabled=true
fegin中使用sentinel
//value:服务名 fallback发生错误返回的处理对象
@FeignClient(value = "shop-user-producer-api",fallback = UserServiceFallback.class)
public interface UserService {
//请求该服务下的这个接口
@PostMapping("/user/byName")
HashMap byName(String name);
}
@Component
public class UserServiceFallback implements UserService {
@Override
public HashMap byName(String name) {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("name","fallback");
return hashMap;
}
}