记录些系统设计架构实现方案(1)

10Wqps会员系统,如何设计?

同程艺龙会员系统的业务场景

在同程艺龙内部,会员系统是一种基础系统。这种基础系统,跟公司所有业务线的下单主流程密切相关。在同程艺龙平台的内部,如果会员系统出故障,会导致用户无法下单。也就是说,如果会员系统出故障,影响范围不仅仅是会员系统本身,而是全公司所有业务线。所以,会员系统必须保证高性能、高可用、高并发,为大的业务平台,提供稳定、高效的基础服务。

随着同程和艺龙两家公司的合并,越来越多的系统需要打通同程APP、艺龙APP、同程微信小程序、艺龙微信小程序等多平台会员体系。例如微信小程序的交叉营销,用户买了一张火车票,此时想给他发酒店红包,这就需要查询该用户的统一会员关系。因为火车票用的是同程会员体系,酒店用的是艺龙会员体系,只有查到对应的艺龙会员卡号后,才能将红包挂载到该会员账号。除了了 交叉营销 场景之外,还有许多、许多的业务场景需要查询统一会员关系。例如订单中心、会员等级、里程、红包、常旅、实名,以及各类营销活动等等。所以,会员系统的请求量越来越大,并发量越来越高。

2022年五一小长假的秒并发TPS甚至超过2万多。在如此大流量的冲击下,会员系统是如何做到高性能和高可用的呢?

会员数据异构存储架构方案

同程艺龙内部,会员系统采用的是MySQL集群+ES集群结合的 异构存储架构

具体如下图所示

图片

  1. 为什么使用MySQL?目前最火的关系型数据库,支持事务, 支持B+树结构高性能索引,数据低延迟(写入即可查),但是有两大缺点:

    • 不宜全文搜索,如果非索引全文搜索,会出现全表搜索, 性能低

    • 使用MySQL 需要分库分表,这种时候,没法做全表关联

  2. 为什么使用ElsaticSearch?ES在搜索方面天生具有优势

    • 倒排索引,天生的全文检索

    • 支持大表、宽表、非结构数据。不像关系型数据库那样一个简单的搜索就需要跨表联表、跨库关联

  3. mysql+elasticsearch的好处

    以mysql存取数据,es作为搜索引擎,保证性能

MySQL会员主库的:高可用+高并发架构

上述讲到,全平台会员的绑定关系数据存在ES,而会员的注册明细数据存在关系型数据库。

最早,会员使用的数据库是SQL Server,直到有一天,单台SQL Server数据库已经存储了十多亿的会员数据,服务器已达到物理极限,不能再扩展了。按照当然的增长趋势,过不了多久,整个SQL Server数据库就崩了。大家想想,SQL Server数据库崩了,那是一种什么样的灾难场景:

  • 会员数据库崩了,会员系统就崩了;

  • 会员系统崩了,全公司所有业务线就崩了。

想想就不寒而栗,酸爽无比,同城艺龙团队 立刻开启了迁移DB的工作。

MySQL双中心Partition集群方案

经过调研,团队选择了双中心分库分表的MySQL集群方案

图片

MySQL分表架构

会员一共有十多亿的数据,团队把会员主库分了1000多个分片。从单个分片的维度来说,平分到每个分片大概百万的量级,单个分片不到千万级,足够使用了。

MySQL主从架构

整个MySQL集群采用1主3从的架构。主库放在机房A,从库放在机房B,两个机房之间通过专线同步数据,延迟在1毫秒内。

MySQL流量路由架构

  • 写入的路由:写数据都路由到master节点所在的机房A

  • 读取的路由:读数据都路由到本地机房,就近访问,减少网络延迟。

会员系统通过DBRoute读写数据,DBRoute 是一个统一的数据库访问sdk 组件,可以根据流量开关,进行流量的切流。

具体的架构图,如下图所示:

图片

这样,采用双中心的MySQL集群架构,极大提高了可用性,即使机房A整体都崩了,还可以将机房B的Slave升级为Master,继续提供服务。

双中心MySQL集群搭建好后,团队进行了压测,测试下来,秒并发能达到2万多,平均耗时在10毫秒内,性能达标。

会员主库平滑迁移方案

接下来的工作,就是把会员系统的底层存储从SQL Server切到MySQL上,这是个风险极高的工作,主要有以下几个难点:

  • 会员系统是一刻都不能停机的,要在不停机的情况下完成SQL Server到MySQL的切换,就像是在给高速行驶的汽车换轮子。

  • 会员系统是由很多个系统和接口组成的,毕竟发展了10多年,由于历史原因,遗留了大量老接口,逻辑错综复杂。这么多系统,必须一个不落的全部梳理清楚,DAL层代码必须重写,而且不能出任何问题,否则将是灾难性的。

  • 数据的迁移要做到无缝迁移,不仅是存量10多亿数据的迁移,实时产生的数据也要无缝同步到MySQL。另外,除了要保障数据同步的实时性,还要保证数据的正确性,以及SQL Server和MySQL数据的一致性。

基于以上痛点,团队设计了“全量同步、增量同步、实时流量灰度切换”的技术方案。

首先,为了保证数据的无缝切换,采用实时双写的方案。

因为业务逻辑的复杂,以及SQL Server和MySQL的技术差异性,在双写MySQL的过程中,不一定会写成功,而一旦写失败,就会导致SQL Server和MySQL的数据不一致,这是绝不允许的。

所以,团队采取的策略是,在试运行期间,主写SQL Server,然后通过线程池异步写MySQL,如果写失败了,重试三次,如果依然失败,则记日志,然后人工排查原因,解决后,继续双写,直到运行一段时间,没有双写失败的情况。

通过上述策略,可以确保在绝大部分情况下,双写操作的正确性和稳定性,即使在试运行期间出现了SQL Server和MySQL的数据不一致的情况,也可以基于SQL Server再次全量构建出MySQL的数据,因为团队在设计双写策略时,会确保SQL Server一定能写成功,也就是说,SQL Server中的数据是全量最完整、最正确的。

如下图所示:

图片

讲完了双写,接下来团队看一下“读数据”如何灰度。

