日请求量过亿,谈陌陌的Feed服务优化之路详解架构师

陌陌上季度的MAU为6980万,Feed作为主要的社交业务,从2013年上线到现在,日请求量超过亿,总数据量超过百亿。下面是Feed系统的整体架构图:

日请求量过亿,谈陌陌的Feed服务优化之路详解架构师

  • 资源层主要使用Redis、MongoDB、HBase等NoSQL类型数据库。

  • 存储层是内部RPC服务,根据业务场景和存储特性,组合各种数据库资源。

  • 业务层调用存储层读写数据,实现产品逻辑,直接面向用户使用。

内容存储(Feed Content)

首先介绍Feed内容海量数据存储优化。

Feed内容是json格式,对schema没有做严格限制,可以根据业务灵活扩展,下面是一个基础的结构:

{“content”:”今天天⽓气不错啊”,”id”:”88888888″,”time”:1460198781,”owner”:”25927525″}

最初使用MongoDB做持久化存储,MongoDB本身对JSON⽀支持非常好,这样从前端API的输出格式到底层数据存储都是统一的,非常简洁。MongoDB另外一个优势是不需要预先定义结构,灵活的增减字段,支持复杂查询,非常适合创业阶段 快速迭代 开发。

Feed内容存储是整个系统访问量最大的部分,QPS达到几十万,MongoDB的查询性能不能满足线上要求,在前端使用Redis集群做LRU缓存,缓存id和对应的content。

移动社交产品的热点数据大部分是最近产生的,LRU缓存可以扛住99.5%的线上请求。通过监控,miss(每秒穿透)和evict(每秒逐出)两个指标,用来评估缓存容量,适时扩容。

这里特别说一下,为了快速大规模扩容,Redis的LRU缓存集群没有采用一致性hash,而是最简单 Mod取余的hash 方式。通过节点数据复制,快速的翻倍扩容,省去了缓存预热的过程。

随着数据量的增长,MongoDB需要不断扩容,单个MongoDB实例占用空间接近硬盘的上限。而且读性能太低成为瓶颈。最终将持久化迁移到Hbase,废弃掉了MongoDB在线上的使用。

好友动态( Feed timeline)

接下来介绍好友动态(timeline)实现和优化过程。好友动态(timeline)通过好友关系聚合内容,按时间排序,类似微信的朋友圈。

Timeline使用Redis的zset结构做存储,天然有序,支持原子的增/删/查询操作。和早期SNS系统MySQL+Memcached相比,实现简单很多,大部分业务一行代码搞定:

  1. ZADD timeline 1460198781 88888888 //插入一条feed_id为88888888的Feed,插入时间为1460198781

  2. ZREVRANGE timeline 0 100 //查看最近的100条Feed

关于Feed系统的 推(push)模式 和 拉(pull)模式 有很多讨论。

陌陌最初使用的是推的模式,也就是发布Feed后,异步插入到每个好友的timeline。这种方式读取效率高,可以看作O(1)的操作。但是写操作开销大,每秒1000条Feed,每人N个好友,会产生1000*N的OPS,而且一个feed_id重复保存N次,产生大量冗余数据。

随着用户产生数据的积累,长尾效应明显,冷数据占比会越来越高。而且redis对小zset采用ziplist的方式紧凑存储,列表增长会转换为skiplist,内存利用率下降。存储timeline的Redis集群近百台服务器,成本太高,推动改造为拉的模式。

通过timeline聚合层,根据用户的好友关系和个人Feed列表,找到上次访问之后产生的新Feed, 增量实时聚合新内容。大致步骤:

  1. 遍历我的好友,找到最近发表过Feed的人

  2. 遍历最近发表过Feed的人,得到id和time

  3. 合并到我的timeline

聚合过程采用多线程并行执行,总体聚合时间平均20ms以下,对查询性能影响很小。

改为拉模式后,timeline从 存储变为缓存 ,冷数据可以被淘汰删除,timeline不存在的则触发全量聚合,性能上也可以接受。redis集群只缓存最近的热点数据,解决了存储成本高的问题,服务器规模下降了 一个数量级 。

附近动态 (Nearby Feed)

最后介绍LBS的附近动态空间查询性能优化,也是有特色的地方。

陌陌上每一条Feed都带有经纬度信息,附近动态是基于位置的timeline,可以看到附近5公里范围内最新的Feed。技术上的难点在于每个人的位置都不一样,每个人看到内容也不同, 需要实时计算无法缓存 。

第一个版本用mongo的2D索引实现空间查询:

feeds.find({location : {“$near” : [39.9937,116.4361]}}).sort({time:-1});

