爱奇艺社交泡泡后台微服务实施 ── 6千万日活+双周发版的系统如何快速迭代

2018阿里云全部产品优惠券(好东东,强烈推荐)
领取地址 https://promotion.aliyun.com/ntms/yunparter/invite.html

推荐:学习 Spring-Cloud - 写一个微服务

[学习 Spring-Cloud - 写一个微服务]

【编者的话】微服务不是万能的,也不要为了微服务而微服务。能够支持业务在各个阶段的成长是第一位的,就好比泡泡平台从最开始定位的的社交(聊天、发帖),到目前的明星、兴趣社区和运营推广,再到未来的不同业务的基础平台,每一步的业务成长都是促使我们技术演进的最强动力。

对于一个6千万日活的系统,任何架构改变都务求谨慎,本文讲述了爱奇艺泡泡系统微服务实施过程中的思考和经验。

泡泡业务系统介绍

爱奇艺移动App的右下角常驻着一个泡泡的图标,点击即可进入一个精彩的世界。泡泡定位的发展战略是融合内容、播出、宣发、粉丝四大平台为一体,目前更通用的产品的说法是娱乐粉丝社区,在此平台上可以聚拢相似爱好的用户群体,并组织内容。熟悉微博和贴吧等产品的读者相信对此一定不会陌生。该系统目前高峰DAU超过6千万,算是一个体量比较大的平台。在一些大咖位的明星来线上做互动(明星翻牌)活动时,单单一条Feed(可以理解成是一条微博)内容一小时内评论楼数超过60万。系统整体QPS超过100K/s。

每一个社区在泡泡产品形态中就是一个”圈子”,圈子的类型有多种(明星圈、视频圈、兴趣圈、游戏圈、漫画圈、图书圈、等等),不同类型的圈子也有不同的玩法,比如明星圈可以做积分任务、为喜欢的爱豆打榜、发起对应明星的应援活动(众筹)、追踪明星的行程信息等等;视频圈里面可以直接看视频等。每个圈子下面汇集了用户自发、编辑运营的相关内容(我们叫做Feed流),以及相关圈子的周边信息(比如明星圈可以查明星资料,视频圈可以查同一个导演的相关视频等)。

技术上与一般的页面服务不同的是,泡泡系统的读写操作同样重要,对于明星圈甚至是写操作更重要,用户非常在意自己做任务的积分、打榜贡献是否正确的加上了。所以写操作要有一定程度的事务支持。

实施微服务前的系统情况

由于历史原因,最初时候,泡泡后台、运营后台和feed系统,这三个系统代码在一个工程当中,共用底层的存储资源,只是在代码层面通过不同的model来区分。每当有一个模块有改动的时候,很容易相互干扰。

随着用户量的增长和业务的发展,这种结构远远不能满足需求。在这种矛盾的情况下,我们进行了拆分,由单一项目拆分成了泡泡后台、运营后台、feed后台三个项目。 同时,将存储也剥离开来。大大降低代码改动和上线的风险。这种方式,也带来了其他的问题,资源占用集中(db、cb、redis等),运维成本增加。

运营层面,最开始的重点是明星圈子,泡泡平台的定位即是明星业务,打造粉丝社区,这是一个单一的平台,业务也相对单一。后续,运营层面增加了视频、游戏、漫画等业务。未来,将向着平台化的方向发展。

综合这些问题,加上分析和调研的结果,我们打算引入微服务。

微服务化的原则

首先说拆分的目的:永远都是为业务服务,实现公司业务的不断增长和演进是我们的终极目标。

泡泡后台从2016年上半年上线初始的几百万日活,到现在的接近7千万日活,后台系统的技术栈也需要不断地迭代。形象点的例子是最初的泡泡系统是一棵小树,不断长大到一定程度就需要不断的剪枝,栽培更多的小树。在当初几百万日活的情况下完全没有微服务化的必要,当时一切都是以功能快速上线为目标的;但是经过一年多的发展,以现在的视角来看,之前的架构慢慢显得臃肿,开发效率逐渐降低,维护的成本却在不断攀高,微服务化能够给我们带来未来一段时间更好的发展。