整体思路是,通过A/B平台逐步灰度流量,刚开始100%的流量读取SQL Server数据库,然后逐步切流量读取MySQL数据库,先1%,如果没有问题,再逐步放流量,最终100%的流量都走MySQL数据库。在逐步灰度流量的过程中,需要有验证机制,只有验证没问题了,才能进一步放大流量。

那么这个验证机制如何实施呢?

方案是,在一次查询请求里,通过异步线程,比较SQL Server和MySQL的查询结果是否一致,如果不一致,记日志,再人工检查不一致的原因,直到彻底解决不一致的问题后,再逐步灰度流量。

如下图所示:

图片

所以,整体的实施流程如下:

图片

首先,在一个夜黑风高的深夜,流量最小的时候,完成SQL Server到MySQL数据库的全量数据同步。接着,开启双写,此时,如果有用户注册,就会实时双写到两个数据库。

那么,在全量同步和实时双写开启之间,两个数据库还相差这段时间的数据,所以需要再次增量同步,把数据补充完整,以防数据的不一致。

剩下的时间,就是各种日志监控,看双写是否有问题,看数据比对是否一致等等。

这段时间是耗时最长的,也是最容易发生问题的,如果有的问题比较严重,导致数据不一致了,就需要从头再来,再次基于SQL Server全量构建MySQL数据库,然后重新灰度流量,直到最后,100%的流量全部灰度到MySQL,此时就大功告成了,下线灰度逻辑,所有读写都切到MySQL集群。

MySQL和ES主备集群方案

做到这一步,感觉会员主库应该没问题了,可dal组件的一次严重故障改变了团队的想法。

那次故障很恐怖,公司很多应用连接不上数据库了,创单量直线往下掉,这让团队意识到,即使数据库是好的,但dal组件异常,依然能让会员系统挂掉。

所以,团队再次异构了会员主库的数据源,双写数据到ES,如下所示:

图片

如果dal组件故障或MySQL数据库挂了,可以把读写切到ES,等MySQL恢复了,再把数据同步到MySQL,最后把读写再切回到MySQL数据库。

如下图所示:

图片

ES高可用方案

ES双中心主备集群架构

同程和艺龙两家公司融合后,全平台所有体系的会员总量是十多亿。在这么大的数据体量下,业务线的查询维度也比较复杂。有的业务线基于手机号,有的基于微信unionid,也有的基于艺龙卡号等查询会员信息。这么大的数据量,又有这么多的查询维度,基于此,团队选择ES用来存储统一会员关系。ES集群在整个会员系统架构中非常重要,那么如何保证ES的高可用呢?

首先团队知道,ES集群本身就是保证高可用的,如下图所示:

图片

当ES集群有一个节点宕机了,会将其他节点对应的Replica Shard升级为Primary Shard,继续提供服务。但即使是这样,还远远不够。例如ES集群都部署在机房A,现在机房A突然断电了,怎么办?例如服务器硬件故障,ES集群大部分机器宕机了,怎么办?或者突然有个非常热门的抢购秒杀活动,带来了一波非常大的流量,直接把ES集群打死了,怎么办?

面对这些情况,让运维兄弟冲到机房去解决?

这个非常不现实,因为会员系统直接影响全公司所有业务线的下单主流程,故障恢复的时间必须非常短,如果需要运维兄弟人工介入,那这个时间就太长了,是绝对不能容忍的。

那ES的高可用如何做呢?

团队的方案是ES双中心主备集群架构。

图片

团队有两个机房,分别是机房A和机房B。团队把ES主集群部署在机房A,把ES备集群部署在机房B。会员系统的读写都在ES主集群,通过MQ将数据同步到ES备集群。

此时,如果ES主集群崩了,通过统一配置,将会员系统的读写切到机房B的ES备集群上,这样即使ES主集群挂了,也能在很短的时间内实现故障转移,确保会员系统的稳定运行。

最后,等ES主集群故障恢复后,打开开关,将故障期间的数据同步到ES主集群,等数据同步一致后,再将会员系统的读写切到ES主集群。

如下图所示:

图片

ES流量隔离三集群架构

双中心ES主备集群做到这一步,感觉应该没啥大问题了,但去年的一次恐怖流量冲击让团队改变了想法。那是一个节假日,某个业务上线了一个营销活动,在用户的一次请求中,循环10多次调用了会员系统,导致会员系统的TPS暴涨,差点把ES集群打爆。这件事让团队后怕不已,它让团队意识到,一定要对调用方进行优先级分类,实施更精细的隔离、熔断、降级、限流策略。

首先,团队梳理了所有调用方,分出两大类请求类型。

第一类是跟用户的下单主流程密切相关的请求,这类请求非常重要,应该高优先级保障。

第二类是营销活动相关的,这类请求有个特点,他们的请求量很大,TPS很高,但不影响下单主流程。

基于此,团队又构建了一个ES集群,专门用来应对高TPS的营销秒杀类请求,这样就跟ES主集群隔离开来,不会因为某个营销活动的流量冲击而影响用户的下单主流程。如下图所示:

图片

ES集群深度优化提升

讲完了ES的双中心主备集群高可用架构,接下来团队深入讲解一下ES主集群的优化工作。有一段时间,团队特别痛苦,就是每到饭点,ES集群就开始报警,搞得每次吃饭都心慌慌的,生怕ES集群一个扛不住,就全公司炸锅了。

那为什么一到饭点就报警呢?

因为流量比较大,导致ES线程数飙高,CPU直往上窜,查询耗时增加,并传导给所有调用方,导致更大范围的延时。

那么如何解决这个问题呢?

