☰
Kamailio+FreeSWITCH高并发架构实战:信令媒体分离指南
2026/10/3 4:01:33 网站建设 项目流程

上个月我帮一个呼叫中心团队排查线上故障,他们上午十点的高峰期就会出现呼入排队、通话单通、坐席掉线,整套高并发企业通信系统眼看就要撑不住。我登录服务器一看进程和会话数,心里大概有数了——他们把信令接入和媒体处理全压在了单独一台FreeSWITCH上,Kamailio根本没用在正确的位置,甚至压根没部署。后来我给他们重搭的架构,核心就一句话:信令和媒体彻底分流。

在开源VoIP圈子里,Kamailio+FreeSWITCH这套组合几乎成了高并发企业通信系统的标配答案。Kamailio站最前面专职处理SIP信令,注册、鉴权、路由、限流都由它扛;FreeSWITCH退到后面专心做媒体处理,IVR、录音、转码、会议都归它管。它俩一个管“接客”,一个管“干活”,各司其职,互不抢资源。这篇实战指南我会把整套系统的架构思路、核心配置模板、RTP代理接入、高并发调优、压测验证和排错经验完整过一遍,配置直接给模板,照着改就能用。适合正在做呼叫中心、统一通信、WebRTC话音接入的工程师,也适合想评估自建通信平台承载能力的架构师。

1. 架构先想明白:信令与媒体为什么必须拆开

1.1 两个服务的分工逻辑

很多人第一次接触这套组合时会犯迷糊:FreeSWITCH明明能自己收SIP、自己出媒体,为什么还要在前面加一个Kamailio?这不就是多绕一跳吗?这个理解没错,但恰恰是没想清楚“信令”和“媒体”是两种完全不同负载这件事。

Kamailio本质上是SIP代理服务器,它不维护呼叫状态,更不碰媒体流。它就像一个快递站的分拣员,拿到一个INVITE请求,查一下路由表,决定下一跳该往哪送,然后把请求转出去就完事了。整个过程中它只关心“谁在哪、要转到哪、怎么转最快”,不创建媒体通道,也不处理RTP包。所以它的并发处理能力极其恐怖,一台2核4G的虚机跑个上万注册、几百CPS(每秒新建呼叫)是很轻松的事,瓶颈更多在网卡和内核参数上。

FreeSWITCH则是标准的B2BUA(背靠背用户代理),一旦呼叫落到它这里,它会拆成两段SIP会话:一段对着主叫,一段对着被叫,中间扛起媒体桥接。它需要维护每个通话的全部状态,处理编解码协商、音频抖动缓冲、静音检测、录音、转码等等。这些操作都是有状态的,每个session都要占内存、占CPU、占线程,并发一高,资源消耗立刻上来了。

打个比方,Kamailio是公司前台,只负责把来访者引导到对应的会议室;FreeSWITCH是会议室里的项目专员,从对方进门到聊完出门,全程陪着。如果让项目专员同时去前台接客,高峰期门口排大队,会议室里也没人服务,两边都耽误。这套架构要解决的就是这个资源隔离问题,把高并发信令负载集中在Kamailio,让FreeSWITCH的线程资源全部用在有效媒体处理上。

1.2 这套组合最适合什么场景

先说结论:只要你的系统里可能出现几百路以上的并发通话,或者有大量SIP终端需要同时注册在线,Kamailio+FreeSWITCH这套组合就值得上。典型场景包括:

  • 客服中心/呼叫中心:几百上千个坐席同时在线注册,外呼、排队、IVR、录音这些业务全在FreeSWITCH上跑,外线中继从Kamailio接入。
  • 企业统一通信:几千分机注册、拨号方案在FreeSWITCH里管理,跨地域多点接入时统一从Kamailio进。
  • 电话会议与语音通知:大量并发外呼由Kamailio按路由策略分发到多个FreeSWITCH节点,避免单点会话数打满。
  • WebRTC话音接入:浏览器/App发起WebRTC呼叫,Kamailio做信令转换路由,FreeSWITCH处理媒体,这套组合很成熟。

但如果你们的规模很小,几十个分机、几十路并发封顶,其实完全没必要上这套组合。单独一台FreeSWITCH或者干脆买云PBX服务更省心。架构复杂度摆在那里,多一个组件就多一套运维,小规模场景用不上这个并发能力,反而增加排障成本。

