BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

微博关系服务与Redis的故事

| 作者 唐福林 关注 1 他的粉丝 发布于 2014年4月17日. 估计阅读时间: 11 分钟 | AICon 关注机器学习、计算机视觉、NLP、自动驾驶等20+AI热点技术和最新落地成功案例。

新浪微博的工程师们曾经在多个公开场合都讲到过,微博平台当前在使用并维护着可能是世界上最大的Redis集群,其中最大的一个业务,单个业务使用了超过 10T 的内存,这里说的就是微博关系服务。

风起

2009年微博刚刚上线的时候,微博关系服务使用的是最传统的 Memcache+Mysql 的方案。Mysql 按 uid hash 进行了分库分表,表结构非常简单:

tid fromuid touid addTime
自增id 关系主体 关系客体 加关注时间

业务方存在两种查询:

  • 查询用户的关注列表:select touid from table where fromuid=?order by addTime desc
  • 查询用户的粉丝列表:select fromuid from table where touid=?order by addTime desc

两种查询的业务需求与分库分表的架构设计存在矛盾,最终导致了冗余存储:以 fromuid 为hash key存一份,以 touid 为hash key再存一份。memcache key 为 fromuid.suffix ,使用不同的 suffix 来区分是关注列表还是粉丝列表,cache value 则为 PHP Serialize 后的 Array。后来为了优化性能,将 value 换成了自己拼装的 byte 数组。

云涌

2011年微博进行平台化改造过程中,业务提出了新的需求:在核心接口中增加了“判断两个用户的关系”的步骤,并增加了“双向关注”的概念。因此两个用户的关系存在四种状态:关注,粉丝,双向关注和无任何关系。为了高效的实现这个需求,平台引入了 Redis 来存储关系。平台使用 Redis 的 hash 来存储关系:key 依然是 uid.suffix,关注列表,粉丝列表及双向关注列表各自有一个不同的 suffix,value 是一个hash,field 是 touid,value 是 addTime。order by addTime 的功能则由 Service 内部 sort 实现。部分大V的粉丝列表可能很长,与产品人员的沟通协商后,将存储限定为“最新的5000个粉丝列表”。

微博关系存储Redis结构

需求实现:

  • 查询用户关注列表:hgetAll uid.following ,then sort
  • 查询用户粉丝列表:hgetAll uid.follower,then sort
  • 查询用户双向关注列表:hgetAll uid.bifollow,then sort
  • 判断两个用户关系:hget uidA.following uidB && hget uidB.following uidA

后来又增加了几个更复杂的需求:“我与他的共同关注列表”、“我关注的人里谁关注了他”等等,就不展开来讲了。

平台在刚引入 Redis 的一段时间里踩了不少坑,举几个例子:

  • 运维工具和流程从零开始做,运维成熟的速度赶不上业务增长的速度:在还没来得及安排性能调优的工作,fd 已经达到默认配置的上限了,最后我们只能趁凌晨业务低峰期重启 Redis 集群,以便设置新的 ulimit 参数
  • 平台最开始使用的 Redis 版本是 2.0,因为 Redis 代码足够简单,从引入到微博起,我们就开始对其进行了定制化开发,从主从复制,到写磁盘限速,再到内存管理,都进行了定制。导致的结果是,有一段时间,微博的线上存在超过5种不同的 Redis 修改版,对于运维,bugfix,升级都带来了巨大的麻烦。后来由田风军 @果爸果爸 为内部 Redis 版本提供了不停机升级功能后,才慢慢好转。
  • 平台有一个业务曾经使用了非默认 db ,后来费了好大力气去做迁移
  • 平台还有一个业务需要定期对数据进行 flush db ,以腾出空间存储最新数据。为了避免在 flush db 阶段影响线上业务,我们从 client 到 server 都做了大量的修改。
  • 平台每年长假前都会做一些线上业务排查,和故障模拟(2013年甚至做了一个名叫 Touchstone 的容灾压测系统)。2011年十一假前,我们用 iptables 将 Redis 端口的所有包都 drop 掉,结果 client 端等了 120 秒才返回。于是我们在放假前熬夜加班给 client 添加超时检测功能,但真正上线还是等到了假期回来后。

破茧

对于微博关系服务,最大的挑战还是容量和访问量的快速增长,这给我们的 Redis 方案带来了不少的麻烦:

第一个碰到的麻烦是 Redis 的 hgetAll 在 hash size 较大的场景下慢请求比例较高。我们调整了 hash-max-zip-size,节约了1/3的内存,但对业务整体性能的提升有限。最后,我们不得不在 Redis 前面又挡了一层 memcache,用来抗 hgetAll 读的问题。

第二个麻烦是新上的需求:“我关注的人里谁关注了他”,由于用户的粉丝列表可能不全,在这种情况下就不能用关注列表与粉丝列表求交集的方式来计算结果,只能降级到需求的字面描述步骤:取我的关注人列表,然后逐个判断这些人里谁关注了他。client 端分批并行发起请求,还好 Redis 的单个关系判断非常快。

第三个麻烦,也是最大的麻烦,就是容量增长的问题了。最初的设计方案,按 uid hash 成 16 个端口,每台 64G 内存的机器上部署 2 个端口,每个业务 IDC 机房部署一套。后来,每台机器上就只部署一个端口了。再后来,128G 内存的机器还没有进入公司采购目录,64G 内存就即将 OOM 了,所以我们不得不做了一次端口扩容:16端口拆64端口,依然是每台 64G 内存机器上部署 2 个端口。再后来,又只部署一个端口。再后来,升级到 128G 内存机器。再后来,128G 机器上出现 OOM 了!现在怎么办?

