如何设计研发一个小红书App?

852 阅读15分钟

在开始之前,我们先简单介绍一下小红书,以便让大家对其有一个初步的了解。

一、背景介绍

小红书:是一款用户基础不错的App,可以简单理解为多媒体版本的知乎,可以搜索一些攻略与建议等。主要为广大优质用户的分享,分享的内容主要有2种方式:图片,视频。

下面将简单介绍小红书的用户画像,用户数据,和用户玩法。

1.1 用户画像

  • 用户画像:小红书将用户划分为几个主要群体,包括Z世代、新锐白领、都市潮人、单身贵族、精致妈妈和享乐一族。这些标签反映了用户的多样性和个性化需求。
  • 市场定位:小红书定位为一个结合了社区和电商功能的平台,重点服务于对时尚、品质生活感兴趣的年轻女性用户群体。

1.2 用户数据

为了尽可能的真实,所以,作者也做了一些市场调研。
基于调研得知小红书的用户数据(有些数据是作者推测):

  • 注册用户:1.5亿
  • DAU:1亿
  • 每天上传的图片数量:500w次
  • 每天上传的视频数量:100W次

1.3 用户玩法

下面是官方的玩法设计:

  • 图片:红书支持上传图片笔记,其中最大图片尺寸为10M,小红书单次上传图片数量最多为30张。
  • 视频:根据官方规定,小红书视频笔记中,视频文件大小不得超过500MB,长度不超过15分钟。

二、需求与资源准备

基于上述的背景介绍和目前小红书拥有的功能,我们本次只涉及2个核心能力:上传笔记,浏览笔记。 至于笔记点赞、转发、私信等能力就暂不考虑。目的是让本篇文章更加聚焦,能够将重点更加清晰的展示。

2.1 需求

2.1.1 功能需求

  • 用户可以上传笔记,笔记的形式有:图片或者视频。用户通过上传笔记来分享生活经验。
  • 用户可以浏览其关注博主发布的笔记。
  • 用户可以查询笔记的详情。
  • 用户可以为笔记点赞。
  • 用户可以为笔记评论。

2.1.2 非功能需求

  • 实时性:由于是社交种草App,所以,必须要考虑Feed流的实时性。打开App展示不了图片,这肯定不行。
  • 可用性:基于产品属性,会存在爆文或者事件热点的情况。那就必须要考虑高可用性,避免系统直接打崩,那可是要上头条的,让本就挣扎的估值会雪上加霜。假设以99.99%为基本标准。

2.2 资源准备

任何系统都离不开资源的评估和准备,这也是我们日常中经常会忽略的环节。也许是系统稳定运行,又或者没有需要梳理的诉求。由于笔者有infra的背景,并且经常会有大型活动的场景,深知这块的重要性。

Q: 为什么要评估资源?
A:
  1.保障系统的可用性。在高光活动时,如果由于你评估的资源不准确导致系统奔了,那是灾难级别的。千万别说你们有自动扩容的能力,那是后置处理方式。
  2.管理成本。并不是资源堆的越多越好。富裕的资源会让你的稳定性得到一定的保障,那企业的成本呢?
  所以,我们要通过技术的手段实现价值和创造价值。那么合理,合适的评估资源,在可用性与成本中取得一个最佳值,也是价值的重要一环

2.2.1 存储

因为小红书会发布图片或者视频类型的笔记,这个业务场景导致我们必须要存储对应的多媒体资源。所以,需要对存储资源进行合理的评估。

计算公式:单个笔记所占用的空间*笔记上传的数量

  • 图片:500w * 10MB * 30张 = 5000,000 * 300MB = 1.5PB
  • 视频:100w * 500MB = 1000,000 * 500MB = 500TB

所以,每天存储图片所需要1.5PB,视频需要500TB。共计需要2PB。

2.2.2 带宽

多媒体资源的上传和下载都是带宽的重度用户,而带宽是所有成本中最昂贵的支出。
带宽,需要分inbound和outbound两个模式来区分。并且,基于业务流量的特性,会存在波峰和波谷的场景。所以,带宽的计算相对较为较复杂。

业务流量场景:均分、极端波峰。

  • 均分:就是假设一天的流量是均分分布的。这样当然是理想情况,现实的业务流量肯定是有周期或者有业务形态的。

    • 计算公式:
      1. inbound: 一天存储的空间/ (606024),得出每s所需要的空间传输,再转化为小b,就是带宽的单位(Gbps,Mbps)。所以,这里的带宽就是:(1500TB + 500TB)/(606024) ~=23.3GB/s = 186.4 Gbps。这个就是inboud带宽。
      2. outboud: 方式同inbound,不过,可以简单假设读请求:写请求为100:1。因为小红书的大多数场景都是读流量,例如:访问feed,或者查询笔记的详情等。
  • 极端波峰:就是假设所有的用户都会在同一时间来访问。当然现实可能不会,但是,是具备代表性的。因为当存在某些热点事件或者笔记的时候,会有明显的波峰。这个是系统可用性需要考虑的。工程师不应该只会写代码。

