- TutorABC研发总监董海冰进行了题为《 用Golang搭建实时音视频云》的演讲。主要内容如下:
什么是TutorMeet+
为什么使用Golang
WebRTC
问题总结
关于SaaS
以下为演讲实录。
No.1
什么是TutorMeet+
大家好,非常高兴来到 Gopher China 分享一下我们用 Go 语言做的事情。可能这个领域稍微有一点特殊,我准备的内容蛮多的。主要还是跟大家讲一下我们实时交互的研发过程和为什么采用 Go 语言,还有关于我们采用了 WebRTC,以及两年多的研发过程中都遇到了什么问题,怎么处理的,最后是 SaaS 方面的东西。
先问一下大家,你们自己或者说你们的家人有没有使用过在线教育的产品?特别有小孩子,近些年大家对于在线教育越来越熟悉,实时的音视频教育已经基本走入千家万户。我这张图是一个典型在线教育的场景。左边是老师,右上角是学生,学生对于我们公司来说,既包括低龄段K12学生还有成人学生,甚至平台上有超过80岁的老人也一直在坚持学习。当然还有一些其他的角色,包括老师给学生上课的时候会有教材,教材实际上我们有专门的教研部门由他们负责编写。对于低龄端学生要有家长参与,我们提供一个比较好的家长端,这样家长可以在不在场情况下也能够监督自己孩子上课的情况。即使课上完了,他也可以比较方便的“回看“上课的录像,看学生当时的学习的情形。
我们“TutorMeet+“是一套可以实时互动的教学系统。这是我们整个系统的界面,后面是老师端,前面是学生端。显示区域主要的部分是课件,也就是俗称”白板“的区域,左侧是视频区,这块采用了WebRTC的技术。白板也是很重要交互的手段,目前在线教育发展越来越快,越来越多采用动态白板,有一些项目我们采用了 Html5 可以实时互动的白板,可以生成动画,对于低幼学生体验会更好。
对于教育来说,有两点非常重要,一个是学习的效果,另一个是学习的效率,其实作为商业的再教育的产品,如果不能够帮学生强化学习效果,或者说能帮他在学习过程中节省时间,那么这样的产品是没有太大的意义。因为大部分学习资料都是可以免费找到。我们主要还是满足用户这两点核心需求。
整个系统平台我们后端基本都是采用 Go 语言开发的。当然有一些地方用了 C++,还有 C 语言。前端我们用的是 ReactJS,我们界面大家已经看到了,音视频是在左侧,有一些是在头部,还有一些是右侧,这个差别不大,因为我们主要是采用H5比较灵活的方式布局,怎么调整都可以,没有什么一定规矩,只是我们在教学过程中,觉得放在这个部分焦点、视点比较理想,其实可以做一些定制,这方面我们是足够灵活的。我们整个UI层变动起来会比较敏捷一些,也比较高效。
视频我们早期采用VP8,不过后来逐步转成H264为主,因为H264有很多好处,特别是在移动端硬件设备的支持上会比较好,支持硬解,也比较方便。还有比较大的好处就是,作为在线学习的场景,我们在服务端肯定有录制的功能,如果用H264很容易转成MP4了,H264作为通用格式,不需要转码,用VP8肯定要重新做一些转码、适配的工作。
我们去年快速上线的阶段统计过,每天会发布30几次,所以整体还是非常快速的一个迭代的节奏。我们采用的是混合云的方式,混合云一方面我们有自建机房,然后有比较长时间的全球的项目运维的经验,我们有一个GIS的部门,他们采购了很多国外的节点和机房,都是用最好的资源,所以能够保证我们整体的线路是非常稳定的。我们也有一些比较好的公有云合作伙伴,像阿里云,也建立了比较好的互动关系,利用阿里云可以弹性扩容的优势,实现了比如说开学季,上课人数较多,特别是在Peak Time上课量大规模增加,这个时候我们不用等自己采购硬件服务器,而是采用公有云的ECS,可以迅速地的做一些弹性的扩充。另外,还有一些高IO的服务,需要更多CPU资源的情况下的服务,做更加灵活和及时性的弹性扩展。
从教学形式上我们支持一对一、一对多,其实我们以一对多为主。我们也推出支持多个讲师、多个主持人,一起为大家提供在线授课的形式。我们在服务端做完了合流以后,再推给客户端,这样可以打造支持万人的公开课-云讲堂。
No.2
为什么使用 Golang
下面跟大家讲一下,我们为什么用 Go 语言开发音频的东西。大概2014年、2015年的时候,我当时在负责 CCTalk 的相关研发。当时我们几个架构师比较困扰的问题是选用的开发语言还是C++,尽管C++性能好,但是面临着一个是优秀的工程师比较难招,另外整个C++的开发效率还是偏低一些,尤其是Server端,好的工程师尤其是音视频通讯这一块非常少,再选一些我们觉得不是那么重要的业务逻辑的时候,比如说跟WEB系统做通讯,网关或者是API这一类的东西,用C++实在太重了,当时曾经打算用Python,但是Python大概试用了一下,性能太差。对Server多核支持也不太好,所以比较犹豫,没有用它。后来Go进入我们视野,经过一段时间的观察,尝试,包括我们团队有同学也搭了一些项目,例如:分布式的文件系统,效果都非常好。
所以在2016年底的时候,当有机会可以做一套全新的“实时互动的音视频平台“的时候,我们大家一致选用了 Golang 做开发语言。目前来看,真是受益颇多。一个是当时我们团队人少,我们现在整个团队包括测试、产品、设计师都算上也只有20出头一点点,而2017才10个人左右,就是这么一点人,而巨大的工作量,多亏了Golang的高效率。我们支撑的产品的品类还有业务的模式还是蛮多样性的,而且整个的产品面向的受众也都是我们收费客户,客单价比较高,整个产品端的交互和服务质量的压力比较大。所以用Go语言还是非常好的一个选择,尤其是他有如下的一些优势,比如说“并发“(Goroutine),可以充分发挥服务器多核的优势。还有语言的性能,比较接近于C\C++的语言性能;比较好的工程特性,工程特性是让我们受益颇多,整个团队组建的时候有做Java,也写C的,也有C++,还有Python,甚至还有开发IOS App的。我们有一个很不错的前端研发经理,他也是对Golang比较感兴趣,熟悉一两周开始写代码了,非常快。大家整体对于Go语言的理解,还有工程使用中没有碰到特别多的问题。但是后面会举一些例子,也有一些小问题的,因为是从不同的语言转过来的。做C的人会Diss Java相对来说很少有人Diss Go语言,比较中立。各种语言出身都比较能接受。
少既是多,Go语言的语言特性包括整体设计思想也是比较影响我们,我们整个系统在设计的过程中没有考虑特别多的很冗余的设计,也是基本以简单介绍大道至简的原则,尽量设计比较简单和通用。
No.3
WebRTC
下面我们稍稍介绍一下WebRTC。WebRTC 是google 2010年6800多万美金收购了瑞典一家音频技术非常强的公司——GIPS,这家公司当时做音频技术是全球领先的。谷歌收购以后没有商业化,而是结合了谷歌收购的另外一家做视频技术的公司,叫ON2。这就是VP8、VP9音视频引擎的出处。我们看到谷歌在做一件非常伟大的事,而且这件事在当时看来很难做成。他的想法是通过浏览器把所有音视频通讯打通。那个时候各个浏览器厂商很强势,当时微软的IE占大头,他的IE市场份额最高,他自己搞了一个ORTC标准,跟WebRTC不一样。谷歌这些年,一直在致力于推动WebRTC成为行业标准,同时利用自己Chrome浏览器的优势,不停提升自己在浏览器市场的份额。其中也包括音视频引擎的进化,除了VP8,又进化VP9,甚至到AV1。VP9国内用得不多,但是用过Youtube的应该知道,VP9的清晰度和流畅度还是非常好的。
WebRTC把浏览器端尤其是客户端关于音视频和通讯方面比较难处理的一些东西,比如说红线框起来的,像iSAC、AEC、NR,还有ICE的“打洞“等网络传输的控制都标准化了,而且固化到浏览器内部,紫色的部分提供了JS的API,非常方便大家基于此做自己逻辑,也方便调用。虚线的地方是开放给各个浏览器厂商,他们自己实现相应的硬件相关的控制接口。
比较好的消息,WebRTC从2011年诞生,到2017年开始被W3C纳入草案,不到十年的发展,WebRTC项目已经成为事实上的W3C的国际标准。主流浏览器必须要支持,所以看微软新推出的edge浏览器内核已经从ORTC换成了WebRTC,Apple的Safari等厂商也都在积极支持,WebRTC最初的目标已经实现。
下面是WebRTC的协议栈,左边的部分是基于TCP协议,音视频系统尤其是实时的,跟普通的WEB应用不一样,通讯中会大量采用UDP的协议,是下图右边的部分。
WEB2.0以后像XHR、SSE的这样的技术已经很少应用了,因为需要不停的和Server做重新连接。而WebSocket连接以后直接就upgrade成稳定的TCP连接,并且发数据了,所以整个效率大大提升,目前音视频的指令服务基本以WebSocket为主。
这是一个最简单的WebRTC的图,我们看看主要是通过Signaling服务器做主要通讯Server,两个浏览器之间通过PeerConnection打通。
这个是WebSocket和Http的比较,比传统的“轮询“的方式优势是传输会更高高效。考虑大部分的Web应用还是走HTTP,所以他对于HTTP做了兼容。当HTTP连接上以后,做upgrade就可以建立稳定的TCP连接了,显然,下图右侧的部分会更加高效。
这个是WebRTC最简单的流程图,已经比较简化,如果要是把它比较完整的画下来,这个可能得好几页,步骤会比较多。先是请求Html5文件,本地搜索好的SDP的信息,或者做一个穿透,最右侧的是Server,也可以理解成浏览器B,相当于下图还有一半建立连接的流程没有画出来。如果两个浏览器打通就比较简化一些,类似于这张图描述的。我把SDP发过去,那边回一个answer。这样整个媒体通讯建立起来了。
刚才讲了SDP相当于纯文本的协议,这个协议也有他的一些规则。比如说他都是用字母标识,我传的协议内容是什么。如果是A就是关于媒体信息的,这个协议为什么这么设计呢?为什么不用XML,那个主要是为了兼容老的通讯设备,让它们可以纳入整个体系里面,所以这个协议这样一个纯文本结构。如果感兴趣,大家可以详细研究每一个字符代表什么意思,这个协议怎么运作的。刚才我举的例子是一个普通的WebRTC自己测试的Demo,这个东西没有办法在线上作为一个产品运行的,如果想成为一个规模化、产品化的实时音视频的应用,还有很多的事情要做。
比如说我怎么保证整个连通率和可靠性,同时比如说我的房间如果人多的话,我怎么样把压力平摊,网络节点采用分布式,包括流的管理,智能路由,实时的监控系统等,还有很多额外工作要做。
接下来,我介绍一些开源的WebRTC Server的项目给大家。
第一个是Licode,也是很多年的一个开源项目了,主要是C++开发,在因特尔公司他们开源版本早期也是基于Licode构建的,影响力比较大。包括我们目前有一些做音视频Saas云的厂商,很多也都选了Licode方案。
Licode是C++开发,然后提供了基于JS的接口,包括服务端也引入了NodeJS,方便大家做一些扩展,但是整体代码量还是非常大的,他的功能比较完善,但是也是会有一些扩展性或者性能上的问题,需要做一些比较深度的优化和改造才能达到工业级的产品标准。
第二个项目是关于Meetecho,之后改名叫Janus,这个项目也是非常有名的。他们是属于学院派,教授带着博士生一起搞的,代码质量也很好,主要是用C,是一个非常不错的WebRTC的网关。
Jitsi历史比较悠久了,超过十年了。里面有很多处理XMPP的东西,legacy 的代码比较多,后期对移动App的支持比较好。
Kurento我们也用过,TM+ 1.0的早期版本就是基于Kurento的。Kurento是比较好的WebRTC Server,功能比较丰富,它也是支持Java,支持NodeJS的API。但是Kurento因为基于GStream,所以最大的问题是性能始终上不来。当你并发量一大,音视频有多路的时候,CPU的损耗就会比较大。
我们自己在搭建的时候,结合之前搞CCTalk的一些经验,我们有一些特别的想法。比如说我们采用了动态分区的技术。比如说我们在系统的最前面自建了一个路由层,在路由层下面我们在部署的时候实际上是可以把整个一套的WebRTC Server不同分区的部署方式。比如说当我们做发布的时候,可以单独更新一个区。可以先调动一部分流量做小规模的验证和跟踪,如果OK可以继续扩大比例,一直到新服务的比例超过50%以上,而且稳定运行的一两个完整周期之后。我们再把其他的区全部更新,采用这样一种方式,方便我们发布,同时我们的新区和老区之间也可以做比较好的AB TEST。
WebRTC的前端HTML5和JS样例
WebRTC的后端Go代码样例
WebRTC的debug工具
WebRTC的Demo Result
我们的API层叫做Actiniae,统一配置用的Consul,服务端采用的gRPC,前端用Websocket和TCP。
这张图更直观,对于我们来说,比如说学生、还有监控的角色还有老师,就是通过Beacon(Router)进行就近分配,可以做到让用户就近接入。老师在国外比较多,有来自美洲、欧洲等地区的老师为我们提供7X24小时的授课服务。学生是大陆地区比较多,也有东南亚地区的,为了更好的上课体验,我们的整个的部署一定要保证低延时和就近接入。
Media Server是比较独立的,彼此之间没有形成资源竞争关系。Media Server首选是SFU,相当于只做转发,一路流进来以后不做任何处理,直接转发出去,这个对于服务端来说最省CPU的策略。当我们需要录制,或者是推流的时候这个时候会用到MCU,就把前端多路流Merge起来合流,再转码推出去。
这是我们用的Go Lib,比如说我们用的gRPC、TCP等等,我们觉得Go语言发展比较快,但是Go社区与Java相比,各种模块和支持还需要不断的完善。像Socket.IO库我们就遇到很多的问题,刚开始的时候几乎找不到一个比较好的比Socket.IO库。当然,社区发展靠大家,之后我们也会多为社区做一些贡献,也期望我们整个Go社区的社区生态能够变得越来越好。
No.4
问题整理
我们遇到了哪一些比较典型的问题呢?这里只举三个比较简单的一些问题。比如说我们看右边的图,它是典型的并发的消息。
发过来之后我通过一个Channel做处理,当它连接的Client比较多的时候,这个消息突然间没转出来,或者卡在哪儿了,后期我们做优化的时候发现这个地方,尽管我们也采用了Goroutine并发的方式,但是后续处理的时候也有一些需要注意的地方。下面我们讨论一些解决方案,例如:A方案,我可以先不做处理,先把大量的消息汇聚起来,统一做一次性高效的处理,这是一种方案。
还有一种方案B,比如说充分利用Goroutine并发的优势,比如说我可以给他16个并发,当处理的时候如果当时超过16个,例如一下子来了20个,有一部分就暂时就Block住,等这里处理完全接下来后续再处理。消息有大有小,有各种各样的消息,他也不至于一下子就完全hold住。这种处理方式比之前线性单路处理好得很多,也是我们后来正式采用的并发处理方式。
还有一个比较简单的issue,但是很有“趣“。在去年12月1日某一天早晨,我们突然出了一个事故,我们就去查,就要那天前后几天都没有发布,工程师很快定位到问题了,一查代码发现是一个日期的格式有问题。后来总结才发现,因为我们团队很多的Gopher 是从JAVA语言和其他语言转过来,所以对于Go语言原码没有仔细看。这个程序员以为是随便写一个日期Format就可以了,如果随便写一个日期,而恰好是很大的数没有问题,这个时间大于这个数没有问题。但是等月初的时候正好做一个转换,一下跑到12月1日的时候,一下变成一个很小的数,这个时候就出问题了。后期看到这个问题的时候,我们尽管觉得这个问题挺低级,但是还是有一定参考性,尤其是其他语言转过来的Gopher 大家多看看API文档和Golang的原码。因为Go是“2016年1月2日下午15:04:05“,它解析也是这么解析的。
下面还有一个Go Module模块版本依赖离工程化有一定距离,尽管Go推出了Go Module,有一些公司访问外网都是受限制的,包括访问内网的时候也是有众多限制的,所以在Go包依赖的时候,有一个模块依赖1.1版的Client,另一模块依赖1.0。我们的bulid server是同一台,目录也没有版本,偏巧某一个人发表Module1的时候,没有做严格的Check,把新版本也发出去,造成我们一个事故。尽管很快解决了问题,但是也是血的教训,还是希望Go的工程工具能够更完善,同时对于我们一般的开发环境不能“安全上外网“还是要多做一些支持。
这是我们整个项目自动化部署的情况,基本是从原码库拉过来,然后Docker化推出去。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200328211416310.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvbW1TaGFyZQ==,size_16,color_FFFFFF,t_70)
No.5
小结
总结一下,TutorMeet+尽管是一个上课的系统,但是消息肯定是实时的,对于实时交互一般控制几百毫秒以内,否则就会让对方感觉不适,最迟不能超过1秒。还有白板、讲义这一块。刚才提到了,有Html5动态讲义的支持,是不是能支持手写笔,因为比如有一些K12小朋友,他们需要做题,做题的结果老师要看到,如果用鼠标来画,就太难了对他来说,可能转化成电子笔,老师通过白板可以看到电子笔书写出来的内容,同时老师可以用电子笔跟他互动。
监控非常重要了,因为整个实时互动的产品,实际上都需要我们提供比较好的监控手段,这是我们自己做的开放平台的工作,包括我们有集中入驻的系统,同时我们还给老师的管理提供了一些方式,还有老师们可以通过一个课表来调节授课的时间,同时学生端可以通过关注微信小程序,可以拉到我们课表上,如果有需要,可以加入进去,基本还是比较开放的,我们也是希望经过这么长时间的努力,把我们一些基础的实时互动能力输出出去,让更多的公司或者感兴趣的一些伙伴们能够用起来,当然这个系统的部署跟我们公司的TM+系统完全隔离的,开放平台是相对独立的。
Q&A
提问:客户端是浏览器全部实现了,服务端是自己实现一套吗?你刚刚列了开源的WebRTC产品,我想知道你是直接用那些,还是用Go语言自建的?
董海冰:我们绝大部分是自建的,当然也借鉴了比较少的开源技术。例如,WebRTC的网关。这个有多个选项,目前开源的WebRTC网关比较多,包括现在有一些纯Go写的网关,也有一些新的WebRTC Server大家都可以尝试。当成单纯的网关来用问题不是很大,因为比较轻。关键还是Media Server怎么构建,怎么使他更稳定可靠,这个比较难。
提问:有没有想在边缘上做扩展,包括网关更多节点上,有CDN节点上PUSH,或者用一些代理节点?
董海冰:这是必须的,因为我们大部分的外教老师会全球分布,所以实际上我们在国外N多个点都部署了自己的media gateway,自己有一个路由算法保证连到他那儿是最近的。除了实时的加速,我们的云讲堂产品就借助了CDN,利用RTMP进行推流分发加速。
提问:比如说中美两国如何接入,您这边直接接入还是做了什么同步?
董海冰:美国我们有好多个节点,美东美西都是分开,为了保证物理上可以就近接入。从美国到中国可能200毫秒delay,我们整体控制在几百毫秒似乎感觉挺难,但是只要骨干网的TCP网络比较稳定,就不会出现超30%的特别严重的丢包延时,基本都没有问题。因为这套系统基本7X24小时在线上跑,如果有问题我们会收到报警,目前看基本还是比较稳定,我们大概一季度SLA接近99.99%。
提问:接通延迟方便透露一下吗?
董海冰:我们在WebRTC的前后端都做了比较充分的埋点,也搭建了比较完善的数据分析的大数据平台,有很好的一些Delay监控。部分地区的延迟是比较高,象非洲那边因为基础网络不太好,延时甚至会达到秒级。