文章来源:https://zhenbianshu.github.io/2020/03/how_to_cal_isolation_limit.html

| 问题

请求被限流

之前的文章提到过我们服务使用Hystrix进行服务限流,使用的是信号量方式,并根据接口的响应时间和服务的峰值QPS设置了限流的配额。

限流配额的计算方式为:

我们接口单机单个接口的峰值QPS为1000,平均影响时长15ms,我们认为Hystrix的信号量是并发量,那么一个信号量在一秒内能允许1000ms/15ms~66个请求通过,那么服务1000QPS配置15个信号量就足够了。

当然这是在忽略上下文切换和GC时间的情况下,考虑上这些因素,每个并发量每秒能服务的时长约为900ms,用同样的公式计算所需要的信号量是17,为了应付突发流量,我将这个值设置为了30。

本以为这样就高枕无忧了,没想到看错误日志中偶然发现了有报错:

HystrixRuntimeException occurred! , failureType:REJECTED_SEMAPHORE_EXECUTION, message:apiHystrixKey could not acquire a semaphore for execution and fallback failed.

我把信号量配置提高到了50,没想到还是没看到问题有明显好转,这就比较诡异了。

| 解决

排查步骤

首先我列了一下排查的步骤,也整理一下出现这种问题的可能。

  1. 看正常请求的平均耗时,排除真实block的可能。接口平均耗时17ms,QPS为1000,如果代码都被block在某处,接口耗时一定会突增。

  2. 查看一下hystrix代码看是否可能有情况导致信号量未释放。简单扫了一遍hystrix相关代码,信号量的释放在请求结束的callback里,如果有泄漏,一定会导致可用信号量越来越少,最终为0。

  3. 写一个小demo,压测看是否能复现。在demo里运行,问题只在刚启动服务未初始化完成时复现,后续就平稳了。

Jdk的Bug ?

从整体上看不出来,就只好从微观时间点上看了,可这个问题出现是一瞬间的事,jstack也无能为力,虽然jmc倒是合适,但它部署有点费劲,而且还会在观察的时候影响到服务,于是优先从历史时间点上排查。

从错误日志里找了一个服务拒绝数较多的时间点,再观察服务当时的状态。错误日志上除了一些请求被拒绝的报错外就没有其他的了,但我在gclog里发现了奇怪的日志。

2020-03-17T13:01:26.281+0800: 89732.109: Application time: 2.1373599 seconds
2020-03-17T13:01:26.308+0800: 89732.136: Total time for which application threads were stopped: 0.0273134 seconds, Stopping threads took: 0.0008935 seconds
2020-03-17T13:01:26.310+0800: 89732.137: Application time: 0.0016111 seconds
2020-03-17T13:01:26.336+0800: 89732.163: [GC (Allocation Failure) 2020-03-17T13:01:26.336+0800: 89732.164: [ParNew
Desired survivor size 429490176 bytes, new threshold 4 (max 4)
- age 1: 107170544 bytes, 107170544 total
- age 2: 38341720 bytes, 145512264 total
- age 3: 6135856 bytes, 151648120 total
- age 4: 152 bytes, 151648272 total
: 6920116K->214972K(7549760K), 0.0739801 secs] 9292943K->2593702K(11744064K), 0.0756263 secs] [Times: user=0.65 sys=0.23, real=0.08 secs]
2020-03-17T13:01:26.412+0800: 89732.239: Total time for which application threads were stopped: 0.1018416 seconds, Stopping threads took: 0.0005597 seconds
2020-03-17T13:01:26.412+0800: 89732.239: Application time: 0.0001873 seconds
2020-03-17T13:01:26.438+0800: 89732.265: [GC (GCLocker Initiated GC) 2020-03-17T13:01:26.438+0800: 89732.265: [ParNew
Desired survivor size 429490176 bytes, new threshold 4 (max 4)
- age 1: 77800 bytes, 77800 total
- age 2: 107021848 bytes, 107099648 total
- age 3: 38341720 bytes, 145441368 total
- age 4: 6135784 bytes, 151577152 total
: 217683K->215658K(7549760K), 0.0548512 secs] 2596413K->2594388K(11744064K), 0.0561721 secs] [Times: user=0.49 sys=0.18, real=0.05 secs]
2020-03-17T13:01:26.495+0800: 89732.322: Total time for which application threads were stopped: 0.0824542 seconds, Stopping threads took: 0.0005238 seconds

