写点什么

日访问量百亿级的微博如何做缓存架构设计

  • 2020-05-29
  • 本文字数:5264 字

    阅读完需:约 17 分钟

日访问量百亿级的微博如何做缓存架构设计

本文首发中生代技术社区,获授权转载。


微博日活跃用户 1.6 亿+,每日访问量达百亿级,面对庞大用户群的海量访问,良好架构且不断改进的缓存体系具有非常重要的支撑作用。

4 月 21 日,中生代技术走进盒子科技的现场技术交流活动上,新浪微博技术专家陈波为大家讲解了微博 Cache 架构的设计实践过程。

刷微博吗?跟我们一起听听那些庞大的数据是如何呈现的吧!


陈波:大家好,今天的分享主要有以下内容,首先是微博在运行过程中的数据挑战,然后是 Feed 系统架构,接下来会着重分析 Cache 架构及演进,最后是总结、展望。

数据挑战

Feed 平台系统架构


总共分为五层,最上层是端层,比如 web 端,客户端,大家用的 ios 或安卓的一些客户端,还有一些开放平台,第三方接入的一些接口。下面是平台接入层,不同的池子,主要是为了把好的资源集中调配给重要的核心接口,这样突发流量的时候,有更好的弹性来服务,提高服务稳定性。再下面是平台服务层,主要是 Feed 算法,关系等等。接下来是中间层,通过各种中间介质提供一些服务。最下面一层就是存储层,平台架构大概是这样。


  1. Feed timeline



大家日常刷微博的时候,比如在主站或客户端点一下刷新,最新获得了十到十五条微博,它这个是怎么构建出来的呢?刷新之后,首先会获得用户的关注关系,比如她有一千个关注,会把这一千个 ID 拿到,根据这一千个 UID,拿到每个用户发表的一些微博,同时会获取这个用户的 Inbox,就是她收到的特殊的一些消息,比如分组的一些微博,群的微博,下面她的关注关系,她关注人的微博列表,拿到这一系列微博列表之后进行集合、排序,拿到所需要的那些 ID,再对这些 ID 去取每一条微博 ID 对应的微博内容,如果这些微博是转发过来的,它还有一个原微博,会进一步取原微博内容,通过原微博取用户信息,进一步根据用户的过滤词,对这些微博进行过滤,过滤掉用户不想看到的微博,留下这些微博后,再进一步来看,用户对这些微博有没有收藏、赞,做一些 flag 设置,最后还会对这些微博各种计数,转发、评论、赞数进行组装,最后才把这十几条微博返回给用户的各种端。这样看,用户一次请求,最终得到十几条记录,后端服务器大概要对几百甚至几千条数据进行实时组装,再返回给用户,整个过程对 Cache 体系强度依赖。所以 Cache 架构设计优劣直接会影响到微博体系表现的好坏。


  1. Feed Cache 架构



然后我们看一下 Cache 架构,它主要分为 6 层,首先是 Inbox,主要是分组的一些微博,然后直接对群主的一些微博,Inbox 比较少,主要是推的方式。然后对于 Outbox,每个用户都会发常规的微博,都会在它 Outbox 里面去,根据存的 ID 的数量,实际上分成多个 Cache,普通的大概是 200 多,如果是长的大概是 2000 条。第三组就是一些关系,它的关注、粉丝、用户。第四个就是内容,每一条微博一些内容存在这里。下面就是一些存在性判断,比如微博里面,这条微博有没有赞过,之前有一些明星就说我没有点赞这条微博怎么显示我点赞了,引发一些新闻,这种就是记录,实际上她在某个时候点赞忘记了。最下面还有比较大的一块——计数。一条微博评论转发等计数,对用户来说,她的关注数粉丝数这些数据。

Cache 架构及演进

  1. 简单 KV 数据类型



接下来我们着重讲一些微博 Cache 架构演进过程,最开始微博上线的时候,都是把它作为一个简单的 KV 证人数据类型来存储,我们主要采取哈希分片存储在 MC 池子里,上线几个月之后发现一些问题,有一些节点机器宕机或者其它方面原因,大量的请求会穿透 Cache 层达到 DB 上去,导致整个请求变慢,甚至 DB 僵死。于是我们很快给它改造增加一个 HA 层,这样即便 Main 层出现某些节点宕机情况或者挂掉之后,这些请求会进一步穿透到 HA 层,不会穿透 DB 层,这样的话可以保证在任何情况下,整个系统命中率不会降低,系统服务稳定性比较大提升。对于这种,现在业界用得比较多,然后很多人说我直接用哈希,但这里面也有一些坑,比如我有一个节点,节点 3 它宕机了,Main 把它给摘掉了,节点 3 的一些 QA 分给其他几个节点,这个业务量还不是很大,穿透 DB,DB 可以抗住。如果后面这个节点 3 又恢复了,它又加进来,加进来之后,节点 3 的访问又会回来,如果节点 3 因为网络原因或者机器本身的原因,它又宕机了,一些节点 3 的请求又会分给其他节点,这个时候就会出现问题,之前分散给其他节点写回来的数据已经没有人更新了,如果它没有被剔除掉就会出现混插数据。



微博和微信很大的区别,实际上微博是一个广场型的业务,比如突发事件,某明星找个女朋友,瞬间流量就 30%,突发事件后,大量的请求会出现在某一些节点,会导致这个节点非常热,即便是 MC 也没办法满足这么大的请求量。这时候整个 MC 就会变成瓶颈,导致整个系统变慢,基于这个原因我们引入 L1 层,还是一个 Main 关系池,每一个 L1 大概是 Main 层的 N 分之一,六分之一、八分之一、十分之一这样一个内存量,根据请求量我会增加 4 到 8 个 L1,这样所有的请求来了之后首先会访问 L1,L1 命中的话就会直接访,如果没有命中再来访问 Main-HA 层,这样在一些突发流量的时候,可以由 L1 来抗住大部分热的请求。对微博本身来说,新的数据就会越热,只用增加很少一部分内存就会抗住更大的量。



简单总结一下,通过简单 KV 数据类型的存储,我们实际上以 MC 为主的,层内 HASH 节点不漂移,Miss 穿透到下一层去读取。通过多组 L1 读取性能提升,对峰值、突发流量能够抗住,而且成本会大大降低。对读写策略,采取多写,读的话采用逐层穿透,如果 Miss 的话就进行回写,对存在里面的数据,我们最初采用 Json/xml,12 年之后就直接采用 Protocol| Buffer 格式,对一些比较大的用 QuickL 进行压缩。


  1. 集合类数据



刚才讲到简单的 QA 数据,对于复杂的集合类数据怎么来处理,比如我关注了 2000 人,新增一个人,这就涉及到部分修改。有一种方式把 2000 个 ID 全部拿下来进行修改,这种对带宽、机器压力会更大。还有一些分页获取,我存了 2000 个,只需要取其中的第几页,比如第二页,也就是第十到第二十个,能不能不要全量把所有数据取回去。还有一些资源的联动计算,会计算到我关注的某些人里面 ABC 也关注了用户 D,这种涉及到部分数据的修改、获取,包括计算,对 MC 来说它实际上是不太擅长的。各种关注关系都存在 Redis 里面取,通过 Hash 分布、储存,一组多存的方式来进行读写分离。现在 Redis 的内存大概有 30 个 T,每天都有 2-3 万亿的请求。



在使用 Redis 的过程中实际上还是遇到其他一些问题,比如从关注关系,我关注了 2000 个 UID,有一种方式是全量存储,但微博有大量的用户,有些用户登陆比较少,有些用户特别活跃,这样全部放在内存里面成本开销是比较大的。所以我们就把 Redis 使用改成 Cache,比如只存活跃的用户,如果你最近一段时间没有活跃之后,会把你从 Redis 里面踢掉,再次有访问到你的时候把你加进来。这时候存在一个问题,Redis 工作机制是单线程模式,如果它加某一个 UV,关注 2000 个用户,可能扩展到两万个 UID,两万个 UID 塞回去基本上 Redis 就卡住了,没办法提供其他服务。所以我们扩展一种新的数据结构,两万个 UID 直接开了端,写的时候直接依次把它写到 Redis 里面去,读写的整个效率就会非常高,它的实现是一个 long 型的开放数组,通过 Double Hash 进行寻址。



对 Redis 来说我们进行了一些其他的扩展,之前的一些分享,大家在网上也会看到,把数据放到公共变量里面,整个升级过程,我们测试 1G 的话加载要 10 分钟,10G 大概要十几分钟以上,现在是毫秒级升级。对于 AOF,我们采用滚动的 AOF,每个 AOF 是带一个 ID 的,达到一定的量再滚动到下一个 AOF 里面去。对 RDB 落地的时候,我们会记录构建这个 RDB 时,AOF 文件以及它所在的位置,通过新的 RDB、AOF 扩展模式,实现全增量复制。


  1. 其他数据类型-计数



