当前位置:首页 > 新闻 > 正文

用Go语言从零搭建一个视频直播系统?别慌,其实就这么回事

  • 新闻
  • 2026-08-03 07:27:31
  • 64
摘要: 说实话,我第一次被问到“用Go怎么做视频直播”的时候,脑子里第一反应是——这玩意儿不是得用C++或者搞个Nginx加RTMP模块...

说实话,我第一次被问到“用Go怎么做视频直播”的时候,脑子里第一反应是——这玩意儿不是得用C++或者搞个Nginx加RTMP模块吗?但后来真上手了才发现,Go语言做直播服务端,简直顺手得不像话,尤其是当你受够了Node.js那种单线程被流媒体一冲就崩的体验,Go的并发模型简直就是为直播量身定做的。

先搞清楚,直播到底在传什么

咱们别急着写代码,先花两分钟把直播这件事儿掰扯清楚,你手机上看直播,本质上就是一个不断产生数据的摄像头(推流端),把视频帧切成一小块一小块,通过RTMP或者WebRTC协议发到服务器上,服务器这边拿到数据流,不存盘,直接转发给正在观看的几百几千个人(拉流端)。

关键点在哪?延迟并发,延迟高了,主播喊“三二一”你三秒后才听见,那体验就废了,并发高了,服务器内存一炸,全体黑屏,这时候Go的优势就出来了——goroutine轻量到可以给每个连接开一个协程,几万并发就跟玩似的,内存占用比Java那套线程模型低了一个数量级。

第一步:别从零造轮子,先选对协议

你肯定不想自己写RTMP解析器,那玩意儿跟天书一样,好在Go社区有现成的库,我推荐你直接看两样:

库名 用途 特点
gstreamer 底层音视频处理 功能猛但API有点糙
gortsplib RTSP/RTP流媒体 延时低,适合安防类
livekit WebRTC服务端 自带SFU能力,适合低延迟直播

以我个人经验,国内直播场景还是RTMP最稳(对,就是那个Adobe搞的老古董,但人家生态就是成熟),推流端用OBS这种软件,直接推RTMP地址到你的Go服务上,拉流端用flv.js或者hls.js就能在浏览器里播。

第二步:Go服务端到底怎么写核心逻辑

给你一个最简框架的感觉,我不写完整代码(那太长),就给你指路核心模块。

推流接入这块,你需要一个rtmp.Server,监听1935端口(这是RTMP默认端口),每个连接进来,go就自动起一个goroutine处理:

func handleRTMPConn(conn *rtmp.Conn) {
    streamPath := conn.URL.Path
    // 把streamPath存到一个全局map里,value是这个流的所有订阅者chan
    // 然后循环读conn里的视频flv tag,每次读到就往所有订阅者的chan里塞一份数据
}

这里最大的坑是背压控制,如果某个观看者网速慢,你往他channel里塞数据塞不进去,就会把整个goroutine卡住,解决办法就是用带缓冲的channel,满了就丢帧(直播里丢几帧完全看不出问题),这个思路,你在别的语言里要实现得想破头,在Go里一个make(chan []byte, 500)就解决了。

拉流这块,就是HTTP服务,用户通过http://yourdomain.com/live/stream1.flv来请求流数据,你从刚才的全局map里找到对应的订阅者chan,然后写进http.ResponseWriter里就行。

func handleFlvPull(w http.ResponseWriter, r *http.Request) {
    streamID := strings.TrimPrefix(r.URL.Path, "/live/")
    pubChan, ok := streams.Get(streamID)
    if !ok {
        http.Error(w, "no such stream", 404)
        return
    }
    // 设置Content-Type为video/x-flv
    // for range pubChan { w.Write(frameData) }
    // 记得要设置flv头文件,9字节+metadata
}

你看,连读写锁都不用加,因为Go的map并发读没问题,只要别并发写就行,这里你只需要在推流那边加一个sync.RWMutex保护整个map的增删,性能损失微乎其微。

第三步:保证直播不卡的三个土办法

第一招:别用TCP默认的滑动窗口。 直播数据是全天候高频小包,你需要调整TCP的ReadBufferWriteBuffer大小,让内核别老做小包合并,Go的net.Option可以直接设,别忘了。

第二招:GOP缓存。 新观众进来的时候,你不能让他从当前帧开始看(那画面是花的),你得先给他发一个关键帧(GOP),所以你要在推流过程中,把最近1~2秒的关键帧数据在内存里暂存一下,新人进来先发这个,这个思路跟HLS直播ts切片一个道理。别小看这一步,没有它新用户全是马赛克。

第三招:用Goroutine池限制推流数量。 虽然Go能开几十万协程,但你如果真让十万个协程同时往一个网络文件描述符上写数据,还是会出现锁竞争,用worker pool模式,比如1000个worker,每个worker对应100个连接,打包批量写,这个优化可以放到压力测试以后再搞,前期别过度设计。

第四步:关于WebRTC的升级之路

你如果觉得HTTP-FLV延迟还是高(大概2~3秒),想做到微信视频那种几百毫秒级别,那就得上WebRTC。livekit这个开源项目就是Go写的SFU服务器,功能全得吓人,你只需要做两件事:

第一,部署livekit服务端,它自带媒体转发和协议协商能力,第二,你的业务服务(比如鉴权、房间管理)用Go的livekit-server-sdk调用它的API去创建房间、获取token。

这里有个要点得说下:WebRTC是UDP协议,所以对你的服务器网络要求很高,尤其要确保UDP端口是通的,很多云服务器默认防火墙只开了TCP端口,这个不调通,WebRTC永远起不来,我踩过这坑,折腾了一晚上,最后发现是安全组没开UDP 50000-60000端口区间。

第五步:千万别忘了监控和日志

直播这行,出了事故你根本不知道是推流端坏了还是网络抖动还是代码bug,所以每个关键操作一定要打日志

  • 推流端断开了,要记录断开的点在第几帧
  • 拉流端的观看时长统计(也可以拿来做热点分析)
  • 全局的当前并发连接数,每个流的观众数(用expvar包就能最简单地暴露出去)
  • 帧率与丢包率,这个要定期从rtmp协议层取统计信息

我见过一个小型直播平台,上线一周后用户报卡顿,排查了半天是CPU核数不够导致垃圾回收频繁,后来在代码里加了runtime.GOMAXPROCS(4)强制只用四核,反而好了——因为Go的GC在CPU数太多的时候,有时候会莫名其妙地频繁触发,这种细节,就得靠监控数据才能定位。

最后聊点实在的

你如果是一个人搞小项目玩,那用Go加个简单的rtmp库,再前端放个ffmpeg转一下hls格式,能跑起来就算成功,但要真做商业化,我建议你直接看livekit的架构设计(它也是Go写的),你会体会到什么叫“用Go优雅地解决流媒体并发”。毕竟,直播这技术圈里,Go已经成了端管理服务的事实标准,这块生态比你想的成熟得多。

对了,查资料的时候别只搜中文,GitHub上搜golang rtmp example出来的宝藏多得很,还有cdn/blog/2023/03/29/go-livekit/那篇文章,作者写清楚了SFU与MCU的区别,看了你会更清楚自己该走哪条路。

行了,思路给你捋顺了,剩下就是你去键盘上把自己那个直播服务敲出来,记着那句话:直播服务器就两点——数据到的了,数据发得出去,Go语言已经帮你把第二点做到了极致,剩下的,你真没理由不行。

用Go语言从零搭建一个视频直播系统?别慌,其实就这么回事