1.3 为什么不用其他一体化方案

不少人问过我:OpenSIPS不也能做吗?Asterisk不行吗?直接上商业SBC不更省事吗?这些方案我基本都试过,各有各的适用场景,但放到“高并发企业通信自建”这个需求下,Kamailio+FreeSWITCH是我的默认选项。

方案组合优点踩坑点
单机FreeSWITCH部署快,文档多,上手门槛低信令和媒体共享进程资源,并发高了互相挤;横向扩展困难
OpenSIPS+FreeSWITCH和Kamailio定位相同,路由能力也很强配置体系较复杂,新手排障成本高;社区资料相对少
Kamailio+Asterisk生态老,资料多Asterisk媒体处理性能明显弱于FreeSWITCH,大并发媒体桥容易成为瓶颈
商业SBC+FreeSWITCH设备成熟,SIP协议兼容性好授权费用高,私有化定制受限,不透明的地方多

我个人选Kamailio还有一个原因:它的路由脚本非常“可读”。request_route里的逻辑就像在读一段伪代码,请求从进来到出去每一步干了什么都能看明白。出问题的时候,打开配置文件从头读一遍基本就能定位到是哪段路由逻辑出了问题。这种排障友好度在生产和应急场景里真的很值钱。

2. 从零搭建:节点规划与核心配置模板

2.1 服务器节点分配和端口规划

先聊部署拓扑。生产环境我建议最低两节点起步:一台信令节点跑Kamailio,一台媒体节点跑FreeSWITCH。信令节点对硬件要求不高,2核4G起步足够应对几千注册和几百CPS;媒体节点的CPU和内存按并发路数给,比如目标是300路并发通话,建议8核16G以上,具体和编码、是否转码、是否录音强相关。

如果业务量再大,可以扩到四节点:两台Kamailio做容灾和负载,两台FreeSWITCH做媒体池,前面用DNS轮询或者keepalived做入口,后面共享同一个数据库或者Redis。这里面有个原则:信令层可以无状态横向扩容,媒体层靠加节点摊并发路数。不要试图在一台FreeSWITCH上无限制调参来扛几百上千路,进程内的线程调度和锁竞争迟早会成为瓶颈。

端口规划也很重要,特别是云上部署。我列一张常用端口清单:

节点协议端口用途
KamailioUDP/TCP5060对外SIP信令入口,需公网放行
KamailioUDP22222和rtpproxy通信,仅内网
FreeSWITCHUDP/TCP5080接收Kamailio转发的SIP信令,仅内网
FreeSWITCHUDP10000-20000RTP媒体端口范围,Kamailio/rtpproxy需可达
rtpproxy/rtpengineUDP30000-40000媒体中继端口范围,需对终端公网放行

如果是把一套自建系统迁移到阿里云ECS这类的公有云环境,我的建议是:系统版本和内核参数尽量和原来保持一致,不要借着迁移顺手升级大版本。很多迁移后出问题的case,本质不是业务代码变了,而是操作系统、依赖库、业务软件版本组合变了。用相同版本镜像重新部署,再跑一轮压测确认性能基线,比直接做跨版本升级再压测要稳得多。

2.2 Kamailio核心配置模板

直接上模板。这是我在生产环境用的精简版,去掉了注释以外的花活,保留了注册、鉴权、NAT穿透、呼叫转发到FreeSWITCH的核心逻辑。保存为/etc/kamailio/kamailio.cfg:

#!KAMAILIO #!define WITH_NAT #!define WITH_RTPPROXY # ----------- 全局参数 ----------- log_level=2 log_stderror=no log_facility=LOG_LOCAL0 children=16 alias="sip.yourdomain.com" # ----------- 模块加载 ----------- loadmodule "sl.so" loadmodule "tm.so" loadmodule "rr.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "sanity.so" loadmodule "textops.so" loadmodule "xlog.so" loadmodule "registrar.so" loadmodule "usrloc.so" loadmodule "nathelper.so" loadmodule "rtpproxy.so" # ----------- 模块参数 ----------- modparam("usrloc", "db_mode", 0) modparam("registrar", "method_filtering", 1) modparam("registrar", "min_expires", 60) modparam("registrar", "max_expires", 3600) modparam("nathelper", "natping_interval", 30) modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:22222") modparam("rtpproxy", "rtpproxy_tout", 2) # ---------- 标识位 ---------- request_route { # 基础校验 if (!sanity_check()) { sl_send_reply("400", "Bad Request"); exit; } # 尝试记录路由信息 record_route(); # NAT穿透处理 force_rport(); if (nat_uac_test("19")) { if (is_method("REGISTER")) { fix_nated_register(); } else { fix_nated_contact(); } setflag(6); } # 注册请求 if (is_method("REGISTER")) { if (!auth_check("$fd", "subscriber", "1")) { auth_challenge("$fd", "0"); exit; } save("location"); exit; } # 呼叫请求统一转到FreeSWITCH if (is_method("INVITE")) { route(CALL_TO_FS); exit; } # 其他请求按路由表转发 if (!t_relay()) { sl_reply_error(); } } # ---------- 转发到FreeSWITCH ---------- route[CALL_TO_FS] { if (has_totag()) { route(RELAY_INVITE); exit; } # 设置下一跳为FreeSWITCH external profile $du = "sip:192.168.1.20:5080"; # 初始INVITE带SDP,交给rtpproxy协商媒体 if (has_sdp()) { rtpproxy_offer("coi"); } if (!t_relay()) { sl_reply_error(); } } # ---------- 会话内INVITE/ re-INVITE ---------- route[RELAY_INVITE] { if (has_sdp()) { rtpproxy_answer("coi"); } if (!t_relay()) { sl_reply_error(); } } # ---------- 应答处理 ---------- onreply_route { if (is_method("INVITE")) { # 处理180/183等早期媒体,回铃音就靠这里 if ((status_code == 180 || status_code == 183) && has_sdp()) { rtpproxy_answer("coi"); } } }

这里有几个点要特别说明。$du是目的地URI,我把所有进到Kamailio的INVITE都强制指向FreeSWITCH的5080端口,也就是external profile。如果你们用域名或者有多台FreeSWITCH,这里可以换成dispatcher模块做负载均衡,或者用dispatch_set从数据库读节点列表。

rtpproxy_offer("coi")里的参数,c代表改写SDP连接地址,o代表改写origin字段,i代表使用rtpproxy的对外接口IP。这些标志位在绝大多数NAT穿透场景下照着抄就行,等你们自己喝透了NAT原理再按需调整。

里面对REGISTER做了auth_check,数据库模块我为了模板简洁没有把subscriber表结构写出来。如果你用的是db_mysql且没有初始化认证表,可以先把这四行注释掉,直接用IP白名单方式信任内网请求,但生产环境建议加上digest认证,否则任何人都能把分机注册到你的系统里盗打,话费哗哗地走。

2.3 FreeSWITCH侧配置模板

FreeSWITCH这边要动的地方主要是Sofia SIP Profile和Dialplan。先看/etc/freeswitch/sip_profiles/external.xml,这是接收Kamailio转发的关键配置:

<profile name="external"> <settings> <param name="sip-ip" value="0.0.0.0"/> <param name="sip-port" value="5080"/> <param name="rtp-ip" value="0.0.0.0"/> <param name="rtp-port-min" value="10000"/> <param name="rtp-port-max" value="20000"/> <param name="apply-nat-acl" value="wan.auto"/> <param name="apply-inbound-acl" value="kamailio_acl"/> <param name="auth-calls" value="false"/> <param name="context" value="public"/> <param name="p-early-media-support" value="true"/> <param name="inbound-late-negotiation" value="false"/> </settings> </profile>

auth-calls=false看着危险,但在这个架构里是合理的,因为认证工作在Kamailio那一层就做完了,FreeSWITCH对Kamailio转发来的请求不需要再验证一遍。当然前提是apply-inbound-acl配置正确,只信任Kamailio的IP。对应的ACL配置在/etc/freeswitch/acl.conf.xml:

<list name="kamailio_acl" default="deny"> <node type="allow" cidr="192.168.1.10/32"/> </list>

Dialplan方面,最简单的版本就是把从external进来的去电转入default上下文,再路由到分机或者呼叫队列:

<extension name="from-kamailio-to-ext"> <condition field="destination_number" expression="^(\d+)$"> <action application="transfer" data="$1 XML default"/> </condition> </extension>