通过深入ES集群,团队发现了以下几个问题:

  • ES负载不合理,热点问题严重。ES主集群一共有几十个节点,有的节点上部署的shard数偏多,有的节点部署的shard数很少,导致某些服务器的负载很高,每到流量高峰期,就经常预警。

  • ES线程池的大小设置得太高,导致CPU飙高。团队知道,设置ES的threadpool,一般将线程数设置为服务器的CPU核数,即使ES的查询压力很大,需要增加线程数,那最好也不要超过“cpu core * 3 / 2 + 1”。如果设置的线程数过多,会导致CPU在多个线程上下文之间频繁来回切换,浪费大量CPU资源。

  • shard分配的内存太大,100G,导致查询变慢。团队知道,ES的索引要合理分配shard数,要控制一个shard的内存大小在50G以内。如果一个shard分配的内存过大,会导致查询变慢,耗时增加,严重拖累性能。

  • string类型的字段设置了双字段,既是text,又是keyword,导致存储容量增大了一倍。会员信息的查询不需要关联度打分,直接根据keyword查询就行,所以完全可以将text字段去掉,这样就能节省很大一部分存储空间,提升性能。

  • ES查询,使用filter,不使用query。因为query会对搜索结果进行相关度算分,比较耗CPU,而会员信息的查询是不需要算分的,这部分的性能损耗完全可以避免。

  • 节约ES算力,将ES的搜索结果排序放在会员系统的JVM内存中进行。

  • 增加routing key。团队知道,一次ES查询,会将请求分发给所有shard,等所有shard返回结果后再聚合数据,最后将结果返回给调用方。如果团队事先已经知道数据分布在哪些shard上,那么就可以减少大量不必要的请求,提升查询性能。

经过以上优化,成果非常显著,ES集群的CPU大幅下降,查询性能大幅提升。ES集群的CPU使用率:

图片

会员系统的接口耗时:

图片

会员Redis缓存方案

一直以来,会员系统是不做缓存的,原因主要有两个:

  • 第一个,前面讲的ES集群性能很好,秒并发3万多,99百分位耗时5毫秒左右,已经足够应付各种棘手的场景。

  • 第二个,有的业务对会员的绑定关系要求实时一致,而会员是一个发展了10多年的老系统,是一个由好多接口、好多系统组成的分布式系统。

所以,只要有一个接口没有考虑到位,没有及时去更新缓存,就会导致脏数据,进而引发数据不一致的问题,例如:

  • 用户在APP上看不到微信订单

  • APP和微信的会员等级、里程等没合并

  • 微信和APP无法交叉营销等等。

那后来为什么又要做缓存呢?

是因为今年机票的盲盒活动,它带来的瞬时并发太高了。虽然会员系统安然无恙,但还是有点心有余悸,稳妥起见,最终还是决定实施缓存方案。

ES近一秒延时导致的Redis缓存数据不一致问题的解决方案

在做会员缓存方案的过程中,遇到一个ES引发的问题,该问题会导致缓存数据的不一致。

团队知道,ES操作数据是近实时的,往ES新增一个Document,此时立即去查,是查不到的,需要等待1秒后才能查询到。

如下图所示:

图片

ES的近实时机制为什么会导致Redis缓存数据不一致呢?

具体来讲,假设一个用户注销了自己的APP账号,此时需要更新ES,删除APP账号和微信账号的绑定关系。

而ES的数据更新是近实时的,也就是说,1秒后你才能查询到更新后的数据。而就在这1秒内,有个请求来查询该用户的会员绑定关系,它先到Redis缓存中查,发现没有,然后到ES查,查到了,但查到的是更新前的旧数据。最后,该请求把查询到的旧数据更新到Redis缓存并返回。

就这样,1秒后,ES中该用户的会员数据更新了,但Redis缓存的数据还是旧数据,导致了Redis缓存跟ES的数据不一致。如下图所示:

图片

面对该问题,如何解决呢?

团队的思路是,在更新ES数据时,加一个2秒的Redis分布式并发锁,为了保证缓存数据的一致性,接着再删除Redis中该会员的缓存数据。

如果此时有请求来查询数据,先获取分布式锁,发现该会员ID已经上锁了,说明ES刚刚更新的数据尚未生效,那么此时查询完数据后就不更新Redis缓存了,直接返回,这样就避免了缓存数据的不一致问题。

如下图所示:

图片

上述方案,乍一看似乎没什么问题了,但仔细分析,还是有可能导致缓存数据的不一致。

例如,在更新请求加分布式锁之前,恰好有一个查询请求获取分布式锁,而此时是没有锁的,所以它可以继续更新缓存。

但就在他更新缓存之前,线程block了,此时更新请求来了,加了分布式锁,并删除了缓存。当更新请求完成操作后,查询请求的线程活过来了,此时它再执行更新缓存,就把脏数据写到缓存中了。

发现没有?

主要的问题症结就在于“删除缓存”和“更新缓存”发生了并发冲突,只要将它们互斥,就能解决问题。如下图所示:

图片

实施了缓存方案后,经统计,缓存命中率90%+,极大缓解了ES的压力,会员系统整体性能得到了很大提升。

Redis双中心多集群架构

接下来,团队看一下如何保障Redis集群的高可用。

如下图所示:

图片

关于Redis集群的高可用,团队采用了双中心多集群的模式。在机房A和机房B各部署一套Redis集群。更新缓存数据时,双写,只有两个机房的Redis集群都写成功了,才返回成功。查询缓存数据时,机房内就近查询,降低延时。这样,即使机房A整体故障,机房B还能提供完整的会员服务。

展望:更精细化的流控和降级策略

任何一个系统,都不能保证百分之一百不出问题,所以团队要有面向失败的设计,那就是更精细化的流控和降级策略。

更精细化的流控策略

  • 热点控制。针对黑产刷单的场景,同一个会员id会有大量重复的请求,形成热点账号,当这些账号的访问超过设定阈值时,实施限流策略。

  • 基于调用账号的流控规则。这个策略主要是防止调用方的代码bug导致的大流量。例如,调用方在一次用户请求中,循环很多次来调用会员接口,导致会员系统流量暴增很多倍。所以,要针对每个调用账号设置流控规则,当超过阈值时,实施限流策略。

  • 全局流控规则。团队会员系统能抗下TPS 3万多的秒并发请求量,如果此时,有个很恐怖的流量打过来,TPS高达10万,与其让这波流量把会员数据库、ES全部打死,还不如把超过会员系统承受范围之外的流量快速失败,至少TPS 3万内的会员请求能正常响应,不会让整个会员系统全部崩溃。

图片

