当前位置: 首页 > 科技观察

微博应对日访问量百亿级的缓存架构设计

时间:2023-03-18 12:44:35 科技观察

微博有一个缓存架构设计,针对日访问百亿的缓存系统起到了非常重要的支撑作用。在这篇文章中,新浪微博技术达人陈波老师将为大家详细讲解那些庞大的数据是如何呈现的。本文提要1.微博运营过程中的数据挑战2.Feed平台系统架构3.缓存架构与演进4.总结与展望数据挑战Feed平台系统架构Feed平台系统架构分为五层,最上层是终端层,比如webclient,client,大家用的一些IOS或者Android客户端,还有一些开放平台和一些第三方接入的接口;下一层是平台接入层,不同的pool主要是为了把好的资源集中分配给重要的核心接口,这样在遇到突发流量的时候,会有更好的弹性去服务,提高服务的稳定性。接下来是平台服务层,主要是feed算法,关系等等。接下来是中间层,通过各种中介提供一些服务。底层是存储层。1.FeedTimeline当你每天浏览微博时,比如在主站或客户端点击刷新,最后会得到十到十五条微博。这是如何构建的?刷新后会优先获取用户的关注关系。比如他有一千个粉丝,他就得到这千个ID,然后根据这千个UID得到每个用户发布的一些微博。同时会获取用户的收件箱,也就是他收到过的一些特殊消息,比如一些群里的微博,群里的微博,关注关系,关注者的微博列表。拿到这一系列的微博列表后,收集整理,得到自己需要的id,然后为这些id取每个微博id对应的微博内容。如果转发了这些微博,它还有一条原微博,还要进一步抓取原微博的内容。通过原始微博获取用户信息,根据用户的过滤词进一步过滤这些微博,过滤掉用户不想看的微博。根据以上步骤留下的微博,我们会进一步查看用户是否对这些微博进行了收藏和点赞,设置了一些flag,同时也会对这些微博的各种统计、转发、评论、点赞等进行汇总,才做***将十几条微博返回给用户的各个终端。从这个角度来看,对于用户一次请求得到的十几条记录,后台服务器可能会实时汇集成百上千条记录,然后返回给用户。整个过程取决于Cache系统的强弱,所以Cache架构设计的好坏。它将直接影响微博系统的性能。2.FeedCache架构接下来我们看一下Cache的架构,主要分为六层。首先最底层是收件箱,主要是一些微博的分组,然后直接到群主的一些微博。收件箱比较小,主要是推送的形式。然后第二层的发件箱,每个用户发一条普通的微博,就进入它的发件箱。按照存储ID的多少,其实是分了多个缓存,一般是200多个,如果长的话大概是2000个左右。第三层是一些关系,它的关注度,粉丝,用户。第四层是内容,每条微博的一些内容都存放在这里。第五层是一些存在性的判断,比如我有没有点赞过某个微博。之前有明星说我不喜欢这条微博,怎么就说明我喜欢了,引起了一些新闻。这是唱片,她在某个时候确实喜欢它,但可能忘记了。最下面是比较大的一层——计数,每条微博的评论数和转发数,还有用户关注数和粉丝数。缓存架构及演进1.简单KV数据类型接下来我们重点介绍微博缓存架构的演进过程。微博刚上线的时候,我们把它存储成一个简单的KV数据类型。我们主要使用哈希分片存储在MC池中。上线几个月后,我们发现了一些问题:部分节点机器宕机或者其他原因,大量的请求会穿透Cache层到达DB,导致整个请求变慢,甚至数据库冻结。所以我们很快做了改造,加了一个HA层,这样即使主层的一些节点宕机或者挂了,这些请求也会进一步渗透到HA层,而不是DB层。这样可以保证在任何情况下,整个系统的攻击率都不会降低,大大提高了系统服务的稳定性。对于这种方式,现在业界用的比较多,很多人说我直接用hash,其实也有一些坑。比如我有一个节点,节点3宕机了,main会去掉,节点3的一些QA会分发到其他几个节点。但是如果这个节点3恢复了,再添加之后,节点3的访问又回来了。之后由于网络原因或者机器原因,节点3又会宕机,节点3的一些请求会分发到其他节点。这时候,就会出现问题。写回其他节点的数据还没有更新。如果不删除,就会有混杂的数据。其实微博是一个方型的生意。比如遇到紧急情况,明星找女朋友,瞬间流量会是30%。紧急情况之后,一些节点上会出现大量的请求,这会导致这些节点非常热。如此大量的请求,即便是MC也无法满足。这时候MC就会成为瓶颈,导致整个系统变慢。为此,我们引入了L1层,它仍然是一个主要的关系池。每个L1大约是主层的N分之一,内存的六分之一、八分之一、十分之一。根据请求我会增加4到8个L1,这样所有的请求来的时候都会先访问L1。如果没有***,L1就直接访问。如果没有***,就会访问Main-HA层,这样在一些突发流量中,L1可以抵挡大部分的热点请求。对于微博本身来说,新的数据越热,只要加少量的内存,就能抵御更大的量。简单总结一下,通过简单的KV数据类型的存储,我们其实是关注MC,层中的HASH节点不漂移,Miss穿透到下一层读取。通过提高多组L1的读取性能,可以承受峰值和突发流量,成本会大大降低。读写策略,采用多写,读采用逐层穿透,对Miss进行回写。其中存储的数据,我们最初使用的是Json/xml,之后2012年,我们直接采用了ProtocolBuffer格式,用QuickL压缩了一些比较大的数据。2.采集数据刚刚提到简单的问答数据,复杂的采集数据如何处理?比如我关注了2000人,加一个新人涉及到一些修改。一种方法是把2000个ID全部拿下来修改,但是这样会给带宽和机器带来很大的压力。还有一些逐页收购。我存了2000,只需要取前几页,比如第二页,也就是第十到二十页。您不能完整检索所有数据吗?还有一些资源的联动计算,会计算出我关注的一些人中,ABC也关注了用户D。这种修改和获取一些数据,包括计算,其实对MC来说不是很好。各种注意力关系存储在Redis中,通过Hash分布、存储、一组多存储方式实现读写分离。现在Redis有30T左右的内存,每天有2-3万亿次请求。在使用Redis的过程中,其实还遇到了一些其他的问题。比如从下面的关系中,我已经关注了2000个UID。一种方法是将它们完整存储。但是,微博拥有大量的用户。有些用户登录较少,有些用户非常活跃。将它们全部存储在内存中会花费很多。的。所以我们把Redis的使用改成了Cache。例如,只存储活跃用户。如果你有一段时间没有活跃,你会被踢出Redis,等有下次访问的时候再加入。这时候就有问题了,因为Redis的工作机制是单线程模式。如果加上某个UV,关注2000个用户,可能会扩展到20000个UID。20000个UID往回插,Redis基本就卡死了。提供其他服务的方式。所以我们扩展一个新的数据结构,20000个UID直接起步,写的时候直接一个一个写到Redis里面,整体读写效率会很高。它的实现是一个long类型的开放数组,由DoubleHash寻址。我们对Redis做了一些其他的扩展。您可能已经在互联网上看到了我们之前的一些分享。将数据放入公共变量。整个升级过程,如果我们测试1G,加载需要10分钟,10G大约需要十几分钟,现在是几毫秒。对于AOF,我们采用滚动AOF,每个AOF都有一个ID,达到一定数量后,滚动到下一个AOF。在实现RDB的时候,我们会在构建这个RDB的时候记录下AOF文件以及它所在的位置,通过新的RDB和AOF的扩展方式实现全增量复制。3.其他数据类型——计数接下来还有一些其他的数据类型,比如计数,其实每个互联网公司都会遇到。对于一些中小企业来说,MC和Redis其实已经足够用了,但是在微博的统计上有一些特点:单个Key有多个统计,比如一条微博,有转发、评论、点赞;用户有粉丝、追随者等数字。因为是计数,所以它的Value大小比较小。根据其各种业务场景,大概2-8个字节,一般都在4个字节以上,每天新增微博10亿左右。记录,总的记录就更可观了,然后一个请求可能有几百个计数要返回。4.CounterCounterService最初可以使用Memcached,但是有一个问题。如果计数超过其内容容量,则部分计数将被清除,关机或重启后计数将消失。另外,可能有很多计数是0,那么这个时候怎么存,存不存,会占用很多内存。微博每天有十亿条统计,0的存储会占用大量内存。如果不存储,会渗透到DB中,影响服务的可溶性。2010年后,我们再次使用Redis访问。随着数据量的增加,我们发现Redis的内存负载还是比较低的。一个KV至少需要65个字节,但实际上我们需要8个字来进行计数。Section,然后是Value,大约4个字节,所以有效的只有12个字节,浪费了40多个字节。这只是一个单一的KV。如果一个Key有多个计数,它会被浪费得更多。比如4个count,一个key是8个字节,4个count中每个count是4个字节。16个字节需要大约26个字节,但是用Redis保存大约需要200个字节。后来通过自己开发的CounterService,将内存降低到不到Redis的五分之一到十五分之一,冷热数据分离。热数据存储在内存中。如果冷数据再次变热,则将其存储在LRU中的Go中。实现RDB和AOF实现全增量复制。这样单机就可以存储百亿级的热数据,可以存储千亿级的冷数据。整个存储架构大致如上图所示,上面是内存,下面是SSD。在内存中,预先分成N张表,每张表按照ID的指针顺序划分到一定的范围内。当任何一个ID过来的时候,先找到它所在的Table,如果里面有直接增加或者减少,就来一个新的count,当发现内存不够的时候,会dump一个小的TableDump进去SSD,并将保留一个新的。该位置放在最上面,供新ID使用。有人质疑,如果在一定范围内,我的ID原本设置的计数是4字节,微博火爆超过了4字节,变成很大的计数怎么办?对于那些超过限制的,我们将它们存储在Auxdict中。对于属于SSD的Tables,我们有专门的IndAux可以通过RDB访问和复制它们。5.其他数据类型——存在性判断微博除了统计之外,还有一些服务和一些存在性判断。比如一条微博显示有没有点赞、阅读、推荐,如果用户已经看过这条微博,就不要再给他看。这种有一个很大的特点,就是检查是否存在,每条记录很小,比如1bit的Value就够了,但是数据总量很大。例如,微博每天发布约1亿条新微博,需要判断的总数据可能有数百亿、上千亿。怎么存是个大问题,里面很多存在都是0,前面说了,要存0吗?如果保存,每天将保存数千亿条记录;如果不保存,大量的请求最终会穿透Cache层到达DB层,没有哪个DB能抗得住这么大的流量。我们也做了一些选择。首先我们直接考虑能不能用Redis。单个KV为65字节。如果一个KV可以是8个字节,那么Value就只有1位。这样,每天添加新内存的效率就很低了。我们新开发的第二种计数器服务,单个KVValue1位,我就存1个字节,一共9个字节就够了。这样每天新增900G内存。如果存放的话,可能只能存放几天。三天后,差不多3T了。压力挺大的,但是比redis好太多了。我们最终的方案是自己开发Phantom,先分段分配共享内存,最后只使用120G内存。该算法非常简单。每个Key可以被哈希N次。如果hash的某位是1,那么再hash3次,三个数都置1。再hashX23次,之后再判断X1是否存在。从三个哈希来看,如果为1,则认为存在。如果某个hashX3,它的bit计算为0,那么***肯定不存在。它的实现架构比较简单。共享内存预先拆分成不同的表,在里面进行open计算,然后读写。实现的时候是AOF+RDB处理的。因为整个进程都放在共享内存中,即使进程升级重启,数据也不会丢失。对外访问时,构建Redis协议,可以直接扩展新的协议来访问我们的服务。6.总结总结一下,到目前为止,我们关注的是Cache集群中组件的高可用、可扩展性和高性能。另一个重要的事情是存储成本。还有一些事情我们没有关注,比如如何运维,微博现在有几千、几万台服务器。7.进一步优化8.Servicing第一个解决方案是对整个缓存和配置实现基于服务的管理,避免频繁重启。另外如果配置有变化,直接用脚本修改即可。Servitization也引入了ClusterManager实现对外管理,通过接口进行管理,进行服务校验。在服务管理方面,可以实现扩容和缩容,SLA也可以得到很好的保障。另外,为了开发,现在可以屏蔽Cache资源。总结与展望***简单总结一下,对于微博Cache架构,我们从数据架构、性能、存储成本、服务等不同方面进行了优化和提升。欢迎对此有研究或疑问的同仁留言讨论。