尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Kamailio内存管理详解:PKG与SHM分配机制、碎片化原因与排障实战
Kamailio用久了早晚会撞上“内存”这堵墙。我第一次被它折腾是在一个线上SIP网关项目里业务高峰一过日志里准时冒出一批shm_mem_alloc: Not enough memory当时团队里的普遍共识是“重启一下就好”结果重启完确实安静几天过阵子又复发。直到我认真把Kamailio的内存管理机制整个捋了一遍才意识到问题根本不是“内存不够大”而是我一直没搞懂它那两套内存池是怎么分工、怎么分配、怎么碎掉的。这篇文章就围绕Kamailio的内存管理展开把两套内存池PKG和SHM的职责划分、底层分配器的设计逻辑、碎片化形成的原因、以及真正遇到分配失败时的排查链路完整讲清楚。不管你是刚接触Kamailio的运维还是已经在用但是每次调参都靠猜的开发者这篇文章都值得你从头到尾看完尤其是后两章排障和压测的地方能帮你少走不少弯路。1. 双池架构PKG与SHM各自管什么1.1 为什么Kamailio要自己管理内存而不是直接用系统malloc很多刚接触Kamailio的人会有一个疑问明明系统自己有malloc/free为什么Kamailio要费劲维护两套独立的内存池原因有两个。第一SIP服务器是典型的长时间运行、高并发、小对象高频分配释放的进程。系统自带的内存分配器在“多线程同时高频malloc”的场景下一方面要做锁竞争另一方面面对大量不同大小的对象容易出现堆内存碎片化。碎片化一旦严重你会发现明明总内存还很宽裕但一次malloc(几百KB)就是失败。Kamailio核心对性能极其敏感任何一次分配延迟都会直接反映在呼叫处理的时延上所以它必须自己控制内存分配策略。第二SIP业务本身需要跨进程共享数据。Kamailio默认是多进程架构每个子进程各自处理SIP消息。但如果每个子进程各自维护一份注册用户表、对话状态表这些状态就无法统一。虽然也可以用外部数据库或Redis来做共享存储但为了极致性能Kamailio选择在进程内部开一块共享内存让所有子进程直接读写这块区域省掉所有序列化、网络传输、加锁开销。于是就有了两套池子每一个子进程独享的PKGPackage Memory池以及所有进程共享的SHMShared Memory池。这两个池子的分配器、大小、生命周期完全不一样调参时也必须分开看。1.2 PKG内存池每个进程独占的“便签纸”你可以把PKG理解成每个人工位上的便签纸。每个Kamailio子进程有一沓自己的PKG内存一般默认只有几MB常见默认是4MB到8MB取决于编译参数和启动参数。它只服务于当前进程内部其他进程碰不到进程退出了这块内存也直接回收不存在跨进程同步的问题。PKG里放的都是“临时工具”SIP消息解析过程中的临时结构体解析URI、headers用的中间buffer解析完一条消息后这些临时对象就释放了。事务处理过程中产生的局部变量$var临时变量、$avp在当前请求处理流程中的存放位置很多会落在PKG上。各种字符串操作、格式转换的临时结果。PKG的特点是分配释放都非常频繁但每个对象生命周期极短基本是“随请求而来随应答而走”。正因为生命周期短所以碎片化相对可控而且单进程的内存使用不会影响其他进程。PKG不够用通常是因为某一个SIP请求瞬时吃了很大一块PKG。比如一条携带超大body的MESSAGE消息或者某个模块在单消息处理里一次性构造了很大的数据结构。这时你会看到类似pkg_mem_alloc: Not enough memory (size 4096)的报错而SHM的内存实际还很健康。注意-S参数限制的是SIP消息体的最大缓冲字节数这条消息本身需要PKG来承载。如果你线上会有超大SIP消息比如视频通话的SDP、大附件IM要在压测时特别关注PKG的占用而不是只看SHM。1.3 SHM内存池多进程共享的“白板”SHM就是团队成员共享的那块大白板谁都能看谁都能写。Kamailio启动时在系统共享内存段里圈出一大块区域所有子进程通过指针直接访问。它的大小由-m参数指定一般默认是32MB或64MB生产环境通常要按业务规模手工调大。SHM里承载的是所有需要跨进程共享的、生命周期较长的数据对话状态dialog每个正在进行的呼叫通道都对应一个dialog结构里面记录Call-ID、双方tag、超时时间等。事务状态tm模块tm模块的每个事务都需要在SHM里保存请求和响应状态保证客户端重传时不重复处理。注册用户表usrloc用户注册的联系地址、过期时间全部放在SHM中的哈希表里所有进程查询的是同一份数据。各类缓存哈希表htable模块的条目、限速模块IP计数器、以及很多其他模块的共享缓存数据。所以SHM的大小和业务并发量、注册用户数直接挂钩。一个生产系统如果SHM配置得很小带来的就是“看起来每个指标都正常一到大促就崩”的典型事故现场。1.4 判断一段数据该放哪个池代码层直觉开发过Kamailio模块的人每天都要面对一笔分配选择。判断标准其实就一条这段数据是否需要跨进程共享、是否要活得比当前请求更久只需要当前请求里临时用一下、用完就扔的放PKG需要在多个进程间保持一致、或者在请求结束后依然存在的放SHM。前者相当于函数里的局部变量后者相当于全局变量。这个道理听起来简单但实际开发中很多人会在“短暂创建、长期持有”的边缘场景犯了迷糊。某个模块里逻辑上只是想在当前请求里随手记个值结果用了一个全局hash表往SHM里写清理逻辑又没做好——恭喜你造了一个内存泄漏点。这句话不是调侃我后面会提到一个真实案例。2. 分配器的底层设计碎片化从哪来的2.1 “自研”分配器要解决什么问题Kamailio核心并不是直接用malloc来管理PKG和SHM而是把大块内存先系统级申请下来再自己内部做二次分配。这套二次分配器才是它内存管理真正的灵魂。这个自研分配器要解决三个问题降低锁竞争SHM是被所有进程共用的如果每次分配都靠系统级的malloc加锁高并发下所有进程都会在这一个点上抢锁。自研分配器通过精细的空闲链表分级、以及尽量缩小临界区把锁粒度做得比系统默认的小得多。精确统计系统malloc你不会知道当前堆里到底用了多少、哪些还空着、碎片有多少。Kamailio的分配器在每个内存块头部记录了大小、状态并且能汇总出total、real_used、fragments等统计数据这些是运维时判断内存健康度的关键指标。避免系统堆碎片缠身自研分配器可以按固定size class大小类别把空闲块分桶管理分配时按需取最合适大小的块。虽然不能完全消灭碎片但至少比系统堆在长期高并发下的表现可控得多。2.2 空闲链表与内存块合并碎片产生的机制Kamailio的内存块设计核心是“块头数据区”的结构。每个内存块在分配时头部会写入一个控制结构记录这块内存的大小、来源、分配时的调用点信息。空闲的块则会加入某个空闲链表按大小归类。分配器拿到一个请求比如“给我128字节”它去对应size class的空闲链表里找一块空闲块找不到就去更大的list里找找到了再切一块出来剩余部分挂回链表。释放的时候分配器会检查正在被释放的这块内存的相邻区域是否也是空闲的如果是就把相邻的块合并成一块更大的空闲块。这个机制听上去很完美但问题恰恰出在“相邻”上。业务对象的分配和释放顺序是交织的释放了一个dialog空间旁边却是一个活跃的tm事务过了几秒tm事务释放了而原dialog的空间已经被其他对象占用了。这种情况下两个空闲块之间永远插着一个忙碌块它们始终无法合并。这种“小块空闲、无法拼接成大块空闲”的状态就是碎片化。碎片化严重时空闲内存的总量可能还有百分之二三十但分配器找不到任何一个足够大的连续空闲块来满足一次较大请求于是分配失败日志上报错。2.3 碎片化为什么是SHM的头号杀手PKG因为生命周期极短、对象大小比较统一碎片化通常不致命。SHM就悲剧了。SHM里的对象大小极度不均一个注册用户记录可能几百字节一个dialog可能几KB而一次cfg_reload、一批路由规则加载可能需要一次性分配几十KB甚至几MB的连续区域。既然对象的生命周期长短不一、大小差异巨大碎片化就注定会随着运行时间越来越重而我前面在开头提到的“上线时64MB足够、跑两月后经常报错”本质就是碎片化积累而不是突发流量把内存吃光了。很多运维看不懂这个逻辑一看shm_mem报错就急着把-m加大加大后问题确实会推迟出现但只要碎片化的根因还在它只是把死亡时间延后了并没有治本。2.4 应对碎片化的内置手段Kamailio自己是知道碎片化这个死穴的所以它在编译、运行上都给了一些缓解选项。编译期可以选择不同的分配器实现。核心代码里有不同版本的malloc实现比如简单快速优先的以及多空闲链表抗碎片倾向的。具体哪个更适合你要看你们的对象分配模式通常默认方案已经是比较均衡的选择。运行期的memlog参数可以帮你观察大块分配的频次和大小。设置memlog1后核心会记录超过一定阈值的分配请求这些日志能直接告诉你“谁在频繁申请大块内存”。碎片整理本身没有自动化的“线程”真正有效的应对手段是给SHM留出足够的余量让碎片化造成的“有效可用空间”始终够用同时在业务低峰期做一次进程重启把碎片清零。这不是偷懒是SIP服务器领域里非常实际的一种“内存隔一段就收缩一次”的做法很多资深的系统管理员都这么干。3. 排障链路当“Not enough memory”真的出现时3.1 先看清是哪个池在报警内存报错出现时第一件事不是急着调参数而是先看清报错文案。常见报错有两条CRITICAL: core [core/mem.c:106]: shm_mem_alloc: Not enough memory (size 524288) CRITICAL: core [core/mem.c:77]: pkg_mem_alloc: Not enough memory (size 4096)第一条是SHM第二条是PKG。看函数名里的shm_mem_alloc还是pkg_mem_alloc这两个的处理方向完全不同。我之前见过一个同事把“pkg_mem_alloc报警”当成SHM内存不足把SHM从128MB调到了512MB结果问题依旧。原因很简单-m只影响SHM而PKG的大小是用-M设置的。他根本没看报警里写的是PKG。做一个快速的经验总结如果是PKG报错优先看本进程是否在处理超大消息、是否路由里写了大量循环拼字符串、是否某个模块在单请求内大量申请临时内存。如果是SHM报错优先看整体业务并发、注册用户规模、dialog/tm事务数量以及运行时长带来的碎片化问题。3.2 用kamcmd还原现场free很大但不够 vs free真的见底确认是哪个池之后立刻用kamcmd看实时内存状态。核心经验是先判断“总量够不够”再判断“碎片严重不严重”。kamcmd core.shmmem输出里重点看这几项total_sizeSHM的总字节数。free_size目前空闲字节数总和注意这是“所有空闲块相加”不代表能分配出这么大。real_used实际使用字节数包括分配器管理开销。max_used历史峰值使用量。fragments碎片数量也就是空闲块的数量。碎片数越多说明内存被拆得越碎。把这几个数组合起来判断情况Afree_size明明还有百分之三四十但分配失败。这是典型的碎片化问题。空闲内存有很多但单个连续块不够大。对策是不用急着加内存低峰期重启释放碎片同时排查哪些模块在频繁申请大块SHM。如果你非要在配置上做文章可以考虑调大SHM但真正治本的是找到那个“大块分配”的源头。情况Bfree_size已经见底real_used接近total_size。这要么是总量配置过低要么是存在内存泄漏。如果max_used一直缓慢上升且没有周期性回落大概率是泄漏。如果max_used从来没有超过某个值但上线一段后free突然从高位跳水说明是某段时间的业务峰值超了预期。3.3 开启memlog与memdbg定位分配调用点确认了SHM是主战场之后就需要把矛头指向“到底是谁在吃内存”。默认情况下Kamailio的内存分配日志是关闭的因为完全打开会很吵、很拖慢性能。但在排障窗口期可以临时开起来看一阵子。配置文件中memlog1 memdbg1memlog1会记录每次超过阈值的分配操作memdbg1会把分配时候调用栈打印出来虽然日志会很庞杂但能直接看到某个模块的某个函数在申请一大块SHM。线上开memdbg要谨慎它可能造成日志盘暴涨、处理性能明显下降。我建议做法是先开memlog1观察几个小时确认大致的大块分配来源实在定位不到再短暂开memdbg收集几分钟日志就关闭。当然如果报错来自某个已知模块也可以先查它的模块参数。例如dialog模块有dlg_hash_size、tm模块有tm_hash_size这些哈希表在初始化时就会一次性申请较大内存调整它们要结合注册用户或事务规模来算而不是随意拍脑袋。3.4 一个被误判为“泄漏”的案例复盘早些年在某项目里遇到过一种情况SHM使用率每周稳定上升业务量没有明显增长大家第一反应都是“完了核心泄漏了”准备升版本、打补丁。后来我们把memdbg开起来盯了半小时发现增长的分配来源集中在某个自定义模块里。那个模块实现了一个“最近联系过的用户”名单功能逻辑上每次呼叫结束后把数据写进一个SHM哈希表并且设置了一个清理周期任务。但代码里清理任务依赖一个时间戳字段而写入时那个时间戳初始化为0清理条件判断永远不成立——写入一直在进行清理从来没触发于是SHM被一点一点吃干净。后来修正了时间戳初始化和清理判断逻辑SHM曲线立刻平了。这个案例说明两件事核心本身的内存管理相当老练绝大多数“泄漏”其实是业务模块写得不严谨排查时要理性不要一上来就怀疑核心先用数据把分配源头锁定再看代码逻辑。4. 监控与容量规划把内存命脉握在自己手里4.1 kamcmd三连命令与输出字段解读运营Kamailio的日子里我最常用的就是三条命令基本每次排查内存都要三连一遍kamcmd core.shmmem kamcmd core.pkgmem kamcmd core.meminfo第二条core.pkgmem看的是当前进程的PKG使用情况。注意kamcmd core.pkgmem在有多个进程时通常查看的是主进程或当前控制socket连到的进程但PKG本来就是每进程独立的看一个进程不能代表所有进程。这也是为什么我会建议再配合日志或者用netstat等看系统层面整体情况。core.meminfo相当于一个汇总视图把它和core.shmmem输出放在一起你可以清楚地看到总内存、已用量、峰值碎片。我的操作习惯是把这些命令的输出接入到周期采集脚本里每小时记录一次保留一周。出现问题时回看这一周的趋势曲线很快就能看出是“锯齿形正常波动”还是“单向爬升的泄漏曲线”比跑到服务器上拍脑袋快得多。4.2 内存消耗估算一种从压测中推算的近似方法很多人问我“一万注册用户、五百并发SHM到底要配多少”这个问题没有标准答案因为内存消耗取决于模块组合、业务配置和消息复杂度。但我有一个非常实用的思路用压测自己测出单路开销再外推。做法是这样先在一个测试环境里用固定的中等SHM配置比如128MB启动Kamailio。记录空闲状态下的SHM used记为基准值。用压测工具比如SIPp从100路并发开始逐步增加到200、400、800路每一档稳定跑10分钟记录real_used。以“每增加100路并发SHM用量增加多少MB”算出一个平均单路开销。比如基准值是30MB跑到800路并发时real_used变成62MB那单路并发大概是40KB上下。这样你要支持5000路并发粗略估算就是30MB 5000 * 40KB约等于230MB再乘一个1.5的余量350MB左右就够了。当然还要叠加注册用户表、缓存表的基础消耗。这个方法的优点是它能把你线上真实的消息大小、路由逻辑、模块状态都折算进结果里比任何经验公式都靠谱。你甚至可以定期做一次这样的压测来校准容量规划。4.3 配置参考表与启动参数如果你暂时没有压测条件下面这张表可以作为起点但记得务必结合业务再调整业务规模PKG-MSHM-m备注演示/小规模8MB64MB注册几百、并发低中小规模8-16MB128MB-256MB注册几千、并发几百中大规模16MB256MB-512MB注册上万、并发上千大规模16-32MB512MB以上务必压测验证别裸奔启动时参数写在命令行里/usr/local/sbin/kamailio -m 256 -M 8也可以把参数固化在配置顶部方便维护和回溯#!define SHM_MEM 256 #!define PKG_MEM 8提醒一句在32位系统上SHM能申请的空间受到地址空间的硬约束通常很难超过2GB。生产环境如果是大内存场景建议直接用64位系统否则SHM配置配得再高系统也分配不出来。4.4 重启策略工具而非迷信前面反复提到碎片化那碎片化有没有自动化的解药坦白说在现有架构下“定期重启”就是最有效的解药。因为分配器虽然有很多花活但它不会像数据库那样自动重整页。我的建议是给Kamailio做好优雅重启的例行安排在业务低峰期比如凌晨三四点用类似kamcmd core.shutdown或者kill配合启动脚本的方式做一次平滑重启让所有子进程的新一轮生命周期从零内存状态起跑。这能让碎片化问题永远没有机会积累到触发分配失败的程度。但重启不是万灵药。如果重启后SHM用量依然在几个小时内就飙升到很高那说明不是碎片化是真实泄漏或者配置不足。重启只是“窗帘”拉开之后你得看清窗外的真实场景而不是每次拉上窗帘假装问题不存在。5. 实践中的几个陷阱与最后的压测技巧5.1 陷阱一把SHM调大就万事大吉很多运维解决内存报错的唯一手段就是调大-m而且调大一倍还不行就调两倍、三倍。这种做法在最早期确实能缓解症状但掩盖了三个真相碎片化会继续累积泄漏不会被发现容量规划永远没有数据支撑。我见过一个系统SHM从128MB一路调到1GB最后连物理内存都吃紧事故反而更大。内存不是KPI正确适配才是。5.2 陷阱二分不清PKG和SHM的报错这看起来是入门问题但在紧急故障时最容易犯。就这一条我怀疑拦住了至少三成的初级运维。以后看到任何内存报错第一眼看函数名是shm_mem_alloc还是pkg_mem_alloc想都不要想先定这个再谈其他。全程把这个错误记在心里能省下大量排查时间。5.3 陷阱三故障时kamcmd连不上Kamailio的控制接口socket是运维的命脉。默认配置下kamcmd需要连接到一个Unix socket或者本地TCP端口。很多部署根本没配置好控制socket又或者改了监听路径导致命令连不上。平时一切正常你感觉不到一旦内存炸了你想查状态却连不上那种滋味我不想再尝第二次。建议在新环境搭建时就验一遍kamcmd连通性确保开机自启动脚本里控制socket路径一致。5.4 陷阱四运行时改配置导致的内存瞬飙Kamailio支持一部分配置参数在运行时通过kamcmd cfg.set修改这很灵活但也很危险。某些模块的参数变更会立即重新构建内部哈希表或者缓存结构一瞬间申请很大的SHM块。如果此时内存本来就紧张一次运行时改参数很可能直接触发分配失败把本来就摇摇欲坠的服务拖垮。除非你有充分把握否则生产环境里这类操作应该选在低峰期并且先看一眼当前内存余量。5.5 最后的压测小技巧最后分享一个我自己的习惯做法在压测环境里故意把SHM配得很小小到只能勉强支撑几百路并发然后用SIPp跑到分配失败。这个过程很反直觉——别人都在想办法把内存调大我却反向操作。但它的价值在于能提前暴露两个问题第一内存不足时Kamailio是被优雅拒绝还是直接崩溃第二你的监控告警链路是否真的能在这个时刻及时响应。毕竟线上永远不会等你调好一切才出问题。我建议每个准备上生产的团队都做一次这样的“内存破产演练”确认你的日志、告警、应急处理方案三者在最坏情况下的表现然后你就会对内存管理这件事真正心里有底。
RELATED

