SRS5優化:如何將DVR性能提升一倍

Written by 王磊(bluestn).

Summary

SRS支持將直播錄制為VoD文件,在壓測時,如果流路數很多,會出現CPU消耗很多的問題。

原因是寫入較小視頻包時,SRS使用了write,由于沒有緩沖能力,導致頻繁的系統調用和磁盤繁忙。

優化方案,可以選擇fwrite和內存盤方案,可將DVR性能提升一倍以上。

Environments

SRS dvr服務器配置如下:

  • CPU: INTEL Xeon 4110 雙路16和32線程
  • 內存:32G
  • 網卡:10Gb
  • 磁盤:兩塊980G的SSD盤做成RAID0(可用空間共1.8T)
  • 操作系統:CentOS 7.6。
  • 流碼率:3Mbps

這里需要說明一下,采用SSD盤主要是為了確保磁盤性能足夠,以確保能夠支撐大的并發壓力,從而在大并發壓測的情況下觀察系統性能情況,如果本身磁盤I/O性能比較低下,大量的I/O等待可能導致觀察不到CPU瓶頸的現象。

另外,在我的測試環境中,SRS經過了多進程改造,能夠支持推流進來后自動將不同的流均衡到不同的SRS進程上面,從而能夠充分利用服務器多核的能力,但是由此得出的結論同樣適合于單進程SRS。

SRS開啟DVR錄存功能,使用如下命令啟動SRS:

env SRS_LISTEN=1935 SRS_MAX_CONNECTIONS=3000 SRS_DAEMON=off SRS_SRS_LOG_TANK=console \
    SRS_HTTP_API_ENABLED=on SRS_VHOST_DVR_ENABLED=on ./objs/srs -e

壓測工具,用srs_bench套件中的sb_rtmp_publish模擬推流客戶端進行大并發量推流模擬,一臺機器壓測能力不夠可以開啟多臺機器進行壓測。

./objs/sb_rtmp_publish  -i doc/source.200kbps.768x320.flv \
    -c 100 -r rtmp://127.0.0.1:1935/live/livestream_{i}

啟動srs后,用壓測工具進行壓測,觀察測試過程中的CPU、網絡IO、磁盤IO相關數據,并進行對比。

write Disk

SRS優化前,默認的方式就是使用write方法,直接寫入磁盤。測試能支持1000路寫入,CPU跑滿。

image-20230107141356751.png

從上圖可以看到,1000路3M的DVR錄制已經將系統的CPU都跑滿了,特別需要關注的是cpu的時間主要消耗在了內核空間上面,占了87.5%。

image-20230107142020562.png

用nload查看當時的輸入帶寬情況,發現系統輸入帶寬平均只有2.17Gb,沒有達到預期的3Gb的帶寬,應該是CPU負載過高導致SRS來不及處理網絡I/O引起的性能下降。

再用perf工具對其中一個srs 進程進行性能采樣分析,得到下面的火焰圖:

image-20230107142537021.png

可以發現,sys_write操作占用的時間消耗是最多的,對比上面用top看到的內核態消耗的時長占比可以得出的結論是一致的。

最后看磁盤I/O情況:

image-20230107143617749.png

從上圖看磁盤的利用率沒有到100%,雖然有一定的波動,但是總體上還是在合理的可以接受的性能范圍內。

fwrite Disk

SRS優化后,使用fwrite寫入磁盤。錄制1000路流,占用32%的CPU,性能提升一倍以上。

image-20230107142837114.png

從上圖可以看到,1000路3M的DVR錄制已經將系統的CPU整體來說還有很多空閑(這里說明一下,部分進程的SRS占比高的原因是因為當時任務分配的不夠均衡引起的)。特別值得注意的是本次測試內核時間占比大幅下降,只有9.1%。

再用nload看網絡i/o情況,如下圖:

image-20230107143330746.png

網絡i/o相當平穩,和預期的3Gb完全吻合。

再看磁盤i/o的情況:

image-20230107143449857.png

從上圖看磁盤的利用率沒有到100%,雖然有一定的波動,但是總體上還是在合理的可以接受的性能范圍內。

最后看火焰圖:

image-20230107145024141.png

系統調用的時間占比大幅度縮短了,在上圖幾乎找不到sys_write的位置了。

write Memory Disk

SRS優化前,也可以掛載內存盤,使用write寫入內存盤。

需要說明一下,由于我手上的服務器只有32G內存,只能分配16G內存給內存盤使用, 由于內存盤比較小,按照3Gb的寫入速度,最多能寫42s的DVR。

采用如下命令掛載內存盤:

mount -t tmpfs -o size=16G,mode=0755 tmpfs /data/memdisk

并且修改srs的配置文件將文件寫入到內存盤:

env SRS_LISTEN=1935 SRS_MAX_CONNECTIONS=3000 SRS_DAEMON=off SRS_SRS_LOG_TANK=console \
    SRS_VHOST_DVR_DVR_PATH=/data/memdisk/[app]/[stream].[timestamp].flv \
    SRS_HTTP_API_ENABLED=on SRS_VHOST_DVR_ENABLED=on ./objs/srs -e