2.2.3 服务器

所有的代码都是运行在服务器上的。无论是曾经的物理机时代,还是现在云原生时代。底层都是服务器,都是通过CPU和Mem来完成系统的运行。
因为我们是个ToC的场景,会有大量的请求接入量。所以,我们要评估好需要多少服务器来支撑。

计算公式:总请求量/单台服务器的支撑量。

  • 总请求量:每人的请求 * DAU
  • 单台服务器的支撑量:这是与服务器的配置,架构的设计,代码的质量等多个因素共同决定的。但是,这里为了流程能继续往下进行,先假设单台服务能够支撑1000的QPS。

在每人每天平均请求20次的情况下。所以需要的服务器:(201亿)/(1000606024)=(100 000 00020)/(1000606024) ~= 24台。
当然,这里也是下限,因为业务流量不是均分的。所以,实际值肯定要大于24台。

三、整体设计

我们先基于需求,完成一个整体的设计,然后再逐级拆解。

Q: 为什么要先做一个整体设计呢?
A: 这是一种结构化的表达方式,从而增加可读性。其本质就是让读者能够有个宏观的结构和预期,知道你下面会从哪些方面展开。

3.1 系统设计

小红书支持我们上传,浏览笔记等能力。比如用户A新发布了一遍笔记;用户B能通过feed流来查看A的笔记;用户C能够查看笔记详情并予以自己的评论。

image.png

3.2 API 设计

基于上述的整体设计,我们能够看出,共需要以下4个API的支撑,他们分别为:

  • 上传笔记
用户将自己的笔记进行发布,以下是对应的参数介绍:
- mediaType: 笔记的类型,是图片还是视频
- mediaUrlList: 笔记的多媒体资源,是对应的uri
- caption:笔记的文本内容
- rTag:笔记的tag。其中rTag与caption共同占用1000字的限制。

uploadRPost(userID,mediaType,mediaUrlList,caption,rTag);
  • NewsFeed关注列表页
viewNewsFeed(userID);
  • 笔记详情
rPostDetail(userID,postID);
  • 评论笔记
commentRPost(userID,postID,comment);

3.3 存储设计

3.3.1 存储组件的选择

Q1:元数据选择SQL还是NOSQL? A1:选择SQL。因为用户,笔记等都是结构化内容,并且,在关注的feed流中保持时序的方式排列。所以,这里选择MySQL。

Q2: 如何解决1亿DAU所带来的性能问题? A2: 如果直接使用MySQL势必会带来性能问题。所以,这里我们选择Redis来承担Cache的职责。从而避免大量的请求增加MySQL的负载。

Q3: 那选择Redis有什么要注意的地方么? A3: 在key-value的设计上,尽量保持简洁,结构化,避免大Key等行为所带来的性能问题。

Q4: 那除了MySQL,有没有更适合存放用户信息的选择?因为社交软件做人物关系会有大量的join行为,势必会影响性能 A4:有。在面对小红书这样用户级别的社交软件,更建议采取GraphDB这样的图数据库来维护人物数据。

下面将对具体的schema展开设计

3.3.2 MySQL Schema的设计

基于上述的系统设计,我们大致需要4张基础表单。他们分别为:用户表,关注表,笔记表,笔记的多媒体表,

  • 用户表:用于存储注册用户的元数据。包括名称,头像,签名等。
  • 关注表:用于存储用户的关注关系。
  • 笔记表:用户存储用户所发布的笔记。包括笔记元数据,笔记状态,和用户ID等信息。
  • 多媒体表:用户存储笔记中的多媒体数据,主要以URL的形式存放。

image.png

但是,这里仔细思考发现是存在问题的,因为1亿的DAU,会导致我们的关注表异常的庞大,一定会拆表。如果这样,又会引入新的问题:主键的选择会导致查询问题。当然,可以走es或者redis来处理。但是,这里笔者更倾向用GraphDB来解决。因为GraphDB天然就是做人物关系的,并且社交型软件一定也离不开GraphDB,否则一些社交裂变等玩法都会遇到挑战。所以,这里的选型,我们做个升级,采取GraphDB来构建人物关系。

3.3.3 Cache的设计

共计3个cache:
用户表Cache(user Cache):key为userID,value为用户的元数据。
笔记表Cache(rPost Cache):key为postID,value为笔记的元数据。
Feed Cache(News Feed Cache):key为userID,value为postID列表,基于时间的排序。

四、详细设计

4.1 发布小红书笔记

