如何让七岁小朋友学会系统设计

今天中午在家吃午饭的时候,孩子妈妈说早上和中午用的筷子比较多,待会吃完可能需要先洗一波筷子,不能直接放到洗碗机里面(我们家基本都是一天开一次洗碗机),否则晚上吃饭筷子就不够了。我最近沉迷于系统设计,于是就和我们家的七岁小朋友聊起了该如何解决这个问题。

需求

功能需求

从妈妈的描述中,我和儿子总结了以下几个功能需求:

  1. 家人可以用筷子进行吃饭。
  2. 筷子可以被清洗和再利用。
  3. 家人可以购买(增加)和淘汰不好的筷子。
  4. 家人可以选择不同的筷子进行吃饭。
  5. 。。。

非功能需求

  1. 需要筷子的时候能够迅速拿到(Low latency)
  2. 已经加入的筷子能够一直都在,不会无故发生丢失的情况。(High Reliability)
  3. 任何想要筷子的时候,都能够得到筷子的状况。(High Availability)

容量分析

  1. 假设家里有三个人,每个人每顿饭需要使用1双筷子,那么每顿饭就需要3双筷子。
  2. 妈妈烧菜需要使用一双筷子进行尝菜,所以每顿饭需要的筷子就是3+1=4双。

存储分析(Storage Estimation)

  1. 假设我们每天清洗筷子一次,那么筷筒需要能够容纳4双/顿*3顿=12双。考虑到70%的使用率,我们购买的筷筒最好能够容纳12/0.7=18双筷子,如果每双筷子占筷筒的两格,那么筷筒需要有18*2=36个格子。

带宽分析(Bandwidth Estimation)

  1. 每顿饭4双筷子,所有QPS就是4双/顿。

API的设计

  1. 获取筷子:

GetChopsticks(UserToken, chopstickCount = 1, option chopstickType = Type.Adult, option chopsticksComments=null)

UserToken: 用来表示请求筷子的用户。

ChopstickCount: 想要得到筷子的数量。

ChopstickType: 想要得到筷子的类型,可以是大人用的筷子或者小朋友用的筷子等。

chopsticksComments: 获取筷子的原因或者别的有用的信息,可选。

返回值:chopstickIds:筷子的ID。

  • 清洗筷子:

CleanChopsticks(UserToken, chopstickId)

UserToken: 清洗筷子的用户。

chopstickId: 用来表示需要清洗的筷子。

返回值:true表示清洗成功。False表示清洗失败。

  • 加入筷子

AddChopsticks(UserToken, chopstickCount, chopstickType)

UserToken: 加入筷子的用户

ChopstickCount:筷子的数量

chopstickType: 筷子的类型

高层的设计

从总体的架构来说,大概可以分成这样几个部分:

  1. 加入筷子,大概的流程是从厨房门过去,找到厨房管理系统(台面,哈哈?),然后把筷子放进筷筒中。
  2. 获取筷子,则是类似的流程,只是这个时候是一个读的过程,我们需要把可用的筷子从筷筒中取出。
  3. 而清洗筷子,我们则可以需要可以假如一个洗碗机模块,因为洗碗机清洗的时间比较长,让我们一直站在那边等待不太现实,我们可以让他后台运行,只要洗好了能放到筷筒中就好了。(来一个自动机械手把筷子从洗碗机中取出放到筷筒中就好了,每天把洗好的碗筷从洗碗机中拿出来也很费劲,哈哈)

数据库Schema设计

相对来说Schema比较简单,你需要保存相关的筷子ID,类型,筷子加入的时间,筷子是否脏了以及谁买的筷子等信息。当然可能还要一张User相关的表格,就不详细列出了。

Data Sharding

现在我们设计的系统在我们家用得很好,于是周围的邻居听说了之后都决定把他们的筷子放到我们家来,这样一来一个筷筒已经没法满足需求了,所以我们需要很多的筷筒放到不同的厨房里。那么我们如何来决定哪些筷筒放哪些筷子呢?

基于UserId来Sharding一个常见的方法就是我们来按照筷子的所有人来放这些筷子,让同一个人的筷子放到同一个筷筒里面,这样一来找筷子的时候也很方面,只要根据传入的用户id就可以到相应的地方去找筷子了。但是这里有几个问题:

  1. 有些人的筷子非常多,这样一来一个家庭的筷筒放不下这么多筷子,有时随着他筷子的增多,我们需要进行各种处理就很麻烦。
  2. 有些人家经常要吃饭,不停地区拿筷子,导致某些筷筒会一直被访问,用得多了就很容易坏,甚至出现大家在一个筷筒那边拥挤的情况。

基于chopstickId来sharding:我们可以通过map chopstickId的方法来决定把筷子放到哪里,然后要找某个人的筷子,可以查询所有的家庭,然后再聚合起来就可以了。这样一台就解决谋一个筷筒被频繁访问的问题,当然问题也很明显,因为每次找一个人的筷子,你需要查询所有的筷筒,这个显然也是一个耗时的操作。

Load Balancing

当有很多人想要来访问厨房的时候,我们可能就需要进行分流,毕竟一个厨房已经不能满足大家的访问需要了,这个时候,我们可以在厨房门口加一个指示牌,不同的人在不同的时间到来的时候回去不同的厨房去拿筷子。这就是负载均衡,加上之后就类似这样了。

Replication和Fault Tolerance

当筷子保存进来的时候,为了防止某些筷筒因为一些不可预知的原因损坏了,我们需要把同样的筷子拷贝一份或几份放到别的地方(好吧这个场景其实有点不太真实,将就着听听,哈哈),这样一来,哪怕真的有某些筷筒出问题了,我们也能够知道筷子的具体信息,而不会丢失。

Cache

假如有个明星也使用了我们的系统,然后他有很多粉丝,都想来看看他使用的是什么筷子,这样一来一些筷子的数据就会被频繁地访问,如果我们每次都去访问筷筒得到这些信息,效率就很低下(毕竟磁盘访问的速度很慢),我们可以在筷筒前面加上一层cache,这样就可以有效地提高访问的效率。当然如何处理Cache的过期或替换也是经常被问的问题。

额外的问题

基本的设计到这个时候其实也差不多了,还有一些比较好玩的问题也列在这里供大家参考。比如说我们同一时间有很多的筷子需要清洗,他们如何处理厨房管理系统和洗碗机之间的调度呢,这个时候你可能需要一个队列管理系统,能够动态地增加或者减少洗碗机数量等等。

总结

本文总结了一个简单的系统设计常见的讨论,希望对正在学习系统设计的同学有所帮助。

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *