← 返回新闻列表

Easegress开源网关遭遇etcd内存占用飙升难题及解决方案

在开源软件Easegress的运行过程中,研发团队遭遇了etcd服务内存占用异常增长的问题,导致网关内存飙升至10GB以上。经过深入调查,发现问题症结在于etcd的Raft Log机制处理大尺寸统计数据时引发的巨大开销。通过调整数据写入策略,将单一大键拆分为多个小键,团队成功解决了这一内存瓶颈。

文 / 编辑部 · 2022/05/05 · 阅读约 3 分钟

分享:
Easegress开源网关遭遇etcd内存占用飙升难题及解决方案

近期,开源软件Easegress在实际运行环境中暴露了一个严重的etcd内存占用问题。当API网关配置上千条处理管道(pipeline)后,其内部集成的etcd服务内存消耗急剧攀升,有时甚至突破10GB,且长时间无法回落。

有用户反馈,在Easegress 1.4.1版本中,创建1000个HTTP pipeline后,系统初始内存占用约为400MB,但在未进行任何请求操作的情况下,运行80分钟后增至2GB,200分钟后更是达到4GB。尽管通常情况下API管理不会配置如此大量的管道,但该用户的细粒度控制需求导致了这一特殊场景的出现。

研发团队随即展开调查,发现内存飙升的根源在于etcd。尽管存储在etcd中的数据总量感觉不足10MB,但内存占用却高达数十GB,这让他们最初怀疑etcd是否存在内存泄漏。然而,在排除了etcd版本和已知泄漏问题后,团队转向探究是否对etcd存在误用。经过对etcd设计原理的深入研究,他们发现了几个导致内存消耗巨大的设计特性。

Raft Log机制被认为是首要原因。etcd利用Raft Log来协助追随者节点同步数据,其底层日志存储并非基于文件,而是完全驻留在内存中。etcd至少会保留5000条最新的请求日志。若键值对尺寸较大,例如持续更新一个1MB的键,即使是同一键,5000条日志也会产生高达5GB的内存开销。这个问题在etcd的社区中也有提及,但尚未得到根本性解决。值得注意的是,etcd官方曾将此默认值从10000降低至5000,可能也是出于对内存消耗的考量。

此外,etcd的B-tree索引机制也会增加内存负担。每个键值对都会在内存中维护一个B-tree索引,其开销与键的长度及其历史版本数相关。mmap技术的使用,将boltDB数据库文件映射到虚拟内存中,导致数据库文件越大,内存占用越大。大量的Watcher客户端和监控数量,同样会显著增加内存。

针对Easegress的特定问题,团队最终认定Raft Log是主因。因为Easegress需对每条pipeline进行统计数据收集(如M1、M5、P99等),并将其合并写入etcd的一个键中。若有1000多条pipeline,每条统计信息约1-2KB,合并后将形成一个平均2MB的大尺寸键。这样,5000条内存中的Raft Log便会消耗掉高达10GB的内存。此前由于没有如此多pipeline的场景,此问题并未显现。

最终解决方案是调整了数据写入策略。团队决定不再将所有统计数据写入一个大尺寸的键,而是将其拆分为多个较小的键。尽管实际存储的数据总量不变,但每条Raft Log中的数据量大幅减少。原先5000条2MB的日志会占用10GB内存,现在拆分后,5000条1KB的日志仅需500MB,从而有效解决了内存过高的问题。相关代码变更已提交至PR#542。

总结而言,有效利用etcd需要遵循以下实践:避免使用大尺寸的键和值,以免Raft Log和B-tree索引造成过高内存开销;通过compact和defreg操作定期压缩和整理数据库,控制数据库文件大小;避免创建过多的Watch客户端和Watch数量;并尽可能使用最新版本的Go语言和etcd,以受益于最新的内存管理优化,比如Go 1.16中对MADV_DONTNEED的改进,能更及时地回收内存资源。

广告位 · 文末横幅