欢迎回看本文原始视频

 

今天我们来聊一聊系统设计的基本方法——SNAKE原则(Scenario,Necessary,Application,Kilobit,Evolve),它是BitTiger创始人冯沁原在准备系统设计相关知识时整理出的一套方法论,非常适合用于系统设计的各个方面,无论宏观还是微观,初期还是晚期。

 

 

首先什么是系统设计?

The process of defining the architecture, components, modules, interfaces, and data for a system to satisfy specified requirements.

系统设计有四大要素。第一,是要满足一个需求即Requirements;第二,对内容进行一个定义;第三,从不同维度去考虑宏观的架构层、组件层、模块层;第四,也要考虑到互相间交流的接口和相关传递的数据。所以这些内容一起构成了整个系统设计。

系统设计有什么层次呢?从上向下是从概念层到逻辑层再到物理层,从清晰度来看,会从不清晰,到越来越清晰,到最清晰,角度则是从宏观到微观。换句话理解,如果系统设计时,我们从顶层向下做设计,那就是一个自上而下的方法;如果是自下层往顶层设计,就是一个自下而上的方法。在后文中我们分别称之为宏观设计与微观设计。我们以Netflix为例来讲这两个方法。

如何设计Netflix

现在我们通过设计Netflix,来具体了解什么是系统设计的SNAKE原则。

 

 

这是Netflix的官方网页,上面有各种用户信息、搜索功能、以及列出的各个频道:Netflix流行的电影(Popular on Netflix)、当前的趋势(Trending Now)、惊险刺激电影(Exciting Movies)、漫画动画(Goofy Comedies)等等。每个电影类目下面会有很多具体的电影,如果点击可以看到详细内容,相信大家十分熟悉。

 

宏观设计

Scenario(场景):用例/接口

  • 第一步:枚举在Netflix里面,到底有哪些场景
    • 注册/登陆
    • 播放电影
    • 电影推荐
  • 第二步:选出里面最重要的排序
    • 基本需求:播放电影
    • 获得频道
    • 获得频道内电影
    • 播放频道内电影

这是三个基本需求的排序。

 

Necessary(限制):查看需求/假设

    • 第一步:询问
      • 首先看用户量,假设有5,000,000日活用户
    • 第二步:预测
      • 用户

– 通过日活用户,计算平均并发用户

=日活用户/每日秒数*平均在线时长(假设为30min)

= 5,000,000 / (24 * 60 * 60) * (30 * 60)

= 104,167

– 高峰并发用户,通常是平均用户的2-10倍,这里假设为6倍

高峰并发用户 = 平均并发用户 * 6 = 625,000

– 假设未来3个月,Netflix用户涨2倍

– 3月后高峰并发用户 = 高峰并发用户 * 2 = 1,250,000

      • 流量

        – 单用户流量 = 3mbps(假设数据,满足看电影基本需求)

        – 3月后高峰流量 = 1,250,000 * 3 mbps = 3.75 Tb/s

      • 内存

– 单用户内存 = 10KB(假设用户数据不清空)

– 3月后高峰内存 = 5,000,000 * 2 * 10 KB= 100GB(当前日活用户500万,3个月后日活用户为2倍)

    • 硬盘

      – 电影数=14,000份,每部电影=50G(假设数据,且每部电影需要存储不同清晰度版本)

      – 总硬盘=电影数*单电影空间=14,000*50GB=700TB

 

Application(应用):服务/算法

  • 第一步:重放用例,添加请求
  • 第二步:组合服务

具体来看,我们有用户服务、频道服务和电影服务三种需求,在上面有一个接待员可以获得各种服务请求,接待员把所有服务接受完以后发送给后台的服务,就可以满足需求。

 

 

 

Kilobit(数据)

  • 第一步:为每个请求添加服务模块
  • 第二步:选择存储方式。对于用户数据用MySQL;频道数据用MongoDB;电影服务由于只是从文件出来,所以往往采取Movies的方式。

 

 

Evolve(进化)

  • 第一步:分析
    • 三个方向
      • 更好:限制性条件,10个用户变为100个
      • 更广:场景,是否还有支付场景等
      • 更深:细节,是否用CDN,如何部署
    • 三个角度
      • 性能:单个点的计算能力如何,或某个算法的性能如何
      • 扩展:如果机器或者用户量翻倍怎么办
      • 鲁棒:发生各种危险,崩溃时服务器能不能处理
  • 第二步:根据场景回溯并进化

