爱奇艺基础数据平台演进

爱奇艺基础数据平台主要是为了统一公司内部的基础数据交换规范,解决不同团队之间ID不统一问题(各团队都有自己独立的ID)、数据定义不统一、数据更新不及时等问题。

随着公司业务发展,除了视频基础数据,还逐步对接了 UGC 视频、全网影视资料、资源位、直播、游戏、文学、电商等公司大部分业务方的基础数据,支持海量业务数据的存储、分发、在线查询、离线分析等服务。

目前已有近百张数据表,总数据量数十亿,数据日增长百万级,日消息量千万级,覆盖公司几十个业务团队。

本文将从爱奇艺数据平台在实际业务中解决HBase高可用、消息服务高可用以及平台整体服务水平扩展能力等方面,叙述爱奇艺的探索和实践。

一、服务能力

爱奇艺基础数据平台主要提供以下能力:

二、整体架构

  • 接入层:提供HTTP、RPC协议访问,提供统一的消息监听和离线扫描SDK
  • 统一管理平台:为对接业务提供便捷的开发工具,表定义查看、数据量、消息量、修改记录,实时查询等功能;同时也包含一站式的字段定义管理系统,可高效便捷地对业务表字段进行调整。
  • 服务治理:对于数据的访问都有精细化的权限控制、流量控制等功能

三、服务流程

首先需要通过统一管理平台,定义好表及其的字段类型结构,随后会发布基于Protobuf的数据定义包,通过这个包来使用分发平台中存储的数据。

生产业务通过ID服务、写入服务将数据写入平台,平台会先将数据入库HBase,后会发送一个更新通知消息,下游业务通过订阅消息获取到具体变更的ID及字段信息,再通过读取服务获取该ID的最新数据。

平台内部也基于消息将本次变动的内容记录于HBase,方便业务排查定位问题,尤其是数据结果不对时,业务可以很快通过这个变更记录查询到是哪个业务在什么时间具体IP地址改动的数据,在实际工作场景中使用频率较高。

对于消息合并服务主要是对写入触发的消息进行相同ID写入的合并,减少发出的消息量,降低下游订阅业务的处理压力。我们针对消息区分了优先级,不同的优先级有不同的合并窗口时间,例如:直播等业务对时效性相对敏感,消息合并则窗口期更短。

四、服务方案

4.1 ID服务的高可用

ID服务使用2个MySQL集群,其中一个MySQL示例只生成单数ID,另外一个MySQL生成双数ID,这样可以做到其中一个MySQL不可用时,另外一个MySQL可以正常提供服务。

4.2 消息分发

平台本身存储了很多不同业务的数据表(例如:视频、直播、图书等),业务可以根据自己的业务需要可以订阅单个或多个不同的业务表消息并做一定规则的过滤,而这种场景属于比较普遍的,所以由平台本身实现比较合理,不用每个业务都做一遍。基于这种背景下我们最初使用了ActiveMQ的VirtualTopic做了大类的区分,但一段时间后我们发现这种方式并不够灵活,无法控制的更精细。为此我们自研了一个ActiveMQ插件来满足相对精细的消息分发控制,整体结构如下图:

通过管理平台我们将规则通过一个特殊的Topic推送到插件,插件本身会监听这个Topic消息,将规则保存在内存中并持久化,插件会在每一条消息发送之前对消息进行一个路由,根据订阅规则匹配发送到1个或多个队列中,原理类似AOP机制。

五、问题及解决方案

5.1 HBase读取性能差的问题

由于本身平台业务场景决定,一次写入对应N次读取,所以在极端场景下,线上偶尔发生过HBase某个RegionServer宕机的情况,进而造成大量的超时情况。

目前我们的主要解决的思路就是加缓存,读多写少就是缓存的主要场景。在数据库选型上,我们在Redis、CouchBase、MongoDB上进行了调研,最终选择了MongoDB,主要的原因是Redis和CouchBase在容量上不满足业务需求。在我们对MongoDB的压测中,性能方面也在可接受范围内。

缓存方案如下:

写入服务每次请求都会生成一个唯一的SessionID,我们将这个ID作为数据的版本号,缓存是否失效使用这个版本号来判断。每次写入时更新缓存以及读取时缓存失效时更新缓存都为异步,主要是为了降低延时,以及避免缓存更新失败导致写入失败。

为了保证缓存和HBase的一致性,每次请求都要读取HBase中存储的版本号,这也对HBase造成了较大的压力,为了解决这个问题,我们将HBase中SessionId设为单独列族,并设置IN_MEMORY => ‘true’来优化。

5.2 HBase可用性

由于全部数据都存储在HBase,所以提升HBase本身的可用性就尤为重要,目前单集群内的单节点故障,HBase本身的机制是可以保证的。但是如果整个集群故障或者集群所在机房出现了故障,如何能保证服务可用?

经过调研目前开源版本HBase还没有相对完善的跨机房部署方案,例如单个机房故障情况下不影响服务正常使用。

我们在结合服务特性的情况下设计的HBase同城主备高可用方案,如下:

Mongo作为写入缓存,保存WAL(WriteAhead Log)Mongo三机房部署,高可用。

Synchronizer服务将WAL写入主HBase,异步服务。

主HBase与备用HBase建立数据同步。

写入流程:Write服务只写入Mongo,由Synchronizer同步服务将数据同步到主HBase。

读取流程:同时读取Mongo和HBase,将WAL最新数据与HBase数据合并得到最新数据返回,读取服务使用Hystrix进行熔断,如果主HBase宕了,Mongo中数据与备用HBase集群仍然可以合并出最新数据返回。

Mongo中WAL设置TTL,时间大于主库到从库的同步延迟。

目前该方案已经在生产环境经历了2次故障,并且故障对读写无影响,上下游业务无感知。

5.3 ActiveMQ碰到的问题

  • 单个慢消费情况下写入消息性能严重下降,直接影响业务方正常生产。集群无法轻易水平扩展,易产生瓶颈,无法通过增加机器方式提高集群的生产和消费能力。
  • 解决方案

对于第一个问题我们的处理这种问题的方式也比较简单粗暴,通过之前开发的插件对每一个队列进行阈值控制,超过一定阈值则不再继续发送消息并通知业务及时消费消息,这对业务本身是有一定影响的,对业务不友好,治标不治本。

在经历了较多次线上问题后,我们决定考虑其他消息中间件,在调研了市面上主流的消息中间件后我们将Kafka和RocketMQ作为备选,在选型的时候我们主要考虑几个因素,可用性、可靠性、水平扩展能力,在这3项中两个中间件都满足需求。

还有一个需要考虑的因素就是消息过滤或分发,因为存量队列都有订阅规则,考虑业务迁移成本问题,这个订阅规则实现还是由平台实现,对比发现RocketMQ支持在服务器端过滤,这个特点吸引了我们,在经过功能验证后,该功能满足需求,最终选型RocketMQ。

部署方案如下:

单集群同城3机房部署,主从部署在不同机房,保证单节点宕机、单机房不可用消息发送和消息消费不受影响,并且消息消费的时效性也不受影响。

我们还开发了基于RocketMQ客户端的SDK,过滤规则都存储在配置服务,由SDK负责将订阅规则推送到FilterServer,业务可以更简单的迁移到RocketMQ,消息过滤在集群端,所以效率更高,可以减少不必要的消息投递到客户端。上线后,彻底解决了之前ActiveMQ的问题。

5.4 扩展读能力

随着业务的不断发展,对接的业务会越来越多,现有读写逻辑相对复杂,读取能力并不能完全达到水平扩展的能力。为了可以更好支撑未来的业务发展,需要进一步提升读取能力,使服务读取能力完全做到水平扩展。在数据库层面,通常有读写分离,也就是正常的读请求操作主库,其余纯读取的请求使用从库来解决这种问题,但是由于业务场景限制,很多业务都是通过订阅以及获取最新数据的方式来同步平台数据经过一定的业务处理、抽取、加工成最终业务想要的数据,所以单纯将所有用户的读取请求都转为读从库显然并不合适,而且这些请求中还有一部分读请求是写入业务的先读后写,但是这种方案也给了我们启发。

由于平台业务较复杂,无法单纯在数据库层面做读写分离,所以就新增一个业务层面的从库,通过业务服务同步主库数据,这样下游业务可以通过从库同步或者单纯读取数据,而且从库可以增加多个。

服务方案如下:

新服务SlaveRead(从库)可通过消息+读取的方式从主库同步最新数据,更新后发出消息,提供给下游服务使用,业务可以以相同的方式在从库同步数据或者单纯的读取请求。

从库与从库之间可以建立同步关系,这样整体同步的压力就不会压在主库上,避免从库多进而增加主库压力,最终实现了平台能力可水平扩展。

六、总结

总体来说爱奇艺基础数据平台通过在技术和服务方案上的不断改进解决业务实际碰到的问题,在RocketMQ、HBase上也积累了一些实战经验。未来将继续探索提高平台整体服务能力、服务稳定性、性能等方面的技术及方案。

猜你喜欢

转载自juejin.im/post/7037384128500596744