兄弟们,最近在信创项目上折腾飞腾和鲲鹏的K8s集群,有点心得,出来跟大伙儿唠唠。
要说国产CPU,纸面参数是真不差,核多,主频也不低。但真把业务压上去,你会发现一个很尴尬的情况:CPU使用率才到50%,吞吐就上不去了,延迟还忽高忽低。开始我以为是咱代码写得烂,后来一查,问题出在内存带宽上——更准确地说,是NUMA架构下内存访问的“远近亲疏”闹的。
把NUMA理解成“食堂”
NUMA是啥?打个比方。一台物理机就是个园区,有多个食堂(CPU socket,也就是NUMA node),每个食堂旁边有自己的食材仓库(本地内存)。厨师(CPU核)在自己食堂炒菜,去旁边仓库拿菜,那是又快又省劲。但如果隔壁食堂的菜快用完了,得跑到老远的中央仓库去调货,路又远,路还窄——这个“跨node访问内存”就是性能杀手。
飞腾、鲲鹏这种多路服务器,尤其明显。本来你分配了16个核给K8s里的pod,K8s调度器一看,哪个node空闲就往哪塞,结果这16个核可能分布在两个食堂。业务一跑,一半的核在“跨食堂抢菜”,内存访问延迟翻倍不说,还会挤占互联通道的带宽,整个集群的内存带宽瓶颈直接被拉满。
K8s里的坑:调度器不懂“远近亲疏”
K8s默认调度器很“直男”,只看CPU和内存够不够,根本不管NUMA拓扑这回事儿。你部署一个高并发的Java服务,say 8个pod副本,它有可能把pod A的CPU分在node0和node1各几个,内存又挂在node0。这就像你让一个厨子同时用两个食堂的灶台,还指定他只能去其中一个仓库拿菜——纯粹给自己添堵。
怎么破?三个组合拳
第一,给节点开CPU Manager的静态策略。Kubelet配置里把cpuManagerPolicy改成static,这样pod绑核,CPU不再乱跳。但这只是第一步,静态策略只能保证CPU不飘,管不了跨node的问题。
第二,上拓扑管理器(Topology Manager)。把topologyManagerPolicy设成single-numa-node,意思是:这个pod要的所有资源,必须从同一个NUMA node里出。如果那个node资源不够,宁可调度失败,也不凑合着跨node分。这样pod就跑在同一个食堂里,内存访问基本是本地命中,性能稳稳的。
第三,对于特别敏感的业务,比如Redis、高并发网关,干脆手动指定NUMA亲和性。在pod的yaml里加上resources.limits和cpu的绑定,或者借助Node Resource Manager这类NUMA感知调度器,让调度器一开始就知道:这pod只认node0。
真实场景的翻车与救回
我们有个压测环境,飞腾S2500双路服务器,跑一个内部网关服务。一开始压到3万QPS就上不去了,CPU平均才45%,但整机内存带宽飙到85%以上。一顿分析发现,pod的4个CPU核分散在node0和node1,大量内存访问在走跨node的link。后来我们开了CPU Manager static + Topology Manager single-numa-node,把pod重启,压测数据直接变样:QPS到了5.5万,CPU利用率上到65%,内存带宽降到60%以下,P99延迟也从20ms降到8ms。就这么粗暴。
最后唠叨两句
国产CPU的底子其实不差,但它在服务器场景下对NUMA的敏感度确实比某些老牌架构更明显——因为飞腾/鲲鹏的访存路径更长,跨node代价更大。你在信创项目里如果发现“CPU不高但性能上不去”的邪门问题,先别急着骂代码,看一眼pod的CPU分布和内存命中的NUMA node,可能问题当场就解了。
这玩意儿就是一层窗户纸,捅破了,你也能当“信创老兵”。
更多方案,可访问 itfangan.com 看看,里面有不少实战案例可以参考。