更精细化的降级策略

  • 基于平均响应时间的降级。会员接口也有依赖其他接口,当调用其他接口的平均响应时间超过阈值,进入准降级状态。如果接下来1s内进入的请求,它们的平均响应时间都持续超过阈值,那么在接下的时间窗口内,自动地熔断。

  • 基于异常数和异常比例的降级。当会员接口依赖的其他接口发生异常,如果1分钟内的异常数超过阈值,或者每秒异常总数占通过量的比值超过阈值,进入降级状态,在接下的时间窗口之内,自动熔断。

目前,团队最大的痛点是会员调用账号的治理。公司内,想要调用会员接口,必须申请一个调用账号,团队会记录该账号的使用场景,并设置流控、降级策略的规则。

但在实际使用的过程中,申请了该账号的同事,可能异动到其他部门了,此时他可能也会调用会员系统,为了省事,他不会再次申请会员账号,而是直接沿用以前的账号过来调用,这导致团队无法判断一个会员账号的具体使用场景是什么,也就无法实施更精细的流控和降级策略。所以,接下来,团队将会对所有调用账号进行一个个的梳理,这是个非常庞大且繁琐的工作,但无路如何,硬着头皮也要做好。

亿级短视频,如何架构?

短视频系统(如TikTok等)的宏观业务架构

以短视频点播为代表的流媒体技术应用在移动互联网时代实现了快速扩张。现在,短视频内容已成为新趋势,每个人都在从TikTok、Instagram、YouTube等平台上消费这些内容。让我们看看如何为TikTok创建一个系统。

在互联网内容趋于多元化的今天,短视频迅速替代了传统的文字图片,席卷了人们的视野和生活,成为信息传播的重要渠道。这样的应用程序看起来很小,但在后台有很多事情正在进行。以下是相关的挑战:

  • 由于该应用程序在全球范围内使用,将会有大量的请求发送到服务器。这最终会增加服务器的负载。

  • 将视频上传到后台将是一个巨大的任务,这将增加服务器的负载并阻塞。

  • 流畅地播放视频,无缓冲。

  • 一个基于用户兴趣推荐视频的推荐系统。

让我们逐一了解每个部分。我将其分为三个部分:

  • 与用户相关的子系统

  • 与视频发布相关的子系统

  • 与点赞和评论相关的子系统

  • 推荐子系统

1)与用户相关的子系统

这是一个包含与用户相关服务的服务,如下所示:

  • 注册: 用户将在应用程序中注册。

  • 登录: 它将对凭证进行身份验证,并向应用程序发送响应。

  • 登出: 用户将从应用程序中注销。

  • 关注: 如果用户想要关注或取消关注其他用户,则可以通过此服务完成。

为了存储与用户相关的数据,我们将使用基于SQL的数据库,如MYSQLPostgreSQL,因为与用户相关的数据(例如追踪关注者)将会是关联数据,所以这是一个适当的选择。为了优化数据库性能,我们将使用主从架构。主数据库用于执行写操作,从数据库用于执行读操作。

图片

现在让我们讨论用户服务的流程。应用程序将发出API调用,API Gateway将管理这些API。它将为用户服务路由请求。请求将通过负载均衡器进行,负载均衡器下将有多个用户服务实例。根据负载,它将决定哪个实例将处理请求。一旦请求被处理,负载均衡器将将响应发送回API网关,然后再发送回应用程序。

2)与视频发布相关的子系统

一般包含视频上传、存储、处理、播放等流程及相应的流程管理与审核。核心的操作如下所示:

  • 上传视频: 将视频上传到后台服务器。

  • 发布: 如果用户想要创建、编辑或删除帖子,则可以通过此服务完成。

与视频发布相关的子系统的技术要点:

  • 如何安全可靠地存储PB级海量数据,并实现视频数据的快速存取;

  • 如何支持多种场景下的视频上传;

  • 如何保障稳定流畅的拉流播放;

  • 以及如何满足视频转码、水印等基本处理需求都成为构建一个视频点播平台需要考虑和解决的技术难题。

核心的核心,就是短视频的存储。为了存储与帖子相关的数据,我们将使用基于NoSQL的数据库,如MiniO。对于每个用户,可能会有成千上万的帖子,这将导致大量数据。为了实现最佳性能,扩展数据库可能会很困难。NoSQL数据库支持水平分片,这有助于我们在不影响性能的情况下扩展数据库。

图片

现在让我们讨论视频服务的流程。

应用程序将发出API调用,API Gateway将管理这些API。它将为视频服务路由请求。请求将通过负载均衡器进行,负载均衡器下将有多个视频服务实例。根据负载,它将决定哪个实例将处理请求。一旦请求被处理,负载均衡器将将响应发送回API网关,然后再发送回应用程序。

如何使文件在全球范围内可访问而不增加下载时间?

视频文件将上传到NOSQL,如MiniO。现在,如果我们想在世界范围内任何地方访问文件而没有任何延迟,那么该文件将发送到**内容分发网络(CDN)**,它将将媒体文件更新到世界各地的不同数据云存储中。

我们能进一步优化以减少下载时间吗?

还有一个挑战需要解决,即原始视频的大小可能较大,因此如果将大文件发送回客户端,则下载时间会更长,这会影响用户体验。文件一旦上传到云存储,您可以在数据库中存储文件路径。然后将帖子/视频详细信息发送到消息队列系统,如KafkaRockerMq。为了使用户体验流畅,我们需要压缩视频并为不同设备创建不同分辨率的视频。视频处理工作者将从消息队列系统接收视频详细信息,然后从云存储中提取文件并进行处理。处理完成后,这些新的视频文件将发送到CDN

如何访问压缩的视频文件?

现在您可能会想,应用程序如何知道上述讨论中压缩的视频的文件路径?由于压缩文件将存储在分类文件夹中,因此可以根据分辨率和文件名轻松查找文件。视频发布API只会返回文件名,而要访问文件,应用程序将在URL本身中添加分辨率细节,例如/media//mediaID/xxxx。当访问此URL时,它将经过API网关,并从URL中提取分辨率和文件名详细信息。然后它将在缓存系统Redis中检查,如果文件不可用,则将访问CDN并通过它获取文件。然后将其添加到缓存中,以便如果再次请求相同文件,则不必从CDN获取。