另外一个方面是泡泡后台的定位也从最初的明星圈+视频圈,演进到了未来平台级的系统,可以更好地支持细分的各类兴趣圈以及公司其他的垂直业务。所以泡泡要做面向业务到面向平台+业务的转变。

从技术角度讲,泡泡系统的微服务化主要为了达到几个目的:

  1. 系统和人员可以分而治之,获得更高的开发和维护效率(DevOps)
  2. 持续优化(ownership)、更专业的人员做更专业的事情

接下来讲泡泡后台微服务化的原则:

松耦合和高内聚

松耦合很好理解,服务彼此独立,修改一个服务不要影响其使用方。比如用户相关的功能全部由用户微服务实现、圈子相关的功能全部由圈子微服务实现,彼此之间如需交互则通过通用(最好还是统一的)接口来实现。内聚性还包括数据的内聚,用户的信息只存在用户微服务中,其他服务需要的时候通过接口去获取。

那么问题来了,使用方是否需要缓存用户信息呢?——我们的答案是,唯一需要缓存的场景是使用方不信任服务提供方的服务级别(万一服务宕机了怎么办,万一网络故障了怎么办)。这种情况在公司内部普遍存在,如果大家自由发挥那么到最后一定变成了调用链上每一步都加一道缓存,对资源是一定的浪费,且容易引起数据不一致的情况。

那么我们的原则是:关键数据(获取不到会较大范围地影响用户体验或数据异常)和使用量大的数据(使用方不缓存的话就会对服务提供方带来超高的QPS)需要缓存,其余的数据尽量通过微服务层面直接获取。松耦合和内聚性还有另外一点,就是服务之间大家建立好契约(往往是接口规范),只要接口不改内部实现随你去折腾吧,结果就是接口层面的更改大家会更加谨慎,减少了很多联调成本。

考虑性能

服务化相比组件化,由进程内部的调用改为了服务间的调用,引入了网络IO等额外的性能损耗,所以拆分服务的过程中通常会遇到的一个难题就是如何保证性能。

目前市面上实现起来比较灵活的方案是以RESTful风格的的接口来界定各个子系统的切面,但是这种基于http的协议牺牲了性能来达到这个灵活,每次请求的http握手无形中浪费了大量的系统资源。与之对比的是各种RPC方案,采用长连接+压缩过的序列化数据传输,可以一定程度地减少网络开销,也就提高了性能。对比一下单机http服务和RPC服务的压测结果就可以看出来,两种协议至少在能够支撑的QPS上相差了10倍以上。但是RPC的缺点也比较明显,对客户端是有侵入的,增加了系统耦合性,且排除故障时也相对困难。

最终鉴于泡泡系统较高的QPS要求,我们在多数对内服务的微服务系统上使用了RPC框架、少量QPS要求不高的系统保留http服务,对App端统一由mixer层实现http服务。以用户RPC服务为例,高峰时段整体QPS为40K,单台QPS保持在2500左右(压测单台QPS可支撑10K+)。接口响应时间在5ms以下。同http服务对比RPC性能优势明显。

使用合理的技术栈

这里拿RPC框架选型为例:市面上存在大量的RPC框架(Solar、gRPC、Dubbo、甚至Spring Cloud等),但是我们最终选择了爱奇艺移动后端团队基于thrift实现的RPCHUB框架。

原因就是这个框架在移动后台线上运行超过1年的时间,泡泡期望可以少踩些坑,并且在出现问题时可以直接找到最熟悉框架代码的开发人员来支持。而使用开源框架的话,虽然团队中的过半数成员在之前其他公司的工作经历中都或多或少地使用过一两种开源框架,但是统计了一下真正读过框架源码的同学并不多,即使读过源代码万一遇到冷门点的问题也很可能抓瞎。