由于mongo的2D查询不能建立联合索引,按时间排序的话,性能比较低,超过100ms。通过数据文件挂载在内存盘上和按地理位置partition的方法,做了一些优化,效果还是不理想。

第二个版本,采用geohash算法实现了更高效的空间查询。

首先介绍geohash。geohash将二维的经纬度转换成字符串,例如经纬度39.9937,116.4361对应的geohash为wx4g9。每个geohash对应一个矩形区域,矩形范围内的经纬度的geohash是相同的。

根据Feed的经纬度,计算geohash,空间索引使用Redis的zset结构,将geohash作为空间索引的key,feed_id作为member,时间作为score。

查询时根据用户当前经纬度,计算geohash,就能找到他附近的Feed。但存在 边界问题 ,附近的Feed不一定在同一个矩形区域内。如下图:

日请求量过亿,谈陌陌的Feed服务优化之路详解架构师

解决这个问题可以在查询时扩大范围,除了查询用户所在的矩形外,还扩散搜索相邻的8个矩形,将9个矩形合并(如下图),按时间排序,过滤掉超出距离范围的Feed,最后做分页查询。

日请求量过亿,谈陌陌的Feed服务优化之路详解架构师

wx4g9相邻的8个 geohash :wx4gb,wx4gc,wx4gf,wx4g8,wx4gd,wx4g2,wx4g3,wx4g6。

归纳为四个步骤: ExtendSearch ->  MergeAndSort ->  DistanceFilter ->  Skip 。

但是这种方式查询效率比较低,作为读远远大于写的场景,换了一种思路,在 更新Feed空间索引 时,将Feed写入相邻的8个矩形,这样每个矩形还包含了相邻矩形的Feed,查询省去了

ExtendSearch和MergeAndSort两个步骤。

通过数据冗余的方式,换取了更高的查询效率。将复杂的geo查询,简化为redis的zrange操作,性能 提高了一个数量级 ,平均耗时降到3ms。空间索引通过geohash分片到redis节点,具有 数据分布均匀 、 方便扩容 的优势。

总结

陌陌的Feed服务大规模使用Redis作为缓存和存储,Redis的性能非常高,了解它的特性,并且正确使用可以解决很多大规模请求的性能问题。通常内存的故障率远低于硬盘的故障率,生产环境Redis的稳定性是非常高的。通过合理的持久化策略和一主多从的部署结构,可以确保数据丢失的风险降到最低。

另外,陌陌的Feed服务构建在许多内部技术框架和基础组件之上,本文偏重于业务方面,没有深入展开,后续有机会可以再做介绍。

互动问答

问题:MongoDB采用什么集群方式部署的,如果数据量太大,采用什么方式来提高查询性能?

我们通过在mongo客户端按id做hash的方式分片。当时MongoDB版本比较低,复制集(repl-set)还不太成熟,没有在生产环境使用。除了建索引以外,还可以通过把mongo数据文件挂载在内存盘(tmpfs)上提高查询性能,不过有重启丢数据的风险。

问题:用户的关系是怎么存储的呢 还有就是获取好友动态时每条feed的用户信息是动态从Redis或者其他地方读取呢?

陌陌的用户关系使用Redis存储的。获取好友动态是的用户信息是通过feed的owner,再去另外一个用户资料服务(profile服务)读取的,用户资料服务是陌陌请求量最大的服务,QPS超过50W。

问题:具体实现用到Redis解决性能问题,那Redis的可用性是如何保证的?万一某台旦旦机数据怎么保证不丢失的?

Redis通过一主一从或者多从的方式部署,一台机器宕机会切换到备用的实例。另外Redis的数据会定时持久化到rdb文件,如果一主多从都挂了,可以恢复到上一次rdb的数据,会有少量数据丢失。

问题:Redis这么高性能是否有在应用服务器上做本地存储,如果有是如何做Redis集群与本地数据同步的?

没有在本地部署Redis,应用服务器部署的都是无状态的RPC服务。

问题:Redis一个集群大概有多少个点? 主从之间同步用的什么机制? 直接mod问题多吗?

一个Redis集群几个节点到上百个节点都有。大的集群通过分号段再mod的方式hash。Redis 3.0的cluster模式还没在生产环节使用。使用的Redis自带的主从同步机制。

问题:文中提到Redis使用mod方式分片,添加机器时进行数据复制,复制的过程需要停机么,如果不停数据在动态变化,如何处理?

主从同步的方式复制数据不需要停机,扩容的过程中一直保持数据同步,从库和主库数据一致,扩容完成之后从库提升为主库,关闭主从同步。

日请求量过亿,谈陌陌的Feed服务优化之路详解架构师

转载请注明来源网站:blog.ytso.com谢谢!

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6744.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论