点击上方“LiveVideoStack”关注我们
作者:Winlin、Azusachino、Benjamin 编辑:Alex
▲扫描图中二维码或点击阅读原文▲
了解音视频技术大会更多信息
当我们的业务超过单台流媒体服务器的承受能力,就会遇到负载均衡问题,一般我们会在集群中提供这种能力,但实际上集群并非是唯一的实现方式。有时候负载均衡还会和服务发现等时髦词汇联系起来,而云服务的LoadBalancer无疑不可回避,因此,这个问题其实相当复杂,以至于大家会在多个场合询问这个问题,我打算系统地阐述这个问题。
如果你已经知道了以下问题的所有答案,并且深刻了解背后的原因,那么你可以不用看这篇文章了:
好吧,让我们详细聊聊负载和负载均衡。
在解决系统均衡问题时,首先我们看看什么是负载?对于服务器来说,负载就是因为有客户端的访问,而导致的资源消耗上升。当资源消耗出现严重不均衡,可能会导致服务不可用,比如CPU跑满了,所有客户端都会变得不正常。
对于流媒体服务器而言,就是流媒体客户端导致的服务器资源消耗。一般我们会从服务器资源消耗角度度量系统负载:
当负载过高,会有什么问题?负载过高会导致系统直接出现问题,比如延迟增大,卡顿,甚至不可用。而这些负载的过载,一般都会有连锁反应。比如:
由此可见,系统的负载,首先需要被准确度量,也就是关注的是过载或超载(Overload)情况,这个问题也需要详细说明。
超过系统负载就是超载或过载(Overload),这看起来是个简单的问题,但实际上却并不简单,比如:
因此,对于CPU来说,知道流媒体服务器能消耗多少CPU,获取流媒体服务器的CPU消耗,才能准确定义过载:
网络带宽,一般是根据以下几个指标判断是否过载,流媒体一般和流的码率Kbps或Mpbs有关,代表这个流每秒是多少数据传输:
磁盘,主要涉及的是流的录制、日志切割以及慢磁盘导致的STW问题:
内存主要是涉及泄露和缓存问题,主要的策略是监控系统整体的内存,相对比较简单。
除了一般的资源消耗,在流媒体服务器中,还有一些额外因素会影响到负载或者负载均衡,包括:
这些问题当然不完全是负载和负载均衡问题,比如WebRTC支持的SVC和Simulcast功能,目的就是为了解决某些弱客户端的问题。有些问题是可以通过客户端的失败重试解决,比如高负载时的连接迁移,服务器可以强制关闭,客户端重试迁移到下一个服务器。
还有一种倾向,就是避免流媒体服务,而是用HLS/DASH/CMAF等切片,这就变成了一个Web服务器,上面所有的问题就突然没有了。但是,切片协议实际上只能做到3秒,或者比较常见的5秒以上的延迟场景,而在1到3秒的直播延迟,或者500ms到1秒的低延迟直播,以及200ms到500ms的RTC通话,以及100ms之内的控制类场景,永远都不能指望切片服务器,这些场景只能使用流媒体服务器实现,不管里面传输的是TCP的流,还是UDP的包。
我们在系统设计时,需要考虑这些问题,例如WebRTC应该尽量避免流和房间耦合,也就是一个房间的流一定需要分布到多个服务器上,而不是限制在一台服务器。这些业务上的限制越多,对于负载均衡越不利。
现在特别说明SRS的负载和过载情况:
特别说明一下SRS单线程的问题,这其实是个选择,没有免费的性能优化,多线程当然能提升处理能力,同时是以牺牲系统的复杂度为代价,同时也很难评估系统的过载,比如8核的多线程的流媒体服务器CPU多少算是过载?640%?不对,因为可能每个线程是不均匀的,要实现线程均匀就要做线程的负载调度,这又是更复杂的问题。
目前SRS的单线程,能适应绝大多数场景。对于直播来说,Edge可以使用多进程REUSEPORT方式,侦听在同样端口,实现消耗多核;RTC可以通过一定数量的端口;或者在云原生场景,使用docker跑SRS,可以起多个K8s的Pod,这些都是可选的更容易的方案。
Note: 除非是对成本非常敏感的云服务,那么肯定可以自己定制,可以付出这个复杂性的代价了。据我所知,几个知名的云厂商,基于SRS实现了多线程版本。我们正在一起把多线程能力开源出来,在可以接受的复杂度范围提升系统负载能力,详细请参考https://github.com/ossrs/srs/issues/2188。
我们了解了流媒体服务器的这些负载,接下来该考虑如何分担这些负载了。
Round Robin是非常简单的负载均衡策略:每次客户端请求服务时,调度器从服务器列表中找到下一个服务器给客户端,非常简单:
server = servers[pos++ % servers.length()]
如果每个请求是比较均衡的,比如Web请求一般很短时间就完成了,那么这种策略是比较有效的。这样新增和删除服务器,上线和下线,升级和隔离,都非常好操作。
流媒体长连接的特点导致轮询的策略并不好用,因为有些请求可能会比较久,有些比较短,这样会造成负载不均衡。当然,如果就只有少量的请求,这个策略依然非常好用。
SRS的Edge边缘集群中,在寻找上游Edge服务器时,使用的也是简单的Round Robin方式,这是假设流的路数和服务时间比较均衡,在开源中是比较合适的策略。本质上,这就是上游Edge服务器的负载均衡策略,相当于是解决了总是回源到一台服务器的过载问题。如下图所示:
源站集群中,第一次推流时,Edge也会选择一台Origin服务器,使用的也是Round Robin策略。这本质上就是Origin服务器的负载均衡策略,解决的是Origin服务器过载问题。如下图所示:
在实际业务中,一般并不会使用纯粹的Round Robin,而是有个调度服务,会收集这些服务器的数据,评估负载,给出负载比较低或者质量高的服务器。如下图所示:
如何解决Edge的负载均衡问题呢?依靠的是Frontend Load Balance策略,也就是前端接入侧的系统,我们下面讲常用的方式。
我们在Round Robin中重点介绍了服务内部的负载均衡,而直接对客户端提供服务的服务器,一般叫做Frontend Load Balancer,情况会有点不太一样:
其实DNS和HTTP DNS在调度能力上没有区别,甚至很多DNS和HTTP DNS系统的决策系统都是同一个,因为它们要解决的问题是一样的:如何根据用户的IP,或者其他的信息(比如RTT或探测的数据),分配比较合适的节点(一般是就近,但也要考虑成本)。
DNS是互联网的基础,可以认为它就是一个名字翻译器,比如我们在PING SRS的服务器时,会将ossrs.net
解析成IP地址182.92.233.108
,这里完全没有负载均衡的能力,因为就一台服务器而已,DNS在这里只是名字解析:
ping ossrs.net
PING ossrs.net (182.92.233.108): 56 data bytes
64 bytes from 182.92.233.108: icmp_seq=0 ttl=64 time=24.350 ms
而DNS在流媒体负载均衡时的作用,其实是会根据客户端的IP,返回不同服务器的IP,而DNS系统本身也是分布式的,在播放器的/etc/hosts
文件中就可以记录DNS的信息,如果没有就会在LocalDNS(一般在系统中配置或自动获取)查询这个名字的IP。
这意味着DNS能抗住非常大的并发,因为并不是一台中心化的DNS服务器在提供解析服务,而是分布式的系统。这就是为何新建解析时会有个TTL(过期时间),修改解析记录后,在这个时间之后才会生效。而实际上,这完全取决于各个DNS服务器自己的策略,而且还有DNS劫持和篡改等操作,所以有时候也会造成负载不均衡。
因此HTTP DNS就出来了,可以认为DNS是互联网的运营商提供的网络基础服务,而HTTP DNS则可以由流媒体平台也就是各位自己来实现,就是一个名字服务,也可以调用一个HTTP API来解析,比如:
curl http://your-http-dns-service/resolve?domain=ossrs.net
{["182.92.233.108"]}
由于这个服务是自己提供的,可以自己决定什么时候更新该名字代表的含义,当然可以做到更精确的负载,也可以用HTTPS防止篡改和劫持。
Note: HTTP-DNS的这个
your-http-dns-service
接入域名,则可以用一组IP,或者用DNS域名,因为它只有少数节点,所以它的负载相对比较好均衡。
SRS支持Vhost,一般是CDN平台用来隔离多个客户,每个客户可以有自己的domain域名,比如:
vhost customer.a {
}
vhost customer.b {
}
如果用户推流到同一个IP的服务器,但是用不同的Vhost,它们也是不同的流,播放时地址不同也是不同的流,例如:
• rtmp://ip/live/livestream?vhost=customer.a
• rtmp://ip/live/livestream?vhost=customer.b
Note:当然,可以直接用DNS系统,将IP映射到不同的域名,这样就可以直接在URL中用域名代替IP了。
其实Vhost还可以用作多源站的负载均衡,因为在Edge中,可以将不同的客户分流到不同的源站,这样可以不用源站集群也可以扩展源站的能力:
vhost customer.a {
cluster {
mode remote;
origin server.a;
}
}
vhost customer.b {
cluster {
mode remote;
origin server.a;
}
}
那么不同的Vhost实际上共享了Edge节点,但是Upstream和Origin可以是隔离的。当然也可以配合Origin Cluster来做,这时候就是多个源站中心,和Consistent Hash要实现的目标有点像了。
在Vhost隔离用户的场景下,会导致配置文件比较复杂,还有一种更简单的策略,也可以实现类似的能力,那就是一致性Hash(Consistent Hash)。
比如,可以根据用户请求的Stream的URL做Hash,决定回源到哪个Upstream或Origin,这样就一样可以实现同样的隔离和降低负载。
实际应用中,已经有这种方案在线上提供服务,所以方案上肯定是可行的。当然,SRS没有实现这个能力,需要自己码代码实现。
其实Vhost或者Consistent Hash,也可以配合Redirect来完成更复杂的负载均衡。
302是重定向(Redirect),实际上也可以用作负载均衡,比如通过调度访问到服务器,但是服务器发现自己的负载过高,那么就给定向到另外一台服务器,如下图所示:
Note: 除了HTTP 302,实际上RTMP也支持302,SRS的源站集群就是使用这个方式实现的。当然这里302主要用于流的服务发现,而不是用于负载均衡。
既然RTMP也支持302,那么在服务内部,完全可以使用302来实现负载的再均衡。若某个Upstream的负载过高,就将流调度到其他的节点,并且可以做多次302跳转。
一般在Frontend Server中,只有HTTP流才支持302,比如HTTP-FLV或者HLS。而RTMP是需要客户端支持302,这个一般支持得很少,所以不能使用。
另外,基于UDP的流媒体协议,也有支持302的,比如RTMFP,一个Adobe设计的Flash的P2P的协议,也支持302。当然目前用得很少了。
WebRTC目前没有302的机制,一般是依靠Frontend Server的代理,实现后续服务器的负载均衡。而QUIC作为未来HTTP3的标准,肯定是会支持302这种基本能力的。而WebRTC逐渐也会支持WebTransport(基于QUIC),因此这个能力在未来也会具备。
SRS Edge本质上就是Frontend Server,它可以解决以下问题:
如下图所示:
特别说明:
由于Edge本身就是Frontend Server,因此一般不需要为了增加系统容量,再在前面挂Nginx或LB,因为Edge本身就是为了解决容量问题,而且只有Edge能解决合并回源的问题。
Note:合并回源,指的是同一个流只会回源一次,比如有1000个播放器连接到了Edge,Edge只会从Upstream获取一路流,而不会获取1000路流,这和透明Proxy是不同的。
当然,有时候还是需要前面挂Nginx或LB,比如:
除此之外,不应该在Edge前面再挂其他的服务器,应该直接由Edge提供服务。
和Edge边缘集群不同,SRS源站集群主要是为了解决源站扩展能力:
SRS源站集群是不建议直接对外提供服务,而是依靠Edge对外服务,因为使用了两个简单的技术:
Note: 其实Edge在回源前也可以先访问流查询服务,找到有流的源站后再发起连接。但是有可能流会切走,所以还是需要一个重新定位流的过程。
这个过程非常简单,如下图所示:
由于流始终只在一个源站上面,因此生成HLS切片时也会由一个源站生成,不需要做同步。一般使用共享存储的方式,或者使用on_hls
将切片发送到云存储。
Note: 还有一种方式,使用双流热备,一般是两个不同的流,在内部实现备份。这种一般需要自己实现,而且对于HLS、SRT和WebRTC都很复杂,SRS没有支持也不展开了。
从负载均衡角度看源站集群,实际上是调度器实现的负载均衡,比如Edge回源时若使用Round Robin,或者查询专门的服务应该往哪个源站推流,甚至当源站的负载过高,可以主动断开流,让流重推实现负载的重新均衡。
WebRTC的负载只在源站,而不存在边缘的负载均衡,因为WebRTC的推流和观看几乎是对等的,而不是直播这种一对万级别的不对等。换句话说,边缘是为了解决海量观看问题,而推流和观看差不多时就不需要边缘做负载均衡(直播可以用来做接入和跳转)。
WebRTC由于没有实现302跳转,因此接入都没有必要边缘做负载均衡了。比如在一个Load Balance,也就是一个VIP后面有10台SRS源站,返回给客户端的都是同一个VIP,那么客户端最终会落到哪个SRS源站呢?完全就是看Load Balance的策略,这时候并不能像直播一样加一个边缘实现RTMP 302的跳转。
因此,WebRTC的负载均衡,就完全不是Edge能解决的,它本来就是依靠源站集群。一般在RTC中,这种叫做Casecade(级联),也就是它们是平等关系,只是一级一级的路由一样地连接起来,增加负载能力。如下图所示:
这里和OriginCluster有本质的不同,因为OriginCluster之间并没有媒体传输,而是使用RTMP 302让Edge跳转到指定的源站,因为源站的负载是可控的,它最多只有有限个Edge来回源取流。
而OriginCluster不适合WebRTC,因为客户端需要直接连接到源站,这时候源站的负载是不可控的。比如在100人的会议中,每个流会有100个人订阅,这时候需要将每个用户分散到不同的源站上,和每个源站建立连接,实现推流和获取其他人的流。
Note: 这个例子很罕见,一般100人互动的会议,会使用MCU模式,由服务器合并成一路流,或者选择性的转发几路流,服务器内部的逻辑是非常复杂的。
实际上考虑WebRTC的常用场景,就是一对一通话,基本上占了80%左右的比例。那么这时候每个人都推一路流,播放一路流,是属于典型的流非常多的情况,那么用户可以完全连接到一个就近的Origin,而一般用户的地理位置并不相同,比如在不同的地区或国家,那么源站之间级联,可以实现提高通话质量的效果。
在源站级联的结构下,用户接入使用DNS或HTTP DNS协议访问HTTPS API,而在SDP中返回源站的IP,因此这就是一次负载均衡的机会,可以返回离用户比较接近而且负载较低的源站。
此外,多个源站如何级联,若大家地区差不多,可以调度到一台源站避免级联,这可以节约内部的传输带宽(在大量的同地区一对一通话时很值得优化),同时也增加了负载的不可调度性,特别是它们会演变成一个多人会议。
因此,在会议中,区分一对一的会议,和多人会议,或者限制会议人数,对于负载均衡实际上是非常有帮助的。如果能提前知道这是一对一会议,那么就更容易调度和负载均衡。很可惜的是,产品经理一般对这个不感兴趣。
Remark: 特别说明,SRS的级联功能还没有实现,只是实现了原型,还没有提交到开源仓库。
特别补充一下WebRTC相关的协议,比如TURN、ICE和QUIC等。
ICE其实不算一个传输协议,它更像是标识协议,一般指Binding Request和Response,会在里面带有IP和优先级信息,来标识地址和信道的信息,用于多条信道的选择,比如4G和WiFi都很好时优先选谁。还会用作会话的心跳,客户端会一直发送这个消息。
因此ICE对于负载均衡没有作用,但是它可以用来标识会话,和QUIC的ConnectionID作用类似,因此在经过Load Balance时可以起到识别会话的作用,特别是客户端的网络切换时。
而TURN协议其实是对Cloud Native非常不友好的协议,因为它是需要分配一系列的端口,用端口来区分用户,这种是在私有网络中的做法,假设端口无限,而公有云上端口往往是受限的,比如需要经过Load Balance这个系统时,端口就是有限的。
Note: 当然TURN也可以复用一个端口,而不真正分配端口,这限制了不能使用TURN直接通信而是经过SFU,所以对于SRS也没有问题。
TURN的真正用处是降级到TCP协议,因为有些企业的防火墙不支持UDP,所以只能使用TCP,而客户端需要使用TURN的TCP功能。当然了,也可以直接使用TCP的host,比如mediasoup就已经支持了,而SRS还没有支持。
QUIC比较友好的是它的0RTT连接,也就是客户端会缓存SSL的类似ticket的东西,可以跳过握手。对于负载均衡,QUIC更有效的是它有ConnectionID,那么经过LoadBalance时,尽管客户端改变了地址和网络,Load Balance还是能知道后端哪个服务来处理它,当然这其实让服务器的负载更难以转移了。
其实WebRTC这么复杂的一套协议和系统,讲起来都是乱糟糟的,很糟心。由于100ms级别的延迟是硬指标,所以必须使用UDP和一套复杂的拥塞控制协议,再加上安全和加密也是基本能力,也有人宣称Cloud Native的RTC才是未来,引入了端口复用和负载均衡,以及长连接和重启升级等问题,还有那改得天翻地覆的结构,以及来搅局的HTTP3和QUIC……
或许对于WebRTC的负载均衡,有一句话是最适用的:世上无难事,只要肯放弃。
所有负载均衡的前提,就是能知道负载,这依赖数据采集和计算。Prometheus(https://prometheus.io)就是做这个用的,它会不断采集各种数据,按照它的一套规则,也可以计算这些数据,它本质上就是一个时序数据库。
系统负载,本质上就是一系列的时序数据,会随着时间变化。
比如,Prometheus有个node_exporter (https://github.com/prometheus/node_exporter),它提供了主机节点的相关时序信息,比如CPU、磁盘、网络、内存等,这些信息就可以作为计算服务负载的依据。
每个应用服务,也会有对应的exporter,比如redis_exporter (https://github.com/oliver006/redis_exporter)采集Redis的负载数据,nginx-exporter(https://github.com/nginxinc/nginx-prometheus-exporter)采集Nginx的负载数据。
目前SRS还没有实现自己的exporter,未来一定会实现,详细请参考https://github.com/ossrs/srs/issues/2899。
喜欢我们的内容就点个“在看”吧!
本文分享自 LiveVideoStack 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!