尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Kamailio + FreeSWITCH 高并发通信架构:信令媒体分离部署实战

Kamailio + FreeSWITCH 高并发通信架构:信令媒体分离部署实战 上周有个做企业通信的朋友找我说他们自研的 FreeSWITCH 语音平台单机跑到 500 并发就开始大量呼叫失败日志里一堆 NO ANSWER 和超时。这问题我太熟悉了——不是 FreeSWITCH 不行而是你让它一个人干了太多事。真正能扛住高并发的企业通信系统几乎都是把信令控制和媒体处理拆开让 Kamailio 在前面顶住 SIP 信令洪峰FreeSWITCH 在后端专注媒体和业务逻辑。这篇文章就是我做了多个呼叫中心、IPPBX 项目之后沉淀下来的实战方案内容包括完整可复用的部署思路、Kamailio 配置模板、FreeSWITCH 调优参数和压测避坑记录适合准备把通信系统从单机撑到集群、从内网放到云上的团队参考。1. 为什么要拆成信令和媒体两层来扛高并发1.1 单机 FreeSWITCH 的瓶颈到底在哪很多团队的起点其实都是单台 FreeSWITCH跑个几十上百并发没问题但一旦用户量上来就开始露馅。FreeSWITCH 要干的事情太杂了它既要做 SIP 注册和鉴权又要执行业务逻辑、呼叫路由还要处理 RTP 媒体流、编码转换、录音、会议、排队。这些东西混在一个进程里互相抢 CPU 和内存任何一个环节成为热点整个平台都会被拖垮。我见过最典型的场景就是企业早上 9 点全员打卡上班几千台分机同时发 REGISTER 刷新注册FreeSWITCH 的 Event 线程瞬间被打满sessions-per-second 参数直接触发保护然后新呼叫全部被拒绝。或者外线呼叫进来时计费、路由查询、数据库读写全部串在一起一个慢 SQL 就能让通话建立时延从几百毫秒飙到好几秒。单机 FreeSWITCH 还有一个隐藏问题就是状态太多。它在内存里维护着大量注册用户、对话、通道、定时器一旦进程崩溃或重启恢复成本很高业务中断时间很难控制。所以单机方案并不是不能跑而是越往后越难扩展。1.2 信令与媒体分离的收益拆成 Kamailio FreeSWITCH 之后分工很清楚Kamailio 只处理和转发 SIP 信令不碰媒体流不执行业务逻辑FreeSWITCH 只负责媒体处理和具体的企业通信业务比如分机互拨、外线接入、语音导航、排队转接。这个拆法带来的第一个好处是信令处理能力大幅提升。Kamailio 是事件驱动的纯信令代理单实例处理几万甚至十几万 CPS每秒呼叫尝试很常见而 FreeSWITCH 单机在高负载下能稳住的 CPS 要低得多。企业通信场景里注册刷新和心跳消息比真实通话多一个数量级这些高频低耗的请求全部由 Kamailio 扛住FreeSWITCH 就不容易被信令风暴打挂了。第二个好处是故障域的隔离。FreeSWITCH 节点出了问题Kamailio 可以继续收信令把新呼叫导到其他健康的媒体节点甚至配合故障转移逻辑返回 503 让终端重试而不是整个平台一起瘫掉。反过来如果 Kamailio 节点出问题已经建立的通话媒体路径不经过它所以已经接通的电话不会断。第三个好处是横向扩展非常自然。信令层不够就加 Kamailio 节点媒体层不够就加 FreeSWITCH 节点两边互不干扰扩容路径比单机堆配置清晰得多。1.3 这套架构适合什么场景不是所有项目都需要上这套架构。我在实战里一般按这几个条件判断并发通话需要稳定跑在 300 路以上或者高峰期有大量注册刷新和呼叫建立请求有双机热备或容灾需求不允许单点故障导致全平台不可用有外网分机、远程坐席、跨地域组网需要处理 NAT 穿透和媒体中转需要把信令入口和媒体资源池分开管理方便后续扩容和对接多种业务如果你的项目只是几十人的内部电话单台 FreeSWITCH 完全够用没必要为了架构而架构。但如果你已经开始考虑高并发、容灾、公网接入这些问题那这套方案基本是绕不开的。2. 部署拓扑与关键模块选型2.1 一条呼叫请求从终端到 FreeSWITCH 的完整路径先把最基础的拓扑讲清楚。我用了一个最小化的落地方案两台 FreeSWITCH 构成媒体资源池一台 Kamailio 作为统一接入入口所有终端、网关、SIP Trunk 都只对接 Kamailio 的地址。一条典型的内部分机互拨流程是这样的软电话把 INVITE 发到 Kamailio 的 5060 端口Kamailio 解析被叫号码根据分机号段找到对应的 FreeSWITCH 节点改写目标地址后转发 INVITEFreeSWITCH 收到 INVITE执行拨号计划通过 user/1002domain 路由到被叫分机被叫摘机后200 OK 沿原路径返回主叫媒体流在两端和 FreeSWITCH 之间直接传输外线呼叫或网关接入也走同样路径SIP Trunk 对接 KamailioKamailio 根据被叫号段或外线字冠把呼叫转发给对应的 FreeSWITCH 节点处理。需要特别说明的是 RTP 媒体流的路径。在内网环境里终端和 FreeSWITCH 之间的 RTP 流直接传输即可不经过 Kamailio这样 Kamailio 的压力非常小。但如果有外网软电话或者终端和 FreeSWITCH 之间存在 NAT 无法直接互通就需要引入 rtpengine 做媒体中转这个后面专门讲。2.2 后端分配策略静态分片优先动态负载兜底很多人在设计后端分发时有误解以为一定要做动态负载均衡所有 FreeSWITCH 节点随便分发就行。但实际上企业通信系统里 FreeSWITCH 是有状态的分机注册在哪个节点被叫路由就要去哪个节点找。如果 INVITE 被随机分发到另一台 FreeSWITCH而两个节点之间没有共享注册数据呼叫就会失败。所以我采用的第一策略是静态分片。把分机号段固定分配到具体的 FreeSWITCH 节点比如 1xxx 号段归节点 A2xxx 号段归节点 B。REGISTER 和 INVITE 都按照分机号码前缀路由到固定节点这样注册状态和业务路由天然一致不依赖任何外部存储故障排查也简单。动态负载均衡并不是不能用但它有个前提所有 FreeSWITCH 节点必须能共享注册和位置信息。一般做法是把注册数据、号码路由数据放到 Redis 或数据库中FreeSWITCH 侧通过 mod_redis 或数据库模块读写Kamailio 侧根据实时会话数或节点权重动态分发。这个方案扩展性好但实现复杂度明显高我通常只在呼叫中心这种话务模型比较动态、对节点利用率要求高的场景才推荐。两种方案的对比如下。对比项静态分片动态负载实现复杂度低只需在 Kamailio 里配置号段映射高需要引入共享存储和状态同步注册一致性天然保证依赖外部存储节点故障处理需要手动调整映射或做冗余组可自动检测并转移呼叫适用场景IPPBX、企业内部通信呼叫中心、话务波动大的云平台2.3 RTP 代理选型rtpengine 还是 rtpproxy当终端和 FreeSWITCH 之间存在 NAT或者出于安全需要隐藏媒体节点地址时必须在信令路径里插入一个 RTP 代理让媒体流经过它中转。Kamailio 生态里最常见的选择是 rtpproxy 和 rtpengine。rtpproxy 是很老牌的方案早期项目里用得很多。优点是简单、占用资源少但缺点是协议较老对 ICE、SRTP、DTLS 等新特性的支持不好维护也基本处于停滞状态。rtpengine 是后起之秀不仅支持常规的 RTP 中转还支持 ICE/STUN/TURN、SRTP 透传、DTLS 交换、端口复用等能力性能和稳定性也更好目前是 Kamailio 官方和社区里更推荐的方案。我的选择很直接新项目一律用 rtpengine。老项目如果只是简单内网使用rtpproxy 还能跑但一旦要上公网、做加密媒体、或者要支持各种奇怪的终端协议栈rtpproxy 会很吃力。rtpengine 部署时需要注意端口范围和网卡绑定。RTP 端口范围要规划好并在防火墙里放通不然媒体流会被丢掉。控制接口一般绑在 127.0.0.1 的 2223 端口只对 Kamailio 开放不要把控制口暴露到公网。2.4 用 Redis 做动态路由与会话缓存增强高并发场景下光靠 Kamailio 内置的静态路由表往往不够灵活。比如你想做动态黑名单拦截、按实时负载切换网关、批量更新号码路由这些需求用纯静态配置会很痛苦。我在这类项目里都会引入 Redis 做缓存和状态同步层。Kamailio 通过 ndb_redis 模块可以直接查 Redis常见用法是把号码段路由表、网关状态、黑名单放进去。路由请求时用 Lua 脚本或 KEMI 逻辑从 Redis 取数据配置变更不需要 reload Kamailio 进程秒级生效。FreeSWITCH 侧也有 mod_redis可以做号码翻译、鉴权校验、动态配置拉取。需要注意的是Redis 在这里定位是“缓存和加速”不是核心业务数据库。你可以用它存热点路由、实时会话数、动态黑名单但不要把通话详单、录音索引这种海量数据硬塞进去。遇到数据量大的东西老老实实让 FreeSWITCH 写数据库或者消息队列Redis 只负责顶住高并发读。3. Kamailio 侧配置模板与路由策略3.1 最简可用的 kamailio.cfg下面给一份我在中小规模企业通信项目里常用的最小配置。它完成了三件事REGISTER 按分机号段分发到固定 FreeSWITCH 节点、INVITE 按被叫号段分发、其他请求直接透传。#!KAMAILIO # kamailio.cfg - 信令入口与分发节点 loadmodule tm.so loadmodule sl.so loadmodule textops.so loadmodule xlog.so loadmodule dispatcher.so loadmodule rtpengine.so modparam(dispatcher, list_file, /etc/kamailio/dispatcher.list) modparam(rtpengine, rtpengine_sock, udp:127.0.0.1:2223) request_route { # 处理注册请求按分机号段分片 if (is_method(REGISTER)) { if ($fU ~ ^1[0-9]{3}$) { $du sip:192.168.10.21:5060; } else if ($fU ~ ^2[0-9]{3}$) { $du sip:192.168.10.22:5060; } else { sl_send_reply(403, Forbidden); exit; } if (!t_relay()) { sl_reply_error(); } exit; } # 按被叫号码分片 if ($rU ~ ^1[0-9]{3}$) { $du sip:192.168.10.21:5060; } else if ($rU ~ ^2[0-9]{3}$) { $du sip:192.168.10.22:5060; } else { # 非分机号段尝试走外部网关组 if (!ds_select_dst(10, 0)) { sl_send_reply(404, Not Found); exit; } } # 需要媒体代理时取消注释 # rtpengine_manage(replace-origin replace-session-connection); if (!t_relay()) { sl_reply_error(); } }dispatcher.list 文件内容对应如下# setid, destination, flags, priority, attrs 10 sip:192.168.10.31:5060 0 0 10 sip:192.168.10.32:5060 0 0这套配置里我用的是最直接的$du方式做号段分流没有引入复杂的路由模块非常适合先跑通再逐步扩展的节奏。等业务复杂了可以把号段映射搬到 Redis 里用脚本动态决定$du。3.2 REGISTER 分片为什么不能随便负载均衡REGISTER 请求的分发是整个架构里最需要注意的细节之一。很多人上来就对所有请求做轮询结果分机一会儿注册到节点 A一会儿注册到节点 B等别人呼叫这个分机时INVITE 被路由到 A 节点但分机实际上注册在 B 节点系统根本找不到被叫。静态分片方案解决这个问题的方式很简单从号码上就能确定这个分机属于哪个节点REGISTER 和 INVITE 都走同一个映射规则不需要查注册状态。在 Kamailio 配置文件里REGISTER 分支用$fUFrom 中的用户部分做判断INVITE 分支用$rURequest-URI 中的被叫号码做判断两者映射规则一致。还有一种思路是用 dispatcher 模块的哈希算法按 Call-ID 或 From URI 做一致性哈希。但这依赖终端的 Call-ID 稳定性部分软电话重启后 Call-ID 会变依然可能导致注册漂移。所以在企业 IPPBX 场景我始终坚持“按号段静态分片”这是最可控、最不容易出问题的方案。3.3 rtpengine 启用方式与参数说明如果你的环境里有外网分机或需要媒体中转就把 Kamailio 配置里rtpengine_manage那行打开。需要注意启用 rtpengine 后所有带 SDP 的 INVITE 和 200 OK 等消息都会被处理媒体流会经过 rtpengine 中转终端看到的实际媒体地址是 rtpengine 的地址而不是 FreeSWITCH 的内网 IP。rtpengine 服务的启动命令参考如下rtpengine -p /var/run/rtpengine.pid \ -i 192.168.10.10 \ --interfaceeth0/公网IP \ -n 127.0.0.1:2223 \ -m 30000 -M 60000 \ -E-i指定内网接口地址用于与 FreeSWITCH 和 Kamailio 通信--interface指定外网接口用于接收终端发来的 RTP 流-n是控制接口Kamailio 通过这个地址与 rtpengine 通信-m和-M定义 RTP 端口范围防火墙需要放通这一段需要提醒的是rtpengine 本身也是资源消耗大户媒体转发对带宽和 CPU 都有要求。一台 8 核 16G 的 rtpengine 节点跑几千路并发媒体流问题不大但不要让媒体流和信令强混在同一个低配节点上。3.4 内核与进程参数调整Kamailio 作为信令入口要处理大量 UDP 和 TCP 连接默认的 Linux 系统参数往往不够用。我在部署时必改的几个参数如下。ulimit -n 655350 # /etc/sysctl.conf net.core.somaxconn 32768 net.ipv4.ip_local_port_range 1024 65535 net.nf_conntrack_max 1048576 fs.file-max 1048576Kamailio 启动时还要注意子进程数。children和tcp_children参数决定了 Kamailio 能并行处理多少个事件CPU 核心数越多可以开得越大。但不要盲目追求大值因为子进程切换也有开销一般每个 CPU 核心对应 2 到 4 个 UDP 子进程TCP 子进程可以单独调。用kamailio -c可以检查配置语法改完参数后用kamailio -s -P之类的方式平滑重启避免中断注册流量。4. FreeSWITCH 侧配置模板与并发调优4.1 会话数上限与创建速率设置FreeSWITCH 的并发能力有两个关键参数一个是最大会话数max-sessions另一个是每秒创建会话速率sessions-per-second。前者决定系统最多同时处理多少路呼叫后者决定系统每秒能承受多少次全新的呼叫建立请求。这两个参数在autoload_configs/switch.conf.xml中配置。param namemax-sessions value1000/ param namesessions-per-second value50/很多人在调优时喜欢把 max-sessions 调到特别大这是误区。FreeSWITCH 的媒体处理能力受 CPU、内存、带宽限制参数只是上限不是保证值。配置过大反而会导致系统在到达真实承载上限后继续接受呼叫然后出现大量超时、抖动、回声用户体验更差。我的建议是先设定一个保守值然后通过压测慢慢往上探找到系统能稳定工作的临界点再留出 20% 到 30% 的余量。sessions-per-second 尤其重要。如果分机批量重启或者呼叫中心突然进线高峰期每秒呼叫建立量可能瞬间飙升。这个参数设得太低会导致呼叫被直接拒绝设得太高又会让系统过载。一般的经验值是单台 8 核机器先设 30 到 50压测后再调整。4.2 external profile 关键参数解读FreeSWITCH 接收 Kamailio 转发过来的 SIP 请求走的是 external profile默认监听 5080 端口。为了方便对接我通常会把 Kamailio 和 FreeSWITCH 之间的内部通信端口固定在 5060并做 ACL 限制只允许来自 Kamailio 节点的流量。external profile 中几个关键参数需要重点看。param namesip-port value5060/ param namedialplan valueXML/ param namecontext valuepublic/ param nameauth-calls valuefalse/ param nameinbound-late-negotiation valuetrue/ param namertp-ip value$${local_ip}/ param nameext-rtp-ip value$${external_rtp_ip}/ param nameapply-nat-acl valuewan.auto/ param nameinbound-call-early-media valuetrue/ param nameoutbound-call-early-media valuetrue/auth-calls设置为 false是因为前面的 Kamailio 已经做了来源信任控制FreeSWITCH 只从内网接收 Kamailio 转发的请求不需要重复鉴权。但这一步的前提是网络层必须做好隔离不能让终端直接访问 FreeSWITCH 的 SIP 端口inbound-late-negotiation建议开启让 FreeSWITCH 在收到最终 200 OK 后再进行编解码协商可以在一定程度上避免早期媒体协商不一致的问题rtp-ip和ext-rtp-ip分别指定内网 RTP 地址和对外通告的 RTP 地址。如果 FreeSWITCH 在 NAT 后面ext-rtp-ip 必须配置为公网地址否则终端收到的 SDP 里是内网地址媒体流完全不通4.3 早期媒体透传与回铃音处理企业通信里回铃音和彩铃是高频问题。很多外线网关、运营商线路会在收到 INVITE 后先回 183 Session Progress携带 SDP 建立早期媒体通道再播放彩铃或者回铃音。如果 FreeSWITCH 这边的早期媒体支持没有打开主叫可能只听到静音直到被叫摘机后才突然接通。早期媒体相关的参数在不同版本里写法略有差异有的叫inbound-call-early-media有的版本也会写成p-early-media-support作用都是控制 180/183 响应和媒体通道的处理。生产环境我基本都保持开启尤其是对接运营商 SIP Trunk 的场景。还有一种常见问题是FreeSWITCH 桥接分机时主叫侧回铃音是本地生成的而不是被叫侧的早期媒体。这在普通分机互拨时问题不大但如果外线用户拨打公司总机希望听到的是定制的彩铃或语音导航就必须确保早期媒体透传链路完整。排查看 183 的 SDP 是否正常到达主叫侧以及 Kamailio 转发时有没有把 SDP 里的地址改错。4.4 park/hold 在企业流程中的落地最后说一下 park 和 hold这两个功能在企业通信里其实很常用但很多新手不知道怎么在配置里落地。park 的典型场景是前台接到电话需要转给某位同事但同事暂时没空前台先把呼叫挂到“等待位”过一会儿再取回来。hold 则更常见通话过程中需要让对方等待然后自己去做别的处理。FreeSWITCH 的 dialplan 里可以直接用 park 应用。下面是一个简单的示例extension namepark-call condition fielddestination_number expression^5900$ action applicationanswer/ action applicationplayback datatone_stream://%(200,700,600,800)/ action applicationpark data5900/ /condition /extension分机拨打 5900 进入 park 状态其他坐席可以通过桥接或取回码把通话接回来。注意 park 不等于挂断通道仍然占用着媒体资源所以企业里如果大量使用 park 和 hold要额外关注媒体资源消耗。很多呼叫中心会用专门的排队和保持模块来做更精细的控制比如设置等待音乐、超时转语音信箱等业务逻辑会复杂很多但底层的 park/hold 机制是一样的。5. 高并发部署后的问题排查与压测记录5.1 高频问题速查表这套架构跑起来之后日常运维里遇到的大部分问题可以归纳到固定几类。我整理了一个速查表基本覆盖了我做过的项目里出现频率最高的故障。现象可能原因排查命令/方法处理方式分机注册漂移偶尔打不通REGISTER 没有按号段固定分发看 Kamailio 日志中的转发目标修正 REGISTER 路由按号段静态分片外网分机能注册但通话单通SDP 中 RTP 地址不可达抓包看 200 OK 中的 SDP 地址配置 ext-rtp-ip 或启用 rtpengine呼叫建立很慢偶尔超时FreeSWITCH 的 sessions-per-second 被打满fs_cli -x status查看 session count上调 sps 或分流到更多节点主叫听不到回铃音早期媒体未透传抓包看是否有 183 SDP开启 inbound/outbound-call-early-media通话一段时间后单通或断线NAT 超时RTP 路径老化查看 rtpengine 日志和 NAT 超时配置缩短 NAT keepalive 间隔检查防火墙会话超时高频注册导致 FS 负载持续走高终端注册间隔太短或批量重启fs_cli -x show registrations调整注册刷新间隔错峰重启5.2 三个真实踩坑案例第一个坑是单通问题。之前有个项目外网软电话注册和呼叫都能建立但一直听不到对方声音。抓包后发现问题出在 SDP 协商软电话上报的候选地址是内网 IPFreeSWITCH 回 200 OK 时也带的内网地址两边的 RTP 包根本无法互通。后来在 external profile 里配好了 ext-rtp-ip同时在 Kamailio 侧启动了 rtpengine 做媒体中转问题才彻底解决。第二个坑是注册风暴。企业一次性上线几百个坐席软电话统一配置后同时启动REGISTER 消息像洪水一样涌向 FreeSWITCH。sessions-per-second 参数瞬间被打满日志里全是 “Maximum sessions per second exceeded”大量分机注册失败。后来我在 Kamailio 层加了针对 REGISTER 的限速控制同时让终端配置了随机启动延迟注册成功率才恢复正常。第三个坑是压测时发现 Kamailio 吞吐上不去。排查到服务器系统参数时发现文件描述符还是默认的 1024UDP 缓冲区也太小并发一高就开始丢包。调大ulimit和内核网络参数后同样的压测脚本跑下来错误率直接降了一个数量级。所以高并发调优绝不只是调应用层系统层参数往往才是瓶颈。5.3 迁移上云后的压测与容量验证很多团队会把这套通信系统迁到云主机上比如从自建的虚拟化环境迁到阿里云 ECS。迁移之后不能只做功能验证就完事一定要做压测否则到了业务高峰期才发现云上网络和 CPU 调度跟原来环境差别很大那就太晚了。我的压测流程分三步。第一步是功能验证用真实终端把注册、内部分机互拨、外线接入、转接、保持、挂断全部跑一遍确认业务逻辑没有因为网络变化而出问题。第二步是信令压测用 SIPp 构造大量并发 INVITE 和 REGISTER验证 Kamailio 和 FreeSWITCH 的 CPS、注册处理能力。第三步是媒体压测跑真实的 RTP 流观察丢包、抖动、单向通话等问题结合 CPU、内存、网络指标确定节点的真实容量上限。如果项目里还配套了管理平台、API 接口这些 Web 服务我一般会用 JMeter 写一组高并发脚本同时打 API 和管理后台验证整个系统的端到端承载能力。压测完毕后整理出各节点的性能基线数据以后每次扩容或变更都可以拿这个基线做对比。最后再分享一个小技巧这整套架构跑顺之后我最大的体会是Kamailio 和 FreeSWITCH 的配合关键不在于某个配置项调得有多精妙而在于始终记住“谁该做什么”。Kamailio 不是业务交换机不要让它越权处理复杂的呼叫业务FreeSWITCH 不要暴露在不可控的信令洪峰里所有入口流量都从 Kamailio 走。按照这个原则去做配置和扩容大部分高并发问题都能在架构层面被化解掉而不是靠堆服务器硬扛。
返回列表