性能优化:SRS为何能做到同类的三倍

2022-03-18 17:13:15 浏览数 (1)

性能无疑是服务器的核心能力,几乎每个开源服务器的介绍都是”高性能XXX服务器“。视频服务器由于业务的超复杂度,特别是WebRTC服务器,要做到高性能是非常有挑战的难点。

为何性能很重要?完备的功能需要用性能交换,安全性需要用性能交换,成本需要用性能交换,产品体验需要用性能交换,甚至系统弹性都需要性能交换。有了基础性能,就有了竞争力的资本;基础性能若有问题,举步维艰,想要干点啥都不容易,就像天生羸弱的身子板。

SRS虽然是单进程单线程模型,性能一直都很高,比如:

•单进程能跑满千兆或万兆网卡,一般的场景完全能覆盖。•性能是NginxRTMP或Janus的三倍左右,目前还没有更高性能的开源同类产品。•提供集群能力,水平扩展性能,在开源项目中也不多见。

这并不是终点,这个性能基准只够5年左右,随着业务发展一定会有更高性能的需求。目前服务器属于第二代高并发架构,也就是单线程架构:

•第一代高并发架构,1990~2010年,多线程架构,一般比较老的服务器都是这种架构,一般无法解决C10K[1]问题,比如Adobe AMS[2],Apache HTTP Server[3],Janus WebRTC Server[4]。核心问题是多线程的水平扩展性问题,并发越多,线程之间的同步和竞争开销就越大(这个问题也是现代语言Go在性能方面的硬伤,特别是在超多CPU比如64核或128核时,多线程的损耗会更大)。•第二代高并发架构,2010~2020年,单线程架构,多进程或单进程单线程都是这种架构,C10K问题得到比较好的解决,比如Nginx[5],SRS[6],MediaSoup[7]。核心问题是单线程引入的异步回调问题[8],新的语言比如Go引入轻量线程goroutine(协程)解决这个问题,老的语言比如C 20、JS await等都有对应的机制。另外多进程的进程间通信也会引入额外复杂性,比如直播和RTC的流的跨进程回源和拉流问题。•第三代高并发架构,隔离的多线程架构,比如云原生的数据面反向代理Envoy线程模型[9],还有比Nginx更高性能的反向代理么,这就是Envoy[10]了,如下图所示。其实Envoy和Nginx都是事件驱动,但是Envoy是完全非阻塞[11]。而Envoy的多线程实际上和第一代的多线程也不同,线程之间几乎没有交互,可以看作是隔离的进程。由于是线程,所以它们之间的(少量)通信也很容易。同样,多线程也可以和轻量线程结合使用。