接下来还有一些其他的数据类型,比如一个计数,实际上计数在每个互联网公司都可能会遇到,对一些中小型的业务来说,实际上 MC 和 Redis 足够用的,但在微博里面计数出现了一些特点,单条 Key 有多条计数,比如一条微博,有转发数、评论数、还有点赞,一个用户有粉丝数、关注数等各种各样的数字,因为是计数,它的 Value size 是比较小的,根据它的各种业务场景,大概就是 2-8 个字节,一般 4 个字节为多,然后每日新增的微博大概十亿条记录,总记录就更可观了,然后一次请求,可能几百条计数要返回去。



  1. 计数器-Counter Service


最初是可以采取 Memcached,但它有个问题,如果计数超过它内容容量的时候,它会导致一些计数的剔除,宕机或重启后计数就没有了。另外可能有很多计数它是为零,那这个时候怎么存,要不要存,存的话就占很多内存。微博每天上十亿的计数,光存 0 都要占大量的内存,如果不存又会导致穿透到 DB 里面去,对服务的可溶性就会存在影响。2010 年之后我们又采用 Redis 访问,随着数据量越来越大之后,发现 Redis 内存有效负荷还是比较低的,它一条 KV 大概需要至少 65 个字节,但实际上我们一个计数需要 8 个字节,然后 Value 大概 4 个字节,实际上有效只有 12 个字节,其他还有四十多个字节都是被浪费掉的,这还只是单个 KV,如果一条 Key 有多个计数的情况下,它就浪费得更多了,比如说四个计数,一个 Key8 个字节,四个计数每个计数是 4 个字节,16 个字节大概需要 26 个字节就行了。但是用 Redis 存大概需要 200 多个字节。后来通过自己研发 Counter Service,内存降至 Redis 的五分之一到十五分之一以下,而且进行冷热分离,热数据存在内存里面,冷数据如果重新变热,就把它放到 LRU 里面去。落地 RDB、AOF,实现全增量复制,通过这种方式,热数据单机可以存百亿级,冷数据可以存千亿级。



整个存储架构大概是这样子,上面是内存,下面是 SSD,在内存里面是预先把它分成 N 个 Table,每个 Table 根据 ID 的指针序列,划出一定范围,任何一个 ID 过来先找到它所在的 Table,如果有直接对它增增减减,有新的计数过来,发现内存不够的时候,就会把一个小的 Table Dump 到 SSD 里面去,留着新的位置放在最上面供新的 ID 来使用。有些人疑问说,如果在某个范围内,我的 ID 本来设的计数是 4 个字节,但是微博特别热,超过了 4 个字节,变成很大的一个计数怎么处理,对于超过限制的把它放在 Aux dict 进行存放,对于落在 SSD 里面的 Table,我们有专门的 IndAux 进行访问,通过 RDB 方式进行复制。


  1. 其他数据类型-存在性判断



然后除了计数的话,微博还有一些业务,一些存在性判断,比如一条微博展现的,有没有点赞、阅读、推荐,如果这个用户已经读过这个微博了,就不要再显示给他,这种有个很大的特点,它检查是否存在,每条记录非常小,比如 Value1 个 bit 就可以了,但总数据量巨大。比如微博每天新发表微博 1 亿左右,读的可能有上百亿、上千亿这种总的数据需要判断,怎么来存储是个很大的问题,而且这里面很多存在性就是 0,还是前面说的,0 要不要存,如果存了,每天就存上千亿的记录,如果不存,那大量的请求最终会穿透 Cache 层到 DB 层,任何 DB 都没有办法抗住那么大的流量。



我们也进行了一些选型,首先直接考虑我们能不能用 Redis,单条 KV65 个字节,一个 KV 可以 8 个字节的话,Value 只有 1 个 bit,这样算下来我每日新增内存有效率是非常低的。第二种我们新开发的 Counter Service,单条 KV Value1 个 bit,我就存 1 个 byt,总共 9 个 byt 就可以了,这样每日新增内存 900G,存的话可能就只能存最新若干天的,存个三天差不多快 3 个 T 了,压力也挺大,但比 Redis 已经好很多。



我们最终方案采用自己开发 Phantom,先采用把共享内存分段分配,最终使用的内存只用 120G 就可以,算法很简单,对每个 Key 可以进行 N 次哈希,如果哈希的某一个位它是 1,如果进行 3 次哈希,三个数字把它设为 1,把 X2 也进行三次哈希,后面来判断 X1 是否存在的时候,进行三次哈希来看,如果都为 1 就认为它是存在的,如果某一个哈希 X3,它的位算出来是 0,那就百分百肯定不存在的。



它的实现架构比较简单,把共享内存预先拆分到不同 Table 里面,在里面进行开方式计算,然后读写,落地的话采用 AOF+RDB 的方式进行处理。整个过程因为放在共享内存里面,进程要升级重启数据也不会丢失。对外访问的时候,建 Redis 协议,它直接扩展新的协议就可以访问我们这个服务了。


  1. 小结



