logo头像

优秀是一种习惯

分布式环境之高并发场景下的限流与熔断

本文于 364 天之前发表,文中内容可能已经过时。

[^引子]:

​ 限流、熔断、服务降级等等词在现在的互联网公司经常被谈及,进入主题之前先来个段子

1
2017年某当红小鲜肉公布恋情,结果微博流量突增好几倍,导致服务挂了,网传当时处理服务异常的工程师正在老家举办婚礼,只好临时先晾下大波客人独自解决问题,求当时工程师的心里阴影面积。
看完段子我们来反思一下。(如果是自己的公司我们应该怎么处理这种突增流量呢)
  1. 如果有足够多的资源,动态扩容可以解决问题,可是必须要先准备大量资源来应对突增的流量成本太高。(不在本文讨论范围内
  2. 在有限的资源情况下,限流也能解决这个问题,比如热点数据的限流,系统负载保护。
那什么是限流 ?

以流量为切入点,从流量控制、熔断降级来帮助服务在流量激增的情况下不会直接被压挂。

限流工具对比
Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持 简单的 Rate Limiter 模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 不提供控制台,可对接其它监控系统
为什么选择 Sentinel 点击跳转
  1. 支持限流维度比较多

  2. 除了对http请求支持限流以外,更无缝衔接DUBBO的RPC接口做限流。

  3. 提供控制台可以查看监控数据及规则

  4. 整体模块划分比较好

项目结构

  • sentinel-adapter 针对主流框架的适配器,(我主要使用web 与 dubbo)

  • sentinel-benchmark 基准测试模块

  • sentinel-cluster 集群限流模块,1.4.0版本正式支持集群限流

  • sentinel-core 核心模块,所有的限流降级等功能

  • sentinel-dashboard 控制台模块,提供可视化监控,及规则配置

  • sentinel-demo 示例模块

  • sentinel-transport 数据传输模块

先来看看Sentinel的集群限流

img

我们通常在上生产时会对服务做一个压测,那我们可以根据压测结果来设置限流的值。由于负载均衡策略不同,可能单机处理QPS不同。这时可以采用集群总流量来控制整个QPS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// web限流
Entry entry = null;
// 务必保证finally会被执行
try {
// 资源名可使用任意有业务语义的字符串
entry = SphU.entry("自定义资源名");
/**
* 被保护的业务逻辑
*/
} catch (BlockException e1) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
} finally {
if (entry != null) {
entry.exit();
}
}

// dubbo RPC 限流
// 模拟多线程调用
private static final ExecutorService pool = Executors.newFixedThreadPool(10,
new NamedThreadFactory("dubbo-consumer-pool"));

public static void main(String[] args) {
//初始化规则
initFlowRule();
AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
consumerContext.register(ConsumerConfiguration.class);
consumerContext.refresh();
FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class);
for (int i = 0; i < 10; i++) {
//用线程程调用
pool.submit(() -> {
try {
String message = service.sayHello("Eric");
System.out.println("Success: " + message);
} catch (SentinelRpcException ex) {
System.out.println("Blocked");
} catch (Exception ex) {
ex.printStackTrace();
}
});
pool.submit(() -> System.out.println("Another: " + service.doAnother()));
}
}
private static void initFlowRule() {
FlowRule flowRule = new FlowRule();
flowRule.setResource(RES_KEY);
//每秒最多5个请求
flowRule.setCount(5);
// 线程数维度
//flowRule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
//QPS 维度
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setLimitApp("default");
FlowRuleManager.loadRules(Collections.singletonList(flowRule));
}
/** 运行结果
Another: 2018-12-28T14:25:50.378
Another: 2018-12-28T14:25:50.379
Success: Hello, Eric at 2018-12-28T14:25:50.378
Success: Hello, Eric at 2018-12-28T14:25:50.378
Success: Hello, Eric at 2018-12-28T14:25:50.379
Success: Hello, Eric at 2018-12-28T14:25:50.379
Another: 2018-12-28T14:25:50.379
Another: 2018-12-28T14:25:50.379
Success: Hello, Eric at 2018-12-28T14:25:50.379
Another: 2018-12-28T14:25:50.387
Another: 2018-12-28T14:25:50.387
Another: 2018-12-28T14:25:50.379
Another: 2018-12-28T14:25:50.388
Another: 2018-12-28T14:25:50.388
Another: 2018-12-28T14:25:50.389
Blocked
Blocked
Blocked
Blocked
Blocked
*/
// 运行结果可以看出当流量达到阀值时会快速失败
Sentinel-dashboard的集群模式想要生效是需要二次开发。

这个是官方建议的架构。我也是按这个架构二次开发。

  • dashboard 配置规则
  • 将规则持久到Zookeeper
  • 所有应用节点监听这个节点
  • 数据变更时,应用重新加载规则

以上需要注意的是集群限流还需在集群限流页配置节点信息 也就是指定上面的谁是Server 谁是Client

这里说明一下。官方的架构图稍微有些抽象,我就用大白话说明一下

  • token service 这里指的是令牌服务端。有两种方式
    • 独立部署一个token server 优点是比较独立。缺点是增加成本,且增加维护的工作量
    • 嵌入式 token server 就是以某个应用节点做为token server 其他节点做为client。优点是简单不需要额外成本,缺点与应用耦合在一起。
  • client 指的是需要被限流的应用,要指定token server IP 及端口。来获得令牌。
注意: 1.4.0 版本的Sentinel 在集群限流模式下,如果token server 重启后,会出现集群限流失效,变成 了单机限流。原因是token server重启后client重试次数过了后就不会再去重试,转而变成单机模式。【官方回应 1.4.1版本会改成一直重试】
熔断 降级

除了对QPS限流外,我们最常用的就是通过熔断降级保证服务不会完全宕机,例如某个RPC接口处理能力接近瓶颈时,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

系统负载保护

限流 或者 熔断降级都是指定到具体的请求,而系统负载保护,是针对整体应用集群,当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

主流框架的适配
  • web servlet (已经集成)
  • dubbo (已经集成)
  • spring boot /spring cloud (未测试)
  • gRPC (未测试)
  • rocketmq (未测试)