Sentinel源码—1.使用演示和简介二
大纲
1.Sentinel流量治理框架简介
2.Sentinel源码编译及Demo演示
3.Dashboard功能介绍
4.流控规则使用演示
5.熔断规则使用演示
6.热点规则使用演示
7.授权规则使用演示
8.系统规则使用演示
9.集群流控使用演示
5.熔断规则使用演示
(1)案例说明熔断和降级
(2)Sentinel Dashboard中熔断规则的配置项
(3)熔断策略之慢调用比例
(4)熔断策略之异常比例和异常数
(1)案例说明熔断和降级
一.熔断
假设电商平台有多个库存服务实例,用于处理商品库存查询。有一个库存服务实例可能由于网络问题等原因而变得不稳定,为了避免该库存服务实例异常而影响整个服务,可以对该实例实施熔断。
在这种情况下,可以设定一个阈值。如果在一段时间内发现对该库存服务实例的请求有50%+是失败的,那么会触发熔断器,在一段时间内停止对该库存服务实例进行调用。这样这个异常的库存服务实例便不会影响到商品的库存查询业务了。
二.降级
假设电商平台有个商品推荐功能,它会根据用户浏览记录推荐相关商品。在大促期间,平台可能会遭受巨大流量冲击,导致服务响应时间变慢。为了避免影响核心功能,可以在高峰期间对商品推荐功能进行降级。这意味着系统只会提供基本的商品推荐,而不会使用复杂的推荐算法。这样虽然会降低用户体验,但可以确保平台的核心功能仍然可用。
通过熔断和降级机制,系统可以在节点不稳定或高负载情况下保持稳定。从而避免系统崩溃或性能下降,这是分布式系统中流量治理的重要实践。
熔断和降级往往都是结合一起使用的。针对单节点进行熔断的时候,适合单独使用熔断规则。针对所有节点进行熔断的时候,适合熔断 + 降级一起使用。
假设整个库存查询服务都出了问题,各个服务实例都触发了熔断。这时就可以考虑结合降级策略,确保系统依然能够提供基本的功能。
比如在库存查询的情况下,可选择的降级方案有:
方案一:降级到可靠介质
可以将查询库存的请求切换到查询备份数据库或其他可靠介质,这样虽然可能会降低性能,但仍然可以为用户提供基本的服务。
方案二:降级到限流方法
可以实施限流,即控制并发请求数,以避免过多的请求加重故障状况。例如实现一个每秒只允许处理一定数量的库存查询请求的方法,从而保护系统免受过载的影响。
(2)Sentinel Dashboard中熔断规则的配置项
资源名:就是通过SphU.entry("xxx")或@SentinelResource("xxx")定义的名称。
熔断策略:比如慢调用比例、异常比例、异常数。
最大RT:RT就是Response Time,即响应时间。当熔断策略为慢调用比例时会出现此选项,其他两种策略不会显示此选项,也就是当请求时间超出多少RT后会进行熔断。
比例阈值:当熔断策略配置慢调用比例时,该值为慢调用占所有请求的比例上限。当熔断策略配置异常比例时,该值为请求异常所占比例的上限。取值范围:0.0 ~ 1.0,小数其实就是百分比。比如配置0.1,则为10%,最大为1.0,也就是100%。
异常数:当熔断策略选择异常数时才会出现异常数选项,含义就是请求的异常数量。值得注意的是:Sentinel中异常降级的统计是仅针对业务异常,而Sentinel本身的异常也就是BlockException是不生效的。比如触发流控报异常了,那么是不会统计到异常数当中。
熔断时长:当达到熔断阈值后,会进入熔断状态。超出配置的熔断时长后会恢复到Half Open状态。也就是说当超出熔断时长后不会立即恢复,而是看新进入的请求是否正常。如果还是不正常,则继续熔断,反之恢复。
最小请求数:请求数目大于该值时才会根据配置的熔断策略进行降级。比如配置该值为10 ,但是请求一共才3个。那么即使比例阈值设置的100% ,熔断策略也不会生效的,因为没达到最小请求数。
统计时长:就是统计慢调用比例、异常比例、异常数时的时长。
(3)熔断策略之慢调用比例
慢调用比例,就是一个用于度量系统中慢速或延迟调用所占比例的指标。即在一定时间窗口内,慢速调用的数量与总调用数量的比例。
例如在过去的10秒内(统计时长),系统总共处理了100个调用,其中有10个调用的响应时间超过了1000毫秒(最大RT),那么慢调用比例就是10%。
下面的配置要达到的效果是:当资源testSlowCall在10秒(统计时长)内请求数达10个(最小请求数)以上,且响应时长超过1秒(最大RT)的请求数量大于1(10 * 0.1)个时进行熔断,熔断5秒后资源testSlowCall会变成Half Open状态。即5秒后的第一个请求如果没有问题则恢复正常,否则继续熔断。
同时针对上述配置会新增一个接口如下:即提供一个RT为3s的接口。接下来就可以实现慢调用比例的效果了,只需要在10s内(统计时长)请求testSlowCall资源10次(最小请求数)即可。由于第10次请求时发现RT已超1s,此时就会触发熔断并熔断时长为5s。这次请求被熔断后就不会进入主方法了,会直接返回默认的异常页。
@GetMapping("testSlowCall")
@SentinelResource(value = "testSlowCall")
public String testSlowCall() throws InterruptedException {
//业务逻辑处理
TimeUnit.SECONDS.sleep(3000);
return "ok";
}
当然,被熔断后也可以通过@SentinelResource指定执行自定义的方法。其中@SentinelResource注解的fallback属性,就能指定执行自定义方法。但该属性有很多限制,比如其方法必须和接口在同一个类中等。
@GetMapping("testSlowCall")
@SentinelResource(value = "testSlowCall", fallback = "testSlowCallFallback")
public String testSlowCall() throws InterruptedException {
// select db
TimeUnit.SECONDS.sleep(3);
return "ok";
}
public String testSlowCallFallback() {
//降级
return "fallBack";
}
(4)熔断策略之异常比例和异常数
这里的异常指业务异常,不包括Sentinel的流控异常BlockedException。
一.异常比例
下面为了测试异常比例的熔断策略创建一个接口,这个接口的资源名为testErrorRate,降级方法为testErrorRateFallback。
@GetMapping("testErrorRate")
@SentinelResource(value = "testErrorRate", fallback = "testErrorRateFallback")
public String testErrorRate(Integer id) {
if (null == id) {
throw new NullPointerException("id is null");
}
return "ok";
}
public String testErrorRateFallback(Integer id) {
//降级
return "fallBack";
}
这个接口的熔断规则如下:当10秒(统计时长)内达到10个请求(最小请求数)以上,而且请求异常比例超过20%时,就会触发熔断并熔断时长为5秒。熔断5秒后资源testErrorRate会变成Half Open状态。即5秒后的第一个请求如果没有问题则恢复正常,否则就继续熔断。
二.异常数
异常数就是在单位时间内超出错误数则触发熔断,和异常比例唯一不同的就是异常比例是计算百分比,而异常数是直接计算错误数量。
(5)总结
一.熔断策略总结
策略一:慢调用比例
衡量在一定时间内,响应时间超过阈值的调用占总调用数量的比例。比如在一分钟内,有10%的调用响应时间超过1秒。
策略二:异常比例
衡量在一定时间内,发生异常的调用占总调用数量的比例。比如在一小时内,有5%的调用发生异常。
策略三:异常数
计算在一定时间内发生的异常调用的绝对数量。例如,过去一天中,发生了100次调用异常。
二.实际应用场景总结
一个在线支付系统有一个接口用于处理支付请求:
场景一:慢调用比例
过去10分钟内有20%的支付请求的响应时间超过了3秒,超过预设的阈值。
场景二:异常比例
在过去一小时内,有2%的支付请求发生了异常,可能是由于网络问题或支付平台故障引起的。
场景三:异常数
昨天共有30次支付请求发生了异常,可能是由于无效的订单号或者连接超时导致的。
6.热点规则使用演示
(1)参数限流的意思
(2)参数限流的实战
(1)参数限流的意思
传统的流量控制,一般是通过资源维度来限制某接口或方法的调用频率。但有时需要更细粒度地控制不同参数条件下的访问速率,即参数限流。
参数限流允许根据不同的参数条件设置不同的流量控制规则,这种方式非常适合处理特定条件下的请求,能更加精细地管理流量。
场景一:假设有一个在线电影订票系统,某个接口允许用户查询电影的放映时间。但只希望每个用户每10秒只能查询接口1次,以避免过多的查询请求。这时如果直接将接口的QPS限制为5是不能满足要求的。因为需求是每个用户每5分钟只能查询1次,而不是每秒一共只能查询5次。
因此可以使用参数限流设置一个规则,根据用户ID来限制每个用户的查询频率。将限流的维度从资源维度细化到参数维度,从而实现每个用户每10秒只能查询接口1次。
场景二:一个SAAS服务/中台服务,希望能根据不同的商家/业务方来做限流规则。比如规模大的商家/业务方,允许调用的QPS是1000,而小的只能是50。
(2)参数限流的实战
下面实现每个用户每10秒只能查询一次电影票信息,首先在pom.xml添加依赖:
<!-- 可以使用热点参数限流功能 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.6</version>
</dependency>
然后新增一个接口:
@RestController
@RequestMapping("/hotKey")
public class TestHotKeyController {
@GetMapping("/testMovieTicket")
@SentinelResource(value = "testMovieTicket", fallback = "testMovieTicketFallback")
public String testMovieTicket(String userId, Integer movieId) {
return "testMovieTicketSuccess";
}
public String testMovieTicketFallback(String userId, Integer movieId) {
return "testMovieTicketFail";
}
}
接着针对该接口配置如下规则:相同参数值(第一个参数)每隔10秒只能请求一次。
新增热点规则之后,就可以对热点规则进行编辑。在编辑框里,提供了高级选项进行进一步的选择。
假如希望影院工作人员可以每秒查询10次,老板可以每秒查询100次,而购票者则只能每10秒查询一次。其中工作人员的userId值为100和200,老板的userId值为9999。那么可以如下配置:注意限流阈值是以秒为单位的,需要乘以统计窗口时长10。
(3)总结
参数限流允许基于不同参数条件来限制不同请求的访问速率。在实际应用中,参数限流可以适用于各种场景。例如秒杀系统、订票系统、广告点击等。通过合理设置参数限流规则,可以有效地保护系统免受过多请求的影响。
7.授权规则使用演示
(1)授权规则的核心概念
(2)授权规则的实战
(1)授权规则的核心概念
有时只允许特定用户或IP地址才能访问系统,这时可以使用Sentinel的授权规则(黑白名单)功能。授权规则功能有两个核心概念:黑名单和白名单。
一.黑名单
一种限制性授权规则,用于限制某些用户、操作或资源的访问权限。
二.白名单
一种授予性授权规则,允许特定用户、操作或资源访问受限制的功能。
授权规则适用的场景:
场景一:用户身份认证
当用户登录系统时,系统会验证其身份,确保用户是合法用户。例如,只有登录的管理员才能发布新的电影信息。
场景二:角色和权限分配
系统会为不同角色的用户分配不同的权限。例如,管理员可管理电影信息,编辑可编辑但不能发布,访客只能浏览。
场景三:操作访问控制
某些操作可能只能被特定角色的用户执行。例如,只有管理员可以删除电影信息。
场景四:数据保护
系统需要保护敏感数据,确保只有授权的用户可以访问。例如,用户只能访问自己的个人信息。
场景五:安全访问
限制只有特定用户或特定IP地址可以访问系统,防止恶意用户或恶意IP地址对系统进行攻击或滥用资源。
(2)授权规则的实战
首先需要定义授权规则,Sentinel授权规则配置类是AuthorityRule。因此自己定义一个类,初始化Sentinel授权规则配置,代码如下:
这里没有借助Sentinel Dashboard来配置规则,而是通过代码方式实现。配置规则并不一定要借助Sentinel Dashboard,通过代码也可以完成。如下代码就设置了一个白名单策略,且白名单的值只能是user1或user2。这意味着只有user1和user2这两个用户才能访问"authority-demo"资源。
@Component
public class SentinelAuthorityRule {
@PostConstruct
public void init() {
doInit();
}
private void doInit() {
//定义资源名称
String resource = "authority-demo";
//定义授权规则,此类为 Sentinel 内部类,并非自己定义的
AuthorityRule rule = new AuthorityRule();
rule.setResource(resource);
//白名单
rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
//限制user1/user2能访问
rule.setLimitApp("user1,user2");
//注册授权规则
AuthorityRuleManager.loadRules(Collections.singletonList(rule));
}
}
当然,对应于Sentinel Dashboard中进行授权规则配置如下:
接着添加接口应用授权规则:
@RestController
@RequestMapping("/sentinelAuthority")
public class SentinelAuthorityController {
//资源名称
private static final String RESOURCE_NAME = "authority-demo";
//userId此处当参数传递仅仅是为了模拟,实际生产环境中还需符合自身公司规范,比如放到header里,从header获取等
@GetMapping("/demo")
public String demo(String userId) {
//进行授权规则验证
ContextUtil.enter(RESOURCE_NAME, userId);
Entry entry = null;
try {
entry = SphU.entry(RESOURCE_NAME);
//执行业务逻辑
return "ok";
} catch (BlockException ex) {
//授权验证未通过
//处理授权验证失败的逻辑
return "fail";
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
最后启动服务进行验证便可发现:
http://localhost:19966/sentinelAuthority/demo?userId=user1返回ok;
http://localhost:19966/sentinelAuthority/demo?userId=user2返回ok;
http://localhost:19966/sentinelAuthority/demo?userId=user3返回fail;
8.系统规则使用演示
(1)什么是系统规则
(2)系统规则实战
(1)什么是系统规则
系统规则是针对整个系统进行流量控制的,是操作系统级别或服务器级别的,不是应用级别或资源级别的。一台服务器可以部署很多应用(资源),虽然可为每个资源设置流控规则,但是服务器也可能被压爆。如果因为一个服务导致服务器垮了,那么也会对其他服务产生影响。所以,服务器本身也需要可靠性,也需要做一些流控规则配置。比如入口QPS阈值指的是当前服务器上所有接口的入口流量。
它提供多种阈值类型,当触发这些阈值时,系统便会拒绝新的流量请求。
一.Load(负载)阈值
Load阈值可以限制系统的负载。当系统负载过高时,限制新的请求进入系统,以避免系统崩溃。
应用场景: 电商平台在大促活动期间,大量用户涌入平台使得系统资源紧张。此时可以使用设置Load阈值,当系统负载过高时,限制新请求进入系统。
二.RT(平均响应时间)阈值
RT阈值可限制系统的平均响应时间,即请求从接收到响应的平均耗时,RT高可能表示系统负载或性能存在问题。
应用场景:在一个即时消息应用中,确保用户能及时收到消息非常重要。此时可以设置RT阈值,限制消息接口的平均响应时间,保障用户体验。
三.线程数阈值
线程数阈值可限制系统的并发线程数,避免过多线程竞争资源影响性能。
应用场景:在一个高并发的在线游戏中,每个用户都可能占用一个独立线程。此时可以设置线程数阈值,限制并发线程数,避免过多的线程占用资源。
四.入口QPS(每秒查询数)阈值
入口QPS阈值用于限制所有接口的入口流量,防止短时间内大量请求涌入。
应用场景: 在一个热门的抢购活动中,用户可能频繁刷新页面以获取商品信息。可以使用入口QPS阈值,限制商品详情接口的访问频率。
五.CPU使用率阈值
CPU使用率阈值用于限制系统的CPU使用率,避免CPU负载过高。
应用场景:在一个图像渲染应用中,每个任务需要大量计算资源。可以设置CPU使用率规则,限制渲染任务的CPU使用率。
(2)系统规则实战
如下是官网的Demo:通过系统规则管理类SystemRuleManager加载initSystemRule()的阈值。一旦超出initSystemRule()中配置的阈值,新进来的请求将被直接拒绝。
package com.alibaba.csp.sentinel.demo.system;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.csp.sentinel.util.TimeUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
public class SystemGuardDemo {
private static AtomicInteger pass = new AtomicInteger();
private static AtomicInteger block = new AtomicInteger();
private static AtomicInteger total = new AtomicInteger();
private static volatile boolean stop = false;
private static final int threadCount = 100;
private static int seconds = 60 + 40;
public static void main(String[] args) throws Exception {
//打印测试结果
tick();
//初始化系统规则配置
initSystemRule();
//启动多线程进行测试
for (int i = 0; i < threadCount; i++) {
Thread entryThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Entry entry = null;
try {
entry = SphU.entry("methodA", EntryType.IN);
pass.incrementAndGet();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
}
} catch (BlockException e1) {
block.incrementAndGet();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
}
} catch (Exception e2) {
} finally {
total.incrementAndGet();
if (entry != null) {
entry.exit();
}
}
}
}
});
entryThread.setName("working-thread");
entryThread.start();
}
}
//初始化系统规则配置
private static void initSystemRule() {
List<SystemRule> rules = new ArrayList<SystemRule>();
SystemRule rule = new SystemRule();
//max load is 3,系统最高负载为3.0
rule.setHighestSystemLoad(3.0);
//max cpu usage is 60%,系统CPU最大使用率为60%
rule.setHighestCpuUsage(0.6);
//max avg rt of all request is 10 ms,系统最大平均响应时间为10毫秒
rule.setAvgRt(10);
//max total qps is 20,系统最大QPS为20
rule.setQps(20);
//max parallel working thread is 10,系统最大并行线程数为10
rule.setMaxThread(10);
rules.add(rule);
//通过系统规则配置管理类SystemRuleManager将规则配置注册进去,一旦超出上述阈值,新进来的请求将被直接拒绝
SystemRuleManager.loadRules(Collections.singletonList(rule));
}
private static void tick() {
Thread timer = new Thread(new TimerTask());
timer.setName("sentinel-timer-task");
timer.start();
}
static class TimerTask implements Runnable {
@Override
public void run() {
System.out.println("begin to statistic!!!");
long oldTotal = 0;
long oldPass = 0;
long oldBlock = 0;
while (!stop) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
long globalTotal = total.get();
long oneSecondTotal = globalTotal - oldTotal;
oldTotal = globalTotal;
long globalPass = pass.get();
long oneSecondPass = globalPass - oldPass;
oldPass = globalPass;
long globalBlock = block.get();
long oneSecondBlock = globalBlock - oldBlock;
oldBlock = globalBlock;
System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock);
if (seconds-- <= 0) {
stop = true;
}
}
System.exit(0);
}
}
}
9.集群流控使用演示
(1)部署集群环境
(2)集群流控实战
(1)部署集群环境
Sentinel源码中就有一个实现了集群流控的Demo。
因此为了演示集群效果,可以直接启动:
com.alibaba.csp.sentinel.demo.cluster.app.ClusterDemoApplication;
接下来会启动三次此项目,分别指定不同的端口。启动之前,需要在IDEA中分别添加如下JVM参数。
第一次启动项目时添加的参数:
-Dcsp.sentinel.log.use.pid=true -Dproject.name=sentinle.cluster.demo.embedded
-Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8080
-Dcsp.sentinel.api.port=8881
对应的截图如下:
第二次启动项目时添加的参数:
-Dcsp.sentinel.log.use.pid=true -Dproject.name=sentinle.cluster.demo.embedded
-Dserver.port=8082 -Dcsp.sentinel.dashboard.server=localhost:8080
-Dcsp.sentinel.api.port=8882
对应的截图如下:
第三次启动项目时添加的参数:
-Dcsp.sentinel.log.use.pid=true -Dproject.name=sentinle.cluster.demo.embedded
-Dserver.port=8083 -Dcsp.sentinel.dashboard.server=localhost:8080
-Dcsp.sentinel.api.port=8883
对应的截图如下:
分别启动三个ClusterDemoApplication项目,然后再分别访问如下地址:
http://localhost:8081/hello/sentinel
http://localhost:8082/hello/sentinel
http://localhost:8083/hello/sentinel
接着打开Sentinel Dashboard机器列表,便会发现注册进来了三个服务。
(2)集群流控实战
首先,需要新增Token Server和Token Client。可以随意选择一个服务作为Token Server,另外两个作为Token Client。
接着,新建一个集群规则:集群QPS阈值为1。这也就意味着三台服务加起来的QPS为1,即整个集群内1s只能访问一次。配置如下图所示:
至此,就完成了集群限流的配置。