3)点赞和评论相关子系统

这是一个包含与视频点赞和评论相关服务的服务。正如名称所示,通过此服务,我们可以为特定帖子更新点赞和评论。与上面讨论的其他流程相同。

图片

4)推荐子系统

通过此服务,基于用户偏好推荐一系列帖子。幕后有很多其他事情正在进行。让我们看看幕后运行的流程。

图片

然后,创建一个帖子后,它将被发送到消息队列系统,然后消费者将提取数据并将数据更新到大数据(Hadoop)中。将为机器学习服务(如PyTorchTensorflow)设置单独的服务器,在这里它将从大数据中提取数据并训练模型。

推荐服务将使用此AI模型为给定用户推荐帖子。

技术选型:常见的NOSQL存储框架选型

当前存储从逻辑上一般可分为三类,即块存储、文件存储和对象存储。

  • 块存储一般指常见的卷或硬盘存储,以及相应的磁盘阵列、NAS、SAN等存储方式,操作对象是磁盘,使用逻辑块编号寻址,数据按字节方式访问,读写速度快。

  • 文件存储则将数据以不同应用程序要求的结构方式组成不同类型的文件,可对文件进行创建、查找、修改、删除等操作,易于实现数据共享。

  • 对象存储将文件以对象的方式进行存储(一个对象包含属性以及内容),通常实现方式为多台分布式服务器内置大容量硬盘,通过对象存储软件组建集群,对外提供读写访问功能。

业内较为主流的开源存储框架MinIO、Ceph、SeaweedFS,从开源协议、扩展性、成本等多方面进行对比如下表:

图片

由于对象存储结合了块存储读写效率快、存储空间可扩展以及文件存储方便共享的优点,同时结合短视频平台数据存储与视频点播需求,建议选取对象存储框架作为短视频点播平台的存储逻辑。

进一步考虑到短视频点播平台数据规模、存储动态不宕机扩容、在线HTTP多媒体播放以及学习运维成本等需求,通过以上对比,建议选用MinIO开源框架作为短视频存储与点播基础框架。

重点介绍:MinIO对象存储框架

对象存储的出现是为解决了存储海量大数据的问题,如存储海量的视频、图片,并进行数据归档、数据备份、大数据分析等操作。

对象存储一般采用key-object的扁平化存储架构,使用方便,调用API就可进行数据的多样化读写。其大容量、动态扩展、数据灾备等性能,是传统文件存储和NAS无法比拟的。

MinIO是一套基于Apache License V2.0协议的轻量级、高性能开源对象存储框架,适用于图片、视频、镜像等海量非结构化数据存储。

MinIO采用Golang实现,客户端支持Java、Python、JavaScript、Golang语言,兼容亚马逊S3云存储服务接口,方便与其他应用结合。

1)存储机制

MinIO使用纠删码(erasure code)和校验和(checksum)来保护数据免受硬件故障和无声数据损坏,可保证N/2节点损坏的情况下数据的正常访问。

2)扩展性

极简性和扩展性是MinIO集群的两个重要设计理念。MinIO支持对等扩容和联邦扩容两种扩容方式。

对等扩容,即通过增加对等的集群节点和磁盘以扩充集群,例如,原集群包含4个节点4块磁盘,则扩容时可同样增加4个节点4个磁盘(或其倍数),以此保证系统可维持相同的数据冗余SLA,降低扩展的复杂性。

联邦扩容,其基本原理是引入etcd作为统一命名空间,将多个MinIO集群组成一个联邦,可在原有联邦的基础上,向联邦中增加新集群,此扩容方式理论可实现无限扩展,且可以实现在原联邦服务不中断的情况下扩容。

3)对外服务

MinIO完全兼容S3标准接口,客户端和服务端之间通过http/https进行通信。MinIO提供客户端mc(MinIO Client)以支持UNIX命令,同时支持多语言的客户端SDK。此外,其存储后端除使用磁盘外,还可通过网关对接其他存储系统与资源。具体如下表所示。

图片

4)多媒体拉流支持

MinIO对于多媒体文件,支持HTTP-Range的方式在线拉流播放与音视频进度条拖拽。

如下图,使用浏览器以流的形式访问存储于MinIO的多媒体文件时,每拖动一次进度条,则会向MinIO服务端发送一条Http-Request请求,请求Headers中包含Range字段,其内容是当前请求视频进度的开始字节数及缓存结束字节数。

这种形式使MinIO天生支持多媒体文件的拉流播放与进度拖拽。

图片

MinIO多媒体在线播放支持

基于MinIO实现简单的短视频系统

出于集群存储可动态扩展性、支持HTTP流式播放、运营成本等因素,建议使用MinIO对象存储作为底层存储,开发部署短视频点播地址映射、地址动态代理等服务,实现一套短视频存储点播平台。其实现框架如下图:

图片

基于MinIO的短视频点播平台架构

点播平台大致可分为存储层、服务层与应用层。

  • 存储层主要部署MinIO对象存储系统及关系数据库,MinIO用来存储视频对象,关系数据库用来存储视频元数据;

  • 服务层提供各类存储访问服务接口,如文件上传下载、视频播放地址生成、对象地址映射等;

  • 应用层为前端提供应用功能,包括视频上传、查询、播放等功能。

基于MinIO对象存储的点播平台数据访问流程如下图所示:

图片

基于MinIO的短视频点播平台数据访问流程图

1)视频上传与转码

统一采用mp4格式作为视频存储和点播格式,为了兼容多种格式视频文件上传,需开发转码模块将其转码成mp4格式进行存储,将其首先存入本地磁盘缓存。

2)直播录制

在直播的过程中开启录制,将录制的文件首先存入本地磁盘缓存。

3)上传文件

视频转码完成或录制完成后,调用MinIO文件上传接口,将视频文件上传至MinIO集群/联邦,由etcd对MinIO集群提供注册发现服务。

4)点播地址映射

服务端部署点播地址映射服务模块,实现MinIO视频点播地址与视频ID的映射,使存储介质的改变不影响视频点播拉流。

5)地址动态代理服务