这样Kamailio把INVITE甩过来之后,FreeSWITCH就开始按自己的拨号方案处理了,路由、排队、IVR全部在FreeSWITCH内部解决。

如果有人想在Windows上装FreeSWITCH先做功能验证,我可以分享两个经验。第一,解压路径千万别带空格,不要装到C:\Program Files下,放到D:\FreeSWITCH这种纯英文无空格目录,否则很多模块加载会莫名其妙失败。第二,启动时用管理员权限运行FreeSwitchConsole.exe,Windows防火墙会弹窗,必须允许UDP 5060端口和RTP端口范围,不然电话只通一半,信令到了但媒体不通,你还会以为是配置写错了。不过Windows版只建议用来测功能,生产环境老老实实上Linux,性能和稳定性差太远了。

3. 重点难点:媒体流转发与早期媒体处理

3.1 NAT与RTP代理原理

这章是整个搭建过程中最容易翻车的部分,也是Kamailio+FreeSWITCH架构里最需要理解透的环节。很多项目搭起来,注册没问题、呼叫也能建立,但电话接通后就是没声音,十有八九是媒体流没处理好。

为什么需要RTP代理?核心原因就是NAT。企业员工的软电话在办公网里,IP是私网地址,比如192.168.x.x。它发出的SIP INVITE里,SDP部分携带的c=行和m=行都是这个私网IP和端口。这个报文经过Kamailio转给FreeSWITCH时,FreeSWITCH看到的SDP地址依然是私网的。如果终端和服务器不在同一个内网,FreeSWITCH把媒体包发往192.168.x.x,这个包在企业路由器上直接被丢弃,结果就是:信令正常、RTP到不了,双方都听不到声音。

rtpproxy和rtpengine解决的就是这个问题。它们作为媒体中继,修改SDP里的连接地址为其公网地址,让呼叫双方的RTP都发往这个公共媒体节点,由它再转发给真正对端的私网地址。可以理解为在中间加了一个“媒体快递站”,谁都不知道对方的真实地址,但快递站知道,于是所有媒体包都经过它中转。

3.2 rtpproxy启用和配置实操

rtpproxy比较轻量,社区资料也多,是很多人的第一选择。先装包:

# Debian/Ubuntu apt install rtpproxy # CentOS/RHEL yum install rtpproxy

启动前要明确它的监听地址,建议直接手动指定。假设服务器公网IP是203.0.113.10:

rtpproxy -l 203.0.113.10 -s udp:127.0.0.1:22222 -u rtpproxy -d

-l指定对外接口IP,-s指定控制socket地址,Kamailio通过这个socket和rtpproxy通信。-d是前台运行便于调试,生产环境可以用systemd管理。注意rtpproxy默认的RTP转发端口范围是40000-41000,如果云安全组没放行这个范围,媒体必然不通。有些发行版自带的rtpproxy默认端口范围和FreeSWITCH的默认RTP范围还不一样,建议统一规划:比如rtpproxy用30000-40000,FreeSWITCH内部用10000-20000,安全组两边都放行。

然后在Kamailio里启用rtpproxy模块,模板里已经写好了:

loadmodule "rtpproxy.so" modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:22222")

同时注意route[CALL_TO_FS]和route[RELAY_INVITE]里的rtpproxy_offer/answer调用,缺了任何一处都会出问题。初始INVITE调rtpproxy_offer,让rtpproxy提前预留一对媒体端口;后续收到200 OK或者早期媒体应答时调rtpproxy_answer,把实际协商后的端口告诉rtpproxy。这个流程和SIP的offer/answer模型是对应的,必须两边都处理。

3.3 rtpengine替代方案

rtpengine可以看成rtpproxy的升级版,功能更多,对ICE、WebRTC的支持更好,但配置也稍微重一点。如果你要接入WebRTC,或者要支持更多编解码的灵活协商,建议直接上rtpengine。

# Debian/Ubuntu apt install rtpengine

启动命令示例:

rtpengine --interface=eth0 --listen-ng=127.0.0.1:22223 \ --port-min=30000 --port-max=40000 --timeout=60

