WhatWhyHow 2017-09-14
摘要:在阿里云数据库技术峰会上,特邀嘉宾映客直播架构师王振涛分享了映客直播作为创业公司从0至日活千万的数据库架构变迁,数据库在直播中的经典应用场景,数据库存储的优化思路,以及如何构建一个高可用数据库架构。
8月24日,阿里云数据库技术峰会到来,本次技术峰会邀请到了阿里集团和阿里云数据库老司机们,为大家分享了一线数据库实践经验和技术干货。在本次峰会上,特邀嘉宾映客直播架构师王振涛分享了映客直播作为创业公司从0至日活千万的数据库架构变迁,数据库在直播中的经典应用场景,数据库存储的优化思路,以及如何构建一个高可用数据库架构。
以下内容根据演讲嘉宾现场视频以及PPT整理而成。
本次分享的内容将主要围绕以下四个部分:
一、映客直播发展历程
二、直播遇上云数据库
三、风口上的数据库架构变迁
四、直播典型应用场景分析
映客直播是2015年5月份成立的一家公司,在移动直播领域,映客算是比较早成立的公司了。如上图中所展示的就是映客APP上的一些页面,左图展示的是映客APP中的热门内容,这里是某一个反串演员主播正在进行直播,并且此时有5万多观众正在观看;右图则是映客直播的发现频道,发现频道里面主要会在热门时段对于平台内的优质内容进行展示推送。在映客APP中会有很多类似于前面提到的这些页面,包括了游戏直播、健身直播以及小视频等等。作为一个刚刚成立两年的公司而言,映客直播的发展速度还是非常迅速的,从2015年5月映客成立之初,刚刚发布APP时的DAU只有200,到当年10月份的时候DAU就已经达到了10万+,再到12月份短短两个月的时间,映客直播的DAU就已经突破了100万,而在2016年中下旬的时候,DAU峰值就已经达到了千万级别。以上就是映客直播大致的整体发展历程。
业务起步:映客APP发布
接下来为大家介绍映客直播从开始到现在的架构发展演变过程。当映客APP刚发布的时候,整个后台其实只有三个人负责,两个开发和一个运维,这时的架构如下图所示。起初的架构其实是非常简单的,后台是由8台虚拟机搭建而成的,其中的6台虚拟机做了服务,而剩下的2台做了存储,存储这部分则是使用了自己搭建的MySQL和Redis。在这个起步阶段,对于后台系统要求最高的一点就是当出现了产品想法就需要尽快地实现,这样的架构非常适合在业务起步阶段的需求条件下实现一个直播APP,当时第一版本的开发工作只花费了2周的时间。
业务发展:规模化
而随着映客直播业务量的提升,从2015年的5月份到10月份,映客的DAU达到了10万+,到12月份的时候就已经达到了100万+,此时的业务发展已经达到了规模化的阶段,这个阶段的整体服务端的架构设计如下图所示。此时的架构也是分为了接入层、业务层、基础层以及数据层,但是此时的架构与第一版的架构相比,其整体的框架已经基本成型了,这一版的架构实现了一些业务拆分、数据拆分以及模块化复用,此时服务的底层仍旧是依赖于Redis和MySQL数据库的。这个阶段最为显著的变化就是引入了云,也就是说此时的服务已经上云了。
对于映客的服务上云,当时的考虑主要基于以下几点:
1. DDoS,因为云主机本身是可以防止DDoS攻击的,这样就可以将代理层全部都部署到云上。
2. 直播存在一个天然的属性就是有很多的明星活动以及其他大V的活动,因为这些活动所造成的瞬时压力是非常大的,如果使用自建机房就需要为这些可能仅仅持续几个小时的活动而囤积很多台服务器,需要购买很多预留资源。而使用在云之后,云主机本身是按照使用量进行收费的,这样可以将很多核心业务都部署到云上,在出现瞬时活动需要进行扩容的时候就可以直接进行扩容,当活动结束之后就可以释放这些多余的资源,通过这样的弹性伸缩可以极大地降低直播平台的运营成本。
3. 云主机以及云数据库等这些云资源使用的都采用的是集群模式的部署,而其集群模式往往也是对于用户透明的,这样就可以理解成云本身帮助我们实现了一层高可用。
4. 平滑可伸缩,比如现在的数据库的配置或者性能不能满足需求了,就可以使用云上的这些资源进行平滑地扩容,并且云上的扩容对于服务是没有任何影响的,而且包括SLB负载均衡器这些对于云的用户而言都是比较友好的。
综合以上四点的考虑,映客直播在业务规模化的阶段就使用了云服务。
而使用云服务之后首先需要面对的一个问题就是:整体迁移。对于整体迁移而言,云服务本身也提供了一些工具可以实现对于存储进行实时地同步以及在最后进行切换,对于云服务本身可以实现隔离以及制作镜像之后就可以打包发布到云上去。映客直播当时的业务量也不是太大,所以整体而言迁移上云的过程还是比较顺利的。但是任何事情可能都不只会有好的一面,当使用了云服务之后同时也会遇到一些问题,映客在上云的过程中也遇到了一些不可控的因素,也就是“水土不服”。比如当时的云服务的MySQL连接数是有上限为2000的限制的,而映客当时采用的很多服务框架都是属于多进程的框架,这样就非常容易造成连接数不够用的情况;并且当时外网带宽限制是200M,很容易就跑满了,而且内网带宽也是一样的情况;另外,在使用云主机的时候也会出现一些资源抢占或者宕机的情况。综上所述,在上云之后有好的一面,也存在着不是特别舒服的一面。在这个过程中,映客不再仅仅是一个云服务的用户了,也演变成为云服务的产品经理或者QA,映客与阿里云也是从开始的接触、了解到后来的“相爱相杀”,并且一起来推进云服务的优化,一直到双方都达到比较好的状态。
业务爆发:千万日活
其实云服务本身是作为基础设施的,它可以为我们提供高质量的运维。但是当业务的日活、流量还在爆发性增长的时候,单单依靠云服务这种基础设施的能力还是不够的,所以映客的服务框架也在不断地迭代中。如下图所示的架构框架是在映客上云之后,进行了一次比较大的改造之后的框架,这个框架就已经实现了一些分层,主要分为了用户层、应用层以及数据层,并且在云服务基础设施上实现了很多服务化的重构、存储的优化等的一些技术中间件,做了方方面面的工作来保障当业务爆发达到千万日活的时候,服务还依旧能够支撑住,这样的框架本身的稳定性也还是比较不错的。近期,映客在做的一件事情就是在目前体量已经非常大的情况下,如何去保证资源的独占和安全,目前也正在基于云服务设施进行VPC的部署,而且这项工作目前推进得也还不错。
云上架构
其实大家可以看到,映客的整体架构是构建在云服务基础设施之上的。目前映客已经达到今天千万日活这样的体量还在使用云服务其实主要基于以下几个方面的考虑:首先,我们认为在现阶段,云服务的发展已经相对比较成熟了,并且也是比较安全的,云服务在内部可以保障一些高可用的支撑;另外,用户使用的所有服务基本上都是集群化的,并且是支持平滑伸缩的,这些对于有活动运营需求的直播平台而言都是非常有价值的;并且云服务的用户也不需要预留太多资源来支撑可伸缩的容量;除此之外,如果使用自建机房,那么运维团队需要足够了解每一个环节,需要能够支撑从硬件设施到监控以及上线等各个方面,这对于一个快速迭代的公司的运维团队而言则是非常困难的,而使用云服务则可以保障基础设施的高质量运维。以上这些就是映客直播到目前千万日活的体量依旧选择在云上架构未来的很重要的原因。
其实很多服务以及系统到最后归结起来都是数据,所以云服务的重中之重或者基础的基础就是云数据库,这也是映客的架构中一直在不断地迭代和优化的技术层面。云数据库作为基础,也经历了从单机时代到集群时代,再到分布式框架的发展演变。
接下来为大家分享映客从0到千万日活过程中数据库架构变迁的历程。谈到数据库,无论是像Oracle、MySQL这样的关系型数据库,还是像MongoDB这样的NoSQL的数据库,大家想到的第一点就是数据库的性能。而数据库主机的性能主要和以下的这几个方面相关:CPU、存储、索引、IO、锁、内存以及内部的线程模型等,这些都是确保数据库设计本身性能非常关键的方面。
在另一个层面,当数据库实现了集群化或者分布式之后,可能会面临着另外的一个问题,也就是所谓的CAP理论。CAP理论就是当想要在一致性、可用性以及分区可容忍性这三个方面进行权衡时,需要结合具体的业务场景,针对于具体不同的业务场景需要有选择地舍弃一些东西来保障核心的诉求。
在映客的数据库设计演变的过程中,大致经历了如下这样的变化:用户量从千级到了亿级,日活从百级到了千万级,大V粉丝从百级到了千万级,送礼峰值的QPS从十级到了万级,关系数据从千级到了千亿级。大家可能会产生疑问,为什么关系数据一下子从千级到了千亿级。其实直播本身存在一些社交属性,比如当用户喜欢一个主播的时候就可能去关注他,每一条关注就可能产生两条记录,所以当用户量已经达到亿级的时候,关系数据的量就会是非常大并且也非常重要的。所以在这样的过程中,千亿级别关系数据的存储所面对的挑战还是非常大的。另外,直播本身也存在大V属性,很多大主播可能具有千万量级的粉丝,那么在大V每开一次直播的时候,千万粉丝都需要收到这个大V的开播提醒。而对于千万粉丝而言,他们的关注列表中都要出现这个大V正在直播的关注信息。同样的,在大V关闭直播时,关注列表中的直播信息就需要立即消失,这些对于实时性、高并发、大容量的要求是非常高的。除此之外,非常重要的一点就是映客直播本身是支持送礼的,而且映客还独创性地支持了礼物连送,也就是比如我喜欢一个主播,可能送的礼物的价值是一毛钱,但是可以进行连续送礼,可以连续送几万次,虽然这对于用户的体验而言是非常爽的,但是对于服务器而言则会造成非常大的压力,所以万级QPS的送礼的金融数据的压力也是映客之前所面临的很大的挑战。
在这个过程中就涉及到基础数据和云数据库之间的碰撞。一方面,基础数据在爆发性地增长;另外一方面,云数据库需要能够支撑数据的指数级增长的量级。
在数据库架构演变过程中,无论是MySQL、Redis还是MongoDB这些数据库层面的服务都经历如下图所示的变化,从单主机的模式到后来独立主机,到后来的主从读写分离,再后来根据用户、房间、资料等维度进行垂直拆分,而在维度已经很大比如像关系数据已经到达千亿量级的时候还进行了水平拆分。
关键技术点-数据迁移工具
在数据库架构演变过程中涉及到一些技术点,在这里可以与大家分享一下。在数据迁移工具这部分在数据库架构演变过程一直都扮演者极为重要的角色。对于同构数据的迁移而言,其实有比较成熟的方案,同构数据迁移相当于上图中的从单主机一直到垂直拆分这部分,这部分由于所涉及到数据表的结构以及数据的拆分策略都没有什么变化,所以这部分工作直接依赖云数据库的迁移工具,就可以实现数据的平滑迁移,并且业务上基本是无感知的。这部分适用的场景就是服务器级别、实例级别的扩容或者读写分离以及业务的垂直拆分等。但是当体量已经足够大到需要做很多水平层面的拆分的时候,很多现有的成熟的工具就已经不太适用了。这个过程中,映客直播的技术团队就基于binlog等日志层面的东西开发了自己的一套异构数据迁移中间件,这个中间件比较适用于异构数据库或者是做分库分表的迁移。因为在分库分表的过程中要想做到业务的尽量的无感知还是比较困难的,特别是在一开始是单库单表,需要分成1000张表,在分完成1000张表之后是很难再回退过去的,这个成本是非常大的。如果在这个过程中异构数据迁移组件做的足够好,就可以将风险降到很低。
对于异构数据的迁移方面可以为大家分享一个比较成熟的方案,比如需要从单库单表的数据维度迁移到32个库,每个库32个分表这样一千多张表的数据维度,此时可以首先把分库分表的一千多张表的数据维度当做之前单库单表的逻辑存库,而这个逻辑存库在一开始是通过基础组件binlog触发保证数据的实时同步的,其延时基本上可以达到秒级。如果是这样就可以先将服务中一些读的操作迁移过去,因为对于读操作而言,即使出现问题进行回滚也是没有任何代价的。等读操作迁移完成,并且通过验证比对没有问题之后,再将比较少的写的操作迁移过去,这样就能够从整体上降低很多的风险。
关键技术点-分库分表中间件
第二个关键技术点就涉及到访问分库分表数据的问题。在实现这部分时,我们也调研了很多业界的方案。目前业界比较成熟的方案主要有两种,分别是基于Client的和基于Proxy的,映客技术团队对于这两种方案在不同的时期也都实现过,但是总的宗旨是实现分库分表的透明化。首先,对于业务而言,不需要关心SQL到底执行到哪一个库表上,也不需要关心分库分表的策略是什么。第二个就是数据库本身配置的统一管理,这部分就是对于线上数据的统一切换以及配置都是非常有用的。第三个就是需要提供统一的监控。第四点,既然使用了这个分库分表中间件,就相当于所有的业务都是依赖于这个中间件的,其高可用设计就是非常关键的。另外,分库分表中间件的设计本身是一个集群模式,所以需要做好中间件和数据库中间链接的管理,通过一些连接池的模型将链接更好的管理起来。包括后面的负载均衡以及更好的扩展能力等都是在实现分库分表中间件时需要考虑的。
结合以上的技术关键点,映客的技术团队实现了两种分库分表中间件,在最开始技术栈还比较单一的时候,实现了基于Client的分库分表中间件——Mdriver Client,基于其分库分表的lab库可以实现将之前提到的大部分技术点都在内部集成。对于用户而言,可能只需要关心初始化这个Client,然后正常地写SQL,最终的结果可以直接汇聚好并返回回来,包括中间的路由策略都是由Client本身通过统一配置中心来实现的。这个过程中更多的会涉及到对于多语言的支持,相对而言其优点是当中间件挂掉或者宕机了所造成的灾难性后果的情况会少一些。除此之前,在最近由于考虑到对于整个公司层面而言,可能不止有一两种语言栈,而且不是所有的业务都需要使用Client来实现,这时候就应该提供更加友好的分库分表中间件,所以后来映客技术团队调研了Atlas Proxy这种形式,也在内部开发了一个支持分库分表的Atlas Proxy版本。
关键技术点-分布式事务
第三个关键技术点就是分布式事务,在之前可能所有的业务在一个数据库上通过一个本地事务就可以解决问题。而现在已经按照水平拆分和垂直拆分之后分成了很多个库表以及很多个维度的模块,这时候就涉及到了数据一致性的管理。而在这一方面,从业务层面来说业务可能处理的比较多的有两种分布式事务,第一类就是预占资源型,预占资源型就是比如是A要给B送礼或者A给B转账的时候,对于A而言需要扣钱,这部分是需要预先将钱扣掉的,然后再给B加上,这个过程对于A而言就是预占资源型。预占资源型有什么特点呢?其实就是如果想要给B加钱,那么就一定需要确保A的钱已经被扣掉了,而且这个操作如果在最终没有成功,那么对于A而言是非常容易回滚的。相当于预占资源型的分布式事务的要求是实时的、同步的、并且能够保证可回滚。这一部分现在业界使用的比较多的模型就是基于补偿的,也就是tcc的分布式事务框架,这种框架模型也是电商以及金融相关的业务中使用的比较多的。
而第二类分布式事务则是给予资源型的,还是用刚才转账的例子,如果A给B转账,使用给予资源型事务,如果事务失败了,但是B已经把钱转走了,这时候想要回滚其实是无法实现的。所以对于给予资源型的分布式事务而言,要么先不要给B把钱加上,也就是通过冻结资源的方式实现,要么就是通过一些异步必达的方式保证一定成功。而给予资源型事务的特点是其实时性要求不像预占资源型事务那么强,往往可以允许一定的延迟,比如A给B转账,B的钱晚到一会是没有太大影响的。所以给予资源型事务的特点就决定了其比较适合实现一些异步操作,并且保证其成功。
以上这些就是在实现了数据库架构演变之后,映客技术团队在分布式事务这部分所面临的问题以及所做的事情。
任何脱离了实际应用场景的架构都很难说它是一个好的架构,所以还是需要去分析架构的应用场景,根据其应用场景来分析究竟应该选择什么样的架构模型。接下来就与大家分享在直播这个应用场景之下,与数据库相关的技术点以及如何进行数据库架构的设计和迭代。
下图中展现的是一个直播大V,直播大V的特点就是其粉丝会非常多,下图中的大V有156万粉丝,而最多的粉丝量可能会达到千万级别。而粉丝量比较多所带来的问题就是无论是推送量还是关注度,还是在直播间中的活跃度都会非常高。而且本身而言,大V的直播也会比较频繁。此时对于平台考验最大的就是关系系统,所以关系系统必须能够支撑高并发、大容量,并且对于关系系统的实时性以及一致性的要求也是比较高的。如果A关注了B,而到最后没有关注成功或者并没有收到B直播的推送信息,那么这就是一个故障。所以这对于关系系统的压力是非常大的,因为任意用户之间都可以建立相互的关系本身就是笛卡尔积的形式。
所以映客技术团队也对于关系系统,特别是关系数据库进行了一些优化。对于关系数据库的优化主要分为了几个层面,首先是读写分析,也就是关系数据库这部分的写和读是分离开来的;另外还实现了写的异步化,写这部分的每个关系的触发所涉及到的写的操作会非常多,这时候就应该确保关键路径的写操作成功之后,其他的都通过写异步化的形式来做;当千亿级的数据爆发之后,数据量已经突破了数据库单实例的限制,这时候就必须要做很多分库分表等数据拆分方面的工作;另外在关系系统这部分还存在一个特点,就是消息推送需要涉及到关系,两人聊天也需要涉及到关系,而且每一种需要关系的场景都是不一样的,所以需要做很多异构数据的处理工作。
通过下图大家可以看到,在关系系统的主关系服务上有很多异步化的优化。首先,要想实现这些优化需要确保关系服务本身以及关系数据本身是收拢的,有统一的入口。在这之后就可以统一地把控这个入口,然后确保关键路径写成功,其他非关键路径都可以通过消息队列分发出去,并组建一些异构的数据源或者不同类型的需要做很多Cache优化、异构数据构建等的工作。
从上图中大家可以看到,如果主播有一千万粉丝,那么每次主播开播的时候,就需要将这一千万粉丝都查询到并进行推送,那么这对于系统所造成的压力将会是非常大的。所以在这个过程中通过Redis,通过一些数据拆分保证即使主播有一千万粉丝也可以实现秒级推送。有一些场景比如在关注直播里面,假如主播有一千万粉丝,如果每次开播推送消息时都对于这一千万粉丝的数据进行写操作,这将会对关系数据库造成非常大的压力,其实可以通过关注列表去反查有所有正在观看直播的观众进行推送,这样就可以通过推拉结合的方式将这些对于系统性能影响非常大的点通过异构的方式解决掉。
下图展现的也是映客平台的一个大主播,在日榜中,这个主播一天收获到了四千多万的映票,而一个映票相当于一毛钱。每个用户可以一个小礼物、一个映票地去送,并且可以连送。这样就会造成这一个问题,因为这四千多万的映票在一天之内,甚至在一个小时或者半个小时内都送给这个主播,势必会对于数据库造成很大的压力,如果主播的数据在数据库中仅为一条数据,那么会涉及到很多次更新操作。所以基于这样送礼的高并发场景,映客也做了很多层面的优化,首先支撑送礼这个场景的金融属性中的强一致性,虽然一个映票只有一毛钱,礼物的价值虽小但是仍旧是一个金融属性,如果这一毛钱没有到主播账户上,这将会成为一个金融故障,其影响将会是非常大的。而且粉丝无论在什么时候送给主播一个映票,都需要看到主播的映票数量是在实时地增长的,如果在短时间内主播的映票数没有增长,用户可能就来投诉了,所以在这个场景下对于实时性的要求也是非常高的。除了需要保证金融数据的强一致性以及实时性的前提,还需要保证在多个粉丝同时向主播送礼时的高并发特性,这对于金币系统的压力也是非常大的。
映客直播金币系统
映客的金币系统在初始时的架构如下图所示,也是依赖于RDS的MySQL集群,一开始是单主机的,后来进行了独立实例的优化,在这之后还进行了读写的分离,实现了读写分离之后其实业务包括送礼、支付和体现还是可以直接操作数据库的。这个时候即便是金币数据库能够支撑的量再大,稳定性再高,当遇到刚才提到的主播一天收到四千万映票的情况时,数据库所产生的延迟也是无法接受的。
于是映客技术团队对于金币系统也做了2.0版本的优化,与之前提到的关系服务类似,对于任何的数据层面的优化的前提就是首先需要进行入口的收拢,也就是让金币系统负责所有的与金币数据相关的操作。当入口收拢之后就可以去进行多方面的优化,而第一个层面就是做分库分表的工作,第二个层面则是对于主播收礼的过程进行异步的优化。为什么要实现这两个层面呢?可能分库分表并不能解决一个主播一天收到四千多万次映票的压力,而它解决的其实是另一个问题,就是将整体的并发吞吐量提升上去。可能最后观众送出的礼物都会汇聚到这一个主播上来,但是对于观众而言,每个用户都是单独的个体,对于他们而言,其操作并不会特别快,而且观众都是离散地分布在不同的数据库上的,这时候就可以通过分库分表的将数据进行拆分从而将观众这一侧的压力降到最低。另一个层面,可以通过异步的优化,如果在主播这一侧的收礼比较多,就可以通过异步串行的处理甚至是合并性的处理来保证系统的压力处于可控范围之内。而这两层优化的前提还是入口必须收拢,这就是映客对于金币系统优化的考量。
送礼逻辑解析
这里为大家分享一个业务中比较核心的送礼逻辑。之前也提到送礼操作和收礼操作需要分为两块,并且每个部分需要采用不同的策略,一种是分库分表的策略,另外一种是使用异步化的策略来解决。
刚才提到的分布式事务中包括了预占资源型事务和给予资源型事务。而送礼恰恰是预占资源型事务,这就必须保障送礼有一个比较好的实时性并且能够进行同步操作。因为送礼所涉及的是同步操作,需要具有实时性,并且送礼时观众端的比较离散的操作,通过分库分表就能很容易地保证其有一个整体吞吐量的提升。这里的同步操作并不是简单的更新操作,一旦更新操作失败了,就很难实现容错处理。这里就涉及到会对于每个操作本身进行本地事务的日志记录,如果失败了或者造成一些幂等性的问题时就有据可查了。另外在收礼端,这里相当于分布式事务中给予资源的操作,既然是给予资源,并且本身还可以容忍一定的延迟性,就可以在扣费成功之后将这个事务投入到一个消息队列中,通过消息队列来异步地处理收礼操作,并且可以对于大小主播进行通道的隔离,这样就很好地满足了收礼方的需求。总体而言,这样的逻辑设计保障了送礼和收礼双方的稳定性、高并发以及实时性。
其实,数据库本身而言是一个基础设施。那么作为基础设施,我们主要可以依赖数据库的稳定性以及基本的性能。而云为数据库提供了专家级的运维,这一点也是比较好的方面。但是云数据库也不是万能的,在前面提到的场景中的很多情况下必须在架构层面以及具体的业务场景层面进行具体的架构梳理和优化。在数据库架构方面,我们一直都在路上,并且将会一直努力前行。
本文为云栖社区原创内容,未经允许不得转载,如需转载请发送邮件至[email protected]