
1. 为什么企业通信非得“Kamailio FreeSWITCH”分开跑大概两年多前我接了一个客户的语音项目3000 分机高峰期同时通话将近 800 路要求注册和呼叫都要在 1 秒内完成预算只够买两台物理服务器。当时我的第一直觉是直接上 FreeSWITCH 集群毕竟 FreeSWITCH 本身就能做注册、路由、媒体处理看起来一台就够。结果压测第一轮就翻了车呼叫接通率只有六成SIP 消息大量超时FreeSWITCH 的 CPU 倒是没满但进程的调度和日志已经把整个盒子拖得半死。后来我把所有对外信令收敛到一台 Kamailio把媒体处理和业务逻辑留给后面的 FreeSWITCH整个系统才真正稳下来。这篇文章就把这套架构的完整搭建过程、配置模板和压测心得整理出来给准备做企业通信系统、呼叫中心或者高并发 SIP 网关的人一个可以直接抄作业的参考。1.1 单台 FreeSWITCH 直接对外的问题很多人觉得 FreeSWITCH 什么都能干就直接把公网 IP 绑到 Sofia profile 上让终端注册进来。小规模确实没事但规模一上来问题就非常明显。首先FreeSWITCH 的 Sofia 模块是单实例运行在用户态进程里的所有 SIP 信令、认证、定位、路由决策都要走它的消息队列。信令风暴来的时候进程忙不过来最先崩的不是 CPU而是它的定时器和事务状态机。你会在日志里看到大量Cannot send 200 SIP response、Invalid state之类的东西呼叫还没建立对话已经乱套了。其次FreeSWITCH 作为媒体服务器真正吃资源的是 RTP 转发、编解码、录音、回声消除这些媒体操作。如果它同时还要承担高并发的注册请求和路由查询媒体能力和信令能力就会互相抢进程资源。等于让一个前台接待员同时去搬货两头都干不好。1.2 Kamailio 在架构里的角色定位Kamailio 是一个专职的 SIP 路由器。它不做媒体不处理音频不玩录音它的任务就是把 SIP 消息以最快的速度接收、判断、转发。因为这个定位它对并发信令的处理能力远高于 FreeSWITCH 的 Sofia。在整套架构里Kamailio 是唯一对外暴露的节点。终端只认识 KamailioREGISTER 发给它INVITE 发给它一切信令都在它这里完成入口控制和分发。FreeSWITCH 则躲在 Kamailio 后面通过 dispatcher 网关池接收转发过来的呼叫请求只专注处理媒体和业务。这样做还有一个实际好处FreeSWITCH 的 IP、端口、分机表对外完全不可见。攻击者拿到的永远只是 Kamailio 这个信令入口而 Kamailio 可以非常轻量地做限流、黑名单、冗余丢弃防护成本比在 FreeSWITCH 上做低得多。1.3 这套组合适合什么场景如果你只是搭一个几十人用的内部 IPPBX单台 FreeSWITCH 完全够用没必要引入 Kamailio。但下面这些场景我觉得用 Kamailio FreeSWITCH 的拆分架构是必须的注册用户量在 2000 以上需要把注册信令和媒体处理分开扩容。有多台 FreeSWITCH 做媒体集群需要在前端做统一负载均衡和故障转移。有公网接入需求终端分散在各类 NAT 网络里需要媒体代理统一处理穿透。业务上要求高可用不希望任何一台媒体服务器宕机导致全区呼叫失败。我见过不少团队跳过 Kamailio 直接上 FreeSWITCH 集群然后用 SLB 或者 DNS 轮询做负载均衡结果信令来回不一致、注册状态不同步、RTP 串路问题比不集群还多。与其这样不如从一开始就让 Kamailio 承担信令分发这本来就是它最擅长的事。2. 看懂一次通话的全过程信令与媒体是怎么分头走的在写配置之前先把一次电话怎么打通的路径讲清楚。很多人配置出错不是因为参数写错而是脑子里没有完整的呼叫模型。2.1 注册与呼叫信令路径终端 A 发起 REGISTER消息先到 Kamailio。Kamailio 打开 usrloc 模块把 A 的 contact 地址、过期时间、NAT 探测结果存进位置表然后回 200 OK。这一步 FreeSWITCH 完全不参与所以哪怕有几千个终端同时注册刷新压力都在 Kamailio 身上FreeSWITCH 只管睡觉。呼叫建立时终端 A 发 INVITE 给 Kamailio。Kamailio 在内存里查被叫 B 的位置如果 B 也是注册用户就可以直接把 INVITE 转发到 B如果 B 是坐席、外线或者需要业务逻辑处理比如 IVR、排队、录音Kamailio 就把 INVITE 转发给后端的 FreeSWITCH 网关池。在有媒体代理的情况下INVITE 里的 SDP 会在 Kamailio 这一层被改写终端 A 看到的媒体地址变成 RTPEngine 或者 RTPProxy 的地址后端的 FreeSWITCH 也一样。两边都不会直接接触到对方的真实 IP。2.2 媒体路径与 RTPEngine 介入时机很多人不理解为什么非要让 RTP 绕一圈走媒体代理而不是让终端和 FreeSWITCH 直连。原因很简单企业通信系统里绝大多数终端在 NAT 后面它们的私有 IP 无法被 FreeSWITCH 直接访问。媒体代理就是那个“中间人”它把两端流都拉到自己这里中转格式、端口、IP 全部由它统一协调。RTPEngine 介入的时机在 Kamailio 里是通过rtpengine_manage()控制的。它会在 INVITE 转发前处理 offer SDP在收到 200 OK 或 183 时处理 answer SDP。这样做的目的是让媒体端口在呼叫建立前就完成预分配避免一听到回铃音才发现媒体不通。2.3 Early Media 场景下的特殊处理这里必须单独提 early media因为企业通信里大量场景依赖它被叫振铃时播放回铃音、IVR 放音、彩铃、坐席排队提示音这些都是在被叫真正接听之前就要有媒体流的。如果只是靠 Kamailio 在收到 180 Ringing 后本地生成一个回铃音那很多业务场景是没法做的。正确做法是让 FreeSWITCH 出 183 Session Progress 并且带上 SDP媒体代理在两方向建立媒体通道用户才能听到真实的服务端媒体。这就是为什么 FreeSWITCH 的 external profile 里要开p-early-media-support。这个参数开启后Sofia 允许在早期对话阶段就发送带 SDP 的 183而不是非得等到 200 OK。配合 Kamailio 的rtpengine_manage()early media 的 RTP 才能正常中转。我见过有人在配置里漏了这个参数结果所有 IVR 场景都是“接通后才出声”用户打电话感觉像断线了一样。后面单独一节讲 FreeSWITCH 配置时我会给出具体模板。3. 实施前的环境规划网络、端口、服务器选型配置模板只是最后落地的东西前面如果规划不对后面全是坑。我这里把环境准备阶段最容易出问题的几个点单独拎出来讲。3.1 服务器与操作系统选择Kamailio 对硬件要求很低信令处理是 CPU 密集但内存占用很小的活。一台 4 核 8G 的机器处理几千注册加几百并发呼叫信令完全没问题。FreeSWITCH 才是吃配置的主力媒体转发、编解码、录音都靠它建议至少 8 核 16G 起步并按并发路数按比例加。操作系统我只建议 Linux发行版选 Debian 或者 Ubuntu 都行CentOS 7 已经过了维护期别再用老古董了。Windows 上虽然能装 FreeSWITCH但性能和模块支持都差不少后面我会专门说这个坑。生产环境一定要用 64 位系统这个不需要解释。我自己的习惯是Kamailio 用独立的轻量实例和 FreeSWITCH 分开部署。两台服务器之间走内网专线延迟越低越好。如果预算允许FR 后面再接一套备用的 FreeSWITCH靠 Kamailio 的 dispatcher 探活自动切换。3.2 网络端口清单与防火墙策略端口规划是新手最容易翻车的环节。很多人只开了 5060 UDP结果呼叫能通但一放音就是单向音频。社区里天天有人问这种问题十有八九是 RTP 端口段没放通。下面是我常用的端口规划表你按这个来基本不会漏服务协议端口用途KamailioUDP/TCP5060SIP 信令入口RTPEngineUDP2223与 Kamailio 通信的控制端口RTPEngineUDP10000-20000媒体流端口段FreeSWITCH internalUDP/TCP5060内网 SIP 信令从 Kamailio 访问FreeSWITCH externalUDP/TCP5080对接 Kamailio 的信令端口FreeSWITCH RTPUDP16384-32768媒体端口段注意FreeSWITCH 的 internal profile 和 Kamailio 不要共用同一个 5060 端口否则在同一台机器上会冲突。我习惯把 internal 留在 5060external 改成 5080Kamailio 只对接 external。防火墙上的安全组策略最基本原则是外部到 Kamailio 只放 5060外部永远不应该能直接摸到 FreeSWITCH 的端口。云服务器上更要注意安全组规则别图省事放行 0.0.0.0/0 的全部 UDP。3.3 上云部署的核心注意点现在大多数企业把通信系统部署在云上我用阿里云 ECS 比较多。上云有一个和物理机完全不同的点云服务器的公网 IP 和私网 IP 是分离的网卡上绑的是私网 IP公网 IP 靠 NAT 映射进来。这意味着 Kamailio 和 FreeSWITCH 都必须显式配置“对外公告的地址”。Kamailio 在listen和alias里要写清楚FreeSWITCH 那边就是external_sip_ip和external_rtp_ip这两个变量。我记得有次帮人排查对方用的腾讯云external_rtp_ip忘了写结果注册全好打电话全部单向音频就是这个原因。另外云的带宽和连接数限制要提前确认。RTP 媒体流非常吃带宽一路 G.711 通话大约 80-90kbps800 路并发就是 70Mbps 左右加上信令和开销至少准备 100Mbps 的出口带宽。别只看 CPU 和内存带宽被占满的时候用户感知到的就是“电话能打通但声音断断续续”。4. Kamailio 配置模板逐段解析下面这套配置是我在生产环境里实际跑过的精简版去掉了加密和数据库持久化这些外围内容核心逻辑全部保留。你自己部署时直接复制改 IP 就能用。4.1 全局参数与模块加载创建/etc/kamailio/kamailio.cfg第一段是全局参数和模块。#!KAMAILIO debug2 log_stderrorno memdbg5 memlog5 listenudp:0.0.0.0:5060 listentcp:0.0.0.0:5060 aliaspbcore.example.com # ----------- 模块加载 ----------- loadmodule tm.so loadmodule sl.so loadmodule rr.so loadmodule pv.so loadmodule maxfwd.so loadmodule textops.so loadmodule siputils.so loadmodule xlog.so loadmodule usrloc.so loadmodule registrar.so loadmodule dispatcher.so loadmodule rtpengine.so # ----------- 模块参数 ----------- modparam(rr, enable_double_rr, 1) modparam(rr, append_fromtag, 1) modparam(usrloc, db_mode, 0) modparam(dispatcher, list_file, /etc/kamailio/dispatcher.list) modparam(dispatcher, ds_ping_method, OPTIONS) modparam(dispatcher, ds_ping_interval, 30) modparam(dispatcher, ds_probing_threshold, 2) modparam(rtpengine, rtpengine_sock, udp:127.0.0.1:2223)db_mode0表示注册信息只存内存不上数据库。这个选择在中小规模下是对的因为 usrloc 查内存比查数据库快一个数量级而且 Kamailio 本身是单点重启后终端会自动重新注册数据库持久化意义不大。如果后面做多台 Kamailio 负载均衡再考虑加 Redis 或者 MySQL 做位置共享。4.2 路由逻辑REGISTER、INVITE、RELAY 三段路由接下来是核心路由。Kamailio 的配置文件从上往下就是一个大路由我用route[]拆成功能区方便维护。request_route { if (!mf_process_maxfwd_header(10)) { sl_send_reply(483, Too Many Hops); exit; } if (has_totag()) { route(RELAY); exit; } if (is_method(REGISTER)) { route(REGISTER); exit; } if (is_method(INVITE)) { route(INVITE); exit; } route(RELAY); } route[REGISTER] { if (nat_uac_test(19)) { force_rport(); fix_nated_contact(); setbflag(1); } if (!save(location)) { sl_reply_error(); exit; } exit; } route[INVITE] { set_dlg_flag(4); if (has_body(application/sdp)) { rtpengine_manage(); } if (!ds_select_dst(1, 4)) { sl_send_reply(503, No available destination); exit; } route(RELAY); } route[RELAY] { if (is_method(INVITE)) { rtpengine_manage(); } if (!t_relay()) { sl_reply_error(); exit; } exit; }这里有一个细节nat_uac_test(19)的值是 1、2、4、8、16 这些标志位的组合19 表示同时检测 Via 里有没有 RFC 1918 私网地址、Contact 是不是私网、收到的来源端口和 Via 端口是否一致、还有 rport 是否存在。只要命中其中一个就按 NAT 终端处理改 Contact、强制 rport。set_dlg_flag(4)是启用 dialog 模块的追踪标志rtpengine_manage()在转发前对 SDP 做改写。注意我在这里调用set_dlg_flag(4)但配置里其实还需要加载dialog.so否则这个标志是不生效的。我这版模板为了展示核心逻辑省略掉了你生产用的时候务必把dialog.so加上。4.3 负载均衡dispatcher 网关池的配置dispatcher 是 Kamailio 做 FreeSWITCH 负载均衡的核心。新建/etc/kamailio/dispatcher.list# 组ID 目的地 优先级 权重 1 sip:10.0.0.11:5080 0 0 1 sip:10.0.0.12:5080 0 0ds_select_dst(1, 4)的第一个参数就是组 ID第二个参数是选择算法。4 表示哈希同一主叫的后续消息会尽量打到同一个 FreeSWITCH保持对话亲和性。中小规模我用 4规模再大可以改成 8权重轮询配合自定义参数。Kamailio 会每隔 30 秒给这两个网关发 OPTIONS 探活包连续探测失败两次后自动把故障节点摘掉恢复后自动加回。这个探活机制是生产环境的高可用基础千万不要为了省资源把它关了。5. FreeSWITCH 侧对接配置让 Sofia 乖乖听命Kamailio 配置好只是第一步FreeSWITCH 这边对接不对前面全白搭。5.1 external profile 与 ACL 收紧FreeSWITCH 安装后默认的 external profile 是对公网开放的这很危险。生产上我强烈建议把 external 网关只对 Kamailio 的 IP 开放所有终端注册一律走 Kamailio不直接打 FreeSWITCH。在sip_profiles/external.xml里做三件事改监听端口、关掉匿名呼叫、开 ACL。profile nameexternal settings param namesip-ip value$${external_sip_ip}/ param namesip-port value5080/ param namecontext valuepublic/ param nameauth-calls valuetrue/ param nameapply-nat-acl valuekamailio.auto/ param nameaggressive-nat-detection valuetrue/ param nameinbound-codec-prefs valuePCMU,PCMA,G722,opus/ param nameoutbound-codec-prefs valuePCMU,PCMA,G722,opus/ param namep-early-media-support valuetrue/ param namertp-ip value$${external_rtp_ip}/ param namertp-timeout-sec value300/ param namertp-hold-timeout-sec value1800/ param namedisable-hold valuefalse/ /settings /profile对应在autoload_configs/acl.conf.xml里定义kamailio.auto这个 ACLlist namekamailio.auto defaultdeny node typeallow cidr10.0.0.0/8/ node typeallow cidr192.168.1.0/24/ /listapply-nat-acl的意思是只有匹配这个 ACL 的请求才做 NAT 穿透处理。Kamailio 来的请求都是内网地址命中 ACL所以 FreeSWITCH 拿到 Kamailio 改写后的 SDP 就能正确处理。外部直接打到 5080 的请求因为不在 ACL 里会被直接拒绝。auth-callstrue加上 ACL 默认 deny这一步直接堵死了匿名的外部 INVITE 扫描比在防火墙上过滤更可靠。5.2 p-early-media-support 到底是什么前面提到 early media这里把p-early-media-support讲透。Sofia 的早期媒体有两个阶段一个是 183 Session Progress 带 SDP一个是 180 Ringing 之后才补 SDP。p-early-media-support控制的就是 183 阶段是否允许携带 SDP。这个参数在默认模板里是false很多人没在意。但在 Kamailio RTPEngine 架构里如果 FreeSWITCH 不回带 SDP 的 183Kamailio 的rtpengine_manage()就收不到早期媒体的 answer SDP媒体代理也就不会在早期阶段建立 RTP 通道。结果是用户打电话能听到回铃音那是终端本地生成的但 IVR 放音、排队提示音这类服务端早期媒体全部没声。我排查过的早期媒体问题里大约一半是这个参数没开。还有一半是 Kamailio 在转发 183 时把 SDP 丢掉了。日志里如果看到 183 没有 SDP优先查这两处。5.3 Park 与 Hold 的落地实现企业通信里 Park呼叫暂留和 Hold呼叫保持是高频功能。FreeSWITCH 内置了这两个能力但很多人不知道 dialplan 里怎么写。Park 的核心是把这通电话放到一个“停车场”其他话机可以拨指定号码把电话接走。在拨号计划里加一个简单的 park 分机extension namepark condition fielddestination_number expression^(\d{2})$ action applicationanswer/ action applicationpark/ /condition /extensionHold 则是在通话过程中由话机侧的 re-INVITE 触发FreeSWITCH 收到asendonly的 SDP 后自动进入保持状态。如果你需要在拨号计划里强制把某路通话置为保持可以用hold应用配合hold_music指定保持音乐action applicationset datahold_musiclocal_stream://moh/ action applicationhold/这里有个实际经验保持状态下的 RTP 并不是断掉的而是流方向变为单向媒体代理必须感知到这个变化。RTPEngine 会在 re-INVITE 的 SDP 里看到sendonly从而调整媒体转发方向。如果这时候媒体代理状态不同步就会出现“对方听不到我我能听到对方”这种怪问题。所以 Kamailio 侧一定要保证 re-INVITE 也走route(RELAY)不要在 has_totag 分支里把带 SDP 的 re-INVITE 直接裸转发。我这个模板里has_totag()直接进route(RELAY)就是为这个准备的。6. 媒体代理选型RTPEngine 还是 RTPProxy媒体代理是整个系统中并发能力最容易成为瓶颈的一层。选型选错了后面改起来是最痛苦的。6.1 两者的核心差异RTPProxy 是老牌方案代码简单、稳定、部署容易很多老项目都在用。RTPEngine 是它的后继者功能上强很多原生支持 ICE、SRTP 加密透传、T.38 传真、DTLS还支持多端口复用。在高并发场景下RTPEngine 的内核转发性能也更好因为它的数据面支持多线程。从趋势上讲新项目没有理由再选 RTPProxy。RTPProxy 对 ICE 的支持基本靠补丁而且多年没有大的更新RTPEngine 还在持续演进Kamailio 的rtpengine模块也比老的rtpproxy模块维护得更积极。6.2 安装与对接步骤以 Debian/Ubuntu 为例RTPEngine 可以直接用官方仓库安装也可以用源码编译。生产上我建议直接装发行版自带包省去编译依赖的麻烦apt install rtpengine配置文件在/etc/rtpengine/rtpengine.conf最简配置如下[rtpengine] table 0 interface 10.0.0.10 listen-ng 127.0.0.1:2223 timeout 60 silent-timeout 600 tos 184 port-min 10000 port-max 20000listen-ng就是 Kamailio 里rtpengine_sock对应的那个地址两边必须一致。port-min和port-max是媒体端口段要和防火墙放行的范围一致。启动后验证一下systemctl start rtpengine kamcmd rtpengine.show all能看到NODE: 127.0.0.1:2223并且状态 UP 就说明对接成功了。6.3 媒体端口预留与 NAT 穿透RTPEngine 的端口段要预留充分。一路通话至少两个端口双向 RTP 实际是一个五元组但考虑到 RTCP通常按每路 2 个端口估。10000-20000 这个范围是 10000 个端口理论承载 5000 路并发中小规模够用了。部署在云上的时候interface参数填私网 IPlisten-ng填本机回环地址注意别把回环地址填到 interface 上去。Kamailio 和 RTPEngine 在同一台机器时用回环控制通道最安全RTP 媒体端口段单独在安全组里对终端所在的 CIDR 放行。如果你要对公网开放媒体端口那就要在 RTPEngine 配置里把公网 IP 也加进 interface不然 NAT 响应包会从错误的地址发出去媒体直接断。7. 高并发调优三板斧内核、Kamailio、FreeSWITCH前面配置都正确系统能跑但离“高并发”还差很远。同样的架构调优前后并发上限可能差一倍。这一节讲我最常用的三个层面的调优。7.1 内核参数一开始就要调对很多运维装完系统什么都不管UDP 相关的内核参数全是默认值。高并发 SIP 系统一定要改这几项# /etc/sysctl.conf net.core.somaxconn 65535 net.core.rmem_max 16777216 net.core.wmem_max 16777216 net.ipv4.ip_local_port_range 1024 65535 net.ipv4.tcp_max_syn_backlog 8192 net.netfilter.nf_conntrack_max 1048576 net.netfilter.nf_conntrack_udp_timeout 60nf_conntrack_max这行尤其重要。Linux 的 conntrack 模块会跟踪每一个经过的 UDP 流默认表大小往往只有几万高并发 SIP 环境下几分钟就打满。表满了之后新包直接丢现象就是“呼叫随机失败重启后立刻恢复正常过一会又不行”。这个坑特别隐蔽排查了好久才发现是 conntrack 满了。7.2 Kamailio 侧的并发参数Kamailio 默认使用单进程事件循环你需要在启动参数里指定子进程数。多核机器上children数量通常设置为核心数TCP 进程数单独设kamailio -D -P /var/run/kamailio.pid -DD -n 8-n 8就是 8 个 UDP 子进程。不是越大越好子进程之间的锁竞争在高并发下会抵消多进程优势8 到 16 之间基本够用。Kamailio 内部还有一个重要参数在kamailio.cfg里调modparam(tm, fr_timeout, 10) modparam(tm, fr_inv_timeout, 30)这是事务超时时间。生产环境默认值有时候偏长导致一个下游 FreeSWITCH 卡住时所有转发给它的请求都在内存里挂着。把超时压到合理范围配合 dispatcher 的探活故障转移速度会明显提升。7.3 FreeSWITCH 侧的会话与定时器FreeSWITCH 的并发调优主要在autoload_configs/switch.conf.xmlparam namemax-sessions value2000/ param namesessions-per-second value100/ param namemax-database-handles value500/sessions-per-second决定每秒钟最多接受多少新呼叫。这个值设太高会导致瞬时 CPU 峰值我把 100 作为默认起点实际压测后按曲线再调。真到了 100 都扛不住的时候说明 FreeSWITCH 进程本身已经到极限了该加机器而不是硬调。Sofia profile 里别忘了关掉不必要的日志param namesip-trace valueno/ param namesilent valuetrue/很多团队上线时开着 sip-trace 忘了关每个 SIP 消息的完整报文都被打进日志高并发下磁盘 I/O 直接成为瓶颈。我在压测时见过日志 I/O 占掉整个系统 30% CPU 的情况关掉后并发上限立刻上去了。7.4 引入 Redis 解决分布式状态问题单台 Kamailio 单台 FreeSWITCH 不需要 Redis但一旦 Kamailio 也要做集群或者 FreeSWITCH 多台横向扩展注册位置和对话状态的共享就成了问题。Kamailio 可以借助db_redis模块把 usrloc 的位置表放到 Redis 里多个 Kamailio 实例共享同一份注册数据实现信令入口的横向扩展。FreeSWITCH 侧也可以用mod_redis做缓存查询比如黑名单、话单补全这类高频读操作避免每次请求都查数据库。Redis 本身也要按高并发设计开持久化RDB AOF关闭save太频繁的策略网络层和 Kamailio 走内网别把 Redis 暴露到公网。缓存键的设计上我习惯用业务前缀加主键的方式比如usrloc:1001、blacklist:2001这样后续按前缀做批量清理非常方便。8. 压测与容量评估别等上线了才暴露问题配置完成了不代表系统真的能扛住高并发。我见过太多项目在验收前才匆匆做压测结果一堆问题暴露上线日期一拖再拖。压测要前置从搭建完成就开始。8.1 SIPp 基础压测脚本思路SIPp 是 SIP 压测的事实标准命令行就能模拟大量注册和呼叫。基础压测场景可以这样写这是注册压测sipp -sf register.xml -m 5000 -l 500 -r 50 -i 10.0.0.10 -p 5060 10.0.0.20:5060含义是用register.xml场景总共发起 5000 次注册最大并发 500每秒新增 50 个。-m是总请求数-l是最大并发数-r是每秒速率。这三个参数基本就是压测的“三板斧”调它们就能控制压力曲线。register.xml里核心就是发 REGISTER、收 200、发 BYE。压测的时候盯着 Kamailio 侧的kamcmd tm.stats和kamcmd corex.shm如果事务堆积数持续上升说明信令处理已经跟不上了。8.2 JMeter 与真实业务混合压测SIPp 适合单一场景压测但真实业务是混合的注册、呼叫、保持、转接、挂断交错发生。我们这边压测人员用的是 JMeter 配合 SIP 采样插件可以编写包含多个业务流程的测试计划更适合验证整个系统的端到端承载。JMeter 的 SIP 插件支持 UAC 和 UAS 两种模式可以在测试计划里用 CSV 数据文件驱动几百个不同主叫号码并发呼叫。压测时要注意 JMeter 本身也会成为瓶颈一台压力机生成不了足够多的 SIP 消息需要多台压力机分布式执行不然测出来的瓶颈在压力机而不是被测系统。8.3 关键指标与性能瓶颈判断压测看完三个维度基本能判断瓶颈在哪第一是注册成功率。5000 个并发 REGISTER成功率要到 99.9% 以上。低于这个数先查 Kamailio 子进程数和 conntrack 表。第二是呼叫建立时延。从 INVITE 发出到 200 OK 收到正常内网环境应该在 100-300ms 之间。超过 1 秒说明媒体代理或 FreeSWITCH 的处理已经积压。第三是媒体质量。压测时用 RTP 质量统计重点看丢包率和抖动。丢包超过 1%用户就会明显感觉声音卡顿这时候瓶颈大概率在带宽或 RTPEngine 的端口处理能力。我自己习惯压测前先记录基线CPU、内存、网络、进程数压测中每 30 秒抓一次快照压测后再对比。这样定位问题快得多不然一屋子告警根本不知道从哪看起。9. 回看这些坑我在真实环境里踩过的雷最后分享几个我不太想在文档里看到、但实际踩过的坑。每一个都是线上真实教训。9.1 Windows 上装 FreeSWITCH 只能用来学习Windows 版 FreeSWITCH 官方一直在出但性能表现和模块兼容性跟 Linux 差不少。我见过有人图省事在 Windows 服务器上跑生产高峰期一到媒体处理延迟暴涨用户普遍反映声音“拖泥带水”。如果你只是想在本地快速验证一下 FreeSWITCH 的功能、跑通拨号计划Windows 版没问题装好就能用对学习很有帮助。但生产系统尤其是要高并发的场景老老实实上 Linux。这不是 Windows 不行是 FreeSWITCH 的主体优化都集中在 Linux 生态上没必要跟这个较劲。9.2 SIP ALG 与 conntrack 的相爱相杀企业网络里的路由器很多默认开启 SIP ALG它会把 SIP 消息里的 IP 和端口自作主张地改写。但 ALG 的识别逻辑非常粗糙遇到 Kamailio 已经改写过的 SDP 再改一遍媒体地址就彻底乱了。症状是注册正常、呼叫正常但媒体永远是单通或者双不通。另一个和它形影不离的是 conntrack 超时问题。SIP 会话中间有长时间静默比如保持状态conntrack 的 UDP 会话超时后会被清掉媒体代理转发的包就穿不过防火墙了。我把 UDP 超时调到 60 秒不是随便写的是配合 RTPEngine 的心跳机制确保媒体会话在 conntrack 表里始终活着。9.3 100rel 与 Early Media 组合拳SIP 的 100rel可靠临时响应机制在对接运营商线路时经常出现。如果终端发来带100rel的 INVITE而 FreeSWITCH 侧不支持 PRACK早期媒体就会异常甚至出现“只听一遍提示音就断”的怪现象。处理方式是在 FreeSWITCH 的 external profile 里显式支持param nameenable-100rel valuetrue/同时 Kamailio 侧不要做任何剥离100rel标志的操作。很多教程让你在转发前删掉 Require: 100rel 来“规避”问题这治标不治本遇到严格要求 100rel 的运营商线路就彻底断了。正确做法是两端都支持让 PRACK 流程完整走完。9.4 日志不是你想打就能打开发阶段为了调试方便我习惯在 Kamailio 的路由里加一堆xlogFreeSWITCH 开着 debug 级别的日志。上线前必须清掉。压测时一台机器如果 xlog 打印每个消息光写日志就能吃掉一个核。我的做法是分级打日志路由入口打一条 L_INFO 记录主叫被叫异常分支打 L_WARN正常转发不打。FreeSWITCH 侧把loglevel调到notice保留alert和crit的审计需求。日志量立刻下降一个量级问题排查反而更清晰因为噪音少了真正的异常一眼就能看到。这套架构我前前后后在不同项目里落地过好几遍踩过坑也总结出了自己的习惯。每次新项目部署我都是先把 Kamailio 的信令入口立起来再把 FreeSWITCH 的媒体层挂上去最后压测验证容量。按照这个顺序走系统出问题的概率会小很多。配置模板都在上面了改改 IP 就能跑起来剩下的就是在你真实的业务场景里反复打磨。