Kamailio侧把模块换成rtpengine.so,函数名从rtpproxy_offer/answer换成rtpengine_offer/answer,参数略有差异但调用逻辑一致。要注意的是,rtpproxy和rtpengine同时在系统里跑并不冲突,但Kamailio的配置文件里只能加载其中一个模块,千万别两边都接,不然媒体流会被抢来抢去,出现极度诡异的单通。

3.4 early media与hold/park场景处理

Early Media是最容易被忽略的环节。什么叫早期媒体?就是被叫在真正接听之前,主叫听到的提示音,最常见的就是回铃音。很多中继和终端是用183 Session Progress消息携带SDP来传这个音频流的。

在Kamailio+FreeSWITCH架构里,早期媒体处理不好,最典型的现象就是:外呼拨出去了,对方手机已经响铃,但主叫这边听不到回铃音,一片死寂,直到对方接听才有声音。原因就是183消息里的SDP没有被rtpproxy改写,媒体流不知道该往哪儿走。

所以我在模板的onreply_route里专门处理了180和183这两个状态码,一旦发现带SDP,立刻调rtpproxy_answer。这个细节很多网上教程都不会写,但生产环境十次单通里至少有三次是这个问题。

FreeSWITCH侧有个参数和这个直接相关,就是我在external profile里写的p-early-media-support。这个参数控制Sofia协议栈对早期媒体透传的支持,默认值是true,一般保持默认。如果发现回铃音经过FreeSWITCH后始终出不来,先检查这个参数有没有被改成false,再查Kamailio的onreply_route有没有正常触发。

至于hold和park场景,很多人在上线后才发现问题:坐席把客户park了,过会儿再取回,一边没声音;或者通话中按hold,恢复后有一方听不到。这类问题的根因,多数是re-INVITE/UPDATE消息里的SDP没被重新改写。通话中的hold操作会触发re-INVITE,对端发来新的SDP,如果Kamailio在route[RELAY_INVITE]里没有再次调用rtpproxy/rtpengine函数,rtpproxy的数据就还停留在旧的媒体地址上,恢复通话后媒体自然就断了。

解决思路很简单:只要请求或应答里带SDP,无论是初始INVITE、re-INVITE还是UPDATE,都要在路由里走一遍RTP代理处理。我见过很多项目初始呼叫完全正常,一进入保持、转接、合并会议等操作就出问题,排查到最后全是这个原因。配置模板里对re-INVITE的处理我已经写好了,大家照着用就行。

4. 高并发调优:从内核到服务都能扛住

4.1 系统内核参数调优

先检查操作系统层,很多并发问题根本轮不到业务软件层面去调,内核参数先把上限卡死了。我每次上新机器,第一件事就是刷一遍sysctl:

cat >> /etc/sysctl.conf <<'EOF' fs.file-max = 2000000 net.core.somaxconn = 65535 net.core.netdev_max_backlog = 10000 net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_max_syn_backlog = 65535 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30 EOF sysctl -p

同时把进程文件描述符限制调大。对Kamailio和FreeSWITCH的服务进程,建议ulimit -n 1048576这种量级,否则单进程打开的socket一旦超过默认1024,并发一上来就会开始大量报错,表现就是莫名其妙的“资源暂时不可用”。还有一点,net.ipv4.ip_local_port_range如果保持默认的32768 60999,呼叫高峰时段很容易出现本地端口不够用,新呼叫无法建立的情况。

4.2 Kamailio高并发关键点

Kamailio本身性能很强,但前提是别把它用歪了。我总结下面几个经验:

  • 少用有状态处理。尽量让请求走无状态转发路径,只有REGISTER、INVITE这类必须跟踪状态的请求才走t_relay。如果每个OPTIONS、每个NOTIFY都进事务管理,TM模块的负担会成倍增加。
  • 进程数要和CPU核数匹配。启动参数-n指定子进程数,一般设成CPU核数的1-2倍。8核机器用-n 16比较合理。共享内存用-m指定,64位系统建议512MB起步,具体看你的location表和路由复杂度。
  • 数据库别放热点路径上。usrloc的db_mode=0是纯内存模式,性能最高,但要配合db_redis或db_mysql做持久化备份。如果直接db_mode=2(每次写库),注册风暴时数据库写入会成为瓶颈,表现出来就是高峰期大量REGISTER超时、分机反复掉线。生产建议用Redis做usrloc后端,或者主备内存模式+定时同步。
  • 路由逻辑别做重活。不要在request_route里查大量数据库记录、不要做复杂的正则处理,这些都是在每一请求上都要跑一遍的。能把判断条件提到最前面的就提,能合并的就合并。

