如何让七岁小朋友学会系统设计
今天中午在家吃午饭的时候,孩子妈妈说早上和中午用的筷子比较多,待会吃完可能需要先洗一波筷子,不能直接放到洗碗机里面(我们家基本都是一天开一次洗碗机),否则晚上吃饭筷子就不够了。我最近沉迷于系统设计,于是就和我们家的七岁小朋友聊起了该如何解决这个问题。
需求
功能需求
从妈妈的描述中,我和儿子总结了以下几个功能需求:
- 家人可以用筷子进行吃饭。
- 筷子可以被清洗和再利用。
- 家人可以购买(增加)和淘汰不好的筷子。
- 家人可以选择不同的筷子进行吃饭。
- 。。。
非功能需求
- 需要筷子的时候能够迅速拿到(Low latency)
- 已经加入的筷子能够一直都在,不会无故发生丢失的情况。(High Reliability)
- 任何想要筷子的时候,都能够得到筷子的状况。(High Availability)
容量分析
- 假设家里有三个人,每个人每顿饭需要使用1双筷子,那么每顿饭就需要3双筷子。
- 妈妈烧菜需要使用一双筷子进行尝菜,所以每顿饭需要的筷子就是3+1=4双。
存储分析(Storage Estimation)
- 假设我们每天清洗筷子一次,那么筷筒需要能够容纳4双/顿*3顿=12双。考虑到70%的使用率,我们购买的筷筒最好能够容纳12/0.7=18双筷子,如果每双筷子占筷筒的两格,那么筷筒需要有18*2=36个格子。
带宽分析(Bandwidth Estimation)
- 每顿饭4双筷子,所有QPS就是4双/顿。
API的设计
- 获取筷子:
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: 筷子的类型
高层的设计
从总体的架构来说,大概可以分成这样几个部分:
- 加入筷子,大概的流程是从厨房门过去,找到厨房管理系统(台面,哈哈?),然后把筷子放进筷筒中。
- 获取筷子,则是类似的流程,只是这个时候是一个读的过程,我们需要把可用的筷子从筷筒中取出。
- 而清洗筷子,我们则可以需要可以假如一个洗碗机模块,因为洗碗机清洗的时间比较长,让我们一直站在那边等待不太现实,我们可以让他后台运行,只要洗好了能放到筷筒中就好了。(来一个自动机械手把筷子从洗碗机中取出放到筷筒中就好了,每天把洗好的碗筷从洗碗机中拿出来也很费劲,哈哈)

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

Data Sharding
现在我们设计的系统在我们家用得很好,于是周围的邻居听说了之后都决定把他们的筷子放到我们家来,这样一来一个筷筒已经没法满足需求了,所以我们需要很多的筷筒放到不同的厨房里。那么我们如何来决定哪些筷筒放哪些筷子呢?
基于UserId来Sharding:一个常见的方法就是我们来按照筷子的所有人来放这些筷子,让同一个人的筷子放到同一个筷筒里面,这样一来找筷子的时候也很方面,只要根据传入的用户id就可以到相应的地方去找筷子了。但是这里有几个问题:
- 有些人的筷子非常多,这样一来一个家庭的筷筒放不下这么多筷子,有时随着他筷子的增多,我们需要进行各种处理就很麻烦。
- 有些人家经常要吃饭,不停地区拿筷子,导致某些筷筒会一直被访问,用得多了就很容易坏,甚至出现大家在一个筷筒那边拥挤的情况。
基于chopstickId来sharding:我们可以通过map chopstickId的方法来决定把筷子放到哪里,然后要找某个人的筷子,可以查询所有的家庭,然后再聚合起来就可以了。这样一台就解决谋一个筷筒被频繁访问的问题,当然问题也很明显,因为每次找一个人的筷子,你需要查询所有的筷筒,这个显然也是一个耗时的操作。
Load Balancing
当有很多人想要来访问厨房的时候,我们可能就需要进行分流,毕竟一个厨房已经不能满足大家的访问需要了,这个时候,我们可以在厨房门口加一个指示牌,不同的人在不同的时间到来的时候回去不同的厨房去拿筷子。这就是负载均衡,加上之后就类似这样了。

Replication和Fault Tolerance
当筷子保存进来的时候,为了防止某些筷筒因为一些不可预知的原因损坏了,我们需要把同样的筷子拷贝一份或几份放到别的地方(好吧这个场景其实有点不太真实,将就着听听,哈哈),这样一来,哪怕真的有某些筷筒出问题了,我们也能够知道筷子的具体信息,而不会丢失。
Cache
假如有个明星也使用了我们的系统,然后他有很多粉丝,都想来看看他使用的是什么筷子,这样一来一些筷子的数据就会被频繁地访问,如果我们每次都去访问筷筒得到这些信息,效率就很低下(毕竟磁盘访问的速度很慢),我们可以在筷筒前面加上一层cache,这样就可以有效地提高访问的效率。当然如何处理Cache的过期或替换也是经常被问的问题。
额外的问题
基本的设计到这个时候其实也差不多了,还有一些比较好玩的问题也列在这里供大家参考。比如说我们同一时间有很多的筷子需要清洗,他们如何处理厨房管理系统和洗碗机之间的调度呢,这个时候你可能需要一个队列管理系统,能够动态地增加或者减少洗碗机数量等等。

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