查看原文
其他

百万在线直播互动平台基于Docker的微服务架构实践

2018-05-03 温情 51CTO技术栈

本文从具体的项目实例出发和大家讨论如何从无到有地去搭建一个能够快速伸缩的微服务架构。


2017 年 12 月 1 日-2 日,由 51CTO 主办的 WOTD 全球软件开发技术峰会在深圳中州万豪酒店隆重举行。


本次峰会以软件开发为主题,新浪微博研发中心平台高级系统研发工程师温情在微服务与容器技术专场带来了主题为“基于 Docker 的微博直播互动微服务架构”的精彩演讲。


本文将围绕以下主题,探讨直播互动的微服务架构设计:

  • 针对现在非常火热的直播场景,如何设计一个稳定的消息互动系统,以支持百万级消息的互动分发。

  • 在服务越来越复杂的情况下,如何对系统进行合理地组件化拆分,进而实现以微服务的方式进行独立部署和升级。

  • 在混合云的体系下,如何充分利用 Docker 技术和公有云的弹性特性,设计一个基于混合云的弹性化的架构,以实现在流量突增且无人看守的情况下,自动完成服务的弹性伸缩。


微博直播互动平台的背景与挑战


2017 年可谓直播大年,新浪微博在自己的 App 推出了直播服务,同时也打通了与淘宝、一直播、红豆等平台的互动。


从技术上说,直播一般分为两个部分:

  • 视频流,包括流媒体的传输部分。

  • 直播互动,包括评论、点赞、送礼等其他部分。


直播的特点是:由于房间里人数众多,直播平台需要能够支持百万用户同时在线的场景。


微博直播平台特点


直播互动系统的基本模型是一个消息转发的系统,即某个用户发送一条消息,其他用户收到该消息,并执行相应的存储。


消息系统一般具备三个基本的评判标准:实时性、可靠性、和一致性。对于直播这种消息系统而言,它对于可靠性和一致性的要求并不高,而对实时性的要求却非常高。


如果用户给主播送完礼物 10 秒钟或者 1 分钟之后,主播才能收到并回复。这是不可接受的。因此我们需要具有“秒级”的实时性。

微博直播互动的特点:

  • 平时的流量不太大,但是当访问多的时候流量会产生明显的猛增。

  • 流量的激增表现在消息推送量的巨大。一般可以达到每秒千万条消息推送。

  • 对于一般短连接的请求而言,用户为了进入界面仅发送一次请求,后续通常不再产生其他操作,因此服务器端的压力不太大。

    而直播互动场景则不同,在用户加入房间之后,就算不做任何操作,也会收到一大堆的推送。这种实时性高的推送方式会给服务器造成持续的压力。


微博直播平台面临的挑战


由于用户对直播的需求量大,且玩法多样,我们面临了来自三个方面的挑战:

  • 如何快速迭代并快速地响应需求,这是对业务以及系统的衡量标准。

  • 如何应对全量 Push 所引发的流量峰值。

  • 如何实现成本最优化,即如何运用有限的资源应对波峰波谷的交替。


微博直播互动平台微服务架构演进


针对上述三大挑战,我们自己搭建了直播互动的微服务架构。上图是直播互动的架构图,我们对业务层进行了模块化的划分,包括发现服务、三方平台服务、推送服务等各种微服务。


架构选型


在架构选型方面,我们先来看传统的单体模式。单体适用于许多场景,特别适合于敏捷开发。


由于微博互动的流量庞大,且系统相对复杂,因此会面临如下问题:

  • 上线成本高,迭代速度慢。单体模式是将所有的服务集中在一块儿,实现一起部署和一起上/下线。因此任何一次细微的改动,都需要我们重新“上线”。

    而为了扫清可能出现的服务问题,我们一般会进行全量回归测试。可见这种缓慢的迭代速度是我们所无法接受的。

  • 可伸缩性差。在服务进行水平扩展时,随着服务增多,Redis 和 DB 的连接数也会逐步增多,而每个资源都有一定的连接数瓶颈,当服务增长到一定数量之后,则会导致服务无法进一步地水平扩展。

  • 鲁棒性差。系统中任何一处的 Bug,都会导致其他的服务,甚至是整个服务的瘫痪。

而微服务与单体之间的区别在于:微服务会将一些共同功能予以拆分,并且在拆分的基础上,每个模块都能维护自己的 DB 与资源。