出于系统安全性考虑,我们不希望暴露MinIO存储地址与存储细节,希望增加一层网关进行媒体流的转发或地址的代理,对外提供统一的服务地址,使用地址动态代理,可以根据点播请求的视频ID不同,动态代理至不同的视频播放地址,实现视频存储细节与服务地址的解耦。

6)拉流播放

客户端或浏览器使用HTTP协议流式拉取视频文件并播放。

7)总结

选用MinIO开源存储框架,快速设计并搭建出一套支持海量短视频上传、存储、点播等功能的视频点播平台,

为当下不断涌现的短视频点播平台及相关应用提供了一定技术选型与设计参考。

短视频架构的核心要点:CDN缓存

除此minio存储之外,短视频对CDN分发也是有很高要求的,跟传统的长视频相比的话,因为长视频会进行预取刷新的操作,会预先将文件分发到CDN节点上去,但是短视频内容因为是UGC,而且视频上传完成之后页面马上就要发布出去,进行播放,所以往往不能像长视频那样,提前预取到各个CDN节点,进行预热,这对视频云平台内部的分发能力是有要求的。

图片

就近上传

用户拍完一段视频,需要立即上传。CDN厂商一般全国各地有多个数据中心,“从基础资源能力上来讲,要求CDN网络有条件为客户提供就近上传的功能”。

图片

如何实现?

通过一套SDK,开发者将这套SDK嵌入到他们APP里面去,最终用户在将视频上传的时候,会通过HTTP DNS的调度去获取离他最近的或者是当前网络中最佳的一个数据中心节点,并且实现这个文件的上传功能。

亿级视频处理系统架构实践

字节跳动火山引擎视频中台支撑了多个亿级应用的视频全生命周期管理:

  • 火山引擎视频的相关 ToB 业务

  • 支持了字节跳动抖音

  • 西瓜视频等产品

全部视频生命周期:

  • 视频生产

  • 视频下发

  • 视频播放等

视频处理整体的生命周期

图片

视频从拍摄到播放的整个过程可以分为四个主要阶段:

  • 端侧生产:视频制作者使用设备拍摄(手机或其他设备),并可对视频增强和编辑。通过使用上传工具,将视频上传到云服务器。

  • 云端生产:云端包含两个关键环节:视频后期处理和审核。这两个环节同时进行。

  • 云端分发:完成上述两个环节后,视频就可以供用户观看,进入云端分发阶段。

    在此阶段,点播服务负责提供视频的播放链接(包括相关的元数据),视频内容通过 CDN 进行分发。

  • 视频播放:此阶段由播放工具负责在用户终端进行视频的处理和展示。

在整个过程中,视频处理系统是云端生产的核心阶段。

接下来,我们探讨一下字节跳动在视频处理方面所面临的几个挑战。

  • 庞大的规模:如今,字节跳动每天处理的视频数量已经达到亿级别,由于每个视频都有不同层次、不同格式的需求,实际上生产的视频数量近十亿级别。这对系统整体稳定性和性能的要求很高,同时,对计算和存储资源造成极大的消耗。

  • 多样化的业务:字节跳动的视频业务涵盖广泛,涉及教育、游戏等不同垂直行业,包括点播、直播、RTC,以及长视频、中视频、短视频 等相关业务。

  • 复杂的资源环境:除了常规的 CPU 资源外,还有许多弹性资源,如其他的硬件转码设备,CPU/GPU/FPGA 等。

  • 大型活动的峰值和业务高速增长:字节跳动每年都有许多大型活动,给系统带来巨大的压力。此外,每年处理的视频数量都以至少翻倍的速度增长。

视频处理系统的目标

面临以上这些挑战,视频处理系统要实现哪些目标呢?

图片

大家可以看上图,这张图更偏逻辑的关系。

在实现视频处理系统的主要目标时,我们需要关注三个重要方面:首先,满足各种业务需求,例如支持短视频、长视频等不同类型的视频业务以及满足各行业的需求;其次,提升用户体验,通过优化画质、流畅性等方面,让用户获得更好的观看体验;最后,降低成本,特别是考虑到字节跳动庞大的业务量所带来的计算、存储以及 CDN 成本。

为达成这些目标,视频处理系统需要具备多种处理能力,例如转码、编辑、分析和图片处理等。这些处理能力都是视频应用的重要组成部分。以转码应用为例,我们需要采用新的编码器、自适应转码等技术来降低码率,同时通过增强技术提高画质等。

此外,所有这些处理能力都依赖于一个高可用性、高可扩展性和高效运维开发效率的基础处理系统。这个系统是整个视频处理系统的核心,为我们提供了各种视频处理能力的支持。

总之,视频处理系统是一个复杂的系统,它以底层系统为支撑,构建了各种视频处理能力,形成了多种视频应用,从而满足了业务需求、提升了用户体验并降低了成本。

视频处理系统架构

图片

视频处理系统的结构图

为了实现这些目标,视频处理系统的结构如图所示,可分为外部和内部两个部分。在外部部分,系统被划分为三个层次:

  1. 数据平面:系统每天产生大量数据,这些数据可用于分析以指导系统优化,同时也用于计量、计费和监控等方面。

  2. 控制平面:服务于开发人员、运维人员和支持人员,他们负责操作和控制系统,并在系统出现问题时进行管理和应急处理。

  3. 用户平面:主要关注用户如何与系统互动,以及如何使用系统功能。

中间的四层分别是:

  1. BMF:动态多媒体处理框架,旨在插件化管理所有多媒体处理原子能力,从而提高系统的可扩展性、开发和运维效率。

  2. Lambda:这是一个高可用的函数计算平台,主要负责管理底层海量资源,实现资源的高效调度和任务执行。

  3. 工作流系统:旨在协调异步、分布式的媒体处理流程。

  4. 服务层:主要负责处理鉴权、任务队列管理、上层模板管理和策略控制等任务。

下面将为大家详细介绍几个核心层。

服务层和工作流系统

系统服务层介绍

图片

