【导语】 微博拥有超过 3.76 亿月活用户,是当前社会热点事件传播的主要平台。而热点事件往往具有不可预测性和突发性,较短时间内可能带来流量的翻倍增长,甚至更大。如何快速应对突发流量的冲击,确保线上服务的稳定性,对于提供全微博数据托管的服务部门数据库团队来说既是机遇又是挑战。本文尝试从一线 DBA 的视角管窥微博热点事件背后的数据库运维应对之道。
正是图 1 这条微博动态,让一个平常的国庆假期变得不同寻常,微博刚一发出就引爆网络,它将明星 CP 动态推向了舆论的高潮,并霸占微博热搜榜好几天,也正是因为这个突发的流量,致使流量过大一度引发微博服务器瘫痪,而成为吃瓜群众热议的话题。微博拥有超过 3.76 亿月活用户,是当前社会热点事件传播的最主要平台,其中包括但不限制于大型活动(如里约奥运会、十九大等)、春晚、明星动态(如王宝强离婚事件、女排夺冠、乔任梁去世、白百合出轨、TFBOYS 生日、鹿晗关晓彤 CP 等)。而热点事件往往具有不可预见性和突发性,并且伴随着极短时间内流量的数倍增长,甚至更多,有时持续时间较长。如何快速应对突发流量的冲击,确保线上服务的稳定性,是一个挑战巨大且颇有意义的事情。
从上述系列流量图中,我们可以看出这两起事件对微博的核心服务如 Feed、评论、赞、话题等带来的压力都是成倍的,创造了历史的新高点。仔细分析不难发现,这类事件基本可以分为:发生期、发酵期、暴涨期和缓解期,只是由于天时地利人和等各种因素的综合影响,各个时期的持续时间、流量变化不尽相同,对服务的压力也有所差异,在暴涨期所表现出的不亚于天猫双 11,以及 12306 抢票的盛况。总体上反映出这类热点事件的特点为:
微博研发中心数据库部门主要负责全微博平台后端资源的托管和运维,主要涉及的后端资源服务包括 MySQL、Memcached、Redis、HBase、Memcacheq、Kafka、Pika、PostgreSQL 等。为了应对峰值流量并保证用户的良好体验,资源层会面临哪些挑战?又将如何解决呢?
微博目前用户基数庞大,DAU 和 MAU 均为数亿。从整个技术体系来看,微博核心总体分为前端和后端平台,端上主要是 PC 端、移动端、开放平台以及企业开放平台。后端平台主要是 Java 编写的各种接口层、服务层、中间件层及存储层。除此之外,微博搜索、推荐、广告、大数据平台也是非常核心的产品。从业务角度看,整体架构图如下:
这里以微博平台的业务为例,微博平台的服务部署架构如图 6。
就平台业务而言,后端的资源每天承载着千亿次的写请求,万亿次的读请求,如 MySQL 在流量极端峰值的情况下承载着几十万,甚至上百万的 QPS,面对这么大体量的业务压力,对于后端资源的稳定性和 RTT 也提出了极高的要求,对业务部门承诺的 SLA 必须达到 3 个 9,部分业务甚至 4 个 9,且平均响应时间不能高于 50ms。因此面临的挑战不言而喻。为了达到这一目标,需要有一个完善的,稳定可靠的,健壮的数据库运维体系来提供支撑和管理全平台 25 个分支下 600 多个产品线的业务运维。
数据库运维体系架构整个运维体系由三大部分组成:UI 层、应用服务层、基础服务层。UI 层是各种管理平台的 Dashboard 和功能入口以及提供基础的 Restful API 等;应用服务层是数据库运维过程中所用到的各种功能模块集合,为 RD 和 DBA 提供最全面的数据库管理功能,这部分是整个运维体系中的核心组成部分,也是整个体系中最庞大和最复杂的部分;基础服务层是整个运维体系中最基础的依赖层,为数据库运维体系提供底层的基础服务,这里需要特别提出是,热点事件应对过程中的弹性扩缩容就是依赖这一层的 DCP 系统和 Docker 镜像,结合私有云和公有云提供的 ECS 虚拟机来完成。
MySQL 高可用架构MHA(Master High Availability)目前在 MySQL 高可用方面是一个相对成熟的解决方案,它由日本 DeNA 公司 Youshimaton(现就职于 Facebook 公司)开发,是一套优秀的作为 MySQL 高可用性环境下故障切换和主从提升的高可用软件。在 MySQL 故障切换过程中,MHA 能做到在 0~30 秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA 能在最大程度上保证数据的一致性,以达到真正意义上的高可用。微博 MySQL 高可用也基于 MHA 方案实现,这里 MHA 是在 Youshimaton 开发的 MHA 思想上自研而成。整个架构由三部分组成:监控、MHA 和 MySQL 集群(主从实例)组成。为了实现 MySQL 的高可用和灾备:
这一模块是基于现有微博的配置服务中心,它主要是管理静态配置和动态命名服务的一个远程服务,能够在配置发生变更的时候实时通知监听的 Config Client。
Proxy这一模块是作为独立的应用对外提供代理服务,用来接收来自业务端的请求,并基于路由规则转发到后端的 Cache 资源,它本身是无状态的节点。它包含了如下部分:
Proxy 启动后会去从 Config Service 加载后端 Cache 资源的配置列表进行初始化,并接收 Config Service 配置变更的实时通知。
Cluster Manager这一模块是作为实际数据缓存的管理模块,通过多层结构来满足服务的高可用。 首先解释一下各层的作用:
其中,Master 是主缓存节点,Slave 是备份节点,当 Master 缓存失效或实例挂掉后,数据还能够从 Slave 节点获取,避免穿透到后端 DB 资源,L1 主要用来抗住热点的访问,它的容量一般比 Master 要小,平台的业务通常部署多组 L1,方便进行水平扩容以支撑更高的吞吐。
Client 客户端这一模块主要是提供给业务开发方使用的 Client(SDK 包),对外屏蔽掉了所有细节,只提供了最简单的 get/set/delete 等协议接口,从而简化了业务开发方的使用。
应用启动时,Client 基于 namespace 从 Config Service 中获取相应的 Proxy 节点列表,并建立与后端 Proxy 的连接。正常一个协议处理,比如 set 命令,Client 会基于负载均衡策略挑选当前最小负载的 Proxy 节点,发起 set 请求,并接收 Proxy 的响应返回给业务调用端。
Client 会识别 Config Service 推送的 Proxy 节点变更的情况重建 Proxy 连接列表,同时 Client 端也会做一些容灾,在 Proxy 节点出现问题的时候,对 Proxy 进行摘除,并定期探测是否恢复。
目前微博平台部分业务子系统的 Cache 服务已经迁移到了 CacheService 之上,它在实际运行过程中也取得了良好的性能表现,目前整个集群在线上每天支撑着超过千万级的 QPS,平均响应耗时低于 1ms。
它本身具备了以下特性。
所有的数据写入请求,Cache Service 会把数据双写到 HA 的节点,这样,在 Master 缓存失效或挂掉的时候,会从 Slave 读取数据,从而防止节点 fail 的时候给后端资源(DB 等)带来过大的压力。
Cache Service Proxy 节点本身是无状态的,在 Proxy 集群存在性能问题的时候,能够简单地通过增减节点来伸缩容。而对于后端的 Cache 资源,通过增减 L1 层的 Cache 资源组,来分摊对于 Master 的请求压力。这样多数热点数据的请求都会落 L1 层,而 L1 层可以方便的通过增减 Cache 资源组来进行伸缩容。
通过整合内部的 Config Service 系统,能够在秒级别做到资源扩容、节点替换等相关的运维变更。目前这块主要结合 DCP 系统,利用 Docker 镜像以及虚拟机资源完成弹性的扩缩容。
微博系统会进行多机房部署,跨机房的服务器网络时延和丢包率要远高于同机房,比如微博广州机房到北京机房需要 40ms 以上的时延。Cache Service 进行了跨机房部署,对于 Cache 的查询请求会采用就近访问原则,对于 Cache 的更新请求支持多机房的同步更新。
异步消息队列更新机制互联网应用有个显著特点,就是读多写少。针对读多有很多成熟的解决方案,比如可以通过 Cache 来缓存热数据以降低数据库的压力等方式来解决。而对于写多的情况,由于数据库本身的写入性能瓶颈,相对较难解决。我们知道春晚发祝福,或娱乐热点的时候发评论、点赞等场景下,会有大量的并发写入,我们为了解决这类问题,引入了 "异步消息队列更新机制"。从微博平台的服务部署架构图中也可以看到,当用户发微博或评论等时候,不是直接去更新缓存和 DB,而是先写入到 MCQ 消息队列中,再通过队列机处理程序读取消息队列中的消息,再写入到数据库和缓存中。那么,如何保证消息队列的读写性能,以及如何保证队列机处理程序的性能,是系统的关键所在。
众所周知,2016 年中旬之前微博的最大长度不超过 140 个字,现在放开了这个限制。但是大部分用户的实际发表的微博长度都比较小,为了提高写入队列的速度,我们针对不同长度的微博消息,写入不同大小的消息队列,比如以 512 字节为分界线,大于 512 字节的写入长队列,小于 512 字节的写入短队列,其中短队列的单机写入性能要远远高于长队列。实际在线结果表明,短队列的 QPS 在万 / s 级别,长队列的 QPS 在千 / s 级别,而 95% 的微博消息长度均小于 512 字节。这种优化,大大提高了微博消息的写入和读取性能。
为了验证队列机处理程序的极限处理能力,我们在业务低峰时期,对线上队列机进行了实际的压测,具体方法如下:通过开关控制,使队列机处理程序停止读取消息,从而堵塞消息队列,使堆积的消息分别达到 10 万,20 万,30 万,60 万,100 万,然后再打开开关,使队列机重新开始处理消息,整个过程类似于大坝蓄水,然后开闸泄洪,可想而知,瞬间涌来的消息对队列机将产生极大的压力。通过分析日志,来查找队列机处理程序最慢的地方,也就是瓶颈所在。通过两次实际的压测模拟,出乎意料的是,我们发现系统在极限压力下,首先达到瓶颈的并非是数据库写入,而是缓存更新。因此,为了提高极限压力下,队列机处理程序的吞吐量,我们对一部分缓存更新进行了优化。
另一方面,通过压测,也可以帮助我们发现队列的承载能力和处理能力,有效帮助我们找到队列的短板,方便我们进行优化和扩容,通常情况下,核心队列都会保证 3 到 4 倍的冗余。
这是 DBA 常采用的手段。扩容通常分为水平扩容和垂直扩容:
垂直扩容,可以理解为保证架构不变的前提下,通过增加硬件投入即可实现的扩容方式。对于缓存而言,扩容是很容易、很方便、很快捷实现的,而对于 DB 来说,通常体积比较大,扩容起来很不方便,周期时间很长,尤其是应对流量的情况下,通常优先扩容缓存来抗热点,减少对后端 DB 的访问。而对于 DB 的扩容,我们会不定期对核心 DB 资源进行评估,随着业务的不断发展,当超过一定的水位线时,为了保证 DB 的可用性,需要保留 3 到 4 倍的冗余,当不满足时,便需要扩容。因为热点总是不期而来,往往扩容缓存并没有那么及时,这时候保证足够的冗余,有助于预防 DB 被打挂,保障服务的稳定性,就显得很重要。
水平扩容,可以理解为通过调整架构,扩展资源的承载能力。比如:对于 Redis 服务来说,原本一个业务是 4 主 4 从,在业务量并发不高的情况下,完全满足业务需求,但是随着业务量上涨后,Redis 的写性能变差,响应时间变慢,此时,我们需要对该业务进行水平扩展,将业务架构调整为 8 主 8 从或者更多,从而部署更多的主库还抗写入量。
降级对于一个高可用的服务,很重要的一个设计就是降级开关,其目的是抛弃非重要服务,保障主要或核心业务能够正常提供服务。对于后端资源,包括 MySQL、MC、Redis、队列等而言,并不能保证时时刻刻都是可用的,一旦出现问题,降级策略就能派上用场。那么如何降级?降级的标准是什么?这里拿话题页举个例子。
任何一个系统,都包含核心系统和非核心系统。在出现异常的情况下,弃车保帅,只保障核心系统的稳定性也是可以接受的。对于上面的这个话题来说,话题首页是核心业务,话题二级页面是非核心业务。当话题 DB 资源出现瓶颈时,我们可以优先让 RD 同学降级二级页,释放 DB 的连接资源并减少查询请求,保障话题首页能够有更多的资源可用。
同样,对于同一个业务,也要区分核心逻辑和非核心逻辑。对于话题首页的这个资源,微博时尚发布的 Feed 流属于核心逻辑,而头像下方的时尚美妆榜 TOP1 榜单则属于非核心逻辑。毫无疑问,为了减负,非核心逻辑必要时是可以被降级的。
在微博复杂的架构体系中,各种依赖和接口调用,错综复杂。就话题首页资源来看,"阅读 11.8 亿 讨论 36 万 粉丝 1.9 万" 的计数就是调用微博平台的接口获取 Redis 计数器资源的结果。对于热点事件而言,尤其是对于这种全平台都可能会调用的资源来说,当时的访问量是极大的,响应时间肯定会比平时差,如果话题首页因为要取该资源而超时,导致话题 "白页",那必然不是想要的结果,所以需要抛弃这类高耦合逻辑。对于后端资源而言,当某个资源,比如某台 MCQ 服务器宕机,严重影响 V4 启动,拖累整体动态扩容时,业务方就会考虑暂时 503 这台服务器实例资源,保证整个扩容流程的顺畅和速度。同时业务方也会根据请求的后端资源使用特点的不同,有时在降级前会使用一些 fast fail 或 fast over 的策略来保障服务的稳定性和可用性。
切流量对于一个大型系统而言,这也是一个常用手段,通常可用从 DNS、LVS、H5、HaProxy(四层负载均衡)、Nginx(七层负载均衡)等层面上来实现流量的切换。常见的使用场景有:
从字面意思可以看出,限流的目的就是为了防止恶意请求(如刷站)、恶意攻击,或者防止流量超出系统的峰值。处理的原则就是限制流量穿透到后端的资源,保障资源的可用性。举个例子:比如图 13 的 QPS 异常现象,请求流量超出平时晚高峰的好几倍,严重影响了服务的稳定性和后端 DB 的承载能力,为了保障 DB 资源的可用性,经排查是属于通过 user_timeline 接口的恶意刷站行为,此时,我们要做的就是封杀该接口,限制流量穿透到 DB 层,封杀后,效果很明显。
限流(封杀)后的效果如图 14 所示:
除了封杀接口之外,其他一些可借鉴的思路是:
这个策略是针对 DB 资源设计的,就是当请求超过 DB 的承载范围时,启动的一个自我保护机制。比如,在某个明星出轨的时候,有用户恶意刷评论列表,导致评论的数据库从库资源出现大量延迟,严重影响服务的性能,为了防止 DB 资源因过载而不可用,通过和业务方沟通后,果断启动了过载保护机制,从而保证了评论服务的稳定性。图 15 就是应用了过载保护机制后的效果。开源的工具可以使用 percona 公司开源的 pt-kill 工具实现。
压测的目的是找出资源或者链路上的瓶颈点或者验证技术选型的可行性或测试优化的效果,这也是一个常态化的过程,一般分为两种场景:
这个通常是配合业务方共同完成,如上面的异步消息队列更新机制中提到的一样,通过切流量、堵塞队列、压测队列的承载能力和队列机极限处理能力,找出资源的瓶颈点,方便作为进行优化和扩容的依据,有时候也作为设置水位线的一个标准。
有时候为了引入新的技术,需要进行性能 / 压力测试,或者有时候为了验证某项优化的效果,然而压测很难模拟真实的线上环境场景,此时在保证服务稳定可控的情况下,通过 TCPCopy 的方式引入线上的真实流量加以验证。
水位线 & 预警机制这可以快速有效地帮助了解线上业务的变化趋势,以及快速定位业务的实时运行情况,如果超过水位线,会触发预警机制,通过这种手段可以辅助 DBA 做出相应的策略判断,比如:扩容、限流、过载保护等。
当前很多情况下,尤其是当有热点的时候,DBA 都是通过申请资源,部署服务,然后上线,完成一系列的手动操作完成扩容,这显然会浪费很多的人力,效率也不高。未来,可以通过设置水位线,增加对热 key 的预判,以及带宽的阈值,当超过警戒位后,通过开发的自动化工具来智能地进行 MC 的动态扩缩容。
故障自愈系统故障无处不在,因为任何硬件都有使用寿命,都有折损率。现在每天都可能有服务器宕机、服务器异常重启、服务器系统 readonly、内存损坏、RAID 卡损坏、CPU 损坏、主板损坏、磁盘损坏、电源模块损坏等,各种硬件的故障都有可能会导致服务异常,那么如何应对这些故障,保证服务器的高可用,是一个普遍面临的问题。微博有很多服务器都过保,它们就像一个 "雷" 一样无处不在,但是这些故障也是有轻有重,有紧急有非紧急的,根据具体部署业务的不同而不同。服务器数量之多,每天报警几千条,全凭人工处理很难覆盖全面,这就急需结合监控和故障采集样本开发一套自愈系统,完成基础的,甚至大部分类型的故障修复工作,比如自动报修。
移动办公移动互联网时代,移动办公已经不是什么时髦的事情,很多的工作都能直接在手机或智能终端设备上完成。虽然目前有部分工作,比如工单审核、登录服务器执行命令等都可以在手机上实现,但是还有很多工作没有办法完成,比如查看监控、手动扩容等复杂操作,需要后续的完善。
服务自助化服务自助化,需要依赖完善的基础服务和健壮稳定的自动化平台,以及充足的冗余资源和完备的标准服务流程和规范才能完成。这是一个长期的过程,目前微博已经能够在 SQL 审核、Online DDL 方面提供自助式服务,但是还有很多方面需要完善和提高,比如:资源申请、资源下线、资源扩容等。
来源: http://blog.csdn.net/dev_csdn/article/details/78949830