相关推荐

技术大牛转管理为何水土不服?80万年薪案例的转型陷阱

技术大牛转管理为何水土不服?80万年薪案例的转型陷阱

前几天一个朋友跟我聊起他一位同事的事:技术能力极强,带过不少核心项目,年包到了80万上下,为了冲百万年薪去竞聘Team Leader,结果干了半年,下面的人不服他,领导也公开说他“没有管理能力”&…

📅 2026/10/18 7:42:21
光纤通信技术PPT课件:从链路预算到工程实训的讲解指南

光纤通信技术PPT课件:从链路预算到工程实训的讲解指南

简介:这是一份面向通信工程、电子信息专业学生及相关技术人员的《光纤通信技术》PPT课件,可作为课程教学、考前复习或光通信岗位入门的系统性参考资料。课件按五大章节展开:光纤通信概述、光纤传输原理与特性、光发送机与光接收机、光纤通信系…

📅 2026/10/18 7:42:21
工程项目进度计划管理:用CPM重建关键路径与资源约束的动态骨架

工程项目进度计划管理:用CPM重建关键路径与资源约束的动态骨架

简介:本资源是一份面向工程管理从业者、水电项目管理人员及土木工程专业学习者的实务型文档,系统解析大型水电工程进度计划管理的核心方法论与落地实践。内容以锦屏一、二级水电工程为典型案例,深入阐述进度管理的四大维度:针对复…

