系统设计题总结(一)

参考:

https://blog.csdn.net/u013007900/article/details/79008993

https://blog.csdn.net/u013007900/article/details/79049187

 

 

 

 

技术面试的系统设计题(一)

本文为课程翻译和学习笔记,课程地址System Design for Tech Interviews
关于课后题,这儿就不公布答案了。应该还是比较简单的。

什么是系统设计题

在面试的时候,面试官经常会让我们设计一些系统,比如:

  • 设计一个像bit.ly一样的URL缩短服务。
  • 你将如何实现谷歌搜索?
  • 设计一个C/S应用程序,允许人们互相下棋。
  • 如何将关系存储在Facebook这样的社交网络中呢?并实现一个当用户的朋友喜欢与他们一样的东西时,用户会收到通知的功能。

这些问题起初似乎很吓人。毕竟,我们怎么可能在20-30分钟内设计出Google搜索。

这些问题的关键点是讨论解题的过程。对面试官来说重要的是你解决问题的过程。这种讨论的典型结果是一个高层次的架构,从而在有限制的情况下解决这个问题。也许面试官会选择一个或多个他们想讨论的系统瓶颈和其他常见问题来更加具体地询问你。

请记住,没有一个标准的正确的答案。系统可以用不同的方式建立。重要的是能够证明你的想法。

最后,请记住,根据面试官的目标,关于同一个系统设计问题的讨论可能会有不同的方向。他们可能愿意看到你如何创建一个涵盖系统各个方面的高层架构。也可能,他们会更关注一些特定的领域并深入研究。无论如何,你应该有一个如何处理不同情况的策略。

第一步:限制与用例

就像算法设计一样,系统设计问题的细节也很可能会被弱化。考虑关于URL缩短服务的问题(“设计一个像bit.ly的URL缩短服务”)。题目的信息非常少,如果不知道更多限制和要求,是不可能设计一个合适的解决方案。事实上,面试官并不会一开始就告诉你所有的信息,很多人都忘记了这一点,并立即开始设计解决方案。

对任何系统设计问题你应该做的第一件事是明确系统的限制,并确定系统需要满足哪些用例。花几分钟询问你的面试官,并统一系统的规模。我们在讨论算法设计时所讨论的许多相同的规则也适用于此处。

通常情况下,面试者希望看到的是你能收集到关于问题的要求,并设计一个能很好地覆盖这些要求的解决方案。永远不要假设面试官没有明确说明的事情。

例如,缩短网址的服务可能只能为几千个用户提供服务,但每个用户都可以共享数百万个网址。这可能意味着要处理数百万次缩短网址。该服务可能需要提供有关每个缩短的URL的统计信息(这会增加需要处理的数据大小),或者根本不需要统计信息。

您还需要考虑预期会发生的用例,您的系统将根据预期的目标进行设计。

用例

  1. 缩短:将一个url转化为缩短的url
  2. 重定向:得到一个缩短的url,跳转到原url
  3. 用户自己设计一个缩短的url
  4. CAP中,取高可用性

限制

一般来说,大部分限制是来自于数据规模的限制。比如每分钟的数据量,访问人数等等。
如果你去问面试官,他可能直接给你数据,比如,我们每秒要处理400个请求;或者,他会说一些更笼统的信息让你去估计,比如,这个网站不是top 3但是却是top 10的。

在估算的时候,一定要注意合理。通常而言,用二八定律是一个非常重要的准则。

  • 系统每个月需要处理的数据是100×106100×106
    • 推特一个月产生15×10915×109的推文
    • 每个月需要进行缩短的新URL只占了10%,总计1.5×1091.5×109
    • TOP3以下的缩短URL网站只处理了20%的数据,也就是300×106300×106的数据
    • 我们的系统,需要处理100×106100×106的数据
    • 5年之内会产生6×1096×109条URL,占用空间3TB;hash表占用空间36GB
      • 每个缩短的URL占用500 bytes和6 bytes的hash
  • 系统需要处理的请求是每个月1×1091×109

    • 考虑URL的寿命,平均为1~2周,这儿可以假设10天
    • 每天有一个请求
    • 100 mln×10 days×1 click/day=1 bln100 mln×10 days×1 click/day=1 bln
    • 请求的分类:10%是进行缩短,90%是进行重定向
      • 每秒有400+的请求,40是进行缩短,360是进行重定向
  • 每秒需要写入的新数据:40×(500+6)=20 K40×(500+6)=20 K

  • 每秒需要读取的数据:360×506=180 K360×506=180 K