在服务层中,有几个关键组件值得关注:

  • 服务网关:它能够进行跨数据中心的流量调度,并负责接口认证和接口层的流量控制。

  • 弹性队列:它可以隔离业务方面的资源。它的功能包括:队列资源设置(例如任务的 QPS 和最大并发任务数量 MRT)、队列管理和弹性资源的管理。

  • 管理服务:它具有两个功能,首先是对整个视频处理系统的元数据进行管理,例如任务队列、模板和工作流信息等;另外是启动底层工作流的执行,并管理整个工作流的生命周期状态。

媒体工作流介绍

图片

在服务层的下方,有一个媒体工作流引擎,负责组织一系列视频处理的操作,这些操作以 DAG 的形式排列。例如,在西瓜视频上传一个视频后,需要提取视频封面并进行无水印转码,还需要进行各种编码格式的转换。

这些处理视频的流程都属于细粒度的任务。一个可行的方法是将这些单独的流程整合成一个工作流。工作流解决了以下问题:

  • 首先,它简化了复杂业务的调用过程。如果没有工作流,处理一个视频需要进行多次调用。

  • 其次,工作流有助于管理视频处理流程之间的依赖关系。在实际处理过程中,前后流程之间存在依赖关系,例如画质增强流程,需要先对原片进行增强,然后进行普通转码,或者通过分片转码功能对视频预先切片,接着对每个切片进行转码,最后将它们拼接在一起。这些都可以通过工作流实现。

  • 最后,工作流提供了任务超时、错误重试等高可用能力,降低了业务使用成本。

下面看一下工作流内部是怎样的结构。

图片

工作流内部的结构

工作流内部主要包括以下几个组件:

  • VWorker:作为上层与下层的连接层,将上层的业务模板转换为底层可执行的函数任务参数。

  • Scheduler:对于每个工作流中的节点,可以进行细粒度的任务调度。

  • Engine:管理所有工作流的运行状态。

  • Gate:负责处理流量调度和授权验证。

工作流的核心部分如图中所示,位于绿色区域。

服务层位于顶部,而下面要介绍的是函数计算平台。

任务执行

视频处理系统是一个批处理系统,每个任务都需要执行几十秒、几分钟甚至更长时间。因此,最关键的是确保每个任务都能最终被执行,并且保持一致性。为了实现这一目标,系统需要有 at least once 的保证。此外,任务还需要满足幂等的要求。

任务幂等有两个意义:

  • 首先,无论任务在何时执行多少次,最终结果都应保持一致,并且对业务方来说透明。

  • 其次,在一定时间内,如果同一个视频进行相同处理并提交多次,系统需要具备去重机制,只执行一次。对调用者而言,这个过程也应透明,这能在某些场景下提高系统效率。

为了确保任务幂等,我们在视频 meta 信息关联和视频存储方面做了大量工作。同时,为了实现 at least once,我们在工作流和节点层面都设置了超时检测和重试机制。

任务执行的难点1:快速响应和恢复

视频处理系统的下游包括计算资源和存储资源。一旦计算资源和存储资源出现问题,很难有一个完美的方案对上层业务做到完全无影响,所以要尽量减少损失,降低对业务的冲击。为此,可以采取以下两个重要措施:

  • 多级限流:限流是一种常用方法,但视频处理有一个任务筛选过程,需要确保在有限资源内,所有重要任务得到优先执行。

    例如,假设底层计算资源突然减少一半,如何降低对业务的影响?

    首先,在工作流层面,需要将一些对任务延迟不敏感的工作流任务进行推迟,这需要一些策略的预设置。此外,在同一个工作流中,需要对不同节点进行优先级配置,比如视频需要转出五个档位,其中两个档位消费概率最高,需要优先转出,其他档位则可以延迟处理。这整体涉及到分级限流以及限流策略配置的能力。

  • 批量重转:假设昨天底层同事上线了一个有问题的功能,但今天才发现。这时需要把昨天这个功能上线后所影响的视频全部筛选出来,快速进行重新处理,且不能影响目前正在运行的业务。

    这有两个问题需要解决:

    第一,如何准确地从某个时间点到另一个时间点,将这一批视频全部挑选出来。

    第二,如何快速重新处理,且不影响线上业务。因此,需要一个单独的子系统来负责批量任务的查找和重新处理。

任务执行的难点2:系统维度

从系统层面来说,我们采取了若干措施,主要包括中间件备份以及对下游异常进行监测。一旦发现某些实例出现问题,我们会立即对这些实例进行隔离和剔除。此外,系统具备较为完善的流量切换策略,因为系统已经经历了多次大型活动考验,同时拥有全面的压测和预案,这些对于确保系统的高可用至关重要。

函数计算平台

上面介绍了工作流的系统。下面介绍一下它的下层函数计算平台。首先,让我们来了解一下函数的概念。函数在媒体工作流中代表一个节点,同时也对应着一个细粒度的视频处理任务。换句话说,函数就是一个可执行的程序段。

那么,这个函数计算平台需要具备哪些能力呢?

  • 首先,也是最关键的,平台需要为视频处理程序提供大规模水平扩展能力,以便轻松地为线上业务提供稳定服务。

  • 其次,平台需要管理多种类型的庞大资源,并且具备高效的资源调度能力。

  • 最后,平台还需要具备处理各种异常情况和容灾等的高可用能力。

图片

函数计算平台的基本架构

上图是这个函数计算平台的基本架构。

在图中的左侧部分,有一个控制平面,开发者可以编写一个函数并通过管理用户界面将其注册到函数计算平台上。接着,我们看到图中的右侧部分展示了整个函数调用过程。首先,该过程会通过函数计算平台的网关,进入集群级别的调度。随后,过程会进入一个独立的集群,而这个集群内部包含了我们自主研发的中心调度系统 Order。Order 系统拥有一个中心调度器,它会将任务分配到一个具体的节点上执行。这个节点会获取整个函数的可执行包,然后运行该函数。

高可用性:多集群

在多集群层面,

  • 首先,我们做了流量的一键切换,多集群的容灾;

  • 其次,我们也会根据预设配置进行流量的自动调节。

图片

简单的多机房示意图

上图是简单的多机房示意图。

图中左右两侧均为机房,每个机房包含多个集群。每个机房设有一个集群级调度器模块,而多个机房之间还有一个负责同步各机房资源状况的模块,包括资源总量和使用情况等。

