🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 性能基准 ### 服务器的top图 命令是 `top -H -p $(cat objs/srs.pid)` ,看CPU和内存。还有每个CPU的消耗情况(进入top后按数字1),比如us是用户空间函数,sy是内核函数,si是网卡软中断。 ### 系统的网络带宽图 命令是 `dstat` ,看出入口带宽。比如视频平均码率是600kbps,那么900个推流时,网卡的recv流量应该是`600*900/8000.0KBps`也就是`67.5MBps`,如果网卡吞吐率达不到预期,那么肯定会出现卡顿等问题,比如可能是系统的网卡队列缓冲区\[18\]太小导致丢包。 ### 服务器关键日志 命令是 `tail -f objs/srs.log |grep -e 'RTC: Server' -e Hybrid` ,查看RTC的连接数和关键日志,以及进程的CPU等信息。如果连接数达不到预期,或者CPU接近100%,也是有问题的。 ### 服务器热点函数列表 命令是 `perf top -p $(cat objs/srs.pid)` ,可以看到当前主要的热点函数列表,以及每个函数所占用的百分比。性能优化一般的思路,就是根据这个表,优化掉排名在前面的热点函数。 ## 工具链 没有工具链就无法做性能优化,查看网络带宽工具`dstat`,查看热点函数工具`perf`,查看CPU工具`top`。 •`sysctl`:修改内核UDP缓冲区,防止内核丢包。•`GPERF: GCP`:使用GCP分析热点函数的调用链,图形化展示。•`taskset`:进程绑核后,避免软中断干扰,便于查看数据。 对于RTC,很重要的是需要把内核协议栈的缓冲区改大,默认只有200KB,必须改成16MB以上,否则会导致丢包: ~~~ sysctl net.core.rmem_max=16777216sysctl net.core.rmem_default=16777216sysctl net.core.wmem_max=16777216sysctl net.core.wmem_default=16777216 ~~~ 可以直接修改文件`/etc/sysctl.conf`,重启也能生效: ~~~ # vi /etc/sysctl.confnet.core.rmem_max=16777216net.core.rmem_default=16777216net.core.wmem_max=16777216net.core.wmem_default=16777216 ~~~ 如果perf热点函数比较通用,比如是`malloc`,那我们可能需要分析调用链路,看是哪个执行分支导致`malloc`热点,由于SRS使用的协程,`perf`无法正确获取堆栈,我们可以用`GPERF: GCP`工具: ~~~ # Build SRS with GCP./configure --with-gperf --with-gcp && make# Start SRS with GCP./objs/srs -c conf/console.conf# Or CTRL+C to stop GCPkillall -2 srs# To analysis cpu profile./objs/pprof --text objs/srs gperf.srs.gcp* ~~~ 图形化展示时,需要安装依赖`graphviz`: ~~~ yum install -y graphviz ~~~ 然后就可以生成SVG图,用浏览器打开就可以看了: ~~~ ./objs/pprof --svg ./objs/srs gperf.srs.gcp >t.svg ~~~ 还可以使用`taskset`绑定进程到某个核,这样避免在不同的核跳动,和软中断跑在一个核后干扰性能,比如一般软中断会在CPU0,我们绑定SRS到CPU1: ~~~ taskset -pc 1 $(cat objs/srs.pid) ~~~ > Note:如果是多线程模式,可以增加参数`-a`绑定所有线程到某个核,或者在配置文件中,配置`cpu_affinity`指定线程的核。 ## 内存交换性能 现代服务器的内存都很大,平均每个核有2GB内存,比如: * ECS ecs.c5.large\[23\], 2CPU 4GB 内存,1Gbps内网带宽。 * ECS ecs.c5.xlarge\[24\], 4CPU 8GB 内存,1.5Gbps内网带宽。 * ECS ecs.c5.2xlarge\[25\], 8CPU 16GB 内存,2.5Gbps内网带宽。 还有其他型号的,比如G5\[26\]每个核是4GB内存,比如R5\[27\]更是每个核高达8GB内存。这么多内存,对于无磁盘缓存型的网络服务器,直播转发或者SFU转发,一般内存是用不了这么多的,收包然后转发,几乎不需要缓存很久的数据。 因此,线上的视频服务器一般内存都是很充足的,有些情况下可以用内存来优化性能的地方,就可以果断的上内存缓存(Cache)策略。 比如,在直播播放时,SRS有个配置项叫合并写入(发送): ## 查找优化 STL的vector和map的查找算法,已经优化得很好了,实际上还是会成为性能瓶颈。 比如,RTC由于实现了端口复用,需要根据每个UDP包的五元组(或其他信息),查找到对应的Session处理包;Session需要根据SSRC找到对应的track,让track处理这个包。 比如,SRS的日志是可追溯的,打印时会打印出上下文ID,可以将多个会话的日志分离。这个Context ID是存储在全局的map中的,每次切换上下文需要根据协程ID查找出对应的上下文ID。 如果每个包都需要这么运算一次,那开销也是相当可观的。考虑根据UDP包查找Session,如下图: ## UDP协议栈 在直播优化中,我们使用`writev`一次写入大量的数据,大幅提高了播放的性能。 其实UDP也有类似的函数,UDP的`sendto`对应TCP的`write`,UDP的`sendmmsg`对应TCP的`writev`,我们调研过UDP/sendmmsg\[30\]是可以提升一部分性能,不过它的前提是: * 在Perf中必须看到UDP的相关函数成为热点,如果有其他的热点比UDP更耗性能,那么上sendmmsg也不会有改善。 * 一般并发要到2000以上,UDP协议栈才可能出现在perf的热点,较低并发时收发的包,还不足以让UDP的函数成为热点。 * 由于不能增加延迟,需要改发送结构,集中发给多个地址的UDP包统一发送。这对可维护性上是比较大的影响。 还有一种优化是GSO,延迟分包。我们调研过UDP/GSO\[31\],比`sendmmsg`提升还要大一些,它的前提是: * 和`sendmmsg`一样,只有当UDP相关函数成为perf的热点,优化才有效。 * GSO只能对一个客户端的包延迟组包,所以他的作用取决于要发给某个客户端的包数目,由于RTC的实时性要求,一般2到3个比较常见。 还有一种优化的可能,就是ZERO\_COPY,其实TCP的零拷贝支持得比较早,但是UDP的支持得比较晚一些。收发数据时,需要从用户空间到内核空间不断拷贝,不过之前测试没有明显收益,参考ZERO-COPY\[32\]。 *****