以上是我们SNAKE五步原则在宏观设计中的应用。

 

微观设计

因为Netflix里推荐电影是最基本的一个需求,那我们就做一个具体的微观设计模块——推荐模块

如何设计推荐模块?

 

 

我们可以看到用户1和用户2重复喜欢的电影数有3个:m3、m5、m7。

我们的需求是:寻找一个用户的最相似用户。

 

Scenario(场景)

我们可以定义一个接口,定义之后直接找到我最相似的用户,输入一个用户ID,输出一个用户ID,接口就出来了。

 

 

Necessary(限制)

在微观设计里我们可以继续做预测,比如针对这个请求,它的QPS即每秒请求量是多少?

 

 

Algorithm(算法)&Kilobit(数据)

算法和数据在微观设计里往往可以放到一起。具体做时,不同的算法和不同的数据结构性能差异很大。

比如有3个用户u1,u2,u3,最简单的方法是两两比较。想算出与u1最相似的用户,就先把u1、u2比,发现相似度是1;u1、u3比,发现相似度是2。最终我们发现,这个算法的性能非常差,可能每一个计算都需要200毫秒,相当于每个计算节点只能计算5个QPS,而我们需要2000多QPS,明显无法达到。

 

Evolve(进化)

我们先看一下当前的架构:原始数据给推荐模块,推荐模块返回请求。

 

 

因此我们需要从性能、扩展、鲁棒三个维度来进化。

 

1. 提升性能

怎么提升性能?提升单点的计算能力?

我们通过建立倒排索引,能够非常简单地优化算法的性能。比如现在把由用户建立改为由电影建立,即先计算出每个电影被哪些用户喜欢。比如m3被用户1、2、3喜欢。我们先找u1喜欢的电影,这里是m1、m3、m7,然后轮流遍历这些电影,每当这个电影还被其他用户喜欢,则相似度+1。于是我们就可以降低n的复杂度,计算所有的相似度。这样复杂度就从n的三次方降为了n的平方。所以性能大大提升,变成了每个20毫秒,能实现单机50QPS,但仍然无法达到需要的2000+QPS。

 

 

这时的架构已经做了提升:通过加入一个准备器,将原始数据输入进来,并且创建索引返回给推荐器。

 

 

2. 提升扩展性

之前有了一个简单的架构,能计算出所有索引,现在需要将推荐模块全部平行地放进来。现在不仅有一个推荐模块,还可以有好几个推荐模块,同时上面还有一个分配器,把整个推荐模块的请求分配给不同推荐器来进行计算。

 

 

那么问题是:需要多少个推荐器来满足2083QPS呢?

其实很简单,因为刚才单个推荐模块能满足50QPS,所以用2083除以50就可以得出答案,当然也可以多算几个来进行缓冲。

 

3. 提高鲁棒性

提高鲁棒性,防止服务器崩溃,需要怎么办呢?答案是做热备。

之前的数据有1个人管理,推荐器有一个模块在运行,上面是管理者在管理。

现在可以热备出另外一个数据集,把它们放一起;同时也热备出一个推荐模块放在一起。下图中每个橙色的虚框表示一个集群组合在一起。顶层可以用Apache接外部服务器顶住请求。还会有一些记录日志,比如Big Brother。它们中间通过一个消息分配者把消息分配出去。

如果要给系统进行升级,我们可以通过添加模块来轻松地实现。比如想加入一个Feed流,相当于新浪微博这样的架构,那只需要把这个模块添加到系统中。

 

 

上图可以看到,绿色的虚线表示它们之间进行消息传递;而绿色粗线表示它们之间有一个数据的快速通道,这样就不用所有消息都通过中间的消息传递者传输,避免影响效率。

 

总结:SNAKE原则

  • Scenario(场景)
    • 枚举&排序
  • Necessary(限制)
    • 咨询&预测
  • Application(应用)
    • 重访&组合
  • Kilobit(数据)
    • 添加&选择
  • Evolve(进化)
    • 分析&回溯

通过项目的搭建,建立Solid的系统设计思维,真正了解系统设计中会遇到的问题,并亲手解决这些问题,提升50%你的学习效率。