限流熔断分享
为什么要有熔断限流:为了防止服务雪崩。那么什么是服务雪崩?
下面分别讲几个概念:
快速失败:在java中集合中 多个线程操作非安全集合 会发生快速失败。类比到我们在开发web应用时候,对于非法参数我们也会进行提前结束(抛出异常或者直接return),避免占用之后的资源,进行无效计算。
缓存雪崩:数据库往往是web 应用的瓶颈,为了避免频繁读取数据库引入本地缓存或者分布式缓存将数据库数据保存在内存中,对于不存在缓存中数据或者未过期数据,才去数据库读取,减少对数据库的访问。缓存往往会设置一定有效时间。如果在某段时间,缓存集体失效。大量数据访问涌入数据库,称为缓存雪崩。
分布式服务:早期使用水平拓展,对于部分服务器可能并没有充分利用资源,如io密集型和cpu密集型。为此引入分布式服务,特定服务使用适合服务器,结果使用rpc或者http进行网络传输。
服务雪崩:多个服务之间相互调用链路,一条核心链路往往可能调用十个服务。我们知道随着并发数增加,系统响应时间在某个时间段会急剧增加。
如果在链路中,某个服务发生这种情况,rt(响应时间)急剧上升,上游服务不断请求,造成恶性循环,上游等待结果线程数越多,使得更上游服务阻塞最终整条链路无法使用,称为服务雪崩。
解决思路:
借鉴 快速失败思想。发生rt超时情况,进行快速失败,返回错误结果。——熔断
借鉴 缓存雪崩是想。避免大量数据访问到该应用,控制数量在合理范围。——限流
单机限流和分布式限流:
单机限流是指限定当前进程里面的某个代码片段的 QPS 或者 并发线程数 或者 整个机器负载指数,一旦超出规则配置的数值就会抛出异常或者返回 false。
分布式则需要另启一个集中的发票服务器,这个服务器针对每个指定的资源每秒只会生成一定量的票数,在执行临界区的代码之前先去集中的发票服务领票,如果领成功了就可以执行,否则就会抛出限流异常或者返回false。
限流算法:
业界主流的限流算法,一般有以下几种。
1.令牌桶限流
令牌桶是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,填满了就丢弃令牌,请求是否被处理要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求。令牌桶允许一定程度突发流量,只要有令牌就可以处理,支持一次拿多个令牌。令牌桶中装的是令牌。
2.漏桶限流
漏桶一个固定容量的漏桶,按照固定常量速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝。漏桶可以看做是一个具有固定容量、固定流出速率的队列,漏桶限制的是请求的流出速率。漏桶中装的是请求。
3.计数器限流
有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数,比如数据库连接池、线程池、秒杀的并发数;计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流,是一种简单粗暴的总数量限流,而不是平均速率限流。
4.固定窗口算法(滑动窗口算法)
在实现它的时候,首先要启动一个定时器定期重置计数,比如你需要限制每秒钟访问次数,而限流的逻辑就非常简单了,只需要比较计数值是否大于阈值就可以了。缺点是在两个时间间隔中间会同时涌入流量,造成窗口失败。
主流工业方案
对于单机限流 采用Guava 的RateLimiter,用的是令牌桶的算法,主要是因为它支持突发流量,并且是比较成熟的现成框架。原因是:接口级的限流,更重要的还是要保证速率均衡,还要允许一定的突发流量。
对于分布式限流采用目前有hytrix 和Sentinel 。
Hystrix限流功能
- Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。
- 基于超时时间降级执行fallback。
- 为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
- 依赖调用结果分:成功,失败(抛出异常),超时,线程拒绝,短路。 请求失败(异常,拒绝,超时,短路)时执行fallback(降级)逻辑。
- 熔断器:提供基于失败比例的熔断(50%),停止当前依赖一段时间(10秒)。
- 提供近实时依赖的统计和监控
接口使用
详细接口使用:http://blog.51cto.com/developerycj/1950881
使用命令模式封装依赖逻辑
public class HelloWorldCommand extends HystrixCommand<String> {
private final String name;
public HelloWorldCommand(String name) {
//最少配置:指定命令组名(CommandGroup)
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
// 依赖逻辑封装在run()方法中
return "Hello " + name +" thread:" + Thread.currentThread().getName();
}
//调用实例
public static void main(String[] args) throws Exception{
//每个Command对象只能调用一次,不可以重复调用,
//重复调用对应异常信息:This instance can only be executed once. Please instantiate a new instance.
HelloWorldCommand helloWorldCommand = new HelloWorldCommand("Synchronous-hystrix");
//使用execute()同步调用代码,效果等同于:helloWorldCommand.queue().get();
String result = helloWorldCommand.execute();
System.out.println("result=" + result);
helloWorldCommand = new HelloWorldCommand("Asynchronous-hystrix");
//异步调用,可自由控制获取结果时机,
Future<String> future = helloWorldCommand.queue();
//get操作不能超过command定义的超时时间,默认:1秒
result = future.get(100, TimeUnit.MILLISECONDS);
System.out.println("result=" + result);
System.out.println("mainThread=" + Thread.currentThread().getName());
}
}
sential支持的功能
- 根据QPS限流
- 根据线程数限流
- 根据调用方限流
- 黑白名单
- 根据调用链限流,比如分别统计从A、B掉C接口的逻辑
- 根据资源读写的竞争条件限流
- 异步限流接口支持
- 全局限流与单机限流
思考与总结
Sentinel本地限流通过读取注解、代码生成一个本地全局的context,通过context保存本地的调用信息:调用链、qps等等。当然对于注解会代理生成限流逻辑。
利用Sentinel全局限流,我们可以做基于优先级的限流,比如:当支付量大时,对其它相关的所有服务限流。
为什么用sentinel
- hystrix只支持api维度的熔断、降级,功能较少
- api使用更加简单,极低的代码侵入性
- 丰富的限流逻辑,满足各种需求
- 丰富的监控接口和dashboard
- 性能高以及限流的pipeline设计思路,基于前人的经验设计更加轻量、以扩展
- 稳定且有阿里中间件团队背书
- 资源限流不限于接口,支持任意的调用资源
功能 | Sentinel | Hystrix(Tesla) |
---|---|---|
功能 | Sentinel | Hystrix(Tesla) |
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
参考内容
Hystrix官方文档 https://segmentfault.com/a/1190000012439580
hystrix,限流入门指南 https://www.cnblogs.com/yepei/p/7169127.html
sentinel对比hystrix https://github.com/alibaba/Sentinel/wiki/Sentinel-%E4%B8%8E-Hystrix-%E7%9A%84%E5%AF%B9%E6%AF%94