因此微服务的好处体现在:

  • 每个服务都被独立地开发和部署,迭代速度明显提高。我们不必测试全部,只需测试自己所开发的服务便可。

  • 单个服务所依赖的资源变少,且扩容的速度明显提高。特别是对于一些轻量级的服务,我们能够保证在 30~40 秒内,完成扩容并启动。

  • 通过 RDC 或者相关的调用,移除一些推送服务对于 DB 和 Redis 的直接依赖,从而解决连接数的问题。

  • 将各个服务独立进行部署,从而避免了那些无关联的服务问题所引起的整体宕机,将 Bug 限定在单个模块中。


微服务化难题


微服务的引入也带来一些新的问题:

  • 以前在单体模式中,我们将各个模块杂糅在一起,不必详细考虑。如今采用微服务了,应当如何进行合理的拆分呢?

  • 以前只是本地调用,现在改成了微服务,它们将如何通信?如何考虑网络开销呢?

  • 在服务部署之后,微服务如何去发现问题呢?


难题一:服务拆分


对于微服务的拆分,大家最为关心的问题是拆分的力度。我们采取了如下简单的方式,大家可以在自己的项目中稍做借鉴:

  • 不知如何拆分时,可“一刀两断”地直接拆分成核心和非核心服务。其好处是对服务做到相应的隔离。

    例如:在服务器不够用时,我们只保护核心的服务,而非核心服务不会影响到核心服务。因此是一种根据业务重要性的拆分方式。

  • 服务治理,可以随时对非核心服务予以降级。我们让有限的机器资源去支撑核心的服务。

完成了基础拆分之后,我们就可以根据业务场景对核心服务进行进一步的拆分:

  • 针对 Short Service,我们有一个叫做 Wesync 的私有协议解析。由于我们平时并不会修改其代码,而且该协议的解析具有通用性,因此我们将其拆分出来单独进行部署。

  • 我们将所有与业务相关的服务,如房间和消息等,全部放置在一个叫 Biz Service 的服务中进行部署。这一部分的代码量会非常大,但是代码量并非服务是否拆分的衡量标准,由于其处理复杂业务的压力并不大,因此可以将它们归纳到一起。

  • 将 Push Service 这一推送服务进行拆分,它可以维护与用户之间的长连关系,从而保证消息推送的实时性。另外服务端“推”的方式会比用户端“拉”的方式更具有实时性。

我们对于 Push Service 进行拆分的原因在于:

  • 由于消息的推送量巨大,Push Service 成为了整个服务的瓶颈点。假想在一个 10 万人的房间中,如果 1 秒内有 10 个人群发了消息到客户端上,那么系统会产生 100 万条每秒的推送量。

    同理,如果房间里有 100 万人,则会产生 1000 万的推送量。可见该消息量的量级是非常大的,因此 Push Service 是我们需要频繁扩容的服务。

  • 我们在设计时会希望 Push Service 尽量简单,并让它对资源减少依赖。

我们简单地从业务角度对非核心服务进行了如下拆分:

  • Barrage Service:包括直播回访功能和获取历史消息。

  • Third Service:与三方平台的接入服务。

我们来看 Barrage Service 的拆分原因:由于该服务是一种用来批量获取历史消息的对外暴露式服务,那么批量地获取历史消息必然会带来大量的带宽消耗。


因此,我们需要采取如下的优化方式:

  • 我们采用 Gzip 的压缩技术对接口和返回的数据进行压缩,从而减缓对于带宽的消耗。

  • 充分利用 CPU 和其他硬件的性能。

  • 增加多层缓存的策略,我们可以直接在本地 load,而不必去 DB 或 message cache 里 load,因此减少了与外界的交互。

如上图所示,我们在进行微服务化拆分之前,各个服务被杂乱无章地杂糅在一起,我们不得不一起部署。因此,我们根据各种策略,按照上述方法进行了服务的拆分。


难题二:服务通信


同时我们也从同步与异步的角度,对服务之间的通信进行了拆分:

  • 同步操作:REST 的 API 方式+RPC 方式。

  • 异步操作:主要围绕的是消息队列。

对于核心服务和非核心之间的通信交互,我们进行了场景分析。如上图所示,第三方服务(Third Service)包含两个方面:

  • 蓝线部分表示第三方服务器推送(Push)消息到我们的系统。由于我们希望第三方来的消息能够更为实时地(同步)展现到我们 App 的前端,因此采用的是 RPC 类型的 Push 方式。

  • 红线部分表示我们从微博 App 产生消息,然后对外传递给第三方服务器,让他们以 Pull 的方式来获取消息。由于我们不希望其他的服务、后面的服务、以及非核心服务影响到我们的核心服务功能,因此我们实现的是异步解耦,即采用 Queue 类型的 Pull 方式。