按这个思路调出来的Kamailio,一台4核8G机器轻松处理几千同时在线注册和几百CPS,完全不是问题。

4.3 FreeSWITCH会话调度优化

FreeSWITCH的高并发瓶颈通常不在SIP解析上,而在线程调度和媒体处理。关键参数都在/etc/freeswitch/autoload_configs/switch.conf.xml里:

<param name="max-sessions" value="1000"/> <param name="sessions-per-second" value="30"/>

max-sessions是最大并发会话数,超出后新呼叫直接拒绝;sessions-per-second是每秒最大新建会话数,防止注册风暴或呼叫风暴瞬间打垮系统。这两个值要根据你的实际资源来设置:8核机器跑纯音频桥接,几百路并发是相对稳妥的区间,别贪心直接设5000,到时候CPU被打满,所有通话一起卡顿,比限制并发更灾难。

日志是另一个隐藏杀手。生产环境默认的日志级别太高,高峰期一个呼叫会产生几百行日志,磁盘IO直接吃满。建议运行时通过console loglevel把控制台日志调低,同时把文件日志统一走syslog:

# 在FreeSWITCH里执行 console loglevel 2 fsctl loglevel 2

数据库也是并发瓶颈的高发区。FreeSWITCH默认的mod_sofia和CDR数据写SQLite,SQLite在几百并发写入时锁竞争非常严重。如果业务上要记录大量CDR,建议一开始就接PostgreSQL或者MySQL,不要等并发上来了再迁移。

媒体能力估算方面,我给大家一个参考范围:PCMA/PCMU这类G.711编码,一路通话大约占用100kbps带宽和少量CPU;G.729编码占用更小,但CPU消耗更高;如果启用转码(比如G.729转PCMU),CPU消耗会成倍增加。做容量规划时,先把“是否转码”“是否录音”“是否放IVR”这三件事搞清楚,否则估出来的并发数根本不靠谱。

5. 压测与容量评估:验证这套系统能扛多少

5.1 SIPp做信令与呼叫压测

系统搭完了,调优也做了,接下来最关键的一步是压测,用数据说话。SIPp是VoIP圈最常用的开源压测工具,能模拟大量SIP终端并发注册、并发呼叫,还能收发RTP媒体流。

先安装:

apt install sipp

注册压测的简单用法:

sipp 192.168.1.10:5060 -sf uac_register.xml -i 192.168.1.100 -m 5000 -r 100 -rp 1s

-sf指定场景XML脚本,-m 5000表示总共发起5000个注册,-r 100 -rp 1s表示每秒新增100个注册。呼叫压测需要写一个UAC场景脚本,核心是一个INVITE流程。下面是一个极简的uac_call.xml:

<scenario name="UAC call"> <send> <![CDATA[ INVITE sip:1001@[remote_ip] SIP/2.0 Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] From: "sipp" <sip:sipp@[local_ip]>;tag=[call_number] To: "dst" <sip:1001@[remote_ip]> Call-ID: [call_id] CSeq: 1 INVITE Contact: <sip:sipp@[local_ip]:[local_port]> Content-Type: application/sdp Content-Length: [len] v=0 o=user1 1376079996 1376079996 IN IP4 [local_ip] s=- c=IN IP4 [local_ip] t=0 0 m=audio [media_port] RTP/AVP 8 a=rtpmap:8 PCMA/8000 ]]> </send> <recv response="100" optional="true"/> <recv response="180" optional="true"/> <recv response="183" optional="true"/> <recv response="200"/> <send> <![CDATA[ ACK [peer_ip] SIP/2.0 Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] From: "sipp" <sip:sipp@[local_ip]>;tag=[call_number] To: "dst" <sip:1001@[remote_ip]>;tag=[peer_tag] Call-ID: [call_id] CSeq: 1 ACK Contact: <sip:sipp@[local_ip]:[local_port]> Content-Length: 0 ]]> </send> <recv response="BYE" method="BYE"/> <send> <![CDATA[ SIP/2.0 200 OK Via: SIP/2.0/[transport] [peer_ip]:[peer_port];branch=[peer_branch] From: "dst" <sip:1001@[remote_ip]>;tag=[peer_tag] To: "sipp" <sip:sipp@[local_ip]>;tag=[call_number] Call-ID: [call_id] CSeq: 1 BYE Content-Length: 0 ]]> </send> </scenario>

