Description
系统设计-开篇
今年的技术文章终于开始写了,先从系统设计开始,打算整理和实现一些经典系统设计的面试题,来提升下自己的思考深度,今天先简单做个前言,介绍下系统设计
前言
我认为,利用现有科技技术高效解决问题是高阶程序员的职业追求,但这些问题的解决方案往往藏在一些生活中的片段之下或者是一块语义化的文本描述之后,直接发现最佳的解决方案并不容易,在简单思考后, 我认为解决问题可以从下面四个步骤进行考虑:
- 挖掘问题的本质
- 评估解决问题的可行性
- 综合现有资源进行方案设计
- 编码实现
上面的每一步都需要我们进行精细的考虑和打量,才可能综合得出最佳的解决方案,而其中的第一步与第二步,往往会由公司的高阶产品经理进行评估,高阶程序员往往需要着重负责后面的两步,也即方案设计与实现,这会是低阶程序员到高阶程序员,进行职业成长的最佳出发点
在大厂的高阶程序员面试题中,经常会出现系统设计类的面试问题,因为系统设计类型的题目基本没有标准答案,所以在面试的过程中,往往能够反映出:
- 应试者的历史的工作经验
- 解决大型复杂系统的能力
- 个人关于工作思考的深度
这是一套跳脱于八股文般的知识点面试法的另一个考核角度。但是这类考核并不适用于初级程序员,对于大型软件没有较多了解的应试者往往不能了解面试官的面试意图,一方面降低了应试者的面试体验,另一方面页浪费了面试官的时间
下面就简单说明下系统设计的主要步骤
步骤一:确认需求
在系统设计前,最重要的一件事就是确认需求,没有人希望花费大量资源后得到的是一台不带屏幕的显示器。这在日常的工作中也是十分重要的一点,我们要确定,需求希望我们实现一台拥有较好外观的显示器,而不是一件拥有较好外观的艺术品
比如说我们需要设计一个 twitter,来解决信息流动率低的问题,同时顺带用户间相互表达的社交需求,那么我们需要确认的问题可能会包含以下内容:
- 用户能发推并且关注其他人吗
- 我们是否需要设计并展示用户的时间轴
- 推特中可以上传图片或者视频吗
- 用户能够搜索推特内容吗
- 我们需要展示热门推特吗
- 我们需要实现系统消息推送系统吗
- ...
步骤二:系统流量预估
在确认需求后,确认设计系统需要支持的流量往往十分必要的,对于小流量的服务我们可能尽可能的复用资源从而降低支出,来达到节流的目的;而对于大流量的服务,我们往往需要花费更多的时间去设计服务架构来满足需求
确认流量能够帮助我们去思考最佳的问题解决方案,比如实现过程中的分区、负均衡、缓存和后续扩展设计
还是跟着上面的例子
- 系统的核心功能流量如何(发推数量、时间轴生成时延、...)
- 我们需要多大的存储空间(是否需要支持图片、视频,存储时长为多久)
- 我们需要多大的带宽(用于设计负载均衡)
步骤三:接口设计
确认接口是系统设计前必须的一个步骤,确认接口可以保证我们理解了问题的本质,所设计的系统能够满足需要,如果发现偏差也能够以最小成本进行调整
比如 twitter 的 API controller 可能如下:
- postTweet(userId, tweetData, tweetLocation, userLocation, timestamp, ...)
- generateTimeline(userId, currentTime, userLocation, ...)
- markTweetFavorite(userId, tweetId, timestamp, ...)
步骤四:确认实体(Entity)结构
如何设计实体所包含的字段,会影响我们后续的数据管理和可能需要进行的数据分区。这里我们需要设计出大型系统中不同实体之间相互交互、数据存储、传输和加密等的依赖关系
比如 twitter 的实体可能如下:
- User
- userID
- name
- dateOfBirth
- creationDate
- lastLogin
- ...
- Tweet
- tweetID
- content
- tweetLocation
- numberOfLikes
- timeStamp
- ...
- UserFollow
- userID1
- userID2
- FavoriteTweets
- userID
- tweetID
- timeStamp
设计完实体后,如何选择数据库类型也是我们需要进行思考的点,是 MySQL-like 的关系数据库,还是类似 mangoDB 的费关系数据库?使用什么样的块存储来存储图片和视频都是我们需要考虑的点
步骤五:高阶设计
高阶设计中少不了的就是设计相关的流程图,我们需要声明足够的组件来声明各个组件之间的交互关系
对于 twitter 来说,我们需要说明后端服务前,多个服务是如何通过负载均衡处理前端发来的读写请求,如果读写请求数量差距较大,我们可以考虑使用读写分离的场景优化架构;后端服务后,我们选用了那些高效的 DB 来存储数据,同时我们可能还需要一块 CDN 来托管我们的图片和视频
步骤六:详细设计
针对复杂的系统模块,我们可以详细分析不同的设计方案,包括其中的优势和劣势,并说明为何选择其中一种方案,组长说过一句话很能启发思考:不用说明为什么选择了这个技术,而要说明为什么没有选择另外一个技术
这些详细的调研,会帮助我们更加详细的了解其中不同方案的 tradeoff,同时了解系统的局限性
在刚刚的例子下,我们可能会有一些关于模块设计思考:
- 我们存储的数据量较大,可能需要进行分区,如果不分区可能带来的影响会是什么
- 对于边缘 case 的处理,比如热门用户的大量发推等
- 我们可以考虑增加 cache 来提升系统运行效率吗,加在那一层呢
- 哪个模块需要考虑负载均衡呢
步骤七:确认和解决性能瓶颈
确认可能存在的性能瓶颈是报障服务稳定性的关键步骤
- 系统中是否存在薄弱环节,如果出现故障如何解决
- 如果服务器着火,我们能保证数据不丢失吗
- 如果服务器着火,我们能保证服务稳定吗
- 我们的性能监控如何设计,当服务波动后能否获取到可用的报警信息(准确警报等级)
为了尝试更好更快的解决此类问题,今年的技术文章打算从系统设计开始,进行相关的调研与学习,希望能和大家一起提升程序员的综合能力