阿里开源,限流之“后”—Sentinel
前言
限流分为单机和分布式两种,单机限流是指限定当前进程里面的某个代码片段的 QPS ,一旦超出规则配置的数值就会抛出异常或者返回 false。我把这里的被限流的代码片段称为临界,注意临界是一个节点。而分布式则需要另启一个集中的服务器,这个服务器针对每个指定的资源每秒只会生成一定量的通过数,在执行临界的代码之前先去集中的服务领取通行证,如果领成功了就可以执行,否则就会抛出限流异常。
Sentinel 在使用上提供了两种形式,一种是异常捕获形式,一种是布尔形式。也就是当限流被触发时,是抛出异常来还是返回一个 false,这是很好理解的代码思路。
一、Sentinel形式
1 .异常捕获形式
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
public class SentinelTest {
public static void main(String[] args) {
// 配置规则
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("tutorial");
// QPS 不得超出 100
rule.setCount(100);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
// 加载规则
FlowRuleManager.loadRules(rules);
// 下面开始运行被限流作用域保护的代码
while (true) {
Entry entry = null;
try {
entry = SphU.entry("tutorial");
System.out.println("hello world");
} catch (BlockException e) {
System.out.println("blocked");
} finally {
if (entry != null) {
entry.exit();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
}
}
}
2.布尔形式
import java.util.ArrayList;
import java.util.List;
import com.alibaba.csp.sentinel.SphO;
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;
public class SentinelTest {
public static void main(String[] args) {
// 配置规则
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("tutorial");
// QPS 不得超出 1
rule.setCount(1);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
// 运行被限流作用域保护的代码
while (true) {
if (SphO.entry("tutorial")) {
try {
System.out.println("hello world");
} finally {
SphO.exit();
}
} else {
System.out.println("blocked");
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
}
}
}
二、限流
1.熔断降级
限流在于限制流量,也就是 QPS 或者线程的并发数,还有一种情况是请求处理不稳定或者服务损坏,导致请求处理时间过长或者老是频繁抛出异常,这时就需要对服务进行降级处理。所谓的降级处理和限流处理在形式上没有明显差异,也是以同样的形式定义一个临界区,区别是需要对抛出来的异常需要进行统计,这样才可以知道请求异常的频率,有了这个指标才会触发降级。
// 定义降级规则
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("tutorial");
// 规定时间内异常不得超出10
rule.setCount(10);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setLimitApp("default");
rules.add(rule);
DegradeRuleManager.loadRules(rules);
Entry entry = null;
try {
entry = SphU.entry(key);
// 业务代码在这里
} catch (Throwable t) {
// 记录异常
if (!BlockException.isBlockException(t)) {
Tracer.trace(t);
}
} finally {
if (entry != null) {
entry.exit();
}
}
触发限流时会抛出 FlowException,触发熔断时会抛出 DegradeException,这两个异常都继承自 BlockException。
2.热点限流
还有一种特殊的动态限流规则,用于限制动态的热点资源。内部采用 LRU 算法计算出 topn 热点资源,然后对 topn 的资源进行限流,同时还提供特殊资源特殊对待的参数设置。比如在下面的例子中限定了同一个用户的访问频次,同时也限定了同一本书的访问频次,但是对于某个特殊用户和某个特殊的书进行了特殊的频次设置。
ParamFlowRule ruleUser = new ParamFlowRule();
// 同样的 userId QPS 不得超过 10
ruleUser.setParamIdx(0).setCount(10);
// qianwp用户特殊对待,QPS 上限是 100
ParamFlowItem uitem = new ParamFlowItem("qianwp", 100, String.class);
ruleUser.setParamFlowItemList(Collections.singletonList(uitem));
ParamFlowRule ruleBook = new ParamFlowRule();
// 同样的 bookId QPS 不得超过 20
ruleBook.setParamIdx(1).setCount(20);
// redis 的书特殊对待,QPS 上限是 100
ParamFlowItem bitem = new ParamFlowItem("redis", 100, String.class);
ruleBook.setParamFlowItemList(Collections.singletonList(item));
// 加载规则
List<ParamFlowRule> rules = new ArrayList<>();
rules.add(ruleUser);
rules.add(ruleBook);
ParamFlowRuleManager.loadRules(rules);
// userId的用户访问bookId的书
Entry entry = Sphu.entry(key, EntryType.IN, 1, userId, bookId);
热点限流的难点在于如何统计定长滑动窗口时间内的热点资源的访问量,其实也就是一个合适的数据结构Sentinel 设计了一个特别的数据结构叫 LeapArray,内部有较为复杂的算法设计后续需要单独分析。
3.过载保护
1.系统的负载水平线,超过这个值时触发过载保护功能
2.当过载保护超标时,允许的最大线程数、最长响应时间和最大 QPS,可以不设置
List<SystemRule> rules = new ArrayList<SystemRule>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(3.0);
rule.setAvgRt(10);
rule.setQps(20);
rule.setMaxThread(10);
rules.add(rule);
SystemRuleManager.loadRules(Collections.singletonList(rule));
从代码中也可以看出系统自适应限流规则不需要定义资源名称,因为它是全局的规则,会自动应用到所有的临界节点。如果当负载超标时,都会触发限流。
总结
我们经常说的限流都是接口限流,我们都了解接口,就是数据,请求的一个中转站,其实内部主要是代码片段的限流,而不是像关键字一样,可以加在某个方法或者类上,这样的好处就是更加细化,更加具有针对性,其实对于一些关键字的使用,我们也推荐使用更加细化的方式,因为这种情况下针对性更强,损耗也更小。