SRS目前还属于第二代架构,第三代架构验证过是可行的(#2188[12]),由于目前SRS性能还不是短板,所以没有合并到主干分支,有需要可以自己合并feature/threads[13]。目前SRS的性能数据如下:

SFU

Clients

CPU

Memory

线程

VM

SRS

4000 players

~94% x1

419MB

1

G5 8CPU

NginxRTMP

2400 players

~92% x1

173MB

1

G5 8CPU

SRS

2300 publishers

~89% x1

1.1GB

1

G5 8CPU

NginxRTMP

1300 publishers

~84% x1

198MB

1

G5 8CPU

SFU

Clients

CPU

Memory

线程

VM

SRS

1000 players

~90% x1

180MB

1

G5 2CPU

Janus

700 players

~93% x2

430MB

24

G5 2CPU

SRS

950 publishers

~92% x1

132MB

1

G5 2CPU

Janus

350 publishers

~93% x2

405MB

23

G5 2CPU

Note: CentOS7, 600Kbps, ECS/G5-2.5GHZ(SkyLake)[14], SRS/v4.0.105[15], NginxRTMP/v1.2.1[16]。虽然系统有8CPU但只能使用单个CPU,选择8CPU是因为只有8CPU的内网带宽才能到10Gbps。

当我们提到性能,一般隐含条件是“满足业务场景的体验”下的性能优化,比如直播要求卡顿率低、延迟在3秒之内,比如WebRTC要求端到端延迟400ms之内(服务器0延迟),一般现在服务器的内存可以是4GB、8GB、16GB、32GB或64GB,这意味着我们可以尽量用内存Cache来降低CPU运算。

SRS

Protocol

VP6

H.264

VP6 MP3

H.264 MP3

2.0.72

RTMP

0.1s

0.4s

0.8s

0.6s

2.0.70

RTMP

0.1s

0.4s

1.0s

0.9s

1.0.10

RTMP

0.4s

0.4s

0.9s

1.2s

4.0.87

WebRTC

x

80ms

x

x

Note: 在音视频服务器的性能优化中,延迟是必须要考虑的一个因素,特别是在RTC服务器,性能优化不能以增大延迟为方法。

性能基准

如果没有压测能力,就无法优化性能。

SRS的基准是并发流,比如使用srs-bench[17]推流可以获得支持的最高推流(发布)并发,和最高拉流(播放)并发。压测工具一般读取文件,可以选择典型的业务场景,录制成样本文件,这样压测可以尽量模拟线上场景。

性能优化前,必须使用压测获得目前的性能基准,分析目前的性能瓶颈和优化思路,然后修改代码获得新的性能基准,如此反复不断提升性能。如下图所示:

上图就是性能分析的主面板,左三右二加一个浏览器:

•左上:服务器的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) ,可以看到当前主要的热点函数列表,以及每个函数所占用的百分比。性能优化一般的思路,就是根据这个表,优化掉排名在前面的热点函数。•右下:压测客户端的top图,如果压测服务器的CPU满载,也一样达不到预期,会出现卡顿等情况。同样也需要先检查系统的网卡队列缓冲区[19],避免系统丢包。•浏览器:在浏览器中播放流,比如webrtc://8.126.115.13:1985/live/livestream100?eip=8.126.115.13[20],可以通过eip指定外网ip,这样压测工具可以推内网地址,而浏览器观看可以看外网的地址。浏览器观看可以随机抽查某个流,判断是否播放流畅,声音和画面是否正常等。

Note: 我们在左上的图中,截图时加上了标注,可以更快的看出这个性能图的摘要,比如ECS/C5 2CPU说明是ECS的C5机型一共是2个CPU,900 publish streams是RTC推流一共900个并发流。

工具链

没有工具链就无法做性能优化,前一章我们分享了压测工具srs-bench[21],查看网络带宽工具dstat,查看热点函数工具perf,查看CPU工具top

还有一些工具链,总结在SRS性能(CPU)、内存优化工具用法[22],我们挑一些和性能优化相关的工具重点介绍。包括:

sysctl:修改内核UDP缓冲区,防止内核丢包。•GPERF: GCP:使用GCP分析热点函数的调用链,图形化展示。•taskset:进程绑核后,避免软中断干扰,便于查看数据。

对于RTC,很重要的是需要把内核协议栈的缓冲区改大,默认只有200KB,必须改成16MB以上,否则会导致丢包:

代码语言:javascript复制
sysctl net.core.rmem_max=16777216
sysctl net.core.rmem_default=16777216
sysctl net.core.wmem_max=16777216
sysctl net.core.wmem_default=16777216

可以直接修改文件/etc/sysctl.conf,重启也能生效:

代码语言:javascript复制
# vi /etc/sysctl.conf
net.core.rmem_max=16777216
net.core.rmem_default=16777216
net.core.wmem_max=16777216
net.core.wmem_default=16777216

如果perf热点函数比较通用,比如是malloc,那我们可能需要分析调用链路,看是哪个执行分支导致malloc热点,由于SRS使用的协程,perf无法正确获取堆栈,我们可以用GPERF: GCP工具:

代码语言:javascript复制
# 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 GCP
killall -2 srs

# To analysis cpu profile
./objs/pprof --text objs/srs gperf.srs.gcp*

图形化展示时,需要安装依赖graphviz

代码语言:javascript复制
yum install -y graphviz

然后就可以生成SVG图,用浏览器打开就可以看了:

代码语言:javascript复制
./objs/pprof --svg ./objs/srs gperf.srs.gcp >t.svg

还可以使用taskset绑定进程到某个核,这样避免在不同的核跳动,和软中断跑在一个核后干扰性能,比如一般软中断会在CPU0,我们绑定SRS到CPU1:

代码语言:javascript复制
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有个配置项叫合并写入(发送):