对于 Barrage Service,我们采取的是共享数据库的方式。由于批量获取回放消息是一项大量消息带宽的服务,因此我们共用一个数据库,通过直接从库里 load,以保证系统资源的提供。


难题三:服务发现


对于 Push Service 类型的服务发现,我们弃用了以前挂在 DNS 处让 DNS 做服务发现的方式,而是采用了自己写的 Dispatch Service。


它的运作机制是:

  • 为了在用户加入房间之前建立一个长连的过程,我们发送一个 Dispatch 与 Push Service 建立相应的连接。一旦有消息产生,则通过 Push Service 进行推送。

  • Dispatch Service 可以动态地去根据用户所属的服务器和区域,按照策略就近做出相应的选择。

  • 该策略可以支持 Push Service 的水平扩容。即在扩容之后,我们不再给老的 Push Service 推送流量,也不再返回相应的 IP,而是把全部流量导入新的 Push Service。

而对于 RPC 类型的服务发现,我们采用典型的 SOA 架构,包括:Registry 提供可用服务列表、Server 提供服务、Client 发现并使用服务。因此,我们采用的是 Motan RPC,以快速地响应各种需求并完成发现。


微服务化总结


总结起来,微服务解决了如下四方面的问题:

  • 独立开发测试,加快了迭代的速度。

  • 通过服务拆分,减少了无关联服务之间的影响。

  • 通过减少对资源的依赖,提高了服务扩展的速度。

  • 当然也增加了服务的部署和运维的难度。


既然采用了快速扩容的框架,那么我们就需要运维同学的参与和部署。下面来讨论直播互动的弹性扩缩容策略。


微博直播互动平台的弹性扩缩容


基于 Docker 的混合云架构


由于对微博的使用是一个典型的流量激增场景,因此如果采用盲目购置服务器这一传统的应对方案的话,会造成整体负载饱和度的不均衡。


例如,大家一般在白天和半夜都不会去“刷微博”,服务器和网络的利用率会比较低。只有在晚高峰出现时才会有爆炸式负载的产生。

因此我们采用了快速弹性扩缩容的应对策略,即利用公有云端的各种快速弹性伸缩的资源服务。


但是,由于之前的私有云环境是在我们自己完全掌控的范围内,而如今引入的公有云则带来了环境上的差异性问题。于是我们参考业界的普遍方案,采用了 Docker。

Docker 的网络模型选择一般有 Bridge、Container 和 Host 等实现方式:

  • 我们起初在测试环境中采用了 Docker 的默认设置 Bridge 模式。

  • Docker 在通过 Daemon 启动时会有一个 Docker0 的虚拟以太网桥。

  • Docker 的 Daemon 运用 veth pair 技术进行虚拟化,即在容器内与宿主机之间建立虚拟网卡连接,而在容器外进行相应的消息转发。

  • 存在的问题:由于容器内使用的是虚拟 IP 地址,我们就使用该虚拟 IP 注册了 RPC 服务。但是在启动之后,Client 端却出现无法真正访问到该虚拟IP。

因此我们采用了 Host 模式,即:

  • 在 Host 模式下的同一个 eth0 可以被共用,因此各方能够共享宿主机的网络命名空间。

  • 同时由于它省去了各种路由的开销,因此会比 Bridge 模式更快。

可见,Docker 的优点在于简单轻便,非常适用于微博的应用场景。另外,再加上公有云端的一些资源,共同构成了基于 Docker 的混合云架构,我们称为 DockerDCP。


值得一提的是 DCP 已经开源了,在 GitHub 上有一张 Open DCP 的服务图,大家可以去搜索一下。

DCP 的作用是能够在 10 分钟之内扩容并部署 1000 台机器,以应对诸如“三大节日”的流量猛增。


因此,它每天都会有着 6000 亿次 API 的调用,以及万亿次的 RPC 调用。

为了让直播互动与 DCP 实现相关的自动化运维部署与扩缩容,我们每次都会将消息推送至 SLB(负载均衡),通过 Push Service 的推送服务来实现跨网服务的部署。

要想实现扩容首先要获知设备的来源。DCP 能够帮助我们区分内网和外网(公有云)不同的机器。


例如:内网的三个业务方— A、B、C 都有自己的多台服务器,而我们将它们设置为一个“共享池”。