我看到连续发生了两次YGC,它们之间的间隔才0.0001873s,可以认为是进行了一次很长时间的GC,总耗时达到了160ms。再仔细观察第二次GC时的内存分布,可以看到它作为一次ParNew GC,发生时eden区的内存才使用了200M,这就不符合常理了。

再看GC发生的原因,日志里标识的是GCLocker Initiated GC。在使用 JNI操作字符串或数组时,为了防止GC导致数组指针发生偏移,JVM实现了GCLocker,它会在发生GC的时候阻止程序进入临界区,并在最后一个临界区内的线程退出时,发生一次GCLocker GC。

至于这次的GC,是JDK的一个Bug,JDK-8048556,具体原因可以看这篇博客:一次不必要的GCLocker-initiated young GC。

而我们的Java版本低于修复版本,出现这种问题实属正常,可是,这个问题就归究于jdk的bug吗?升级了jdk版本就一定会好吗?

“平均”的陷阱

重新来计算一下,即使JVM每秒都有160ms在进行GC,可系统有服务时间也还有840ms,使用上文中的公式,信号量的还是完全足够的。

一时想不明白,出去倒了杯水,走了走,忽然想到原来自己站错了角度。我一直用秒作为时间的基本单位,用一秒的平均状态来代表系统的整体状态,认为一整秒内如果没有问题,服务就不应该会发生问题,可是忽略了时间从来不是一秒一秒进行的。

试想,如果平稳运行的服务,忽然发生了一次160ms的GC,那么这 160ms内的请求会平均分配到剩余840ms内吗?并不会,它们会挤在第161ms一次发送过来,而我们设置的信号量限制会作出什么反应呢?

@Overridepublic boolean tryAcquire() {int currentCount = count.incrementAndGet();if (currentCount > numberOfPermits.get()) {count.decrementAndGet();return false;} else {return true;}}

上面是Hystrix源码中获取信号量的代码,可以发现,代码里没有任何block,如果当前使用的信号量大于配置值,就会直接拒绝。

这样就说得通了,如果进行了160ms的GC,再加上请求处理的平均耗时是15ms,那系统就有可能在瞬间堆积1000q/s * 0.175s = 175的请求,如果信号量不足,请求就会被直接拒绝了。

也就是说即使jdk的bug修复了,信号量限制最少还是要设置为95才不会拒绝请求。

| 限流配额的正确计算方式

概念

那么限流配额的正确计算方式是怎样的呢?

在此之前我们要明确设置的限流配额都是并发量,它的单位是:个,这一点要区分于我们常用的服务压力指标QPS,因为QPS是指一秒内的请求数,它的单位是 个/S,由于单位不同,它们是不能直接比较的,需要并发量再除以一个时间单位才可以。

正确的公式应当是:并发量(个)/单个请求耗时(s) > QPS(个/s)。

但由于Java GC的特性,我们不得不考虑GC期间请求堆积的可能,要处理这种情况,第一种是直接拒绝,像Hystrix的实现(有点坑),第二种是做一些缓冲。

信号量缓冲

其实信号量并不是无法做缓冲的,只是Hystrix 内的”信号量”是自己实现的,比较low。

比较”正统”的方式是使用jdk里的java.util.concurrent.Semaphore,它获取信号量有两种方式,第一种是tryAcquire(),这类似于Hystrix的实现,是不会block的,如果当前信号量被占用或不足,会返回false。第二种是使用acquire()方法,它没有返回值,意思是方法只有在拿到信号量时才会返回,而这个时间是不确定的。

我猜想这可能也是Hystrix不采用这种方式的原因,毕竟如果使用FairSync会有很多拿到信号量发现接口超时再抛弃的行为,而使用UnFairSync又会使接口的影响时长无法确定。

线程池缓冲

线程池的缓冲比信号量要灵活得多,设置更大的maximumPoolSize或BlockingQueue都可以,设置rejectHandler也是很好的办法。

只是使用线程池会有上下文切换的损耗,而且应对突发流量时,线程池的扩容也比较捉急。

考虑到它的灵活性,以及可以通过Future.get()的超时时间来控制接口的最大响应时间,和信号量比,没有哪一种方式更好。

| 小结

解决了一个服务隐藏了很久的问题,又积累了排查此类问题的经验,得到了问题不能只从一个角度看待的教训,还是比较开心的。

当然,也又一次证明了看源码的重要性,遇到问题追一追源码,总会有些收益。