📅 2026/10/18 7:42:21
MORE NEWS

更多资讯

📰

AI论文写作工具有哪些?4类工具实测拆解与避坑指南

写论文这件事,说实话,绝大多数人卡住的环节根本不是“写”,而是“从零到一”那段最黑暗的启动期:题目定了却理不清逻辑,文献读了不少但捋不出主线,终于憋出几百字又觉得不像人话。我自己带过不少学生&#…

📰

QColor 深度解析:从底层机制到实战避坑指南

实际开发 Qt 项目时,颜色处理几乎躲不开。无论是 QSS 样式表、自绘控件、数据可视化图表,还是简单的按钮状态切换,QColor 都会以一个高频工具类的身份出现在代码里。但这个类并不是大家想象中那么“一眼望穿”,很多细节、坑点和性…

📰

开源愿景论坛议程深度解读:从参会策略到会后复盘

一年一度的开源大会又进入议程发布季了。今年最让我惦记的,还是那个把“全球开源发展愿景”直接写进名字的论坛。这几年大家聊开源,已经从“用什么、怎么用”跳到了“边界在哪、下一步往哪走”,能有一个专门的场子,把来自不同背景…

📰

Multi-Paxos核心优化解析:选主、日志连续性与幂等机制

前两篇把 Basic Paxos 和它的核心证明讲完之后,评论区和私信里问得最多的一个问题就是:光是一个值都要 Prepare Accept 两轮 RTT,那生产环境里一份日志几千上万个条目,总不能每一条都这么来回折腾吧?问得没错。Basic …

📰

Canvas 2D 实时渲染心电波形:高频数据流可视化与性能优化实践

简介:这份资源面向前端开发者与医疗健康类应用开发者,聚焦如何用HTML5 Canvas实现动态心电图绘制,解决浏览器端实时展示ECG波形、模拟监护仪效果的问题。包内共7个文件,以js脚本、html页面、css样式为主,另含txt说明与…

📰

零基础编程学习路线:从第一行代码到完整项目实战

我到现在还记得第一次在终端里敲下那行代码时的感觉,说不上激动,更多是不确定。光标闪了两下,屏幕上跳出一句"Hello, World",我盯着它看了很久,心里只有一个念头:这件事好像没有想象中那么难。就…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