System Design——系统设计过程(四)系统的可扩展性设计

时间:2021-08-24 03:40:01

英文原文链接:https://www.hiredintech.com/classrooms/system-design/lesson/58

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

-----------------------------分隔符------------------------------------------------------------------------------------

系统设计过程:

  • Step 1  约束和用例
  • Step 2  抽象设计
  • Step 3  理解瓶颈
  • Step 4  可扩展性设计

-----------------------------分隔符------------------------------------------------------------------------------------

当前面的准备工作都已经做好,必要的估算和预期也已经准备妥当后,便可以进一步做详细的设计了——通常来说这意味着需要从系统的可扩展性角度来做。

基础!

首先需要了解和scaling相关的一些概念。知道这些概念,理解这些概念怎么用,同时能够讨论他们的优势和劣势在scaliablity问题中非常重要。

  • Vertical scaling:垂直扩展;这意味着提高在系统中单个节点的能力,比方说增加更多的CPU更多的核数、扩充RAM使得性能更优处理速度更快。
  • Horizontal scaling:水平扩展;意味着通过增加更多,但是性能并非更强的节点,来处理更多的负载工作
  • Caching:
  • Load Balancer:
  • Database Replication:
  • Database Partition:

分析!

1、Vertical vs. Horizontal

  • 对于垂直扩展和水平扩展,更形象的一种比喻人们在关卡门口排队,目的是从这道门通过关卡。在垂直扩展的情况下,相当于是把门扩大了一圈,使得更多的人可以通过;而对于水平扩展,相当于是多增加了几个门(但是每个门和最开始的那个是一样的),让人能够更好的通过。这两个都是各有利弊,没有绝对的好与坏,选择哪种扩展方式取决于你的用例以及待解决的问题,同时开销和软件供应商也是需要考虑的因素。
  • 由于水平扩展是通过增加数目来达到提升性能的,因此它不存在单点故障的问题;而垂直扩展如果只有单台机器,则需要考虑单点故障。
  • 水平扩展可以使得系统在工作的时候完成容量升级、性能提升;但是垂直扩展是需要下线时间的(系统不可用期)。在某些机房,可以实现不停机的情况下自动扩种机器从而完成水平扩展;而垂直扩展需要停机来更换更强更优的部件。
  • 状态数据问题在两种扩展模式下的不同处理方式;往往服务器上保存着许多用户的状态数据,比如用户session数据等。对于这类数据往往不推荐用水平扩展的方式来扩展而推荐用垂直扩展的方式。因为如果采用了水平扩展之后,没有实现很好的数据拷贝,万一用户访问到了一台没有用户数据的机器上,则会导致出错。
  • 水平扩展模式下的机器往往是可以任意处理的,就像电池,经常会被废弃;而垂直扩展模式下的机器就像“圣殿”,往往会保存很多内容而且会保持很久。
  • 并不是对于所有的情况水平扩展都是work的,还需要谨慎的考虑吞吐量和延迟;而垂直扩展往往会通过把所有的依赖安装在本地来构建。
  • 在水平扩展中,需要以用例或者应用来分割cluster,如果将不同的服务放在同一个集群中——比如100台机器的集群,50台服务于服务A,另外50台服务于服务B——那么现实的情况中往往这两个服务的访问量是不同的,可能其中一个服务的机器过于繁忙而另一个服务的机器几乎处于空闲,这样使得整个集群很不健康。因此一个很重要的原则是:依照用例或者应用来进行cluster的划分。
2、Caching 尽管有了分布式数据库用来做数据的存储,使得可以存储TB级别的数据,但是仍旧会发现若用户访问某个需要从数据库取大量的数据的网页时,往往访问很慢,体验很差。为了解决这个问题的方法就是利用cache(缓存)。 当提到缓存时,往往是像memcache或redis这样的内存型缓存。需要注意的是,永远不要试着去做基于文件的缓存,它只会让你的数据备份和服务器自动扩展充满痛苦。 回到内存型缓存上来。缓存是一个简单的key-value存储,作为缓冲层存在与应用和数据存储中心之间。当你的应用需要读取数据时,无论何时第一步应当是从缓存中获取数据,当且仅当所要取的数据不在缓存中时才回去主数据源中取数据。为什么需要这么做呢?因为缓存读写速度非常快,保证RAM中的数据集和请求能够尽可能快地被处理。举个例子,redis可以完成每秒百万千万次的读操作。同样写操作也很快,特别是增量操作。
以下是两种缓存方式: #缓存数据库请求: 这是最常用的缓存模式。当你对数据库的数据做一次查询,就会在缓存中存一份数据,其key为你的请求的hash值。当你下一次执行这次请求,会先去查缓存中是否存在这条查询,看是否有结果。这种模式有很多问题。最主要的一个问题是缓存内容的时效(expiration)。当你缓存一个复杂的请求时,删除缓存结果是一项很麻烦的工作。比如一个表格中一个单元数据发生了变动,你需要删除所有请求结果包含这个表格单元的缓存请求。Got it?
#缓存对象: 这种缓存模式相较于上者要优秀。就像编程中一样,把需要缓存的数据看作对象(object)。在程序中将你从数据库中取出的数据集组装成对象,然后把类对象或者组装的数据集缓存起来。下面举一个例子: 例如你写了一个“Product”类,有一个“data”属性,它是包含“价格”、“文本”、“图片”、“用户意见”的队列。这个data属性通过类中几个方法来填充,这几个方法包含了很多难以缓存的访问数据库的操作,因为其中许多东西都相互关联。现在做以下操作:当你的类完成对这个队列的组装,直接把这个队列存入缓存,或者更好的做法是,将这个class直接存入缓存!这使得不论何时哪些属性发生了变动都能更好的修正,同时让你的代码更加快速同时更加具有逻辑性。 最重要的部分是,这种缓存方式使得异步操作变成了可能!应用背后一直有一支“军队”在为应用最新的缓存对象。而且应用只会去消费最新的缓存对象,而不会接触到数据库。
3、数据克隆 高扩展性的web service服务器都有一个负载均衡器(load balancer)在前面负责均衡,这个负载均衡器将负载(用户的请求)分发到应用服务器的不同组别或者不同集群,这就意味着一名用户在第一次访问该服务的时候访问到了server 2,然后第二次请求访问到了server 9, 然后第三次请求又访问到了server 2。 不论尝试访问多少次,这名用户都应当访问到相同的结果,不论它访问到哪台具体的server服务器上。这个问题引出了scalability的第一条金律:每台服务器上都包含相同的基准代码,而不应该在服务器硬盘或者内存中存储任何比如session,用户图片等用户相关联的数据。 session应该存储在可以被所有应用服务器可以访问的*数据存储中心中。它可以是一个外部的数据库或者外部持久化缓存,比如redis。外部可持久化缓存比外部数据库往往有更好的表现。 下一个需要考虑的问题就是部署(deployment)。如何才能保证每次的代码变动会被成功更新到每台服务器上,不遗漏一台服务器呢?这个问题已经被Capistrano(是一种在多台服务器上运行脚本的开源工具,它主要用于部署web应用。它自动完成多台服务器上新版本的同步更新,包括数据库的改变。)解决。
(今天暂时更到这里,后续明天再补上...)