小结一下,到目前为止,关注 Cache 集群内高可用、它的扩展性,包括它的性能,还有一个特别重要就是存储成本,还有一些我们没有关注到,比如 21 运维性如何,微博现在已经有几千差不多上万台服务器等等。


  1. 进一步优化



  1. 服务化



采取的方案首先就是对整个 Cache 进行服务化管理,对配置进行服务化管理,避免频繁重启,另外如果配置发生变更,直接用一个脚本修改一下。




服务化还引入 Cluster Manager,实现对外部的管理,通过一个界面来进行管理,可以进行服务校验。服务治理方面,可以做到扩容、缩容,SLA 也可以得到很好保障。另外对于开发来说,现在就可以屏蔽 Cache 资源。

总结与展望


最后简单总结一下,对于微博 Cache 架构来说,从它数据架构、性能、储存成本、服务化不同方面进行优化增强。


2020-05-29 15:283548

评论

发布
暂无评论
发现更多内容

官宣!DataPipeline2021数据管理与创新大会将于7.29北京重装开启!

DataPipeline数见科技

大数据 数据融合 数据管理

图扑软件受邀核电数字化技术大会,技术创新助力行业革新

一只数据鲸鱼

数据可视化 核电 核电站 数字大会

ARTS 打卡计划-1

语霖

怎么借助Camtasia给电脑游戏录屏

淋雨

视频剪辑 Camtasia 录屏

什么是网络单纯型算法

华为云开发者联盟

算法 线性规划 网络单纯型 计算矩阵

手写QuickSort算法

实力程序员

程序员 算法 成长 C语言

对话系统简介与OPPO小布助手的工程实践

OPPO小布助手

人工智能 对话 智能助手 智能对话

Jira 要停售本地私有化部署的版本了,这对国产项目管理软件是机会吗?

万事ONES

项目管理 Atlassian Jira ONES

Git工作流中常见的三种分支策略:GitFlow、GitHubFlow和GitLabFlow

华为云开发者联盟

git 软件开发 工作流 GitFlow GitHubFlow

极狐 GitLab 初探(上)

极狐GitLab

ci DevOps gitlab CD 敏捷开发管理

【LeetCode】减小和重新排列数组后的最大元素Java题解

Albert

算法 LeetCode 7月日更

图解堆排序,带你彻底了解清楚!

程序员的时光

Java 面试 算法 排序算法 堆排序

带你换个角度理解图卷积网络

华为云开发者联盟

神经网络 卷积神经网络 图神经网络 卷积 图网络

Go 学习笔记之 方法

架构精进之路

Go 语言 7月日更

工卡融合柔性屏,办公效率和信息安全性可能会有质的飞跃?

船医特拉法尔加

EasyRecovery深度扫描以恢复桌面遗失数据的方法

淋雨

EasyRecovery 文件恢复 硬盘数据恢复

IPFS算力挖矿排行榜,IPFS挖矿公司排行榜

IPFS挖矿值得投资吗?IPFS挖矿前景如何?

架构之:REST和RESTful

程序那些事

微服务 软件架构 程序那些事

超视频化到来,你能看见什么?

阿里云CloudImagine

阿里云 计算机视觉 音视频 视频 视频云

万万没想到,低功耗也会烧毁元器件?

不脱发的程序猿

嵌入式 电路设计 低功耗 ADI 稳压器

建立对分布式锁的系统认知-从Redlock开始

刘绍

程序员 分布式 分布式锁 RedLock redisson

Vue进阶(幺捌伍):动态设置系统字体

No Silver Bullet

Vue 7月日更 字体设置

索信达首席科学家张磊:以AI创新技术满足金融场景的“私人定制”

索信达控股

大数据 数字化转型 银行数字化转型

北鲲云超算平台解决生物科学领域困境,探索更多可能性

北鲲云

Serverless 时代下大规模微服务应用运维的最佳实践

阿里巴巴中间件

云计算 Serverless 微服务 云原生 中间件

Vue进阶(幺零六):子组件处理父组件异步值传递给子组件处理

No Silver Bullet

Vue 组件 监听 7月日更

牙膏踩爆!Intel 5nm工艺曝光:直逼IBM 2nm

E科讯

对产品来说,颜值、体验是不是很重要?

石云升

用户体验 职场经验 7月日更

《面试补习》- 你来说说什么是限流?

九灵

Java 面试 分布式 sentinel 限流

数据驱动决策,可视化推动传统电力发展革命史?

一只数据鲸鱼

数据可视化 智慧能源 水力发电 智慧水利

日访问量百亿级的微博如何做缓存架构设计_文化 & 方法_技术琐话_InfoQ精选文章