压测命令:

sipp 192.168.1.10:5060 -sf uac_call.xml -i 192.168.1.100 -m 2000 -r 20 -rp 1s -rtp_echo -trace_stat

-rtp_echo让SIPp在收到媒体后原样回送,这样可以验证媒体通路是否真正通了。-trace_stat会输出每秒钟的统计信息,包含响应码分布、事务成功率、RTT等,压测结束认真看这些数字。

5.2 指标怎么看、瓶颈在哪

压测不能只盯着“能不能跑完”这一个指标。我一般看三方面:

第一,SIP层指标。SIPp统计里注意Success、Failed、Retransmission这几项。成功率掉到99%以下就要开始查了;重传率高说明信令链路存在丢包或处理不及时,常见原因是Kamailio的tm模块超时设置过短,或者系统网络队列满了。

第二,服务器资源。压测过程中盯top、free、iostat。Kamailio的CPU如果接近满载,大概率是路由逻辑里有耗CPU的操作;FreeSWITCH的CPU飙升一般都在媒体处理上,可以通过top -H看线程名,确认是mod_sofia还是媒体引擎在忙。

第三,RTP质量。如果-rtp_echo模式下SIPp显示媒体丢包率明显上升,说明RTP经过rtpproxy/rtpengine或FreeSWITCH时出现了瓶颈,有可能是端口范围太小、带宽不够、或者媒体服务器CPU过高。

5.3 JMeter在这种项目里压什么

很多人有个误区,觉得压测就一定要用JMeter。JMeter在HTTP接口压测上确实很强,但SIP不是HTTP,没有标准的JMeter原生协议支持,虽然也有插件能发SIP报文,但基本只适合做最简单的信令连通性验证,做不了真正有意义的SIP注册风暴和媒体并发压测。

那JMeter在这类项目里的正确用法是什么?我的经验是:它压业务API,SIPp压语音链路,两者配合印证。比如呼叫中心里,坐席登录、点击外呼、工单回调、话单查询这些操作都是HTTP接口,这些接口由业务平台处理,跟Kamailio和FreeSWITCH不直接相关,但它们的高并发响应时间会间接影响坐席操作体验和呼叫接通效率。这时候用JMeter打这些业务接口,就能发现业务系统的瓶颈。

我做过一个从自建机房整体搬到阿里云ECS的项目,迁移完成后的验收就是双轨压测:SIPp打信令和呼叫链路,JMeter跑配套的业务接口脚本。两边数据合起来看:业务接口的响应时间、错误率达标,同时SIP呼叫成功率不掉,才算这个云上环境真正具备上线承载能力。只盯着一边都不够,语音链路没问题但业务接口超时,坐席照样没法干活。

5.4 压测中的典型误区

压测踩坑我见过太多了,列几个最常见的方向:

  • 只压注册不压呼叫。注册并发高只能说明usrloc模块扛得住,但呼叫涉及TM事务、RTP代理、FreeSWITCH媒体桥,链路长得多。很多系统注册压测完美,一到呼叫压测就现原形。
  • 不模拟真实媒体流。SIPp不带-rtp_echo去做压测,相当于只测了信令,不知道媒体面是否真的通了,瓶颈根本发现不了。
  • 忽略了RTP端口范围。压测呼叫到几百路时,rtpproxy或FreeSWITCH的RTP端口范围如果只有几千个,端口耗尽后新呼叫全部失败。压测前先预估端口池够不够用。
  • 直接在办公室局域网压测。局域网环境网络干净,丢包率极低,压出来的数据比真实生产环境乐观得多。有条件的话,压测机放在和真实用户网络类似的位置,效果才有参考价值。

6. 常见问题排查与避坑指南

6.1 单通/无语音问题排查

这是整个架构里出现频率最高、也最让人抓狂的问题。排查思路我总结成一条固定链路,跟着走基本都能定位:

第一步,抓SIP信令看协商结果。用sngrep抓包,重点看INVITE和200 OK里的SDP,确认两端的c=行IP地址是否都是公共可达地址。如果发现有一方的SDP里还是192.168.x.x这类私网IP,说明NAT穿透没生效,问题出在rtpproxy/rtpengine没有正确改写SDP,或者Kamailio的nat_uac_test没有识别出NAT请求。

sngrep -d eth0 port 5060

第二步,抓RTP包看媒体路径。tcpdump直接抓RTP端口范围,看媒体包是否真正到达服务器:

tcpdump -i eth0 udp portrange 30000-40000 -n -c 1000

如果只有单方向流量,说明媒体在链路上断了;如果双向流量都有但听不到声音,检查编解码是否协商一致,比如一端只支持G.722,另一端只支持PCMU,两边没有交集,媒体自然无法解码。

第三步,检查云安全组和本地防火墙。这一步看着简单,但真的会坑人。RTP使用UDP大范围端口,很多团队只开了SIP的5060端口,忘了放行RTP端口,结果就是:呼叫能建立、注册能成功,一说话就断。安全组和服务器firewalld/iptables都要看。

6.2 注册失败与会话掉线

注册失败最常见的有三类。第一类是认证失败,Kamailio里auth_check没过,先确认数据库里的subscriber表有没有对应账号、密码是否正确、db_mysql模块有没有正常加载。第二类是ACL拒绝,FreeSWITCH的kamailio_acl里没加Kamailio的IP,导致转发过去的INVITE被拒。第三类是usrloc模块的数据库写入失败,如果db_mode设成写库模式且数据库连接不稳定,注册会时好时坏。

会话掉线要特别关注注册过期时间。有些终端默认注册间隔长,比如3600秒,但Kamailio的min_expires设成了60秒,注册请求一进来就被393刷新,终端如果实现不规范,就可能导致周期性掉线。建议把min_expires调大一点,同时检查终端的保活机制。

6.3 高并发下的503/408

高并发压测时出现大量503、408,这是后端过载或超时的典型信号。503通常是Kamailio转发到FreeSWITCH时收到“服务不可用”的响应,要先看FreeSWITCH的max-sessions是否被打满,有没有在日志里看到“Too many sessions”之类的提示。408则是请求超时,常见原因是TM模块的事务超时时间过短,或者网络链路本身存在延迟。

还有一个容易被忽略的点:数据库慢查询会拖垮整个信令链路。如果认证、usrloc写入都依赖数据库,高峰期数据库响应延迟拉高,所有请求的处理时间都会变长,最终表现为大量超时和重传。我之前在压测中遇到过类似的case,最后定位到是usrloc的DB写入频率太高,切换到Redis后端之后才解决。

6.4 高频问题速查表

现象最常见原因排查/解决
拨号后回铃音听不到early media SDP没被RTP代理改写检查Kamailio onreply_route,确保180/183带SDP时调用了rtpproxy_answer
接通后单通RTP路径不对,NAT穿透失败抓SIP包看SDP里的IP地址,抓RTP包看媒体流向,查安全组端口
分机注册后周期性掉线注册过期时间设置不一致检查Kamailio的min/max_expires和终端注册间隔
并发一上来大量503FreeSWITCH max-sessions打满调大max-sessions或增加媒体节点,检查日志确认拒绝原因
高峰时段新呼叫连接超时本地端口范围太小或TCP backlog不足调大ip_local_port_range,确认sysctl参数生效
hold/park恢复后一方没声音re-INVITE的SDP未重新授权确认re-INVITE/UPDATE在Kamailio路由里调用了RTP代理函数
Windows上装FreeSWITCH启动失败路径带空格或缺少VC运行库解压到纯英文无空格路径,安装对应运行库,管理员权限运行

最后再分享两个我自己的小习惯。第一,部署完第一件事就把Kamailio的xlog打开,日志独立写到文件,FreeSWITCH的日志也同步送到统一的日志平台。真出了问题,没有日志全靠猜,效率太低了。第二,版本卡死。Kamailio和FreeSWITCH版本升级务必先在测试环境完整压测一遍再上生产,别指望在线热更不会出事。这套架构本身很成熟,绝大多数线上问题都出在版本、配置和环境的组合偏差上,把这些控制住了,系统基本就稳了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询