共计 4602 个字符,预计需要花费 12 分钟才能阅读完成。
文|张稀虹(花名:止语 )
蚂蚁团体技术专家
负责蚂蚁团体云原生架构下的高可用能力的建设
次要技术畛域包含 ServiceMesh、Serverless 等
本文 3631 字 浏览 8 分钟
PART. 1 故事背景
往年双十一大促后,依照常规咱们对大促期间的零碎运行数据进行了具体的剖析,比照去年同期的性能数据发现,MOSN 的 CPU 使用率有大概 1% 的上涨。
为什么减少了?
是正当的吗?
能够优化吗?
是不可避免的熵增,还是人为的节约?
带着这一些列灵魂拷问咱们对系统进行了剖析
PART. 2 问题定位
咱们从监控上发现,这部分额定的开销是在零碎闲暇时已有,并且不会随着压测流量减少而升高,CPU 总耗费减少 1.2%,其中 0.8% 是由 cpu_sys 带来。
通过 perf 剖析发现新版本的 MOSN 相较于老版本,syscall 有显著的减少。
通过层层剖析,发现其中一部分起因是 MOSN 依赖的 sentinel-golang 中的一个 StartTimeTicker 的 func 中的 Sleep 产生了大量的零碎调用,这是个什么逻辑?
PART. 3 实践剖析
查看源码发现有一个毫秒级别的工夫戳缓存逻辑,设计的目标是为了升高高调用频率下的性能开销,但闲暇状态下频繁的获取工夫戳和 Sleep 会产生大量的零碎调用,导致 cpu sys util 上涨。咱们先从实践上剖析一下为什么这部分优化在工程上通常是有效的,先来看看 Sentinel 的代码:
package util
import (
"sync/atomic"
"time"
)
var nowInMs = uint64(0)
// StartTimeTicker starts a background task that caches current timestamp per millisecond,
// which may provide better performance in high-concurrency scenarios.
func StartTimeTicker() {atomic.StoreUint64(&nowInMs, uint64(time.Now().UnixNano())/UnixTimeUnitOffset)
go func() {
for {now := uint64(time.Now().UnixNano()) / UnixTimeUnitOffset
atomic.StoreUint64(&nowInMs, now)
time.Sleep(time.Millisecond)
}
}()}
func CurrentTimeMillsWithTicker() uint64 {return atomic.LoadUint64(&nowInMs)
}
从下面的代码能够看到,Sentinel 外部用了一个 goroutine 循环的获取工夫戳存到 atomic 变量里,而后调用 Sleep 休眠 1ms,通过这种形式缓存了毫秒级别的工夫戳。内部有一个开关管制这段逻辑是否要启用,默认状况下是启用的。从这段代码上看,性能开销最大的应该是 Sleep,因为 Sleep 会产生 syscall,家喻户晓 syscall 的代价是比拟高的。
time.Sleep 和 time.Now 比照开销到底大多少呢?
查证材料(1)后我发现一个反直觉的事实,因为 Golang 非凡的调度机制,在 Golang 中一次 time.Sleep 可能会产生 7 次 syscall,而 time.Now 则是 vDSO 实现的,那么问题来了 vDSO 和 7 次零碎调用相比晋升应该是多少呢?
我找到了能够佐证的材料,恰好有一个 Golang 的优化(2),其中提到在老版本的 Golang 中(golang 1.9-),Linux/386 下没有这个 vDSO 的优化,此时会有 2 次 syscall,新版本通过优化后实践性能进步 5~7x+,能够约等于一次 time.Now <= 0.3 次 syscall 的开销。
Cache 设计的目标是为了缩小 time.Now 的调用,所以实践上这里调用量足够大的状况下可能会有收益,依照下面的剖析,假如 time.Now 和 Sleep 零碎调用的开销比是 0.3:7.3(7+0.3),Sleep 每秒会执行 1000 次(不思考零碎精度损失的状况下),这意味着一秒内 CurrentTimeMillsWithTicker 的调用总次数要超过 2.4W 才会有收益。
所以咱们再剖析一下 CurrentTimeMillsWithTicker 的调用次数,我在这个中央加了一个 counter 进行验证,而后模仿申请调用 Sentinel 的 Entry,通过测试发现:
- 当首次创立资源点时,Entry 和 CurrentTimeMillsWithTicker 的放大比为 20,这次要是因为创立底层滑动窗口时须要大量的工夫戳计算
- 当雷同的 resource 调用 Entry 时,调用的放大比⁰为 5:1
|注 0: 外部应用的 MOSN 版本基于原版 Sentinel 做了一些定制化,社区版本放大比实践上低于该比值。
思考到创立资源点是低频的,咱们能够近似认为此处调用放大比为 5。所以实践受骗单机 QPS 至多超过 4800 以上才可能会获得收益 …… 咱们动辄据说什么 C10K、C100K、C1000K 问题,这个值看上去仿佛并不很高?但在理论业务零碎中,这实际上是一个很高的量。
我随机抽取了多个日常申请量绝对大的利用查看 QPS(这里的 QPS 蕴含所有类型的资源点,入口 / 进口调用以及子资源点等,总之就是所有会通过 Sentinel Entry 调用的申请量),日常峰值也未超过 4800QPS,可见理论的业务零碎中,单机申请量超过这个值的场景是十分常见的。¹
|注 1: 此处监控为分钟级的数据监控,可能与秒级监控存在肯定的出入,仅用于领导日常申请量评估。
思考到这个优化还有一个益处,是能够升高同步申请工夫戳时的耗时,所以咱们能够再比照一下间接从 atomic 变量读取缓存值和通过 time.Now() 读取工夫戳的速度。
能够看到单次间接获取工夫戳的确比从内存读取开销大很多,然而依然是 ns 级别的,这种级别的耗时增长对于一笔申请而言是能够忽略不计的。
大略是 0.06 微秒,即便乘以 5,也就是 0.3 微秒的减少。在 4000QPS 这个流量档位下咱们也能够看一下 MOSN 理论 RT。
两台机器的 MOSN RT 也没有显著的差别,毕竟只有 0.3 微秒 …
PART. 4 测试论断
同时咱们也找了两台机器,别离禁用 / 启用这个 Cache 进行测试,测试后果佐证了上述剖析的论断。
从上图的数据能够看进去,启用 Cache 的状况下 cpu sys util 始终比不启用 Cache 的版本要大,随着申请量减少,性能差距在逐渐放大,然而直至 4000QPS 依然没有正向的收益。
通过测试和实践剖析可得悉,在惯例的场景下,Sentinel 的这个 Cache 个性是没有收益的,反而对性能造成了损耗,尤其是在低负载的状况下。即便在高负载的状况下,也能够推论出:没有这个 Cache 不会对系统造成太大的影响。
这次性能剖析也让咱们意识到了几个问题:
- 不要过早优化,正所谓过早优化是万恶之源;
- 肯定要用主观数据证实优化后果是正向的,而不是凭借直觉;
- 要结合实际场景进行剖析,而不应该优先思考一些小概率场景;
- 不同语言间底层实现可能存在区别,移植时应该认真评估。
PART. 5 有必要吗?
你下面不是说了,不要过早优化,那这个算不算过早优化呢,你是不是双标?
“过早优化是万恶之源”实际上被误用了,它是有上下文的。
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. —— Donald Knuth
Donald Knuth 认为许多优化是没必要的,咱们可能破费了大量的工夫去做了投入产出比不高的事件,但他同时强调了一些关键性优化的必要性。简而言之就是要思考性价比,不能自觉地、没有数据撑持地去做性能优化,premature 仿佛翻译成“不成熟、自觉的”更为贴切,因而这句话的本意是“自觉的优化是万恶之源”。这里只须要一行代码的改变,即可省下这部分不必要的开销,性价比极高,何乐而不为呢?
从数据上看,这个优化只是升高了 0.7% 的 cpu sys util,咱们缺这 0.7% 吗?
从零碎水位的角度思考或者还好,毕竟咱们为了保险起见准备了比理论需要更多的资源,这 0.7% 并不会成为压垮咱们零碎的最初一颗稻草。但从环保的角度,很有必要!往年咱们强调的是绿色环保,提效降本。这区区一行代码,作为 Sidecar 跑在数十万的业务 Pod 中,背地对应的是上万台的服务器。
用不太谨严的一种形式进行粗略的估算,以惯例的服务器 CPU Xeon E5 为例,TDP² 为 120W,0.7% 120W 24 * 365 / 1000 = 73584 度电,每一万台机器一年 7 万度电,这还不包含为了放弃机房温度而带来的更大的热交换能耗损失(简略说就是空调费,惯例机房 PUE 大略 1.5),依照不晓得靠谱不靠谱的专家估算,节约 1 度电 = 减排 0.997 千克二氧化碳,这四舍五入算下来大略缩小了 100000kg 的二氧化碳吧。
同时这也是一行开源社区的代码,社区曾经驳回咱们的倡议(3)将该个性默认设置为敞开,或者有上千家公司数以万计的服务器也将失去收益。
|注 2: TDP 即热功耗设计,不能等价于电能功耗,热设计功耗是指处理器在运行理论应用程序时,可产生的最大热量。TDP 次要用于和处理器相匹配时,散热器可能无效地冷却处理器的根据。处理器的 TDP 功耗并不代表处理器的真正功耗,更没有算术关系,但通常能够认为理论功耗会大于 TDP。
「扩大浏览」
- time: Sleep requires ~7 syscalls #25471:
https://github.com/golang/go/issues/25471 - How does Go know time.Now?:
https://tpaschalis.github.io/golang-time-now/ - It’s Go Time on Linux:
https://blog.cloudflare.com/its-go-time-on-linux/ - 69390: runtime: use vDSO on linux/386 to improve – – time.Now performance:
https://go-review.googlesource.com/c/go/+/69390
(1)查证材料:https://github.com/golang/go/issues/25471
(2)Golang 的优化:https://go-review.googlesource.com/c/go/+/69390
(3)咱们的倡议:https://github.com/alibaba/sentinel-golang/issues/441
感激艺刚、茂修、浩也、永鹏、卓与等同学对问题定位做出的奉献,本文局部援用了 MOSN 大促版本性能比照文档提供的数据。同时感激宿何等 Sentinel 社区的同学对相干 issue 和 PR 的积极支持。
本周举荐浏览
技术风口上的限流
深刻 HTTP/3(一)|从 QUIC 链接的建设与敞开看协定的演进
网商双十一基于 ServiceMesh 技术的业务链路隔离技术及实际
降本提效!注册核心在蚂蚁团体的变质之路