当SIP遇上WebRTC:自研SIP视频网关架构深度解析
当传统电话网络遇到现代实时音视频平台,系统真正的挑战,从来不只是协议互通,而是如何让呼叫接入、节点调度、音视频桥接与多方会议体验同时成立。
| 前言 |
SIP 会话初始协议与 WebRTC 实时音视频平台,长期以来像是运行在两个世界中的两套体系。一边是传统电话、视频会议硬终端、PBX 与运营商网络,强调标准协议、稳定接入和终端兼容;另一边则是浏览器、App 和云端实时会议系统,强调互动性、灵活性以及多媒体协作能力。
这个项目中的 SIP 视频网关,正是连接这两个世界的桥梁。
它解决的问题,表面上看似直接:
• 让电话可以拨入 WebRTC 会议
• 让会议可以主动呼出到 SIP 终端
• 让 SIP 终端接收视频、屏幕共享,甚至参与 BFCP 楼层控制
但真正把这件事做成一个能在生产环境长期运行的系统,远不只是把 SIP 信令翻译一下这么简单。
它至少要同时解决四类核心问题:
• 呼入请求从哪里进入系统,最终落到哪一个 SIP 节点
• 呼出请求如何在多个 SIP Pod 之间选中最合适的执行者
• 多人会议中的多路音频,如何稳定回送给只接收单路音频的 SIP 终端
• 多路视频与屏幕共享轨道,如何统一汇聚并桥接到 SIP视频链路
如果说 SIP 网关是一座桥,那么真正撑起这座桥的,并不只有 SIP 协议本身,而是围绕负载均衡、房间接入、音频合流、视频汇聚与媒体编排形成的一整套分布式设计。
本文将围绕这些关键问题,系统梳理这套 SIP 视频网关的架构思路与实现要点。
| 一、整体架构:K8s编排 + 多副本SIP节点 + Redis RPC总线 |
系统整体采用云原生架构设计,基于K8s实现容器化编排和弹性扩展:

系统架构图
在这张图里,系统实际上由四条主线共同构成:
• 呼入主线:外部 SIP 请求如何通过 K8s 进入某个 Pod
• 呼出主线:内部业务请求如何通过 Redis RPC 选中一个 Pod 去执行外呼
• 音频主线:房间中的多路音频如何混合后回送给 SIP 对端
• 视频主线:房间中的多路摄像头与屏幕共享轨道如何统一汇聚并桥接到 SIP 视频链路
也就是说,这个系统的本质并不是单纯处理 SIP,而是在做一件更复杂的事情:把外部 SIP 接入、内部节点调度,以及房间中的音视频媒体桥接,组织成一个完整的分布式网关。
| 二、两类负载均衡:呼入靠K8s,呼出靠Redis RPC |
很多人在第一次看这类系统时,会默认认为 既然跑在 Kubernetes 里,那负载均衡就是 K8s 做的。然而这句话只说对了一半。在这个项目里,呼入和呼出并不是同一种流量,因此它们的负载均衡方式天然不同。
2.1 呼入:K8s服务/负载均衡器负责负载均衡
呼入场景是外部 SIP 终端主动向网关发起 INVITE。请求最先接触到的是系统对外暴露的 SIP 服务地址。这条路径非常直接:
SIP终端 → K8s服务/负载均衡器 → 某个SIP服务节点
因此,呼入的第一跳分发由 K8s 完成。哪个 Pod 接到这通电话,并不是 SIP 服务先在内部协调决定,而是入口层先把流量送到了某个副本。这也意味着:呼入的负载均衡,本质是入口流量分发。一旦请求落到某个 Pod,该 Pod 就要负责后续认证、调度、媒体协商与房间接入等完整流程
需要特别注意的是: SIP信令可以由K8s入口进行分发,但音视频媒体流是有状态的,一旦协商完成,后续媒体流必须持续到达同一个节点 - 这也是为什么媒体端口通常要通过固定映射方式暴露,而不能简单依赖”无状态”转发
2.2 呼出:Redis + 内部RPC负责负载均衡
呼出场景完全不是这个模型。呼出时并不是外部 SIP 终端先打过来,而是系统内部某个业务请求先产生了 创建一个 SIP 参与者的需求。它的路径不是 外部流量进入 K8s,而是:
上层服务 → Redis/内部RPC → 选中某个SIP节点 → 发起外呼
也就是说,呼出请求首先进入内部 RPC 总线,而不是外部入口。因此,呼出要解决的问题不是 哪个入口 Pod 收到流量,而是多个 SIP Pod 里,应该让哪一个去执行这次外呼。因此,呼出的负载均衡,本质上是 Redis 加 PSRPC 完成的内部节点选址。
2.3 为什么要拆成两种机制
因为呼入与呼出的触发方式本质不同。呼入:来自外部网络,天然先经过K8s/负载均衡器入口;呼出:来自系统内部业务调用,天然先经过Redis/RPC总线