代码语言:javascript复制
vhost __defaultVhost__ {
    play {
        # Set the MW(merged-write) min messages.
        # default: 0 (For Real-Time, min_latency on)
        # default: 1 (For WebRTC, min_latency off)
        # default: 8 (For RTMP/HTTP-FLV, min_latency off).
        mw_msgs         8;
    }
}

如果是非低延迟(默认)模式是8,也就是收到了8个音视频包后,才会转发给播放器。关键代码如下:

代码语言:javascript复制
srs_error_t SrsRtmpConn::do_playing(SrsLiveSource* source, SrsLiveConsumer* consumer, SrsQueueRecvThread* rtrd)
{
    mw_msgs = _srs_config->get_mw_msgs(req->vhost, realtime);
    mw_sleep = _srs_config->get_mw_sleep(req->vhost);

    while (true) {
        consumer->wait(mw_msgs, mw_sleep);

        if ((err = consumer->dump_packets(&msgs, count)) != srs_success) {
            return srs_error_wrap(err, "rtmp: consumer dump packets");
        }

        if (count > 0 && (err = rtmp->send_and_free_messages(msgs.msgs, count, info->res->stream_id)) != srs_success) {
            return srs_error_wrap(err, "rtmp: send %d messages", count);
        }

如果是25fps,那么8个包大约是在320ms,考虑音频包大约是160ms延迟,这个队列的额外延迟在直播中也是可以接受的。

如果是8个包一次发送,按照平均码率1Mbps,差不多是300Mb也就是40KB的数据。如果按照峰值5Mbps码率计算,那就是一次发送200KB的数据。我们可以用writev一次发送这些数据,就可以极大的提高分发的性能了。

每个连接我们需要的内存按照1MB来计算,那么4000个连接需要4GB内存。如果是7000个连接,需要7GB的内存。可以认为直播分发的性能优化,是典型的内存(加少量延迟)来换更低的CPU使用。

Note: 当然服务器引入额外的160ms延迟对于RTC场景就是不可以接受的,只能在直播中使用这种优化。

Note: RTC的UDP发送是否能使用类似的优化?我们调研过UDP/sendmmsg[28]和UDP/GSO[29]是可以提升一部分,但是由于UDP每个连接可合并发送的数据很少,目前压测分析热点也不在这里,所以优化有限,目前SRS并没有做这两个优化。

查找优化

STL的vector和map的查找算法,已经优化得很好了,实际上还是会成为性能瓶颈。

比如,RTC由于实现了端口复用,需要根据每个UDP包的五元组(或其他信息),查找到对应的Session处理包;Session需要根据SSRC找到对应的track,让track处理这个包。

比如,SRS的日志是可追溯的,打印时会打印出上下文ID,可以将多个会话的日志分离。这个Context ID是存储在全局的map中的,每次切换上下文需要根据协程ID查找出对应的上下文ID。

如果每个包都需要这么运算一次,那开销也是相当可观的。考虑根据UDP包查找Session,如下图:

代码语言:javascript复制
int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout)
{
    nread = srs_recvfrom(lfd, buf, nb_buf, (sockaddr*)&from, &fromlen, timeout);
    getnameinfo((sockaddr*)&from, fromlen,  (char*)&address_string, 64, (char*)&port_string
    peer_ip = std::string(address_string);
    peer_port = atoi(port_string); 

srs_error_t SrsUdpMuxListener::cycle()
{
    while (true) {
        SrsUdpMuxSocket skt(lfd);
        int nread = skt.recvfrom(SRS_UTIME_NO_TIMEOUT);
        err = handler->on_udp_packet(&skt);

srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt)
{
    string peer_id = skt->peer_id();
    ISrsResource* conn = _srs_rtc_manager->find_by_id(peer_id);
    session = dynamic_cast<SrsRtcConnection*>(conn);

ISrsResource* SrsResourceManager::find_by_id(std::string id)
{
    map<string, ISrsResource*>::iterator it = conns_id_.find(id);
    return (it != conns_id_.end())? it->second : NULL;
}

这个逻辑有几个地方会有热点,通过压测可以在perf上看到:

•每个UDP包都调用getnameinfo将sockaddr转成字符串的ip:port,也就是地址标识,会有大量的string开辟和释放。•每个UDP包都需要根据ip:port,在map中查找出对应的Session(Resource或Conneciton),字符串查找的速度是很慢的。

改进其实也容易,查找时不转成string,而是生成uint64_t的地址,目前支持的是IPv4地址只需要6字节就可以表达ip:port(如果是IPv6则需要两个uint64_t),如下所示:

代码语言:javascript复制
int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout)
{
    nread = srs_recvfrom(lfd, buf, nb_buf, (sockaddr*)&from, &fromlen, timeout);
    sockaddr_in* addr = (sockaddr_in*)&from;
    fast_id_ = uint64_t(addr->sin_port)<<48 | uint64_t(addr->sin_addr.s_addr);

srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt)
{
    uint64_t fast_id = skt->fast_id();
    session = (SrsRtcConnection*)_srs_rtc_manager->find_by_fast_id(fast_id);

虽然解决了string查找的热点,随着并发的提升,map<key: uint64_t>的查找也变成了热点,在perf上可以看到map的不断平衡,我们还可以改成vector查找:

代码语言:javascript复制
ISrsResource* SrsResourceManager::find_by_fast_id(uint64_t id)
{
    SrsResourceFastIdItem* item = &conns_level0_cache_[(id | id>>32) % nn_level0_cache_];
    if (item->available && item->fast_id == id) {
        return item->impl;
    }

    map<uint64_t, ISrsResource*>::iterator it = conns_fast_id_.find(id);
    return (it != conns_fast_id_.end())? it->second : NULL;
}

Note: 首先我们在vector中取余查找,如果取余碰撞了(两个不同id但是取余后一样,比如100100和200100是一样的),那么就用map查找。

通过不同的查找方式,string变uint64_t优化了查找速度,而更快的优化是不用map查找,直接使用数组取余就是无查找了。

当然,这样的优化,让逻辑变得复杂了。

无代码优化

当我们优化完明显的热点,优化完头部热点,会发现perf显示已经没有明显的热点,有时候有些不太明显的函数也会排在前头,比如拷贝RTP Packet:

代码语言:javascript复制
SrsRtpPacket* SrsRtpPacket::copy()
{
    SrsRtpPacket* cp = new SrsRtpPacket();

    cp->header = header;
    cp->payload_ = payload_? payload_->copy():NULL;
    cp->payload_type_ = payload_type_;

    cp->nalu_type = nalu_type;
    cp->shared_buffer_ = shared_buffer_? shared_buffer_->copy2() : NULL;
    cp->actual_buffer_size_ = actual_buffer_size_;
    cp->frame_type = frame_type;

    cp->cached_payload_size = cached_payload_size;
    // For performance issue, do not copy the unused field.
    cp->decode_handler = decode_handler;

    return cp;
}

这个函数有啥可以优化的么?没有什么可以优化的,全都是赋值和拷贝(无深拷贝),但是在perf上它就是排名在前头。

Note: 这时候千万别怀疑perf有问题,确实热点是这个拷贝是没有错的,perf不会出错,perf不会出错,perf不会出错,千万不要把焦点挪开去优化其他函数。

并不是函数性能高,就不会成为瓶颈,有个公式如下:

代码语言:javascript复制
性能热点 = 函数执行效率 x 函数执行次数

一般我们会优先优化函数执行效率,让函数更高效。但是我们也不能忽略了函数的执行次数,如果一个高效的函数被反复的执行,一样也会变成性能热点。这时候我们的优化思路就是:如何让代码不执行,或明显减少执行次数

通过分析可以发现,这个SrsRtpPacket::copy调用点有:

•从Publisher拷贝到每个Consumer,函数是SrsRtcSource::on_rtp。•包发送后,放到Track的NACK队列,函数是SrsRtcRecvTrack::on_nackSrsRtcSendTrack::on_nack

上面第二个拷贝可以省略,由于每个Player都有NACK,所以可以减少一倍的调用,优化后这个热点也就不排在前头了:

代码语言:javascript复制
srs_error_t SrsRtcRecvTrack::on_nack(SrsRtpPacket** ppkt)
{
    rtp_queue_->set(seq, pkt);
    *ppkt = NULL;

同样的,这个优化的代价就是增加了风险,参数也从指针,变成了指针的指针,一路都改成了指针的指针,可以犯错的概率就大太多了。

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个比较常见。

Note: 从上图可见,开启Padding后,UDP组包效能可以提升10%左右。GSO虽然不能减少实际网络上UDP包的数目,但是可以让内核延迟到最后才组UDP包,可以把GSO发送的多个包认为是一个包,相当于减少了发送UDP包的次数。

还有一种优化的可能,就是ZERO_COPY,其实TCP的零拷贝支持得比较早,但是UDP的支持得比较晚一些。收发数据时,需要从用户空间到内核空间不断拷贝,不过之前测试没有明显收益,参考ZERO-COPY[32]。

多线程

文章开头我们提到,第三代高并发架构,将是隔离的多线程架构,比如云原生的数据面反向代理Envoy线程模型[33]。

我们也调研过SRS可能的多线程架构,参考#2188[34]。和Envoy不同,SRS涉及到了TCP和UDP,API和媒体服务,级联和QoS等问题,可能的架构也比较多。

SRS 1/2/3/4一直都是单线程(第二代架构),如下图所示:

Note:这个架构的风险一直都存在,写磁盘可能是阻塞的,DNS解析可能是阻塞的,RTC无法使用多CPU的能力(直播可以用集群或REUSE_PORT扩展多核能力)。

很显然,写磁盘应该由单独线程完成,可以避免阻塞,这就是SRS 5.0使用的架构:

Note: 其实DVR和HLS也是写磁盘操作,未来也会由写磁盘的线程实现,目前还没有实现。

Note: DNS解析也是阻塞的,和写磁盘不同,DNS解析本质上是UDP请求,是可以自己实现协议解析,不需要用多线程做。

针对RTC的多核扩展能力,有一种很自然(也是改动较小)的思路,就是将更多的能力拆分到线程中。比如SRTP加解密,占用了30%的CPU,如果能拆分到独立线程肯定对并发能力有提升。比如UDP收发也可以放到独立线程,也可以避免内核UDP收发效率不高的问题。如下图所示:

Note: 这个架构是被标记为废弃,原因就是SRTP和UDP确实效率不高,但是Hybrid里面的QoS算法是瓶颈所在,而这部分不方便拆分多线程。

Note: 另外,就算QoS拆分成多线程,这个架构最多用到大约3~4CPU,并不能用到32或64核CPU,也就是并发能力还是受限。

Note: 最后,这种多线程架构,线程之间交互较多,所以会有锁的开销,也不算第三代服务器架构。

最终的多线程架构,是能水平扩展的多线程架构,实现的原型参考feature/threads[35]分支,如下图所示:

Note: 我们实现的一个版本是多端口模型,通过端口分割不同的Hybrid线程,Hybrid线程之间独立不需要交互。实际上多线程之间也是可以复用同样端口的,只是切网时需要考虑新五元组的绑定。

Note: 这个架构完全解决了水平扩展的问题,也避免了线程之间需要交互数据,压测在32核C5机器上,能跑到10K左右并发(可以在更多核的机器上扩展)。

当然,最后这个架构也并非没有问题,目前看还需要解决以下问题,才能在线上使用:

•API必须非常简单,如果是Janus那种复杂的API,就无法使用这种结构。SRS目前的API是比较合适实现这种架构。API实际上承担了调度的能力。•直播需要改进,适配这种多线程结构。直播Edge相对比较容易改造,可以用REUSE_PORT,相当于多个进程。而直播Origin改造比较麻烦。•统计和API需要改造,系统的CPU使用率,告警和水位统计,限流策略,都会因为多线程有所不同。比如在线人数,需要汇总每个线程的连接数。•全局变量和局部静态变量,必须仔细Check,保障是线程安全(thread-safe),或者是线程局部(thread-local),虽然我们在原型中已经改造得差不多,但还是需要更多确认。

由于多线程本质上和集群的能力是有一部分重合的,只是多线程的效率更高。比如直播其实可以用多个Edge部署在一台机器上(Edge后面挂一堆Edge),实现多核的扩展。RTC如果实现了级联,也一样是可以扩展多核能力,比如单核支持800个并发,每个SRS跑在一个Pod中,也可以用级联扩展能力(当然会造成进程之间的带宽比多线程要高,多线程是不走带宽)。

硬件加速

SRS没有使用硬件加速,但这个是一个很不错的思路,包括:

•CPU指令集优化:加解密和编解码的算法,有些可以用到CPU的特殊指令,增加批处理的能力,比如AVX512[36]。•专用加解密硬件卡,加解密是比较通用的算法,有专门硬件,可以调研看看。•UDP收发优化,不经过内核协议栈,直接从用户空间和网卡交互:DPDK[37]。

总结

性能优化,是全面的和持续的工程能力,涉及到了对系统的理解、对业务的认知、对编程语言的熟悉、对硬件和软件框架的挖掘、性能分析工具链的使用,心肝脾肺肾只有全面提升,最终才能获得性能的提升。

References

[1] C10K: http://www.kegel.com/c10k.html [2] Adobe AMS: https://helpx.adobe.com/adobe-media-server/tech-overview/introduction-ams.html [3] Apache HTTP Server: https://httpd.apache.org/ [4] Janus WebRTC Server: https://github.com/meetecho/janus-gateway [5] Nginx: https://nginx.org/ [6] SRS: https://github.com/ossrs/srs [7] MediaSoup: https://github.com/versatica/mediasoup [8] 异步回调问题: http://callbackhell.com/ [9] Envoy线程模型: https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 [10] Envoy: https://www.loggly.com/blog/benchmarking-5-popular-load-balancers-nginx-haproxy-envoy-traefik-and-alb/ [11] 完全非阻塞: https://dropbox.tech/infrastructure/how-we-migrated-dropbox-from-nginx-to-envoy [12] #2188: https://github.com/ossrs/srs/issues/2188 [13] feature/threads: https://github.com/winlinvip/srs/tree/feature/threads [14] ECS/G5-2.5GHZ(SkyLake): https://help.aliyun.com/document_detail/25378.html [15] SRS/v4.0.105: https://github.com/ossrs/srs/commit/2ad24b2313e88a85801deaea370204f225555939 [16] NginxRTMP/v1.2.1: https://github.com/arut/nginx-rtmp-module/releases/tag/v1.2.1 [17] srs-bench: https://github.com/ossrs/srs-bench/tree/feature/rtc#usage [18] 网卡队列缓冲区: https://www.jianshu.com/p/6d4a89359352 [19] 网卡队列缓冲区: https://www.jianshu.com/p/6d4a89359352 [20] webrtc://8.126.115.13:1985/live/livestream100?eip=8.126.115.13: http://localhost/players/rtc_player.html?autostart=true&api=1985&eip=8.126.115.13&server=8.126.115.13&vhost=8.126.115.13&stream=livestream100 [21] srs-bench: https://github.com/ossrs/srs-bench/tree/feature/rtc#usage [22] SRS性能(CPU)、内存优化工具用法: https://www.jianshu.com/p/6d4a89359352 [23] ECS ecs.c5.large: https://help.aliyun.com/document_detail/25378.html#c5 [24] ECS ecs.c5.xlarge: https://help.aliyun.com/document_detail/25378.html#c5 [25] ECS ecs.c5.2xlarge: https://help.aliyun.com/document_detail/25378.html#c5 [26] G5: https://help.aliyun.com/document_detail/25378.html#g5 [27] R5: https://help.aliyun.com/document_detail/25378.html#r5 [28] UDP/sendmmsg: https://github.com/ossrs/srs/issues/307#issuecomment-609020985 [29] UDP/GSO: https://github.com/ossrs/srs/issues/307#issuecomment-612953502 [30] UDP/sendmmsg: https://github.com/ossrs/srs/issues/307#issuecomment-609020985 [31] UDP/GSO: https://github.com/ossrs/srs/issues/307#issuecomment-612953502 [32] ZERO-COPY: https://github.com/ossrs/srs/issues/307#issuecomment-615884074 [33] Envoy线程模型: https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310 [34] #2188: https://github.com/ossrs/srs/issues/2188 [35] feature/threads: https://github.com/winlinvip/srs/tree/feature/threads [36] AVX512: https://zhuanlan.zhihu.com/p/83399457 [37] DPDK: https://www.dpdk.org/

0 人点赞