业务 C 会因为峰值流量而申请池中的 3 台服务器,而在业务空闲时则将资源归还到池中。如此,我们可以自由地在私有云和共有云上实现快速扩容,即为“双云扩”。


自动化扩缩容流程


我们直播互动的自动化扩缩容流程大致分为:

  • 制定监控的指标,即设定达到何种监控指标的阈值,才开始扩容操作。一般是通过压力测试来获取到。

  • 监控指标的采集,包括如何进行采集,以及采集哪些指标。

  • 数据流转到容量决策系统,以决定是否进行扩容。

  • 一系列服务扩容的标准流程,如上图所示。


而缩容的流程与上述扩容流程较为类似,在此就不赘述了。

对于上述提到的监控指标,我们分为两大类:

  • 业务性能指标,不同的业务之间会存在着差异,有的 API 服务能够支撑 1000 个 QPS(Query Per Second),而有的却只能支撑 200 个。因此根据业务的不同,我们所采集和监控的性能指标也不尽相同。

  • 机器性能指标,侧重的是通用化的机器性能指标,包括带宽、PPS、CPU、性能、IOPS 等。只要发现哪一块出现了瓶颈,就应当去尽快扩容。


相对应地,指定监控指标的流程为:对性能系统进行相应的压力测试>发现服务的瓶颈点>对瓶颈点进行分析>制定监控的指标。


如今我们也正在尝试着通过机器学习来实现自动化监控。我们一般会每周或是定时对各种服务进行压力测试,以及时地去发现服务的瓶颈。


由于新引入的机器可能会导致整体性能的不一致,而且随着服务需求和代码量的增多,整体服务的瓶颈点也可能会相应地迁移到其他地方,因此我们通过进化版的压力测试,实现了对瓶颈点的实时把握。

就 Push Service 的指标监控而言,我们在压力测试时所监控的业务性能包括:

  • 用户数据的长连数。因为单台机器可能会撑起几千个用户长连数。

  • 消息推送量。在某些的业务场景中,可能长连数并不多,但是其消息推送量却非常大。因此我们需要从不同的维度实施监控。


而对于机器性能的指标,同样会包括带宽、PPS、CPU、内存、IOPS 等。

就监控指标的采集而言,我们分为两个方面:

  • 业务的性能指标是由各个业务系统负责相应的采集。

  • 机器的性能指标是由运维监控服务执行统一的采集。


弹性扩缩容总结


总结起来,我们在弹性扩缩容方面实现了如下三点:

  • 通过容器技术 Docker 化的服务解决了环境差异性问题。既实现了更快速扩缩容,又让整个虚拟化更为标准。

  • 通过混合云架构 DCP 解决了资源弹性伸缩的问题。

  • 在架构搭建之后,通过自动化扩缩容实现了直播无人看守的场景。


典型案例分享


下面是在实现扩缩容架构之前与之后的两个直播案例的对比。

未实现自动化扩缩容时,我们曾做过对神州飞船回收的直播。由于发生在凌晨 3 点多、且各方人员并未被通知到,因此我们在不清楚会有多少观看流量的情况下采用了全量 Push。


通过次日的事后分析,我们发现:服务的流量已触及瓶颈点,出现了许多的报警,幸好有人员值班,所以并未出现故障。


从维护团队过于疲惫和服务保障的角度出发,我们决定开始着手实施自动化扩缩容。

如上图所示,这是我们在实现了自动化扩缩容后的一次直播。左侧图中蓝线的每一次波谷代表一次扩容操作。


在波谷处于最小长连数时,由于自动扩容出了一堆机器,因此那一时刻并无流量的进入,连接数基本为零。


之后连接数随即迅速上升,服务在 30 分钟之内做了 4 次快速自动扩容。而这些自动化扩容对于运维人员来说都是透明的,只是在扩缩容时会有邮件提醒而已。


作者:温情

编辑:陈峻、陶家龙、孙淑娟

投稿:有投稿、寻求报道意向技术人请联络 editor@51cto.com


温情,新浪微博高级系统研发工程师,从事微博视频和通讯相关系统的研发。当前负责微博直播消息互动系统的研发,推崇高可用,可弹性伸缩,低耦合的微服务架构设计。技术上擅长消息通讯方向,针对系统应对突增流量和高并发方面有丰富的实践经验。

精彩文章推荐:

运维的苦,谁懂?一次“心惊肉跳”的迁库经历!(有彩蛋)

新浪微博如何应对极端峰值下的弹性扩容挑战?

腾讯IT老兵:云端微服务架构下的运维思考

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存