负载均衡机制对比
所以如果把两者都简单写成”K8s做负载均衡”或者都写成”内部RPC做负载均衡”,都会与实际架构不符。正确的架构表述必须是: 呼入通过K8s做入口层负载均衡,呼出通过Redis + 内部RPC做内部节点选择与负载均衡。
| 三、每个SIP节点内部的七层结构 |
每个SIP服务节点内部采用严格的七层分层架构设计,层级清晰,各司其职。可以把它理解为七层。这种分层设计使得呼入链路主要由入站服务驱动,呼出链路主要由出站客户端驱动,两者在同一个节点内共享通话统计、监控与资源约束

3.1 CLI 入口层(main.go)
CLI 层是整个程序的启动入口,负责应用的初始化和生命周期管理,本身不包含任何业务逻辑。
其主要职责包括:
• 解析启动参数及配置文件
• 初始化日志、配置中心、监控、链路追踪等基础组件
• 初始化 RPC Client、Redis、数据库等公共资源
• 创建并启动 Service
• 监听系统退出信号(SIGTERM、SIGINT),优雅关闭整个服务
因此,CLI 层更像整个应用的 Bootstrap(启动器),负责将各个模块组装起来,而不参与具体业务处理。
3.2 服务层(service/)
Service 层负责整个 Pod 的生命周期管理,可以理解为整个 SIP 网关的管理中心。
主要职责包括:
• 向控制中心注册服务(Service Register)
• 定时上报 CPU、内存、带宽、房间数量等运行状态
• 接收控制中心下发的 RPC 请求
• 创建、销毁和管理 Room
• 统一管理整个 Pod 的生命周期
3.3 SIP 服务层(sip/service.go)
SIP Service 层专门负责 SIP 协议处理,它是整个系统与 SIP 终端交互的入口。
主要职责包括:
• SIP REGISTER 注册
• INVITE 呼叫建立
• ACK、BYE、CANCEL 等呼叫控制
• OPTIONS 保活检测
• REFER 呼叫转移
• INFO(DTMF 等)
• SUBSCRIBE / NOTIFY(如 Cisco Provision)
3.4 Room(会议核心层)
Room 是整个系统最核心的一层,也是所有会议业务逻辑的集中管理者。
如果把整个视频会议比作一场演出,那么 Room 就像导演,负责决定:
• 谁加入会议
• 谁离开会议
• 谁能看到谁
• 当前采用什么布局
• 是否开始合流
3.5 媒体处理层(Media Port)
Media Port 位于业务层和媒体处理层之间,是整个系统的媒体抽象层。
它负责屏蔽不同协议、不同编码格式之间的差异,为上层提供统一的数据接口。
主要职责包括:
• RTP / RTCP 处理
• Jitter Buffer,处理网络乱序和抖动:
• 时钟同步
3.6 视频管道层(Pipeline)
Pipeline 是真正执行音视频处理工作的地方,也是整个系统计算量最大的模块。
主要负责:
• 视频解码
• 视频编码
• 音视频混流
• 分辨率缩放
• 帧率转换
• RTP 封装
3.7基础设施层(Infrastructure)
基础设施层为整个系统提供通用能力,是所有模块共享的公共组件。
包括:
• Config(配置管理)
• Logger(日志)
• Redis Client
• RPC Client
• Prometheus Metrics
• OpenTelemetry Tracing
• Worker Pool
• Timer
• UUID
• Retry
• RingBuffer
这一层不包含任何业务逻辑,只负责提供稳定可靠的基础能力。
| 四、启动流程:从配置加载到服务注册 |
系统启动过程可概括为几个关键阶段:
• 加载配置文件,初始化日志与监控能力。
• 连接Redis集群,建立内部RPC消息总线 。
• 初始化SIP服务,启动入站和出站处理能力 。
• 注册内部RPC服务端点,开始接收业务请求。
• 等待关闭信号,处理优雅关闭流程。
在启动阶段,为了应对复杂的生产与网络环境,系统设计了两个至关重要的技术点:
1. SIP 服务代理共享同一个 UA(UserAgent):入站 Server 与出站 Client 强制共享同一个 sipgo.UserAgent。这在真实网络中至关重要:SIP 的 BYE、re-INVITE 等后续会话信令必须精确返回到同一物理端口。若出入站随机占用不同端口,极易引起 NAT 映射失效与会话事务不一致。这是基于海量生产实践所驱动的高可靠设计。
2. 顶层服务在内部 RPC(PSRPC)总线上的注册:顶层服务启动后,创建 SIP 内部 RPC 服务端点,并调用 RegisterCreateSIPParticipantTopic 将当前节点注册到 Topic。这与“呼出通过 Redis RPC 做负载均衡”深度配合,将当前节点作为可用副本接入分布式调度池,使其能够接受并执行来自业务层的主动外呼任务。
| 五、呼入流程:外部SIP终端如何进入会议 |
呼入流程可以概括为以下几个核心步骤: 1. 外部SIP终端发起呼叫请求 2. K8s负载均衡器将请求转发到某个SIP节点 3. 节点进行中继认证,验证呼叫合法性 4. 评估调度规则,决定呼叫去向 5. 若需要PIN码验证,引导用户输入 6. 进行媒体能力协商,确定音视频参数 7. 建立媒体端口和会议房间连接 8. 终端成功加入会议,开始双向音视频通信。

呼入流程
5.1 呼入为什么是K8s负载均衡
因为在呼入场景里,第一个到达系统的是外部SIP呼叫请求,请求首先落到K8s暴露出来的入口地址,然后由K8s/负载均衡器将其转发到某个SIP节点。此时并不存在”上层先调用某个内部RPC再选节点”的过程,因此负载均衡责任天然属于入口层。
认证通过后,服务还会查询对应的业务调度规则,决定该呼叫应该进入哪个房间、是否需要验证 PIN 码、或者是否需要拒绝丢弃。在此必须将“业务调度”与“节点负载均衡”清晰剥离开。
节点负载均衡:在呼入流量进入系统的那一刻,已经由外部的 K8s Service / LB 完成了节点分发与 Pod 选址。
业务调度:落到具体 Pod 之后,由 DispatchCall 执行的业务路由动作。这属于纯业务侧的路由与调度,绝不参与多 Pod 之间的物理节点竞争和分发逻辑。理解这一点对排除分布式流量故障至关重要。
认证通过后,服务还会查询对应的调度规则,决定该呼叫应该进入哪个房间、是否需要PIN码验证、是否需要拒绝或丢弃。这里解决的是”业务去向”问题,不是”节点选址”问题。节点选址在呼入链路中已经由K8s完成。
| 六、呼出流程:内部RPC如何选中一个SIP节点发起外呼 |
呼出场景下,外部SIP终端还没有发起请求,而是系统内部先发起一个”创建SIP参与者”的命令。流程如下:
1. 上层业务服务发起外呼请求
2. 请求进入Redis承载的内部RPC总线
3. 内部RPC机制在多个可用SIP节点间进行选址
4. 请求被投递到选中的目标节点
5. 目标节点创建外呼任务,连接对应的会议房间
6. 向外部SIP终端发起呼叫请求
7. 协商媒体能力,建立媒体流连接
8. 通话成功建立,返回结果给上层服务。