关于这些限制的估计,人为主观因素有较大影响,不过在面试的时候除非估计得特别离谱,面试官一般不太会纠结于这方面,基本按照二八原则来估计就行了。

具体的估算过程见下图。

这里写图片描述

第二步:抽象设计

一旦你确定了你要设计的系统,你应该描述一个高层次的抽象设计。这步的目标是概述您的架构将需要的所有重要组件,而不是深入到抽象设计的某个方面,或者,直接一部分一部分来设计。

你可以告诉面试官,你想这样做,并画出你的想法的简单图表。勾画您的主要组件和它们之间的连接,在面试官面前证明你的想法,并试图解决每一个约束和用例。

如果你这样做,面试官会很快也很轻易地给你反馈。

通常,这种高级设计是人们已经开发的众所周知的技术的组合,比如,缓存,数据库读写分离等等。你必须确保你熟悉那里的东西,并且能够流畅地使用这些知识。这个地方就需要大家去多看书或者技术博客了。

值得注意的是,如果您的设计不能改满足面试官的要求,如,面试官要求高度的一致性约束,而您只能做到基本一致性,这是绝对不允许的。

  1. 应用服务层
    • shortening server
      • 可以简单地解释一下是如何实现的:使用hash,查看URL是否已经存在于存储中,若是没有,则进行hash和存储
    • redirection server
  2. 数据存储层
    • 用于存储hash→url的映射关系
    • 不必提到具体的数据库或者其他的细节,可以大致说明,它的运行方式像一个非常大的缓存系统:存储新的映射关系,通过key得到value。
  3. 设计hash方式
    • 结合一些著名的hash算法,如,md5
    • 结合随机数,并将结果转化为base62编码,base62编码非常适用于tiny URL
    • 取前6 bytes
    • hashed_ulr = convert_to_base62(md5(origin_value+random_salt))[:6]

第三部:理解瓶颈所在

考虑到问题的限制,您的高层设计有可能会有一个或多个瓶颈。这其实非常好。没有人能够一步到位地设计一个能够直接处理世界上所有负载的系统。我们希望的是这个系统具有可扩展性,以便您能够使用一些标准的工具和技术来改进它。

现在你有了高层次的设计,开始思考它有什么瓶颈。也许你的系统需要一个负载平衡器和许多机器来处理用户请求。也许需要存储的数据量太大了,系统应该使用分布式的数据库。这样做有什么缺点?分布式数据库是否太慢,是否需要一些内存中的缓存?

这些只是为了使您的解决方案完整而必须回答的问题的示例。面试官可能会希望在一个特定的方向上进行讨论。那么,也许你不需要解决所有的瓶颈问题,而是更深入地讨论一个特定的领域。无论如何,您需要能够之处系统的弱点,并能够解决这些问题。

请记住,通常每个解决方案都是某种权衡:改善一些方面会导致其他方面的恶化。然而,重要的是要能够谈论这些权衡,并根据所定义的约束和用例来衡量它们对系统的影响。

  1. 应用服务层
    • 缩短操作,每秒处理20k的数据,包输入输出和一个简单的hash操作。这明显不是瓶颈所在。
    • 重定向操作,每秒处理180k的数据,包括输入输出。这明显不是瓶颈所在。
  2. 数据存储层
    • 如何准确地得到key的value,我们的数据有5TB,所以系统不可能线性扫描整个数据集。

总结

当你和面试官就上面的问题达成一致,你可以深入到某些方面的细节。

 

 

 

 

 

 

技术面试的系统设计题(二)