两相权衡我们最后还是选择了内部使用经验丰富的框架RPCHUB,即使这样我们在使用的过程中也遇到了一些问题,花了些时间来解决(框架升级了2个小版本来解决这些bug)。

围绕业务功能组织微服务拆分

我们的理论是只要拆分合理,各自为战是可以的。目前泡泡系统已经做到了谁构建、谁运行,我们发现大家的主人翁意识(内部我们通常说ownership)更强了,上线前开发自己把关更严格了,线上问题处理也更加积极。但架构以及服务实现的编程语言,还是尽量做到统一,主要也是为了少踩坑。

我们团队目前都是Java背景,即使需要实现一个多线程的微服务听起来用Go比Java简单很多,但是把大部门第一个Go语言的服务推上线,需要做的相关测试工作听听就头大。

那么多小的服务算微服务呢?——没有拆分的动力就可以了!说明没有痛点了,也就没有拆分的必要了,目前我们根据自身的业务情况选择了以业务实体和业务功能两种拆分方式。

按业务实体拆分

推荐:微服务(Microservices)

[最近好像谈论微服务的人比较多,也开始学习一下,但是都有E文,看起来半懂不懂的。 Martinfowler的《微服务》,也算是入门必读了。有人翻译过,但是只有一半。还是自己

在泡泡业务上,我们把实体划分为圈子、用户、事件、资源位等。在拆分前,泡泡API和运营后台会共同维护这些实体,经常会出现数据不一致的情况,所以我们决定对这些实体进行拆分,按照实体类型拆分出多个微服务,每个微服务只负责该实体的功能,并在泡泡API和运营后台及其他使用方中接入对应的微服务。

当然,拆分是一个缓慢的过程,在拆分时,我们优先选择了业务较为单一的事件实体进行了拆分,为该实体申请了独立的存储资源,并采用统一的RPCHUP框架完成了事件微服务的拆分。之后针对事件实体相关的新需求,在不需要修改RPC接口协议的情况下,我们只需要在事件微服务中增加功能即可。

同时,别的业务系统若需要获取某种实体信息,直接接入对应的微服务即可。基于此思路,我们后续又完成了圈子、用户、资源位等其它单独实体的拆分。

按业务功能拆分

在业务功能上,我们从泡泡API中优先选择了签到和圈友关系两个模块作为微服务,用于服务各种类型的圈子。以签到模块为例,在拆分前,经常会有用户报障说签到不成功,在定位问题时发现,缓存的写操作偶尔会出现操作成功,但是数据未更新的情况,怀疑是缓存QPS太高导致(当然还有其他问题,包括数据库压力大、表结构设计缺陷、缓存结构的设计缺陷等)。刚开始我们打算通过拆资源的方式解决,但又鉴于签到功能的相对独立以及泡泡后期的平台化,我们最终决定申请新资源并将签到功能拆分为一个独立的服务。

从实施效果来看,拆分出的微服务功能还是非常稳定的,未再出现过类似报障。

去中心化地治理服务

包括服务之间的逻辑关系和通讯协议。

目前主流的思想并未把SOA算作真正的微服务,因为SOA方案往往有个ESB汇总了所有数据流和业务流,即将单进程大服务的复杂度转移到了ESB上,问题并没有解决。而我们真正期望做的事情可以参考第一条原则,即松耦合和高内聚,所以我们期望的是,各个微服务之间自己维护自己的调用关系,按需交互。(顺便说一下,笔者之前就做了很长时间的消息中间件和ESB产品的开发,对这个领域可以说是充满了感情,目前在银行业中他们其实还算是主流方案)

另外,这里提一个经典的误区:有些人认为把一个单进程系统改造为若干微服务,可以简单地实现为把进程内的方法调用改为RPC调用。这就大错特错了,这样会导致微服务之间产生繁琐的通信(单进程间的方法调用是没问题的,所以大家很容易忽略其中的问题,可是RPC调用要考虑网络开销),这个也是必须要避免的,微服务一定要考虑通信机制,好在微服务一旦实现了松耦合和高内聚,这个问题就可以很大程度避免。

小心地设计容错机制

服务之间的调用不比进程内的方法调用,出错的概率大大加强了,所以我们考虑了几个容错的机制:异步化、熔断机制、写操作的数据最终一致性。

方法调用基本都是同步的,进程内的调用没问题,但是服务间很多同步调用就需要使用异步化了,否则同步等待的时间过长服务可能会被拖死,我们大量地使用了MQ机制。

万一网络故障了10分钟怎么办?这时熔断机制(我们使用了Hystrix熔断框架)就大显身手了。

以前同一个进程写两张表很容易做到事务一致性,现在两张表拆到了两个不同的服务中了怎么办?考虑到成本和真实的需要,我们使用了重试和最终一致的方案。

受限于CAP原则(感兴趣的读者可以在网上自行学习相关内容),一致性、可用性和分区容忍性已经多次被证明只能满足其中的两个,对于泡泡这样的大并发分布式后台系统来说分区容忍性是基本需求,那么只能在一致性和可用性之间取一个权衡。我们分析了自己的业务,很多敏感的数据其实不见得需要强一致性,比如用户的积分数据,HBase和缓存之间就不需要强一致性,保证最终一致就可以了,所以我们取的是可用性舍的是强一致性。

举个例子,用户在获得了新积分后我们在接口层面就更新了缓存以保证用户尽快能看到变更,但是写HBase和ElasticSearch等持久化存储是一个相对较慢的过程,没必要与写缓存捆绑在一个全局事务中(而且也不支持)。我们采用了异步(通过MQ)的方式写这些持久化存储。但是写HBase和ElasticSearch有可能因为各种原因失败,用户的缓存失效后看到的数据不对就尴尬了。所以需要保证缓存和持久化存储里面的数据最终一致。

TCC(每个业务方实现try、confirm、cancel接口,出现事务不一致时由中心协调器来统一调度事务恢复,感兴趣的读者可以自行学习一下相关概念)是一种比较常见的最终一致性方案,但是改造的成本不小,涉及到的各个子系统均需要改造。针对积分的场景,泡泡使用了简单的重试的方式,即写缓存失败的话接口就返回失败,任何数据都保持在写接口被调用前的状态,由业务来重新提交(其实深究起来这一步也可能有不一致的情况,比如写缓存超时,但是其实是成功了,这个需要业务能够有一定的去重能力,在这里咱们先不追求一步到位);如果写持久化存储失败了,我们引入了一个任务表,写失败了就记录在这个任务表中,由后台task定期地重试写入,直到超过了一定的时间或次数还一直写不成功,那么我们把这样的脏数据报出来单独处理(上线以来一直没遇到过)。

我们可以有很长的时间窗口去异步写入以及重试写入持久化的数据,这个方案的改造成本非常小,没有任何阻力就在团队内部推广到位了(当然,这个方案也不是100%完善的,比如写持久化存储和写任务表有可能都失败了,那么缓存中和持久化存储中的数据还是会不一样。但是这种场景出现的概率实在太小了,所以我们没有再花额外的精力去处理)。

推进相关工具的演进

第一个微服务上线,监控报警、持续交付(部署、自动化测试)、数据统计等等工具还属于空白。在移动后端运维团队(其实也只有两个人)的帮助下这些工具完成了从东拼西凑到整齐划一,保证了后续微服务的快速上线。未来也会考虑在技术积累的前提下输出方案。

不断地迭代演进

微服务拆分始终是个动态的过程,今天看似合理的架构或系统边界,随着业务的增长和需求的迭代,半年以后可能就跟不上业务了,那么到时候再继续拆分工作和演进。泡泡系统一路走来,也是边实现需求边思考架构演进的状态。

目前拆分的方案

目前我们的拆分方案

  1. 为了兼容历史版本App,原来的服务还继续保留(泡泡1.0老功能)。
  2. 新的服务以及比较独立成块的功能(比如圈子服务、用户服务、群聊服务等)都相继拆分成了独立的服务(对App服务,http接口)。
  3. 公用的组件由组件化改为了公共微服务(RPC接口,比如圈子实体、用户实体、用户加圈关系、事件话题、资源位、签到等)。
  4. 之前拆分的运营后台还是基于JSP的,使用VUE做了前后分离,前后端开发人员可以更专注自己擅长的工作。
  5. 此外,泡泡后台组还维护了一些全公司级别的服务,比如评论、点赞、投票、积分(用户成长体系)等,这些也都重新规划了系统边界,该拆的拆该合的合,使每一个系统的维护更加容易。

实施中遇到的问题

拆分过程中也遇到了一些问题,比如是由上向下拆还是由下向上拆。通常的做法是为了避免重复劳动,肯定是先拆最下层(DB)再逐步拓展到上层。但是泡泡后台组日常还在不断迭代新功能的开发(也是我们团队最重点的工作),爱奇艺移动端统一采用双周发版后开发同学的时间被严重碎片化,导致投入到微服务化的精力和资源都非常有限,整个进度进展较慢。好在老板们都支持,大家做起来也比较有动力,我们花了比预计长一些的时间,收获了比较稳定的结果,也算是功德圆满。

此外还有一个问题,是RPC服务上线初期,由于QPS较大,我们发现系统在运行了一段时间后就会出现不稳定的情况,RPC client请求RPC server抛大量异常导致触发了熔断。经过对Client端连接池和Server端内存优化,修复了Client调用失败及服务端Full GC等导致服务不稳定问题。

未来改进的方向

做到目前大家对系统的拆分算是比较满意了,当然后续还有一些欠缺的东西可以慢慢补全,比如调用链路分析和全链路监控(压测)系统,如过有了这个我们对每一次新功能的迭代和问题的排查都会更加得心应手。此外还有更好地集成各阶段测试和监控的CI系统、全局事务一致性系统、全局任务统一调度系统等等。相信这些随着未来我们在演进过程中不断细化具体的需求点,以及在公司各个内部团队的大力支持下,都会慢慢实现的。

结束语

微服务不是万能的,也不要为了微服务而微服务。

能够支持业务在各个阶段的成长是第一位的,就好比泡泡平台从最开始定位的社交(聊天、发帖),到目前的明星、兴趣社区和运营推广,再到未来的不同业务的基础平台,每一步的业务成长都是促使我们技术演进的最强动力。此外,也希望我们的团队不断成长,锻炼出我们过得硬的技术,在未来可以应对公司不断变化的业务挑战。服务根据业务领域,而不是技术进行建模。

作者:

  • wdn626,爱奇艺移动客户端泡泡后台组开发经理,自2016年底开始接手泡泡后台及几个互动后台系统的研发工作,对中间件、后台系统优化、以及复杂系统架构梳理等具有一定的经验。
  • orange_zjl,爱奇艺移动客户端泡泡后台组开发人员,自2015年加入泡泡团队,负责泡泡后台版本需求开发、后台系统优化以及微服务拆分等工作。
  • thomas,爱奇艺移动客户端泡泡后台组开发人员,自2016年加入泡泡团队,负责泡泡后台需求、微服务拆分以及系统优化工作。
  • rayzhao,爱奇艺移动客户端泡泡后台组开发人员,自2016年加入泡泡团队,负责泡泡后台需求开发以及微服务拆分工作。

原文链接: https://mp.weixin.qq.com/s/670j5YlCeWvhkkAP6TCBaA

推荐:spring cloud之简单介绍

[以下是来自官方的一篇简单介绍: Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configurat

相关推荐