如何计算服务限流的配额相关推荐

  1. 亿级流量架构之服务限流思路与方法

    为什么要限流 日常生活中,有哪些需要限流的地方? 像我旁边有一个国家AAAA景区,平时可能根本没什么人前往,但是一到五一或者春节就人满为患,这时候景区管理人员就会实行一系列的政策来限制进入人流量, 为 ...

  2. 微服务架构 — 服务治理 — 服务限流、服务降级、服务熔断

    目录 文章目录 目录 服务限流 服务降级 服务熔断 服务限流 C ⇄ S 的异常问题:C 的请求太多,超出 S 的服务能力,导致 S 不可用.例如:DoS 攻击,企图耗尽被攻击对象的资源,让目标系统无 ...

  3. 架构设计之「服务限流」

    架构设计之「服务限流」 原文:架构设计之「服务限流」 上一篇我们聊过了架构设计中的「服务隔离」模式,今天我们继续来探索一下在分布式系统架构中的另一个常用的设计:服务限流. 那么,什么是「服务限流」呢? ...

  4. java服务熔断_springcloud-Hystrix-服务降级、服务熔断、服务限流

    Hystrix主要能解决三个问题:服务降级.服务熔断.服务限流:一般用在调用端,比如表现层去调用服务层,常见会用在表现层,但是服务层也行. 1.服务降级 服务降级是:因为某些原因,服务调用出现故障,本 ...

  5. 服务降级,服务熔断,服务限流

    服务降级 概念:服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行. 服务接口拒绝服务:页面能访问,但是添加删除提示服务 ...

  6. 03.服务限流实现方案

    Sentinel概述 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流.流量整形.熔断降级.系统负载保护等 ...

  7. 10分钟带你彻底搞懂服务限流和服务降级

    文章目录 十分钟搞懂系列 服务限流 计数器法 滑动窗口法 漏桶算法 令牌桶算法 服务降级 十分钟搞懂系列 序号 标题 链接 1 10分钟带你彻底搞懂企业服务总线 https://blog.csdn.n ...

  8. Spring Cloud入门-Sentinel实现服务限流、熔断与降级(Hoxton版本)

    文章目录 Spring Cloud入门系列汇总 摘要 Sentinel简介 安装Sentinel控制台 创建sentinel-service模块 限流功能 创建RateLimitController类 ...

  9. 儒猿秒杀季!微服务限流熔断技术源码剖析与架构设计

    疯狂秒杀季:49元秒杀 原价 299元 的 <微服务限流熔断技术源码剖析与架构设计课> 今天 上午11点,仅 52 套,先到先得! === 课程背景 === 成为一名架构师几乎是每个程序员 ...

最新文章

  1. Learn About Salesforce Flow for Service
  2. 内嵌WEB服务器加载原理
  3. RxJava 中的Map函数原理分析
  4. TensorFlow 笔记3--模型的保存与恢复
  5. 设计模式(1)单例模式(Singleton)
  6. Jquery事件委托之Safari
  7. Integration Services 学习(5):容器
  8. ENVI入门系列教程---一、数据预处理---3.1 基于自带定位信息的几何校正
  9. (转) Csrss进程剖析
  10. 【python爬虫】喜欢看小说又手头紧的小伙伴一定要看这篇文章,带你一步步制作一个小说下载器
  11. nginx 正反代理(超级玛丽小游戏)
  12. 心理正常与异常的区分_判断心理正常异常三原则
  13. arcgis多面体数据转面_ArcGIS多面体(multipatch)解析——引
  14. HTMLCSS 笔记(三)
  15. 基于屏幕空间渲染的液体模拟
  16. webservice连接验证用户名密码
  17. javaEE面试-文章推荐-1
  18. 国际顶级的摩托车越野锦标赛落户上海
  19. Node.js:nodemailer发送163邮件
  20. wed语言翻译HTML及使用规范,Web前端开发规范

热门文章

  1. mac微软雅黑字体_【字体字重】常见设计稿字体对应字重
  2. 了解下WSDL 绑定
  3. mqtt 传文件断开连接的原因_mqtt 发送消息断开链接
  4. CSS中表格的一些属性和使用
  5. poj3508(高精度模拟减法)
  6. 2021牛客暑期多校训练营3 I-Kuriyama Mirai and Exclusive Or (差分+位运算)
  7. 2019秦皇岛ccpc A题:Angle Beats[计算几何:统计符合直角三角形的个数]+[向量hash+3hash]
  8. python代码创建数据库_如何使用python ORM创建数据库表?
  9. BZOJ 2156 「国家集训队」星际探索(最短路)【BZOJ计划】
  10. luogu P5142 区间方差(线段树、乘法逆元)