可扩展性

基础

现在您已经设计了一个可靠的抽象体系结构,下一步就是将其扩展。如果你从来没有建立过大规模的系统,这个任务看起来有点令人生畏。

除了接下来的一个视频和四篇博文的翻译,你也可以看看An Unorthodox Approach To Database Design : The Coming Of The Shard

可扩展的Web开发

视频地址
课件地址

比较建议看一看视频,视频里面讲的非常杂乱,感觉没有什么逻辑,所以我整理的也是乱七八糟。他说的东西也是非常基础的,但是巩固一下也是很不错的。

具体的一些技术方面的大致情况可以看看这篇博文,里面讲到了Web服务器从基础到复杂的扩展过程。比这个课更有逻辑。

垂直扩展

对于一台服务器使用更多的更好的硬件,如下资源

  • CPU
    • cores, L2 Cache, …
  • Disk
    • 接口:PATA, SATA, SAS, …
    • RAID
  • RAM

限制:没有那么多物理资源

水平扩展

不同于垂直扩展那样选用最贵最好的服务器,水平扩展接受了便宜的硬件设施,但是用了更多的便宜且更慢的服务器实现了扩展。也就是,使用了大规模的集群分布式。

负载均衡

通过Load Balancer将服务集群黑盒化,那么对于外界而言,他们始终以为访问的是一个服务器。

Load Balancer具有一个公开的IP地址,而后端服务器不需要公开的IP地址,而是用内网IP地址。类似NAT协议那样。

关于负载均衡的算法由很多,比如,Round Robin算法(一种以轮询的方式依次将一个域名解析到多个IP地址的调度不同服务器的计算方法。)

开源的DNS系统BIND对于同样的域名请求,可能会返回不同的IP地址。

  • 软件
    • ELB
    • HAProxy
    • LVS
  • 硬件
    • Barracuda
    • Cisco
    • Citrix
    • F5
共享存储

RR可能会导致服务器1总是受到请求,从而导致堵塞。即,因为负载均衡算法的缺陷,导致负载均衡效果不理想。

sticky session问题:如果你登陆一个网址好多次,你的cookies和session等信息会分布在不同的机器上。那么如果你的请求被分发到一个新的机器上,那么这些就不复存在了,你必须重新登陆,重新添加购物车,这明显是不行的。

为了让负载均衡器能够明白每一个Server所处的状态,为了让所有Server的信息都保持一致,我们会使用共享存储的设计。

但是同样的共享存储也是有一定缺陷的:

  1. 如果所有系统都使用一块硬盘,如果这块硬盘挂了,那么所有的服务器都不能使用了。
  2. 如果按照功能进行分机和存储相应的数据,如,处理用户登录请求和用户cookies的记录,视频媒体等存储。如果最关键的那台服务器宕机了,整个服务也就都没有用了。

于是,我们可以使用增加硬盘数量实现备份。比如,我们同时用两块硬盘存储同一份数据的两个副本,这样虽然增加了一些overhead,但是能够实现备份。(关于这点还有很多论文去解释和设计相应的系统,比如raft一致性协议等等)。

同时我们也可以使用两块硬盘存储一份数据的不同部分,当一块在写的时候,我们可以写另一块。并行地运行写入程序。

Cacheing

HTML文件

简单地将HTML存储下来,或者以HTML模板的形式存储一些不怎么需要更改的内容。

SQL Cache

查询的缓存,可见其他数据库的设计情况。

Memcached

其他第三方内存数据库,用于缓存。还有比如,reids等等。

但是也需要一些别的协议或者机制来维护这类的缓存,如,同步问题, 垃圾回收

复制

主从模式

多主机模式

负载均衡+复制

有些类似数据库读写分离或者数据库的备份

数据分块存储

简单易懂的可扩展

克隆

可扩展Web服务的公共服务器通常是做为黑盒,并隐藏在负载平衡器后面。此负载平衡器将负载(来自用户的请求)平均分配到您的组/应用程序服务器群集上。这意味着,例如,如果小明与服务器进行交互,则他的第一个请求可能在服务器2进行处理,然后由服务器9处理第二请求,然后第三请求上再次由服务器2处理。