化蝶

为了从根本上解决容量的问题,我们开始寻找一种本质的解决方案。最初选择引入 Redis 作为一个 storage,是因为用户关系判断功能请求的数据热点不是很集中,长尾效果明显,cache miss 可能会影响核心接口性能,而保证一个可接受的 cache 命中率,耗费的内存与 storage 差别不大。但微博经过了 3 年的演化,最初作为选择依据的那些假设前提,数据指标都已经发生了变化:随着用户基数的增大,冷用户的绝对数量也在增大;Redis 作为存储,为了数据可靠性必须开启 rdb 和 aof,而这会导致业务只能使用一半的机器内存;Redis hash 存储效率太低,特别是与内部极度优化过的 RedisCounter 对比。种种因素加在一起,最终确定下来的方向就是:将 Redis 在这里的 storage 角色降低为 cache 角色。

前面提到的微博关系服务当前的业务场景,可以归纳为两类:一类是取列表,一类是判断元素在集合中是否存在,而且是批量的。即使是 Redis 作为 storage 的时代,取列表都要依赖前面的 memcache 帮忙抗,那么作为 cache 方案,取列表就全部由 memcache 代劳了。批量判断元素在集合中是否存在,redis hash 依然是最佳的数据结构,但存在两个问题:cache miss 的时候,从 db 中获取数据后,set cache 性能太差:对于那些关注了 3000 人的微博会员们,set cache 偶尔耗时可达到 10ms 左右,这对于单线程的 Redis 来说是致命的,意味着这 10ms 内,这个端口无法提供任何其它的服务。另一个问题是 Redis hash 的内存使用效率太低,对于目标的 cache 命中率来说,需要的 cache 容量还是太大。于是,我们又祭出 “Redis定制化”的法宝:将 redis hash 替换成一个“固定长度开放hash寻址数组”,在 Redis 看来就是一个 byte 数组,set cache 只需要一次 redis set。通过精心选择的 hash 算法及数组填充率,能做到批量判断元素是否存在的性能与原生的 redis hash 相当。

通过微博关系服务 Redis storage 的 cache 化改造,我们将这里的 Redis 内存占用降低了一个数量级。它可能会失去“最大的单个业务Redis集群”的头衔,但我们比以前更有成就感,更快乐了。

作者简介

唐福林(@唐福林),微博技术委员会成员,微博平台资深架 构师,致力于高性能高可用互联网服务开发,及高效率团队建设。从2010年开始深度参与微博平台的建设,目前工作重心为微博服务在无线环境下 的端到端全链路优化。业余时间他是一个一岁女孩的爸爸,最擅长以45°凉开水冲泡奶粉。


感谢张龙对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

redis变为cache之后storge是啥 by Qiu Tao

redis变为cache之后storge是啥,hbase?

Re: redis变为cache之后storge是啥 by 吴 勇智

同问

化蝶? by 江南 白衣

从storge降级到cache,旁观者看不出化蝶的快感啊。

Re: 化蝶? by 悟 王

我也很愚钝,看不出来快感.

123 by 张章 鸥翔鱼游

好文章。总是支持一下的

101 by 张章 鸥翔鱼游

我也很愚钝,看不出来快感

木有鱼丸 by n yuxiao

wow 每次都没有干货 这些东西需要保密吗 还是作者没时间整理

SSDB? by Wang Stero

据说新浪内部在测试SSDB?一个用LevelDB做存储,前段提供Redis网络协议的开源DB?

优化 by Zhao Tong

取列表按时间排序为啥不用sortedset,插入socore用时间戳,自然排好序,取出直接range出来。
这样“我与他的共同关注列表”的需求直接在服务器上执行innerjoin就出来了。
另外redis作为storage,事物如何保证,关注的时候一定要更新成功??

Re: redis变为cache之后storge是啥 by Tang Fulin

storage 一直是 mysql

Re: 化蝶? by Tang Fulin

storage 出现问题的时候,服务是会受很大影响的;cache 出现问题的时候,只要不是大批量的出问题,服务可以无感知的。快感就在这里:资源不需要 ha 了,可以随便挂,没有关系

Re: 优化 by Tang Fulin

sorted set 对比 hash 有大约 30% 的额外内存开销。如果你的内存占用已经是 T 级别,30% 意味着额外 n 台机器的时候,那么这就不是一个很好的选项了

innerjoin 也一样,微博的原则是:sql 不允许 join

关于事务,微博几乎不用事务,所有的写入操作通过重试保证尽可能的成功。ps,所以有一定的概率数据不一致。

Re: redis变为cache之后storge是啥 by eric eric

那原来redis作为storage和mysql什么关系?

Redis数据持久化 by liu ming

首先文章写的很好,思路也很清晰。里边说到了redis数据的持久化,即:rdb和aof,然后rdb是内存要多占一半,aof是写速度的问题,都导致数据访问效率的地下。现在在想能不能把redis当做不落地的数据,然后另辟蹊径对数据进行持久化的落盘。就像电脑上内存和硬盘的原理一样

Re: Redis数据持久化 by liu ming

做恢复的时候,从硬盘上读取数据再到redis的内存中

关于cache 更新的疑问 by xia xiang

redis只是cache的话,当有关注时更新完数据库是否要使缓存失效?失效后如果查询时去数据库并放入cache中的话,如果关注的人特别多的情况下,一次去取几十万的id 直接set cache吗?

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

16 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT