为什么绕不开 etcd内存到底被谁吃了我们踩的到底是哪个坑解决方案其实很简单用 etcd 的几条实操建议
前几天有个朋友跟我吐槽,说他们网关服务跑着跑着内存飙到 10GB+,还死活降不下来。查了一圈,锅全在 etcd 身上。今天就把这个坑拆开讲讲,顺便把 etcd 那些吃内存的设计一次说清楚,免得你以后踩同样的雷。
先说说背景。任何分布式系统,都得有个强一致性的组件来选主、同步配置。比如 Kubernetes 靠 etcd 组集群,Kafka 早期靠 Zookeeper,后来干脆内置了选主算法把它甩掉了。etcd 是 Go 社区的主力,也是 K8s 的关键组件,基本绕不开。
我们做的 API 网关 Easegress,一开始用 gossip 协议同步状态,后来发现太复杂、太难调试,三年前换成了内嵌版 etcd,把所有配置、统计监控数据、用户自定义数据全塞进去。数据量其实不大,感觉连 10MB 都不到——但内存就是莫名其妙涨到了 10GB,你说气不气人。
把 etcd 的设计翻了一遍之后,发现它至少有四个吃内存的地方,而且个个都是"为了性能"的设计。
第一个,也是最狠的:Raft Log。etcd 用 Raft Log 帮 follower 同步数据,但这玩意儿不是落盘的,是常驻内存的,而且硬编码至少保留 5000 条最新请求。你想想,如果 key 很大,比如一个 key 有 1MB,不断更新它,5000 条 Log 就是 5000MB,5GB 就这么没了。这个 5000 还是写死的,改不了。
第二个,B-tree 索引。etcd 每一对 key-value 都会在内存里建一个 B-tree 索引,跟 key 的长度、历史版本数量都有关系,key 越大、版本越多,内存越肥。
第三个,mmap。etcd 用 mmap 做文件映射,把底层 boltdb 直接映射到虚拟内存里,db 文件越大,内存占用越大。
第四个,Watcher。watch 多、连接数多,内存也会往上堆。
回到 Easegress 的场景:用户配了上千条 pipeline,每条 pipeline 都要统计 M1、M5、P99 这些指标,统计信息大概 1-2KB。我们把这些统计数据合并写到一个 key 里,一千多条合并完,这个 key 平均 2MB。
然后问题就来了:2MB 的 key × 5000 条 Raft Log = 10GB 内存。之前 pipeline 少没暴露,这次上千条直接爆了。
没动 etcd,改的是自己的写法:把大 key 拆成多个小 key 写。数据总量没变,但每条 Raft Log 从 2MB 降到 1KB,内存直接从 10GB 干到 500MB。
说白了,etcd 本身没泄漏,是我们往里面塞了大 value,踩中了它内存级 Raft Log 的设计。
第一,避免大尺寸的 key 和 value,这是最要命的,Raft Log 和 B-tree 索引都会因此爆炸。
第二,控制 db 体积,定期做 compact 和 defrag,压缩加碎片整理,能降内存。
第三,别开太多 Watch,Watch 客户端和连接数都是内存大户。
第四,能用新版本就用新版本。比如 Go 1.12 用的 MADV_FREE 内存回收,进程标记释放但 RSS 不降,看着像泄漏;Go 1.16 改成 MADV_DONTNEED 立刻回收就好了。etcd 3.4 还是用 1.12 编译的,这种坑防不胜防。
你在项目里遇到过 etcd 内存飙升吗?是怎么定位解决的?评论区聊聊,说不定你的经验能救一个正在加班的人。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.