小明每次请求都应该得到相同(或者说一致)的结果,这与哪台服务器处理他的请求无关。

这是可扩展性的第一条黄金法则:每台服务器都包含完全相同的代码库,不会在本地磁盘或内存中存储任何与用户相关的数据,如会话或配置文件图片。

会话需要存储在所有应用程序服务器均可访问的集中式数据存储中。它可以是外部数据库或外部持久性缓存,如Redis。外部持久缓存比外部数据库具有更好的性能。换句欢说,通过外部存储是数据存储不驻留在应用程序服务器上。相反,它位于应用程序服务器的数据中心内或附近的节点。

但是如何同时部署多台呢?如何确保将代码更改并成功发送到所有服务器,而不是有些服务器运行新代码而有些服务器仍旧用旧代码?幸运的是,这个棘手的问题已经由伟大的工具Capistrano解决了。这需要一些学习,特别是如果你的Ruby不够熟练中。

在将您的会话“外包”并从所有服务器都能访问的代码块提供相同的代码后,就可以从这些服务器之一(AWS称之为AMI - Amazon Machine Image)创建映像文件。将此AMI用作“超级克隆”所有的新实例都基于。每当你开始一个新的实例/克隆,只要做一个最新的代码的初始部署就足够了!

数据库

现在,所需的更改比仅添加更多克隆的服务器更为激进,甚至可能需要一些勇气。你可以选择2个路径:

路径#1:是坚持使用MySQL,并保持野蛮的运行。聘请数据库管理员告诉他做主从复制和读写分离(从从机读取,写入主机),并通过添加RAM来升级主服务器。在几个月的时间里,你的数据库管理员会想出“分片”,“反规范化”和“SQL调优”这样的词汇,并且会在接下来的几周里担心必要的加班。那时,每一个新的动作来保持你的数据库运行将比以前更昂贵和耗时。如果您选择了Path#2,而您的数据集仍然很小并且易于迁移,那么您可能会变得更好。

路径#2:从头开始正确反规范化,并且在任何数据库查询中不再包含连接。你可以使用MySQL,像NoSQL数据库一样使用它,或者你可以切换到一个更好,更容易扩展的NoSQL数据库,比如MongoDB或者CouchDB。现在需要在应用程序代码中完成联接。越早执行此步骤,您将来必须更改的代码越少。但即使您成功切换到最新,最好的NoSQL数据库,并让您的应用程序执行数据集连接,您的数据库请求很快也会变得越来越慢。你将需要引入一个缓存。(关于这一点,我不是很赞同,还是要根据应用场景分类,不是所有的场景适合使用NoSQL的,而且不同的NoSQL适用的地方也不同,HBase和Redis就很不一样)

缓存

对于“缓存”,这儿指像Memcached或Redis这样的内存缓存(或者说,内存数据库)。不要执行基于硬盘文件的缓存,它会使服务器的克隆和自动扩展付出高昂的代价。

内存缓存通常是一个简单的KV键值存储,它应该作为应用程序和数据存储之间的缓冲层。无论何时您的应用程序必须读取数据,它应该首先尝试从缓存中检索数据。只有当它不在缓存中时,才会尝试从主数据源获取数据。

缓存将每个数据集保存在RAM中,所以能够尽可能快地处理请求。例如,Redis可以在标准服务器上托管时每秒执行几十万次读取操作。还写操作,特别是增量。

有两种缓存数据的模式。一个旧的和一个新的:

#1 - 缓存的数据库查询

这仍然是最常用的缓存模式。每当您对数据库执行查询时,都会将结果数据集存储在缓存中。key是我们查询的hash值。下次运行查询时,首先检查它是否已经在缓存中。

这种模式有几个问题。主要问题是数据到期。缓存复杂查询时,很难删除缓存的结果。当一条数据发生变化时(例如一个表格单元格),您需要删除可能包含该表格单元格的所有缓存查询。这样将会非常混乱。