大致分为3步骤:上传多媒体文件,持久化小红书笔记内容,预写feed流。

  1. 上传多媒体文件:用户在App端将多媒体文件(图片/视频)上传至TransCoding Service,该service会做2件事。1.将视频文件进行转码,并存放至CDN上;2.以消息的方式通知R_Post Service,以此来更新多媒体文件的状态。
  2. 持久化小红书笔记内容:将发布的笔记内容持久化至MySQL,并更新rPost Cache。
  3. 预写feed流:这里牵扯到技术选型的问题,会有push,和pull两种数据处理模式。为了更聚焦这里选择push的方式。就是当用户发布一篇新笔记的时候,会fanout,将笔记预写至关注的用户列表中。但是,针对一些大V用户,这将会是一个极其耗时耗力的事情。所以,这里我们需要加一个MQ来执行异步请求,并且该topic的消费者是个works的计算集群,可以很好的分担压力。最终将对应的的笔记写入每个follower的feed里。
    这里New Feed Cache可以选择sort set类型的数据结构,其中key可以设计为userID,value为feed id,得分为feed对应的时间戳。这样当后续查询已关注的博主时,可以按照时间顺序的方式展示对应的笔记。

上传笔记.png

4.2 浏览关注的博主

当用户请求R_Feed Service来获取feed列表。先从News Feed Cache中获取feed list,feed list中存放的目标feed id。通过feed id在rPost Cache中获取对应的feed详情,并且通过userID来填充对应的用户元数据。

浏览关注列表的笔记 (1).png

五、非功能需求的设计

我们在最非功能需求中有提到需要关注:延迟和高可用性。

  • 延时:
    Feed流对于延时的要求极高。所以,这里我们可以采用多级缓存的策略来实现。
    1. 客户端缓存:可以在App侧缓存最近加载的笔记内容。防止,在弱网环境下由于加载慢而导致的白屏问题。
    2. CDN缓存:可以将对应的多媒体资源存放于CDN上,以此来提高多媒体的加载速度。
    3. Redis缓存:将Metadata等数据存放在Redis Cache上,以此来提高数据的响应率。
    4. 策略缓存:feed列表页可以进行内容的截断处理。例如:展示一张主图和部分文字。这样能够保障在用户点击笔记详情的时候能进行平滑过渡,不会出现闪屏的现象。
  • 可用性:
    构建高可用系统是确保应用程序在面对各种故障时仍能保持正常运行的关键,因为可用性导致上头条的大厂数不胜数:我的前司就是之一。第二天微信就爆了,全是问原因的,甚至一百年不联系的朋友都来凑个热闹,赶个时尚。
    1. 冗余设计:在系统的关键组件上实施冗余,包括硬件(如服务器、存储)和软件(如数据库、应用服务器)。这意味着使用多个实例来避免单点故障。例如:我们设计中的rPost Service等各个Service必须杜绝单点问题。
    2. 负载均衡:使用负载均衡器分散流量到多个服务器上,以避免任何单个服务器的过载,并在服务器故障时提供无缝的故障转移。无论是软件性质的LB,例如Nginx。还是硬件H5等。都能够通过检查检查来完成故障节点的摘除,以此来实现failover。
    3. 故障切换和故障恢复:实施自动故障切换机制以在主要服务失败时切换到备用系统。同时,确保有一个有效的备份和恢复策略来处理数据丢失的情况。
    4. 灾难恢复计划:建立并定期测试灾难恢复计划。这应该包括数据中心故障的情况,例如通过在不同地理位置部署数据中心。虽然,现在是云原生时代。但是,底层的物理机还是存放在不同的IDC的。所以,我们的各个Service可以选择不同的DR或者不同的hostgroup来保障物理意义上的可用性。
    5. 实时监控和告警:实施全面的监控系统来实时跟踪应用程序的健康状况,并在检测到问题时触发告警。告警,其实是个很大的Topic。但是,只要做到有效告警,那么即使出现了意外,我们也能第一时间感知并介入。
    6. 数据库复制:使用主从复制或对等复制确保数据的高可用性和一致性。这个是DBA的工作职责,不扩展。
    7. 性能测试:定期进行压力测试和容量规划,以确保系统在高负载下的性能。这个很重要!!!作为一个工程,必须要时刻保持压测和敬畏。因为业务瞬息万变,尤其是拥有1亿DAU的小红书。很有可能因为某个热点话题,导致流量凸增。所以,这是一个很有意义的事项。
    8. 版本控制和持续部署:实施可靠的版本控制和持续集成/持续部署(CI/CD)流程,以确保代码的稳定性和可追溯性。因为笔者多年infra的经验,所以,建议从最小的commit message开始做起。
    9. 服务降级和限流:在系统超载时,实现服务降级和限流策略,以保护关键服务不受影响。这里有个前提,必须要评估各个模块的重要等级。因为笔者就遇到过整个service模块挂的情况,从而影响一连串服务。此时低优的服务该断就断,力保核心服务。必要时刻的断臂自救还是需要的。

六、可以优化的点

Fanout的时候,可以进一步优化:

  • 优化场景:虽然大V的粉丝有百W甚至千W。但是,有个细节,不是所有的关注者都是活跃用户,那针对这些僵尸用户,其实可以不用采取预写的策略。他们都不在DAU的范围里,那选择pull的方式会更合理。当然,还可以更进一步,结合用户画像,加入智能的分析,以此来进行fanout。