測試數據如下,占用CPU27%左右:

image-20230107144531967.png

從CPU的情況看,采用內存盤也比較理想,load average只有 7.5,性能也不錯。如果不需要錄制大量的流,這種方式也是非常好的。

macOS Test Data

在macOS環境下,也做了一組數據,供參考:

  1. macOS: MacBook Pro, 16-inch, 2019, 12CPU(2.6 GHz 6-Core Intel Core i7), 16GB memory(16 GB 2667 MHz DDR4).
  2. v5.0.132優化前: RTMP to HLS, 200 streams, SRS CPU 87%, 740MB
  3. v5.0.133優化后: RTMP to HLS, 200 streams, SRS CPU 56%, 618MB
  4. v5.0.132優化前: DVR RTMP to FLV, 500 streams, SRS CPU 83%, 759MB
  5. v5.0.133優化后: DVR RTMP to FLV, 500 streams, SRS CPU 35%, 912MB
  6. v5.0.133優化后: DVR RTMP to FLV, 1200 streams, SRS CPU 79%, 1590MB

Conclusion

從以上4個測試可以得出以下結論:

  1. 無論ssd盤還是內存盤,采用fwrite的性能比采用write的性能有明顯的提升,其主要得益于fwrite內置的緩存功能減少了系統調用的數量,帶來內核時間消耗的減少,從而提升了性能。
  2. 在ssd盤情況下,fwrite的緩沖能力可以大幅度降低對于CPU的消耗,但是在采用內存盤的情況下,CPU的消耗雖然也能夠降低,但是不是那么明顯。
  3. 錄制到內存盤性能也很好,如果流路數不多也可以考慮這種方案。

Note: 之前想當然地認為用write寫內存盤,因為系統調用引起的用戶態到核心態的切換還是會導致cpu大量消耗,一樣會導致CPU消耗高居不下,但是事實看到是采用內存盤以后cpu消耗明顯下降了,是不是可以認為系統調用引起的用戶態到核心態的切換消耗實際上并沒有想象的那么大,而是內核態在處理小塊的文件write寫入磁盤的時候還存在著其他因素引起消耗大量的cpu。譬如,因為最終寫入磁盤都是按照扇區寫入的,而小塊寫入需要操作系統將這個小塊對齊并填充到一個完整的磁盤扇區,從而引起性能大幅下降,而內存盤是不是就不會存在這個問題?由于我自己沒有內核方面的經驗,所以只能存疑了,也請懂內核的朋友給予指點。

What's Next

在linux環境中,對于文件進行讀寫操作的時候,我們可以采用libc提供的fread/fwrite系列的一套函數,也可以采用操作系統提供的read/write系列的一套系統api函數。

對于libc提供的文件讀寫函數,首先它可移植性比較好,因為libc為我們屏蔽了操作系統的底層差異,在linux、windows等不同的操作系統環境下面都有標準的接口實現,因此不需要我們為不同的操作系統進行適配。其次,libc提供了帶緩沖功能的讀寫能力,而操作系統底層文件讀寫API卻不提供這種能力,緩沖能力在大多數情況下能夠為我們帶來文件i/o性能的提升。

當然libc的文件讀寫api函數也存在不足之處,缺少了writev/readv之類的函數。不過readv/writev的功能無非就是將多個緩沖區的內容合并成一次批量讀寫操作,而不需要進行多次API調用,從而減少實際物理I/O的次數,我想libc沒有提供這類函數主要也是因為其緩沖功能已經能夠將本來需要多次的小塊物理I/O操作合并成了一次更大塊的物理i/o操作,所以就沒有必要再提供readv/writev了。

不管SRS也好,還是NGINX也好,雖然前者采用st-thread框架的協程能力來實現網絡異步i/o,但是和后者一樣,最終還是采用epoll事件循環來實現網絡異步i/o的,但是對于文件i/o,目前存在的問題是,無論是write還是fwrite都是同步操作,在磁盤請求比較繁忙的情況下,必然會導致進程或者線程阻塞,從而引起系統并發性能的下降。

由于操作系統本身不支持epoll異步(linux下的ext4本身沒有實現poll的回調),所以寄希望于epoll來實現文件i/o的異步操作是行不通的。NGINX對于文件異步i/o采用了aio+多線程的方式來實現的,個人感覺是由于和epoll模型來說是一套獨立的框架,還是相對比較復雜。

不過,好在linux在5.1內核以后提供了io_uring的異步i/o框架,它可以統一網絡i/o和磁盤i/o的異步模型,并支持buffer IO,值得我們去關注學習一下,也值得我們后面一起去探討一下未來如何在srs上采用io_uring來實現帶有fwrite一樣的緩沖能力的磁盤i/o的操作,來徹底解決磁盤i/o引起的性能瓶頸的問題。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容