#2 - 缓存的对象

这是我强烈的建议,我总是喜欢这种模式。一般来说,将数据视为一个对象,就像你已经在你的代码(类,实例等)中做的那样。让您的类从您的数据库中组合一个数据集,然后将该类的完整实例或组合数据集存储在缓存中。

听起来很理论,我知道,但看看你通常的代码。例如,您有一个名为“Product”的类,它有一个名为“data”的属性。这是包含产品的价格,文本,图片和客户评论的数组。属性“data”由类中的几个方法填充,执行多个难以缓存的数据库请求,因为许多事情相互关联。

现在,请执行以下操作:当您的类完成数据数组的“组装”时,直接将数据数组或更好的类的完整实例存储在缓存中!这样,只要有事情发生变化,就可以轻松地摆脱对象,并使代码的整体操作更加快速和合理。

最好的部分是:它使异步处理成为可能!应用程序只访问最新的缓存对象,几乎从不接触数据库!

缓存对象的一些想法:

  1. 用户会话(user sessions)从不使用数据库
  2. 完全呈现的博客文章
  3. 活动流
  4. 用户< - >朋友关系

一般来说,我更喜欢Redis的Memcached,因为我喜欢Redis的额外数据库特性,如持久性和内置的数据结构,如列表和集合。有了Redis和一个聪明的钥匙,你甚至有可能完全摆脱一个数据库。但是如果你只是需要缓存,就可以使用Memcached。

异步(Asynchronism)

请想象一下,你想在你最喜欢的面包店买面包。所以你走进面包店,点了一个面包,但那里没有面包!相反,在你点了的两个小时之后,你的面包才能被做好。这很烦人,不是吗?

为了避免这样的“请稍等” - 情况,需要完成异步。面包店有什么好处,也许对你的网络服务或网络应用也有好处。

一般来说,有两种方法/范例可以完成不同步。

异步#1

让我们想想前面的面包店例子。异步处理的第一种方式是“晚上烘烤面包,早上卖”的方式。没有等待时间。提到一个网络应用程序,这意味着提前完成耗时的工作,并以较低的请求时间完成完成的工作。

很多时候,这个范例被用来将动态内容转化为静态内容。一个网站的页面(可能是用一个巨大的框架或CMS构建的)在每次更改时都被预先渲染并作为静态HTML文件存储在本地。通常这些计算任务是定期完成的,也可能是由cronjob每小时调用一次的脚本。整体通用数据的预先计算可以极大地改善网站和网络应用程序,并使其具有很高的可扩展性和性能。试想一下,如果脚本会将这些预渲染的HTML页面上传到AWS S3或Cloudfront或其他付款云网络,就可以想象网站的可扩展性!您的网站将可以达到超级响应,可以处理数以每小时百万计的游客!

异步#2

回到面包店。不幸的是,有时顾客有特殊的要求,比如生日蛋糕-“生日快乐,小明”!面包店无法预见到这种客户的意愿,所以当客户在面包店时要开始工作,并告诉他在第二天回来。引用一个Web服务,意味着异步处理任务。

这是一个典型的工作流程:

用户来到您的网站,并开始一个非常计算密集的任务,这将需要几分钟时间完成。因此,您的网站前端将作业发送到任务队列,并立即发回给用户:您的任务正在被执行(或者等待执行),请继续浏览页面。任务队伍被一群worker检查,如果有新的任务出现,那么worker就干这个任务,几分钟后发出一个信号表明任务已经完成。前端不断地检查新的“任务已经完成” - 信号,看到任务完成并通知用户。(这只是一个简单的例子,检查的机制也有很多,可以参见数据库并发执行里面如何执行查询任务的。)

如果你现在想深入细节和实际的技术设计,我建议你看看RabbitMQ网站上的前3个教程。 RabbitMQ是帮助实现异步处理的许多系统之一。你也可以使用ActiveMQ或简单的Redis列表。基本的想法是有一个worker可以处理的任务或任务队列。