呼出流程
6.1 呼出为什么是Redis RPC负载均衡
因为这条链路不是从K8s入口过来的,而是上层业务服务发起内部RPC调用,消息进入Redis承载的内部RPC总线,多个SIP节点共同参与处理该类请求, 由RPC机制决定最终哪一个节点处理这次外呼。因此,呼出的负载均衡属于内部RPC级别的节点选择,而不是外部入口流量分发。
6.2 呼出时如何理解”负载均衡”
呼出的负载均衡可以分成两个层次看: 1. 机制层面:靠Redis + 内部RPC实现多节点请求分发 2. 策略层面:应优先让更空闲的节点处理请求
呼出的负载均衡可分为两个核心层面:
• 分发机制:由 Redis 承载的 PSRPC 总线实现多节点间的命令广播与分配。
• 选址策略:总线优先选择当前“总活跃呼叫数(Active Calls)”最少的 Pod 节点来执行此次外呼。
这是一种极其科学的自适应负载选址算法。系统会同时统计节点的出站与入站活跃通话,相加得到其总活跃通话数。这一指标完美地契合了 SIP 网关的物理资源模型,能确保请求流向负载最轻的节点。
| 七、合流:为什么它是 SIP 视频网关真正的技术难点 |
如果只从功能列表看,很多人会把这个系统理解成 一个把 SIP 和 WebRTC 接起来的协议转换器。但真正进入多人会议场景后,系统面对的核心问题很快就会变化。对于 SIP 终端来说,它看到的媒体世界通常是 一路音频、一路视频;但对于 SFU中房间来说,里面可能同时存在:
• 多个参与者的音频轨道
• 多个摄像头视频轨道
• 屏幕共享轨道
• 动态加入、离开、静音、取消静音、切换共享等实时变化
也就是说,网关不能只是 把一条流转给另一条流,而必须先把房间里的多路媒体整理成 SIP 终端能够消费的形态。这件事,就是本文所说的合流。
从架构上看,这里的 合流 其实包含两类完全不同的问题:1.音频合流:把多人会议中的多路声音混成一路稳定输出。2.视频汇聚:把多路摄像头与屏幕共享轨道统一接管,再桥接到 SIP 视频链路。
两者都发生在 Room 这一层附近,但处理方式完全不同。音频是混合,视频是汇聚与桥接。理解这一点,才能真正看懂这个网关为什么不是一个简单的 SIP 适配器,而是一个媒体编排系统。
7.1 为什么合流是网关而不是终端的问题
在纯 WebRTC 会议里,终端通常可以各自订阅多路轨道,界面也可以自由布局;但传统 SIP 终端不具备这样的交互模型。对 SIP 终端而言,音频侧通常只能接收一条连续的媒体流,视频侧即便支持视频,最终也必须被桥接成一个可协商、可持续输出的单一发送链路, 它并不理解 房间里当前有多少人发言、多少人开摄像头、谁在共享屏幕 这些会议语义。因此,系统必须在网关内部完成一次 会议语义 到 SIP 媒体语义 的转换。
换句话说,SIP 网关并不是被动转发媒体,而是在主动回答两个问题:
• 房间里这么多路音频,最后哪一路或者哪几路应该被送给电话侧
• 房间里这么多路视频,最后该由哪条视频链路代表会议输出给 SIP 终端
这就是为什么合流问题天然落在网关内部,而不是交给终端自己解决。
7.2 音频合流的核心不是 MediaPort,而是 Room
很多人会下意识认为音频处理都在 MediaPort 里完成。实际上,在这个项目中,真正负责 多人音频合成 的核心在 Room()中。在这里,系统会创建两个关键对象:
out := msdk.NewSwitchWriter(RoomSampleRate)
mix := mixer.NewMixer(r.out, rtp.DefFrameDur, 1, mixer.WithStats(&st.Mixer), mixer.WithOutputChannel())
这两个对象共同构成整个音频合流中心,mix负责把多路输入混成一路,out负责把混合后的输出送往当前下游。这意味着,Room在这里并不是一个单纯的房间连接包装器,而是承担了会议侧音频汇聚中心的职责。只要有新的房间音轨进入,它首先不是直接进入 SIP 编码器,而是先进入混音体系。也就是说,系统内部实际存在这样一条链:
多路房间音频 → mixer → 统一输出 writer → SIP 编码链路
这个设计的意义非常大,因为它把 会议侧多路输入 与 SIP 侧单路输出解耦开了。上游只需要不断把新的房间音轨接入 mixer,下游只需要消费稳定的单路 PCM 输出,两侧不必直接感知彼此的复杂状态
7.3 多路音频到底是怎么被接进 mixer 的
Room.NewTrack()返回的是 r.mix.NewInput()。这个细节非常重要,因为它暴露了整个音频合流模型的真实结构。它不是 先创建一条固定输出流,再把所有人往里写,而是采用了 更典型的混音器输入口模型:房间中每一路待进入混音器的音频源,都会拿到一个独立的 mixer input。每个 input 代表一条独立的上游音频写入。mixer 按统一的 frame duration 和采样率,对这些输入做时间对齐与混合,混合完成后,再把结果写给统一输出
从工程视角看,这种设计有三个好处:
• 输入隔离:某一路音频的抖动、暂停或结束,不会破坏整个输出结构。
• 节奏统一:所有输入最终都被拉齐到同一混音节拍,便于稳定编码输出。
• 易于扩展:新增或移除一个说话者,本质上只是新增或关闭一个 input。
这也是为什么这个项目的音频合流可以自然适配多人会议,而不是只能处理一对一通话。
7.4 混音后的音频最终如何回到 SIP 对端
当 Room 产出一路稳定的 PCM 音频后,这路音频会继续进入 SIP 侧的媒体发送链路。在 MediaPort.setupOutput() 中,系统会建立 RTP 写出链路,并通过 rtp.EncodePCM() 把 PCM 编码成当前协商好的 SIP 音频格式,再封装成 RTP 包。完整链路可以理解为:
房间多路音频→ Room.NewTrack()→ mixer.NewInput()→ Room.mix→ Room.out SwitchWriter→ MediaPort audioOut→ rtp.EncodePCM()→ RTP writer→ SIP 对端
这条链路里,每一层职责都非常明确,`Room` 负责会议侧多路输入管理与合流, mixer 负责时间对齐与音频混合,`SwitchWriter` 负责统一输出与动态切换,`MediaPort` 负责编码、RTP 封装与网络发送。
因此,如果只把音频链路理解成 MediaPort 在发 RTP,会错过真正的核心。真正决定 多人会议音频能否正确回送给 SIP 终端 的,是前面那套由 Room 驱动的合流结构。
7.5 视频合流:这套网关真正复杂的,不是视频编码,而是多路视频如何被接管、筛选和桥接
如果说音频合流解决的是 多路声音如何回到单路电话音频,那么视频合流要解决的问题其实更棘手。
因为视频不能像音频那样直接做采样叠加。多人会议里的多路 camera、屏幕共享、动态开关摄像头、切换发言者、共享开始与结束,这些变化都必须被网关实时接住。但最终发往 SIP 终端的,却仍然是一条可协商、可持续发送、可被传统终端理解的视频链路。所以,这个项目里的视频合流,准确说并不是把多路视频混成一张画布,而是把多路视频统一纳入一套 接管、筛选、订阅、桥接 的流程。
如果用一句话概括它的本质,视频合流的关键不是混,而是管。先把房间里的多路视频管起来,再把其中合适的一路或一类内容稳定送进 SIP 视频链路。这也是为什么,视频部分比音频更像一个控制系统,而不只是一个媒体处理链路。
7.5.1 视频合流的起点仍然是 Room,而不是 GStreamer
很多人一看到视频桥接,就会立刻把注意力放到 GStreamer 上。但在这个项目里,GStreamer 只是后半段的处理引擎,真正的合流入口其实仍然在 Room。原因很简单,系统必须先决定房间里的哪些视频值得被接入桥接链路,然后才轮到媒体管道去处理这些数据。在 Room.Subscribe()中,系统会枚举远端参与者及其轨道,并按轨道类型做不同处理:
• 音频轨道会直接订阅。
• 屏幕共享轨道在启用时会直接纳入订阅流程。
• 摄像头轨道则不会无条件全部订阅,而是先收集到 UnmutedCameraTracks,再交给后续订阅管理协程处理。 Room 在视频侧承担的不是 简单转发,而是第一层策略控制。
7.5.2 摄像头与屏幕共享为什么要分开处理
系统并没有把所有视频一视同仁。在 Room.onTrackSubscribed()]中,摄像头轨道与屏幕共享轨道会被送往不同的 source。 摄像头轨道走r.cameraSource.OnTrackSubscribed()屏幕共享轨道走 r.screenshareSource.OnTrackSubscribed()。摄像头视频和屏幕共享虽然都属于视频,但它们在会议里的语义并不相同。摄像头强调人像、发言者、参会者可见性,屏幕共享强调内容展示,通常更接近当前会议主内容。两者在订阅策略、切换时机、BFCP 联动以及终端体验上都有所不同。
也正因为如此,系统在房间侧就把两类视频拆成两条逻辑链路,而不是先揉成一个抽象的视频池再说。这正是 视频合流 的第一层关键能力:先分类管理,再决定如何桥接。
7.5.3 Room 如何把视频入口真正接到后续桥接链路上
无论入站还是出站呼叫,系统都会在媒体编排器创建完成后,把 orchestrator 暴露出的视频 source 注入到 Room。对应代码分别在:
room.SetSource(orchestrator.cameraRtcPad.RtcSrc,orchestrator.screenshareRtcPad.RtcSrc)
room.SetSource(orchestrator.cameraRtcPad.RtcSrc,orchestrator.screenshareRtcPad.RtcSrc)
最终落到 SetSource()。因为在这一步之前,Room只知道 房间里有什么视频;而在这一步之后,Room才真正拥有了把这些视频交给后续媒体系统处理的能力。换句话说,`Room` 负责感知与管理房间里的视频轨,orchestrator 提供实际可消费这些轨道的 source,SetSource()把两者连成一条完整通路。一旦这个连接成立,后续房间里的 camera 与 screenshare 订阅事件,才不只是会议事件,而会真正转化为 SIP 视频桥接链路的输入事件。
7.5.4 真正的视频桥接是如何由 VideoManager 接管的
当 Room 把视频轨道交给对应 source 之后,真正负责把这些内容送进 SIP 视频链路的是 VideoManager。从结构上看,VideoManager 内部持有几个关键要素,RTP 与 RTCP 连接,用于和 SIP 终端建立真实的视频网络通路。 SDP 媒体描述,用于记录当前协商后的方向与编解码信息。controller. *pipeline.Controller,用于承接具体的视频处理管道。 rtcSrc 与 rtcSink,用于连接 WebRTC 侧轨道与 SIP 侧输出。这意味着 VideoManager 的角色不是 单纯发包,而是负责把会议侧视频轨道 与 SIP 侧协商后的视频会话 参数绑定在一起。
7.5.5 pipeline.Controller 才是视频合流落地成媒体链路的地方
如果说 Room 负责收拢与挑选视频轨道,VideoManager负责绑定 SIP 会话参数,那么真正把视频跑起来的是 pipeline.Controller。在 pipeline.New()中,控制器会同时创建两条视频管道:down 管道:WebRTC → SIP;up 管道:SIP → WebRTC。
这两条管道在 BuildPipeline() 中完成 GStreamer pipeline、source、sink 与 video bin 的构建。对于我们关心的视频合流文章主题而言,最关键的是下行链路,也就是 WebRTC 房间的视频如何被送往 SIP 终端。这一步的本质是上游 source 接收来自房间订阅后的轨道数据,GStreamer 完成 depay、parse、decode、convert、compositor、encode、pay 等处理, sink 把处理好的 RTP 视频输出到 SIP 对端,到了这里,所谓 视频合流才真正从房间轨道管理走到了 SIP 媒体输出。
所以完整的视频合流链路应该理解为:
房间多路 camera / screenshare tracks
→ Room.Subscribe()
→ 候选轨道筛选与订阅管理
→ Room.onTrackSubscribed()
→ cameraSource / screenshareSource
→ VideoManager
→ pipeline.Controller
→ GStreamer video pipeline
→ RTP 输出
→ SIP 视频终端
这个流程里,每一层都在完成不同意义上的 合流:
• Room 做的是会议多路轨道的收拢与筛选
• source 层做的是被选中轨道的统一接管
• VideoManager 做的是 SIP 会话参数与媒体链路绑定
• pipeline.Controller 做的是真正的视频转码合流与桥接输出
| 八、优雅关闭与滚动升级的分布式平滑摘流机制 |
系统的优雅关闭流程是典型的“平滑摘流”设计。其与网关的两层负载均衡机制构成了极其完美的分布式闭环配合:
• 主动注销 PSRPC 注册:工作节点一旦触发优雅下线,首先主动向 Redis PSRPC 总线注销外呼 Topic 的注册。这使得业务侧新发起的呼出选址,再也不会将指令派发给这个 Pod 节点,直接实现了新外呼的主动屏蔽。
• 入口流量渐进过渡:对于入站流量,K8s 会继续把新连接分配给集群中其它健康的 Pod 实例,原节点不再承担新呼入。
• 5秒周期间隔轮询:服务进程每隔 5 秒钟进行一次活跃呼叫(Active Calls)数量检测。对于已在当前节点上建立并通话中的存量呼入/呼出连接,进程不会粗暴切断,而是等待它们自然、平滑地挂断结束。
• 资源完全清零后正常退出:直到节点承载的总活跃通话数精确归零,所有媒体端口和房间连接资源被全部优雅释放,服务才正式停止 SIP 用户代理并干净利落地关闭进程。这套机制保证了整个集群在滚动升级或缩容时,用户侧可达到 100% 的“完全无感知”平滑摘流体验。
这与”呼出通过Redis RPC做负载均衡”天然配合。节点一旦开始优雅关闭,就不会再被RPC机制选中处理新外呼,已经建立的呼入/呼出通话继续自然结束 滚动升级过程中,集群可以实现平滑摘流,用户无感知。而呼入侧则继续由K8s在仍然健康的节点之间分发新流量。
| 九、结语 |
这个SIP网关的价值,不仅在于”把SIP接进WebRTC”,更在于它采用了适合生产环境的双层负载均衡思路。面向外部SIP流量,依赖K8s提供稳定的入口分发能力,面向内部呼出请求,依赖Redis + 内部RPC在多个SIP节点之间做细粒度节点选择
这使系统既能适应传统SIP网络的入口接入模式,又能在内部业务调用场景下实现更灵活的横向扩展。最终,这个架构的关键不是单纯”有多少节点”,而是让呼入流量通过K8s可靠进入系统,让呼出请求通过Redis RPC被分配到最合适的SIP节点。
更多推荐
所有评论(0)