FEATURED · 精选文章

Jitsi JVB Colibri 协议详解:会议控制接口与带宽调优

发布时间 / 2026/9/18 9:07:58
来源 / 创域科博编辑部
栏目 / 资讯中心
Jitsi JVB Colibri 协议详解:会议控制接口与带宽调优 Colibri 这个词你可能在好几个地方都见过西班牙语里它是蜂鸟有个做了快一百年的打火机牌子叫 Colibri某家嵌入式模组厂也拿它当产品线命名。但干我们这一行的真正会天天跟它打交道的地方只有一个——Jitsi Videobridge下称 JVB对外暴露的那套会议控制接口官方名字就叫 Colibri。它不是用户能看到的界面也不是媒体流本身而是一本账本谁在开会、每个人在发哪几路流、每一路流该往哪几个端点转发、什么时候该把谁踢出去。我最早接触到它是给一家做在线教育的客户做私有化部署会议室一开就两百多人前端画面卡成幻灯片后台日志里全是colibri开头的报错。那会儿我以为是网络问题折腾了两天带宽和 NAT最后发现是 Colibri 描述里last-n的取值和 simulcast 层选择没配合好单个端点的下行被拉到了十几兆。这件事之后我把这套协议从头到尾捋了一遍。下面这篇东西就是我这几年在 Colibri 上踩过的坑、拆过的包、改过的配置适合正在自建视频会议后端、或者已经在用 JVB 但被性能和排障折磨的同学看。不需要你先懂 WebRTC 底层我会从它在整套系统里站的位置讲起。1. Colibri 指的是哪一套东西1.1 同名不同物先把范围钉死因为Colibri这个名字被用得太散先花半分钟把边界划清楚省得你搜出来的资料驴唇不对马嘴。蜂鸟那个是生物学命名跟技术无关打火机品牌是消费品的商标嵌入式领域有个模组系列也叫这个名字属于硬件范畴。而我们这篇文章要聊的是Jitsi 生态里 JVB 组件的控制平面协议。严格说Colibri 不是一份 RFC也没有独立的规范文档它更像JVB 这个服务对外承诺的一组接口约定包含两个部分一是走 HTTP 的Colibri REST 接口用来做会议对象的创建、查询、局部更新和删除二是走 WebSocket 的Colibri WebSocket 通道用来在会议进行中做高频的增量控制比如某个端点切换了订阅目标、某个 source 的转发层级变了。前者是慢通道后者是快通道两者描述的其实是同一个对象的两面。所以你在日志里看到colibri-ws、/colibri/conferences、ColibriWebSocket这些字眼说的都是同一件事。搞混了容易白折腾——我就见过有人为了排查 WebSocket 断连把 JVB 的 REST 端口从头配了一遍方向完全跑偏。1.2 视频会议后端为什么需要一套专门的控制协议要理解 Colibri 存在的意义得先明白 JVB 在架构里的角色。Jitsi 走的是 SFU选择性转发单元模型所有参会者把音视频流推到 JVB 这台上JVB 再按需转发给其他人。它不做混流合成除了可选的音频混合所以一个 200 人的会议JVB 里同时存在 200 路上行和理论上的 200×199 路下行——当然实际不会这么干靠的就是选择性三个字。问题来了谁来决定转发给谁这件事不能由客户端自己做主因为它不知道全局情况也不适合塞进 SDP 里反复协商因为 SDP 是会话级的改一次要把整个会话重谈一遍开销太大。于是就需要一层独立于信令和媒体之外的控制面专门回答三个问题有哪些会议存在每个会议里有哪些端点——这是对象管理每个端点在发几路流每路流的 SSRC 是什么编码参数是什么——这是转发依据某个端点此刻想看谁最多同时看几路——这是订阅策略。Colibri 就是这三个问题的答案载体。它的设计取向很明确控制面只描述状态不关心怎么实现。JVB 拿到一份描述之后自己决定用哪个 socket、发到哪个 IP、丢包了怎么办。这种分工让 JVB 可以专注于转发性能也让上层Jicofo可以灵活地做策略调度。1.3 Colibri 在整套架构里处在哪一层先把几个角色的分工列清楚不然后面看字段会晕。组件角色和 Colibri 的关系ProsodyXMPP 信令服务器不直接碰 Colibri负责 MUC 和信令转发Jicofo会议协调者focusColibri 的主要调用方负责拼装会议描述JVB媒体转发服务Colibri 的服务提供方解析描述并执行转发客户端浏览器 / 移动端不直接调 REST通过 Colibri WebSocket 提交订阅意图一次典型的开会流程是这样的客户端在 MUC 里出现Jicofo 感知到之后从它掌握的可用 JVB 列表里挑一台把这台 JVB 的地址拿出来然后用 HTTP 调它的 Colibri REST 接口创建会议创建的时候会把当前已知的端点、每个端点的 ICE 候选、DTLS 参数、SSRC 列表一股脑儿塞进去。JVB 收到之后分配内部资源返回一个会议对象。后续有人加入、有人退出、有人切换画面Jicofo 就通过后续的更新请求或者让客户端直接走 WebSocket 去改。这里面有个容易被忽略的点Jicofo 并不是只调一台 JVB。大会议里它可以做级联把不同会场的端点挂到不同 bridge 上Colibri 描述里的channel-bundle就是实现这个的关键后面细讲。另外 Jicofo 怎么知道有哪些 JVB 可用它和 JVB 都加入了同一个 XMPP 的 brewery MUCJVB 在 presence 里上报自己的状态和地址Jicofo 从里面挑。所以 Colibri 虽然不走 XMPP但它的入口信息是 XMPP 给的两条路配合着用。2. Colibri 的设计思路拆解2.1 声明式描述而不是一串命令Colibri 最值得学的一点是它选择了声明式而不是命令式。你发给 JVB 的不是请把 A 的流发给 B这种动作指令而是一份完整的状态快照会话里有哪些 content、每个 content 下有哪些 channel、每个 channel 里有哪些 endpoint、每个 endpoint 有哪些 source。JVB 自己去做 diff判断哪些是新增、哪些是更新、哪些该清理。这么做有很实际的好处。第一是幂等同一份描述发两次结果是一样的网络抖动导致的重试不会造成重复资源。第二是可恢复如果 JVB 重启了Jicofo 只要把当前该有的状态重新推一遍就能恢复不需要回放历史指令。第三是可调试出了问题时你只要GET一下会议对象就能看到 JVB 心里的真相是什么不用猜。代价也很明显描述会很大。一个 50 人会议、每个人带音频视频RTX再加上 ICE 候选一份完整的 JSON 轻松上到几百 KB。所以 Colibri 设计了 PATCH 语义的局部更新以及 WebSocket 通道来做增量避免每次都推全量。2.2 REST 管生命周期WebSocket 管高频意图这两条通道的分工是我觉得 Colibri 设计得最聪明的地方。REST 通道处理的是低频、重状态的操作会议创建、端点加入、端点离开、会议销毁。这些操作频率低一个人一分钟也就能进出一次但携带的数据量巨大ICE 候选、DTLS 指纹、所有 SSRC。用 HTTP 很合适因为可以复用成熟的连接池、超时、重试机制。WebSocket 通道处理的是高频、轻状态的操作最典型的就是我现在想看谁的画面。在一个 20 人的会议里用户快速翻页切换画面一秒内可能发好几次意图。这种东西如果每次都走 HTTP光 TLS 握手和 JSON 解析就能把 CPU 吃掉一大块。走 WebSocket 之后一条几百字节的消息就搞定了。我实测过一个对比同样的画面切换操作走 REST 更新平均延迟在 120ms 上下走 Colibri WebSocket 能压到 20ms 以内。对用户体验来说这就是点了就切和点了等一下的区别。所以如果你的部署里 WebSocket 没通、退回了 HTTP 模式用户不会收到报错但会明显感觉卡顿、迟钝。这个坑我在一个客户的现场遇到过他们换了 nginx 之后忘了把Upgrade头带上去客户端默默降级谁都没报错只有用户抱怨切画面慢。2.3 expire 机制让资源自己会过期Colibri 描述里有个字段叫expire一般出现在 channel 或 endpoint 层面单位是秒。它的含义是如果我这么久没有收到关于你的更新我就认为你已经不在了主动把你清掉。这是个租约lease式的设计。好处是容错如果 Jicofo 崩了、客户端断网了、进程被 kill 了JVB 不会永远抱着一个僵尸端点不放白白占用转发资源。坏处是要求调用方必须周期性刷新——只要会议还在Jicofo 或客户端就得定期把描述重新推一遍哪怕内容没变相当于心跳。注意expire设得太短会在大会议里造成刷新风暴每次刷新都是一次完整的端点列表比对设得太长端点异常退出后资源回收不及时表现为幽灵参与者一直占着下行带宽。我的经验是把刷新周期设为 expire 值的三分之一左右比如 expire 给 60 秒刷新周期 20 秒既留了两次重试余量又不会太频繁。2.4 三个决定带宽的设计channel-bundle、SSRC、last-N如果说 Colibri 描述里只有三个字段真正影响你的服务器成本那一定是这三个。channel-bundle决定的是连接复用。传统做法是音频一条 ICE 传输、视频一条、数据一条三条 DTLS 握手、三组候选、三份连通性检查。channel-bundle 把这些 content 绑在一个传输上只做一次 ICE 和 DTLS。对 JVB 来说这意味着每端点省下两次握手对客户端来说意味着更少的连接数和更快的建立速度。层级关系是一个 content 里的多个 channel 共享同一个 channel-bundle而 channel-bundle 里放着真正的 ICE 参数和 DTLS 指纹。SSRC 与 ssrc-groups决定的是转发粒度。SSRC 是每一路 RTP 流的身份证视频的 RTX 重传流有另一个 SSRC两者用FID语义成组simulcast 的三个分辨率层各有自己的 SSRC用SIM语义成组。JVB 要靠这些组信息才能只转发某一层而不是把全部层都推给订阅者。这也是为什么不能随便伪造 SSRC——JVB 会校验对不上就直接丢弃。last-N决定的是下行上限。它规定每个端点最多同时接收多少个其他端点的视频流。这个值直接把每人下行从 N-1 路砍到 N 路是 SFU 能在普通带宽下撑住大会议的核心。后面我会单独拿一节讲怎么算。3. 关键字段逐个拆解一份 Colibri 会议描述长什么样3.1 顶层骨架与 contents 数组先看整体形状。Jicofo 发给 JVB 的 JSON 大致长这样这是我从实际流量里精简出来的最小骨架真实报文会胖得多参数也随版本有增减{ id: a1b2c3d4e5f6, contents: [ { name: audio, channels: [ { id: a1b2c3d4e5f6-audio, expire: 60, endpoints: [], sources: [] } ] } ] }顶层的id就是会议标识通常是随机 hex 串它在 JVB 内部唯一。contents是个数组每个元素代表一个媒体类型常见的name有audio、video还有一类特殊的data用来承载 SCTP 数据通道。每个 content 下面是channels数组channel 才是真正装端点和 source 的容器。这里有个实践中的细节值得说同一种媒体类型可以有多于一个 content。比如你想给演讲者的大屏和其他人的小窗走不同的 payload type 配置就可以拆成两个 video content。JVB 不关心你为什么拆它只按你给的配置转发。我见过有人为了给不同分辨率设置不同的 RTCP 反馈策略而这么干可行但会让描述体积明显变大得不偿失。还有一点channels数组的存在感经常被误解。很多人以为一个端点对应一个 channel其实不对。channel 是一组有共同转发策略的端点的集合一个 channel 里可以挂多个 endpoint一个 endpoint 也可以出现在多个 channel 里比如它既在音频 channel 里又在视频 channel 里。理解这一点看日志的时候就不会被为什么同一个 ID 出现在好几处搞懵。3.2 endpoint、channel-bundle 与 ICE 参数endpoint 是 Colibri 里最核心的对象你可以把它理解成一个参会者的媒体身份。它至少包含三个信息块自己的标识、统计用的 ID、以及怎么连上它的传输参数。{ id: 5f3c1a7e, stats-id: client-5f3c1a7e, channel-bundle: { id: 5f3c1a7e-bundle, transport: { xmlns: urn:xmpp:jingle:transports:ice-udp:1, rtcp-mux: true, ice: { ufrag: abcd, pwd: 0123456789abcdef0123456789, candidates: [ { component: 1, foundation: 1, generation: 0, id: 1, ip: 203.0.113.10, port: 51234, priority: 2130706431, protocol: udp, type: host } ] } } } }id一般就是端点在 MUC 里的资源标识8 位左右的短串。stats-id是给统计上报用的跟id不一定相同——如果你做自有监控认准这个字段来关联数据不要用id硬拼。channel-bundle里的transport才是干货。ufrag和pwd是 ICE 的凭据用来生成消息完整性校验candidates是候选地址列表type有host、srflx、relay三种protocol有udp和tcp。这里最容易出问题的是地址通告如果你的 JVB 在 NAT 后面而它通告的是内网地址客户端根本连不上反过来通告了公网地址但防火墙没放行表现是 ICE 一直卡在 checking。提示改动 ICE 相关配置后一定要清空会话重启因为 ICE 参数是会话级协商的结果改了配置但复用旧会话的连接不会自动重协商你会看到配置明明改了却没生效的假象。这个坑我至少栽过两次。rtcp-mux: true表示 RTP 和 RTCP 复用同一个端口现代浏览器默认都开别关掉关掉会让端口占用直接翻倍。3.3 sources、ssrc-groups 与 simulcast 的对应关系再往里一层看 source。它是给 JVB 的转发指令告诉它这个端点会从这条传输上发来哪些流。{ name: 5f3c1a7e-video, ssrc-groups: [ { semantics: SIM, sources: [1111, 1112, 1113] }, { semantics: FID, sources: [1111, 2111] } ], sources: [ { ssrc: 1111, rtp-level-relay-type: translator }, { ssrc: 1112, rtp-level-relay-type: translator }, { ssrc: 1113, rtp-level-relay-type: translator }, { ssrc: 2111, rtp-level-relay-type: translator } ] }semantics里SIM是 simulcast 组通常按分辨率从高到低排列FID是 RTX 重传组第一个是主流的 SSRC后面是它对应的重传流。rtp-level-relay-type常见取值是translator音频在开启混音时会变成mixer。我特别想强调 simulcast 和 last-N 的联动关系因为这是性能问题最常见的根因。一个开启 simulcast 的视频源会有 3 路不同分辨率的流同时到达 JVBJVB 会根据订阅者的意图只转发其中一层。如果你在描述里只声明了 SSRC 却没有正确分组JVB 会把它们当成三个独立的视频源于是订阅者会被分配三倍的转发资源而下行看起来只有一路画面——带宽莫名其妙涨了三倍日志里却没有任何异常。这个现象我在一个客户那里定位了整整一个下午。3.4 payload-types 与 header-extensions能力协商的落点剩下两个块一个是payload-types一个是header-extensions它们决定了媒体流的语法。payload type 描述的是编码格式和它的参数比如 Opus 音频{ id: 111, name: opus, clock-rate: 48000, channels: 2, parameters: { minptime: 10, useinbandfec: 1 }, rtcp-fb: [{ type: transport-cc }] }clock-rate是采样时钟channels是声道数parameters里useinbandfec打开带内前向纠错弱网下能显著减少断音。rtcp-fb声明 RTCP 反馈能力transport-cc是传输层拥塞控制nack是丢包重传goog-remb是接收端带宽估计。三个里至少要有transport-cc或goog-remb之一否则 JVB 没法做带宽自适应弱网下画面会一直糊着不回升。header-extensions 是 RTP 头扩展常见的比如urn:ietf:params:rtp-hdrext:ssrc-audio-level音量指示、urn:ietf:params:rtp-hdrext:toffset音视频同步、urn:3gpp:video-orientation画面旋转。这些扩展的id在 SDP 里协商、在 Colibri 里再声明一次两边必须一致不一致的话 JVB 会静默丢弃对应的扩展数据。表现是音量指示条不动、竖屏拍摄的视频方向不对、音画不同步。都不是致命问题但用户会明确感觉到哪里不对劲。3.5 last-N 到底该给多少last-N不是一个写在 Colibri 描述里的字段而是 Jicofo 或客户端在订阅策略里施加的约束但它通过 Colibri 通道生效所以放在这里讲。它的逻辑很简单每个端点最多同时接收 N 路其他端点的视频。选中的 N 个人按优先级排序正在讲话的、被固定在画面上的、最近活跃的其余一律不转发。这样每个端点的下行从随人数线性增长变成了常数。代价是画面质量不公平没被选中的人在你的界面上显示为静止头像直到他说话或者你手动切过去。对会议场景来说这完全可以接受因为人眼在一个屏幕上本来就只能看清少数几路画面。怎么定 N我的算法是拿单端点上行带宽倒推。假设你要保证每路视频在 360p约 500 kbps到 720p约 1.5 Mbps之间考虑到浏览器通常同时显示 4 到 9 个画面会议规模建议 last-N单端点下行估算视频适用场景2-6 人-1不限与人数线性相关最多约 7 Mbps小团队评审要求所有人都清晰7-20 人8-12约 6-10 Mbps日常例会画面以缩略图为主21-50 人5-8约 4-8 Mbps培训、直播式会议50 人以上3-5约 3-6 Mbps大课、全员会只关注讲者要注意的是-1不是性能最好的选择而是最贵的选项。我见过一个 30 人的项目例会用了-1结果所有人的笔记本风扇都狂转原因是每台机器都在解码 29 路视频。改成 6 之后CPU 占用直接掉了一个数量级。4. 动手本地把 Colibri 调通并抓一次真实会话光看字段不够得真的跑一次把数据抓在手里你才有手感。4.1 环境与端口规划先在脑子里过一遍端口这块糊涂后面排查会很痛苦。端口协议用途是否对公网开放8080TCPColibri REST 接口绝对不要只给 Jicofo 访问9090TCPColibri WebSocket、统计接口通过反向代理暴露10000UDP媒体传输的主端口是必须放行4443TCPICE-TCP 回退视情况弱网环境建议开443TCP网页与信令是注意Colibri REST 接口本身没有身份认证任何能访问到 8080 的人都可以创建、查询、删除会议。这是 JVB 的设计假设——它默认跑在受信任的内网里。我见过有人图省事把 8080 直接映射到公网做接口调试等于把整个媒体服务交出去了。别干这个。4.2 起一套最小可用环境最省事的办法是用官方的容器编排仓库git clone https://github.com/jitsi/docker-jitsi-meet.git cd docker-jitsi-meet cp env.example .env ./gen-passwords.sh mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri} docker compose up -dgen-passwords.sh会往.env里写入各组件的内部凭据这一步不能跳跳了各组件之间认证会失败。启动完成之后.env里几个关键变量建议按需改PUBLIC_URL填你实际访问的域名或 IPHTTP_PORT、HTTPS_PORT按需改ENABLE_LETSENCRYPT本地测试填 0 免得反复申请证书。改完.env之后docker compose up -d会重建受影响的容器。我这套环境跑在一台 4 核 8G 的机器上同时开 3 个 720p 的视频端点CPU 占用大概在 40% 左右。如果你只是学习单机完全够用。4.3 从容器网络里手写 curl 创建会议JVB 的 8080 端口默认没有映射到宿主机这正好符合安全原则。但我们调试的时候又需要访问它。有两个办法一是临时docker compose exec进容器二是起一个临时容器挂到同一个网络里。我更喜欢后者因为不污染业务容器# 先看一眼网络名通常是 项目名_default docker network ls | grep jitsi # 起一个带 curl 的临时容器挂进同一个网络 docker run --rm -it --network docker-jitsi-meet_default \ curlimages/curl:latest sh进去之后先列一下当前有哪些会议大概率是空的curl -s http://jvb:8080/colibri/conferences然后手工创建cat /tmp/conf.json EOF { id: deadtester01, contents: [ { name: audio, channels: [ { id: deadtester01-audio, expire: 60, endpoints: [], sources: [] } ] } ] } EOF curl -s -X POST -H Content-Type: application/json \ --data-binary /tmp/conf.json \ http://jvb:8080/colibri/conferences返回的就是 JVB 眼中的会议对象。这里返回的 JSON 和请求略有不同JVB 会把默认值补全你把两边对比着看能很快搞明白哪些字段是必填的、哪些是可选的。接着做一次局部更新模拟端点加入curl -s -X PATCH -H Content-Type: application/json \ --data-binary /tmp/patch.json \ http://jvb:8080/colibri/conferences/deadtester01PATCH的语义是合并而不是替换所以你只需要给出变化的部分。这一点很关键我在早期做运维脚本的时候误用了POST全量替换结果每次更新都把没有变化的端点当成新增JVB 里堆了一大堆重复的 SSRC日志刷得飞起。用完记得删curl -s -X DELETE http://jvb:8080/colibri/conferences/deadtester014.4 观察真实会议的 Colibri 描述手工造的会议太干净看不出门道。真正的收获来自观察一次真实会议。在浏览器里开一个会议室让两三个客户端加入并打开摄像头然后在刚才那个临时容器里执行curl -s http://jvb:8080/colibri/conferences | python3 -m json.tool拿到会议 ID 之后curl -s http://jvb:8080/colibri/conferences/会议ID | python3 -m json.tool你会看到一份胖得多的描述。这时候拿它对照第 3 节的字段说明逐个核对有几个点特别值得看endpoints数组的长度应该和当前参会人数一致如果你发现有 5 个客户端但只有 3 个 endpoint说明有两个端点的注册请求丢了去翻 Jicofo 的日志找colibri相关的错误每个视频 source 的ssrc-groups里是不是有SIM组组内元素个数是不是 3三层 simulcast少了说明客户端的编码配置没生效candidates里的 IP 是不是你期望的那张网卡的地址如果不是八成是通告地址配错了payload-types里rtcp-fb有没有transport-cc没有的话带宽自适应是关着的。顺手再看一眼统计接口能对上号curl -s http://jvb:9090/colibri/stats | python3 -m json.tool统计里有一堆计数器我平时最关注的两个是当前会议数和端点总数用来做容量告警的基线。当端点总数逼近这台机器的压测上限时就该考虑加 bridge 了。4.5 Colibri WebSocket 的验证WebSocket 这条通道最容易静默失败因为它不通的时候客户端会自动降级界面上什么都不显示只是变慢。所以要主动验证。从浏览器开发者工具的 Network 面板里过滤colibri-ws你能看到一条 WebSocket 连接URL 形如wss://你的域名/colibri-ws/会议ID/端点ID?pwd一串令牌这个令牌是短期有效的用来校验连接的合法性客户端不需要理解它的内容照拼就行。要注意的是不同版本的拼接细节是否带 JVB 主机段会有差异以你自己的部署里实际发出的请求为准别照抄网上的示例。想从命令行验证可以用 websocatwebsocat -v wss://meet.example.com/colibri-ws/会议ID/端点ID?pwd令牌连不上时第一时间去看反向代理有没有传递升级头。nginx 里这段配置必须存在缺一不可location /colibri-ws/ { proxy_pass http://jvb:9090/colibri-ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; }proxy_http_version 1.1和Connection upgrade这两行是很多换了个 nginx 就出问题的元凶。默认的 HTTP/1.0 不支持协议升级连接会被直接掐断。5. 常见问题与排查实录5.1 故障速查表这几年我在生产环境遇到过的问题归纳下来其实就那么几类。先给你一张表命中之后直接跳到后面的定位方法。现象高概率原因定位入口会议创建返回 400JSON 结构不合法缺必填字段JVB 日志里的解析异常栈能进会议室但看不到别人last-N 过小或订阅意图没送达浏览器 Network 面板的 colibri-ws 帧画面切换明显迟钝WebSocket 未升级退回 HTTP 轮询反代配置、WebSocket 握手状态码带宽异常高simulcast 未分组被当成多路独立源Colibri 描述中的 ssrc-groups参与者列表出现幽灵expire 未刷新或设得过长定期刷新日志、会议描述中的 endpoint弱网下画面持续模糊payload-types 缺拥塞控制反馈Colibri 描述中的 rtcp-fb只有声音没画面source 缺失或视频 content 未创建Colibri 描述中的 contents 数组连接卡在建立阶段ICE 通告地址错误或防火墙未放行jvb 日志中的 ICE 状态5.2 三条定位路径第一条看描述。GET /colibri/conferences/{id}是排查的起点它能告诉你 JVB 心里的真相。我习惯把它存成文件跟上一小时的快照做 diff变化的部分往往就是问题所在。第二条看统计。/colibri/stats里的计数器能反映趋势。比如端点总数在缓慢上升但会议数没变说明有端点在泄漏多半是 expire 刷新逻辑写错了。第三条看 WebSocket 帧。订阅意图是走这条通道的浏览器开发者工具能直接看到收发的 JSON 帧。这一层的信息比前面两条都近因为它是客户端的真实意图能看到客户端到底有没有发出这个请求。排查的顺序我一般反过来先看 WebSocket 帧确认意图发出去了再看 Colibri 描述确认 JVB 收到了最后看统计确认资源分配了。这样能快速判断问题出在没发出、没收到还是收到了没执行省掉大量瞎猜。5.3 我踩过的几个坑第一个坑是用会议 ID 做业务标识。Colibri 的会议 ID 是 JVB 内部生成或 Jicofo 指定的随机串JVB 重启之后可能就变了而且级联场景下同一场会议在不同 bridge 上的 ID 也不一样。拿它当业务主键做报表的时候一定会对不上。要么用 MUC 的房间名要么自己生成一个业务 ID 塞进自定义字段。第二个坑是忘了在描述里声明数据通道。Jitsi 的很多高级功能举手、聊天、屏幕共享的元信息走的是数据通道如果描述里没有对应的 content这些功能会全部静默失效。现象很奇怪视频音频都正常就是聊天发不出去、举手没反应。我查了两个小时信令最后发现是数据通道没建。第三个坑是把 refresh 做成全量推。前面说过PATCH是合并语义但如果你每次刷新都推一份完整的描述而这份描述里包含了刚刚离开的端点因为你本地缓存的列表没更新JVB 会认为那个端点又回来了于是幽灵永远清不掉。刷新逻辑要保证推的是当前真实状态不是我记得的状态。第四个坑是忽视 rtp-level-relay-type。音频如果配成了mixer但 JVB 没启用混音模块音频会直接断掉。改这个字段之前先确认你的部署里到底有没有开音频混合。6. 从 Colibri 的视角做容量与调优6.1 让 last-N 和 simulcast 配合起来这两个是配套的单看任何一个都调不好。simulcast 让一个视频源产生多个清晰度层last-N 决定你能同时看几个人。理想的状态是至少要有人看的那一层在订阅者的带宽里放得下。如果 last-N 给到 12 而总带宽只有 5 Mbps那平均每路分不到 450 kbps只能拿到最低层效果是能看但不清晰。这时候把 last-N 降到 6、每路分到 800 kbps拿到的就是中间层主观体验反而更好。我的调法是从小往大试先把 last-N 设成 4观察主观画质和下行带宽逐步加直到带宽接近用户端可承受的上限家宽场景我按 8 Mbps 预留为止。这个上限跟会议性质有关——如果是每个人都要看清的评审会宁可少几个人同时入画如果是听讲为主的大课last-N 给 3 都够。另外要注意发送端的 simulcast 层数是有限的。低端设备或者弱网下浏览器可能只发一层这时候 last-N 调多大都没意义因为根本没有更高的层可以选。遇到画质上不去又查不出原因先看一眼 ssrc-groups 里 SIM 组到底有几路。6.2 端口和带宽的估算单台 JVB 的容量主要受三样东西限制CPU做 RTP 转发和加密、网络带宽、以及并发端点数。带宽的估算方法是上行总带宽 端点数 × 每端点上行码率下行更复杂因为取决于订阅关系。一个粗算公式是下行总带宽 ≈ 端点数 × last-N × 单路视频码率 端点数 × 音频码率。举个例子50 人会议、last-N 给 6、每路视频平均 600 kbps、音频 40 kbps下行 ≈ 50 × 6 × 0.6 Mbps 50 × 0.04 Mbps ≈ 180 2 182 Mbps上行 ≈ 50 × 0.64 Mbps ≈ 32 Mbps也就是说这样一台机器至少要 250 Mbps 以上的带宽余量。这是我给出50 人以上建议 last-N 降到 5 以下这个建议的算法来源——不是拍脑袋。CPU 方面我实测的经验值是现代 x86 服务器上单核大约能扛 60 到 100 路并发媒体流取决于是否开启加密和各层码率。加密是绕不开的因为 WebRTC 强制 DTLS-SRTP。所以 8 核机器大致在 500 到 800 路流之间换算成端点大概是一两百人的规模具体还是要压测。6.3 什么时候该加 bridge判断信号有三个一是统计接口里的端点总数持续接近压测出的上限二是网络出口的带宽利用率长期超过 70%三是对端点到 JVB 的 RTT 明显上升——这通常意味着流量绕远了也就是该在被服务的区域附近再放一台 JVB 了。加 bridge 的好处是 Colibri 的级联支持能让你把端点分散到多台上而且对客户端几乎是透明的。加完之后Jicofo 会根据它掌握的负载信息来挑你要做的是保证新 JVB 的配置尤其是 ICE 通告地址和 WebSocket 域名和现有的完全一致否则会出现有的人连得上、有的人连不上的诡异现象。顺便提一句Jicofo 选 bridge 的依据是负载而不是 Colibri——Colibri 只管建好了之后怎么转发建在哪台是 Jicofo 决定的。所以调优的时候别只盯着 Colibri 的字段把 Jicofo 的负载阈值也一起看两边是对称的。最后分享一个我自己的小习惯每次做完配置变更我都会用第 4 节那套 curl 流程手工建一个会议、跑一轮真实通话、再GET一次描述存成基线文件。下次出问题的时候拿当前描述和基线做 diff通常五分钟之内就能定位到是哪块配置动了手脚。这套土办法比翻日志快得多尤其是当你手上有几十台 JVB、配置又不可能全部记住的时候。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