高可用性:单集群

我们的单集群采用中心调度系统,其中心调度器名为 Server,另有一个执行单元称为 Client。Server 具有多实例、无状态的特点,能够平稳、动态地进行升级。

在 Server 和 Client 之间,有状态检测机制以及对问题节点的熔断和任务重试等措施。

通常情况下,Server 通过心跳检测来判断节点是否正常运行。除此之外,Server 还会关注节点的整体状态,例如任务超时较多或失败率较高等情况。当出现这些情况时,也会对节点执行熔断策略。

控制面——服务治理

之前我们提到过函数计算平台分为几层,分别是网关层、集群调度层和机器内部调度层。这些层次均为多实例服务。因此,每个上游都会对下游进行异常检测和隔离,这意味着所有组件都具备单点异常处理能力。此外,还有一些中间件熔断策略。

动态多媒体框架 BMF

BMF,即 ByteDance Media Framework,是字节跳动自主研发的多媒体处理框架。我们决定开发一个视频处理框架,是因为发现传统的视频处理框架存在一定的局限性。

  • 首先,传统的框架通常使用 C/C++ 进行开发和扩展,这导致扩展的门槛较高,且扩展后需要重新编译。在一个大型系统中,这是非常麻烦的。

  • 其次,随着越来越多的人参与框架的开发和维护,框架的依赖关系会变得越来越复杂,最终降低开发和运维的效率。

  • 另外,传统的视频处理流程较为固定,例如视频转码,传统框架都可以支持。但在一些更为复杂的场景下,如视频编辑或 AI 分析,传统框架在灵活性上存在限制。

  • 最后,传统框架在性能方面也存在瓶颈。以 ffmpeg 为例,filter graph 是单线程执行的。如果在 filter graph 中加入一个 GPU 的 filter,执行效率会大幅降低,同时 GPU 的利用率也不会很高。

为了解决上述问题,我们研发了 BMF 多媒体处理框架,其目标包括:

  • 通过一套框架支持各种复杂的应用场景,具备较高的灵活性。

  • 屏蔽底层硬件差异。随着业务越来越多地使用不同异构硬件,如 GPU,我们希望这个框架能原生支持这些硬件。

  • 通过该框架将所有视频处理的原子能力模块化,并实现动态管理和复用,以解决大规模协同开发的问题。同时,也能使这些能力在不同场景和业务上得到较好的复用。

  • 降低视频应用开发的成本,使应用开发标准化。

图片

BMF 框架的整体架构

上图展示了 BMF 框架的总体结构。

在最上层,即应用层,每一模块都代表一个视频应用,例如前面提及的视频转码、视频编辑、图片处理等。在下层,是模块层,其中的每个模块都代表视频处理的一个细粒度原子功能,如进行视频编解码或 ROI 检测等。

应用层和模块层通过中间的框架层连接在一起。

框架层的核心是一个引擎,它向上提供一套通用、简洁的流式接口,便于开发者容易地构建视频处理应用。此接口支持多种语言,包括 C++、Python 和 Go。向下,它提供一套完整的模块开发 SDK,同样支持这几种语言。

在核心引擎周围,我们还开发了一些相关服务和工具集,主要用于管理模块的版本、依赖等信息。

这种架构的最大优点在于,它为开发者提供了一个较好的划分。不同模块的开发者可以专注于自己模块的开发,并选择熟悉的编程语言。

模块开发完成后,可以将整个模块注册到系统中。上层的应用开发支持业务,业务无需了解底层模块的实现方式以及所使用的编程语言。只需利用框架提供的接口,就可以无缝连接并使用这些模块。

图片

BMF 的动态开发模式

上图进一步展示了 BMF 的动态开发模式。以实际情境为例,算法开发者负责研究视频处理算法。

  • 首先,算法优化人员会对算法进行优化。优化完成后,算法将形成一个模型。

  • 接下来,算法优化人员会将模型注册到系统中,而模块开发人员会将模型封装成具体模块,并注册到系统中。这些模块代表着具体的原子能力。

  • 然后,函数开发者,即业务开发人员,可以将模块串联成具体的视频处理应用,并将函数注册到函数管理平台,然后进行灰度测试和上线。

在整个流程中,各个团队的分工非常明确,独立开发协作效率大大提高。此外,流程中的所有模块原子能力都是可重复使用的。且流程不会涉及任何编译依赖,全部都是动态进行的。

亿级视频处理宏观流程

图片

视频转码的完整流程示例

上图展示了视频转码的完整流程示例。当用户上传一个视频后,该视频将首先进入服务端的存储,从而触发转码流程,即提交一个工作流任务。此任务将首先经过转码服务,然后被放入弹性队列;接下来,任务将从弹性队列出队,进入工作流引擎执行;工作流引擎将拆分任务为细粒度的子任务,并将它们发送到函数计算平台执行。每一个函数都将采用前面介绍的 BMF 动态开发方式进行构建。最终,在所有细粒度节点任务完成后,整个工作流程也将完成,然后转码或视频处理流程将完成,并逐步返回。

在此,让我们回顾一下本文的一些关键点:

首先,视频处理系统需要满足几个重要要求,包括高可用性(系统稳定性)、高可扩展性(在支持众多业务场景时,可扩展性对整体高可用性具有重大影响)以及开发和运维效率。

总的架构可以概括为媒体工作流、函数计算平台以及动态多媒体框架 BMF 这三个核心部分。在高可用性方面,服务层将提供任务幂等、多级限流和批量重传;平台层将实现多机房、多集群的切流策略、单集群内部的冗余、上下游的异常检测等。最后,底层的动态多媒体框架虽然并未直接提高系统的高可用性,但提升了系统的可扩展性、开发和运维效率,因此也对系统起到了至关重要的作用。

未来,系统将朝着更智能化的方向发展。我们希望构建一种分布式调度执行平台,用户只需关注处理流程,而平台的拆分、资源调度和执行方式将由平台自行决定。

参考文献:

https://blog.csdn.net/weixin_37604985/article/details/132179317

https://zhuanlan.zhihu.com/p/381259391

https://blog.csdn.net/csdnnews/article/details/117915142

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值