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

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

据挑战

5e1c34fa500dee375ca808efe48d45c963810e5b

Feed平台系统架构

e1766d9549dbf0c3c91a41f1a210467810b77aa3

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

1. Feed timeline

05b661d7dedb5de58df24e85db516534dd2466be

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

2. Feed Cache架构

979f0a22465ed77b4925966ce5195de69794ed72

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

Cache架构及演进

1. 简单KV数据类型

d7884983d05dff21376569f54e8e5807843dea9c

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

73d870ea1fb3aa8329c6e56eabbe9eb0fc8ede9a

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

9039f4a2731b7a303c3080343cdfa1597b166cd2

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

2. 集合类数据

8c0861acfb85cba31ae97286c3a04cfedaff4bc9

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

a3d3000f5674af9f1f3602ee3ba2e4a00c5766ab

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

1676aab97907d3e67d8f3b5ce764845213f203bf

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

3. 其他数据类型-计数

f4fca15a5e5780f97ce3984e4cf5543e05b8c610

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

90832778543cbd4ea94da3b63fcd86315f60522d

4. 计数器-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,实现全增量复制,通过这种方式,热数据单机可以存百亿级,冷数据可以存千亿级。

627e5dd9d66c3bc4d6a44b9ce555d683be070a4a

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

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

afde0da2ff269dbeab4451da4b671f2ea1fce78a

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

35331c162c8b4e8d2ce77797c312c2cb7ff9dc59

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

e89a81e71038b8dac2e984c1b6f1f0bfc30de095

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

5219f9232fc88b4b751879372f2226ce25497c56

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

6. 小结

8fce6a5008c817f1c397dfd675a4cad0466958e6

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

7. 进一步优化

a2a30768223d44096d2739e806ee3d01edccc542

8. 服务化

316eb3f9e7bc136c475a0e2ceb7a9cfb83a9cabe

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

ce7beffc56fef063e7b2a84d46d88f232bcad43c

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

总结与展望

bd5beeb556f0b738e3ced565e9afb1337a04ce14

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


原文发布时间为:2018-05-7

本文作者:陈波

本文来自云栖社区合作伙伴“中生代技术”,了解相关信息可以关注“中生代技术”。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
16天前
|
Prometheus 监控 Kubernetes
Prometheus 在微服务架构中的应用
【8月更文第29天】随着微服务架构的普及,监控和跟踪各个服务的状态变得尤为重要。Prometheus 是一个开源的监控系统和时间序列数据库,非常适合用于微服务架构中的监控。本文将详细介绍 Prometheus 如何支持微服务架构下的监控需求,包括服务发现、服务间的监控指标收集以及如何配置 Prometheus 来适应这些需求。
42 0
|
16天前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
39 0
|
1天前
|
存储 搜索推荐 数据库
MarkLogic在微服务架构中的应用:提供服务间通信和数据共享的机制
随着微服务架构的发展,服务间通信和数据共享成为关键挑战。本文介绍MarkLogic数据库在微服务架构中的应用,阐述其多模型支持、索引搜索、事务处理及高可用性等优势,以及如何利用MarkLogic实现数据共享、服务间通信、事件驱动架构和数据分析,提升系统的可伸缩性和可靠性。
10 5
|
1天前
|
机器学习/深度学习 测试技术 数据处理
KAN专家混合模型在高性能时间序列预测中的应用:RMoK模型架构探析与Python代码实验
Kolmogorov-Arnold网络(KAN)作为一种多层感知器(MLP)的替代方案,为深度学习领域带来新可能。尽管初期测试显示KAN在时间序列预测中的表现不佳,近期提出的可逆KAN混合模型(RMoK)显著提升了其性能。RMoK结合了Wav-KAN、JacobiKAN和TaylorKAN等多种专家层,通过门控网络动态选择最适合的专家层,从而灵活应对各种时间序列模式。实验结果显示,RMoK在多个数据集上表现出色,尤其是在长期预测任务中。未来研究将进一步探索RMoK在不同领域的应用潜力及其与其他先进技术的结合。
13 4
|
1天前
|
运维 Cloud Native Devops
云原生架构的崛起与实践云原生架构是一种通过容器化、微服务和DevOps等技术手段,帮助应用系统实现敏捷部署、弹性扩展和高效运维的技术理念。本文将探讨云原生的概念、核心技术以及其在企业中的应用实践,揭示云原生如何成为现代软件开发和运营的主流方式。##
云原生架构是现代IT领域的一场革命,它依托于容器化、微服务和DevOps等核心技术,旨在解决传统架构在应对复杂业务需求时的不足。通过采用云原生方法,企业可以实现敏捷部署、弹性扩展和高效运维,从而大幅提升开发效率和系统可靠性。本文详细阐述了云原生的核心概念、主要技术和实际应用案例,并探讨了企业在实施云原生过程中的挑战与解决方案。无论是正在转型的传统企业,还是寻求创新的互联网企业,云原生都提供了一条实现高效能、高灵活性和高可靠性的技术路径。 ##
9 3
|
1天前
|
缓存 JavaScript 中间件
优化Express.js应用程序性能:缓存策略、请求压缩和路由匹配
在开发Express.js应用时,采用合理的缓存策略、请求压缩及优化路由匹配可大幅提升性能。本文介绍如何利用`express.static`实现缓存、`compression`中间件压缩响应数据,并通过精确匹配、模块化路由及参数化路由提高路由处理效率,从而打造高效应用。
13 3
|
3天前
|
缓存 运维 NoSQL
二级缓存架构极致提升系统性能
本文详细阐述了如何通过二级缓存架构设计提升高并发下的系统性能。
|
8天前
|
传感器 Cloud Native 物联网
Micronaut在物联网中的应用探索:轻盈架构赋能万物互联新时代
【9月更文挑战第6天】Micronaut是一个现代、轻量级的Java框架,以其高效、易用及对云原生环境的支持,在物联网开发中展现出独特优势。它通过AOT编译技术优化应用,减少内存消耗,适合资源受限的设备。Micronaut支持反应式编程和HTTP/2,提升并发处理能力和网络传输效率。本文通过一个温度传感器数据收集服务的例子,展示了如何利用Micronaut简化物联网应用开发,使其成为该领域的理想选择。
26 3
|
11天前
|
存储 缓存 前端开发
缓存技术在软件开发中的应用与优化策略
缓存技术在软件开发中的应用与优化策略
|
18天前
|
Kubernetes Cloud Native 开发者
云原生技术在现代IT架构中的应用与挑战
【8月更文挑战第27天】 随着云计算的飞速发展,云原生技术已经成为推动企业数字化转型的重要力量。本文将深入探讨云原生技术的核心概念、优势以及在实际应用中遇到的挑战,并通过具体代码示例展示如何利用云原生技术优化IT架构。

热门文章

最新文章