异步似乎很复杂,但绝对值得您花时间来了解它并自己实现。后端变得几乎可以无限扩展,前端变得活泼,这对整体用户体验是有利的。

如果你做了一些耗时的事情,试着总是异步地做。

例子

课程提供了一些公司的扩展性例子,参见原网址

https://www.hiredintech.com/classrooms/system-design/lesson/61

综合

一切都是一个权衡

这是系统设计中最基本的概念之一。

希望在这一点上,这对你来说不是一个惊喜。如果你看过真实的架构,就会发现很少有一种完美的方式来做事。每家公司都有不同的架构。设计一个可扩展的系统是一个优化任务:有大量的约束(时间,预算,知识,复杂性,当前可用的技术等等),并且你需要建立适合这些约束的最好的东西。每一种技术,每种模式对某些事物都是有益的,而对其他事物则不是那么好。了解这些优点和缺点,优点和缺点是关键。

记住:没有一个最佳的系统设计。

当然,有最好的做法,你可以使用。但是最后,这一切都归结为在市场时间,系统复杂性,开发成本,维护成本,可用性等诸多方面之间的平衡。

能够理解和讨论这些权衡是系统设计(以及系统设计问题)的全部内容。

在你的准备中,不要试图找到完美的东西。相反,关注每个可伸缩性模式的优点,缺点是什么,以及为什么人们比其他模式更喜欢它。

把它放在一起,保持最新状态

在这一点上,你可以做的最有用的事情是拿出一个或两个系统,其中包含你学到的可扩展性课程。

最后,你将在整个职业生涯中得到良好的服务,以便及时了解可伸缩性如何演变。例如,10年前,没有亚马逊网络服务,公司被迫管理自己的基础设施。如今,使用EC2,RDS,S3,Elastic MapReduce等服务,您可以建立一个巨大的公司。所以,虽然AWS没有改变基本的可扩展性原则,但它确实改变了人们在扩展时需要熟悉的技术格局。因此,保持最新是非常重要的(否则你会重新发明轮子)。

面试

那么面试时你应该做什么?

首先,请遵循系统设计流程。你已经知道如何应用它,所以我们将会简短。不要跳过步骤,不要做假设,当问及时开始广泛深入。

其次,请记住,系统设计问题是一个观念交流平台。准备讨论权衡利弊。准备提供替代方案,提出问题,找出并解决瓶颈问题,根据面试者的偏好进行广泛深入的讨论。

不要保守:每当面试者挑战你的架构选择,承认很少一个想法是完美的,并概述了你的选择的优点和缺点。开放讨论期间面试官提出的新约束,并即时调整架构。

最重要的是,玩得开心。梦想建筑是一个非常刺激的心理过程 - 享受和保持积极。你已经具备了正确的知识,只要在面试中应用,你就会做得很好。

Tiny URL

在上一章中,我们说到了我们的系统面临着几个挑战:

  1. 每秒400个请求
  2. 有3TB的数据去存储并且快速查询

那么我们就要在抽象设计的基础上进行修改,使其成为可扩展的设计,并能够解决上面的限制:

    1. 应用服务层:
      • 从单独一台服务器可以处理多少数据开始
      • 用负载测试去测试速率
      • 增加一个负载均衡器,以及逐渐增加集群:解决通信量,增加可用性
    2. 数据存储
      • 数据特性
        • 上亿个对象
        • 每个对象都特别小(<1k)
        • 对象与对象之间没有关系
        • 读 是 写 的9倍(每秒360次读,40次写)
        • 5TBs urls,36GBs hashes
      • MySQL
      • 映射表
        • hash: varchar(6)
        • origin: varchar(512)
      • 在hash属性上建立聚集索引(36GB+),我们希望将这个索引存在内存中。
        • 垂直扩展MySQL服务器
        • 数据分片:5片数据,600GBs数据,8GB索引
        • 读写分离,主从复制(从多台从机读取,写入主机,由主机更新数据到从机)

 

posted on 2021-02-05 21:23  秦羽的思考  阅读(1318)  评论(0编辑  收藏  举报