即使一个系统当前工作的很可靠,但也不意味着未来它也必定会可靠的工作下去。退化的一个常见原因是负载增加:也许系统的并发用户数已经从10000增长为100000,或是是从1百万多1千万。也许他需要处理比之前体量更大的数据。
可扩展性Scalability是我们用来描述系统处理负载增加能力的术语。这里需要注意的是,这并不是我们给系统附上的一个单维度的标签,就好比说X是可扩展的,而Y是不可扩展的,这是毫无意义的。讨论可扩展性意味着要考虑这样的问题:
负载的描述
首先,我们需要简要描述系统上的当前负载。只有这样我们才能讨论增长问题(如果负荷增加一倍会发生什么?)。负载可以用一些我们称为负载参数的数字来描述。最佳的参数选择取决于您的系统架构:它可能是每秒向Web服务器发出的请求,数据库中读写的比率,聊天室中同时活动的用户数,缓存的命中率或其他内容。也许一般情况对您来说很重要,或者您的瓶颈由一小部分的极端情况的所把控。
为了使这个想法更具体一点,让我们用Twitter来作为例子,使用其2012年1月份公布的数据。Twitter主要做的两件事是:
简单的处理每秒12000次的写是相当容易的。然而,对于Twitter来说,其扩展的挑战不是消息的大小,而是fan-out – 每个用户都关注了很多人,并且每个用户又被许多人关注。一般有两种方式实现这两种操作:
- 把发布的某条信息简单的插入一个全局的信息集合中。当用户请求刷新主页时,就在其中查找所有他关注的人,然后找到每个人所发的信息,然后再把他们按时间排序合并在一起。在一个如下图的关系型数据库中,你也学会这样写一个请求:
SELECT tweets.*, users.* FROM tweetsJOIN users ON tweets.send_id = users.idJOIN followd ON follows.followee_id = users.idWHERE follows.follower_id = current_user
- 为每个用户的主页时间轴维护一个高速缓存,就像为每个接手的用户准备一个信息信箱。如下图,当一个用户发布一个信息时,查找所有关注这个人的用户,把这个新信息插入到每个人的主页时间轴的告诉缓存中。这样的话对于主页时间轴的请求就会变得便宜,因为都是提前算好了的。
第一版的Twitter使用的是方案1,但系统对于主页时间轴的查询表现的很挣扎,所以切换到了方案2。这样的效果更好,因为发布推文的平均速率比本地时间轴读取速率低两个数量级,因此在这种情况下,最好在写入时进行更多工作,而在读取时进行更少的工作。
但是,方案2的缺点是每发布一条信息需要很多额外的工作。平均下来,每条信息要推送给75个关注者。所以4.6k/s变成了345k/s去写主页时间轴的缓存。但这平均的背后隐藏的是每个用户的关注者的数量是变化很大的,一些用户可能会有3000万个关注者,这意味着单条信息可能会导致超过30000万次的写入主页时间轴!Twitter尝试着及时做到这一点,它们尝试着在5秒内把信息发送给所有的关注者,这是一项巨大的挑战。
在Twitter的例子中,每个用户的关注者分散(或许还要权衡这些用户发布信息的频次)是讨论扩展性的一个关键元素,因为这决定了fan-out的负载。您的应用程序可能具有完全不同的特征,但是您可以将类似的原理应用于有关其负载的推理。
Twitter轶事的最后一个转折:虽然稳健地实施了方法2,Twitter正在转向这两种方法的混合体。大多数用户的推文在发布时仍会被fan-out到主页时间轴上,但是少数的有大量关注者(即名人)的用户除外。用户关注的任何来自名人的推文都是分别获取的,就像在方法1中一样,在读取时与该用户的主时间轴合并。这种混合方法能够始终如一地提供出色的性能。
性能的描述
一旦你描述完你的系统的负载后,你就能开始调查当复杂增加时发生了什么。你可以以这两种方式观察:
这两个问题都需要性能数字,因此让我们简要介绍一下系统的性能。
在像Hadoop这样的批处理系统中,我们通常关心的是吞吐量 – 每秒处理了多少条记录,或是对于一个特定尺寸的数据集运行完一次工作一共花了多少时间。在在线系统里,通常更重要的是服务的相应时间,也就是客户发送请求和收到响应的间隔。
Latency延迟和response time 响应时间
延迟和响应时间经常被用来表示相同的意思,但他们是不同的。响应时间是指除了真实处理请求的时间,还包括了 络延迟以及队列延迟。延迟指的是一个请求等待被处理的时间。
即使你一遍遍的发送相同的i请求,你得到的每次请求的响应时间还是会有轻微的不同。实际上,在一个处理各种各样请求的系统中,响应时间也是有很大不同的。因此,我们不能只把响应时间看作一个单一的数字,而是作为你测量的值的分布distribution。
某100次服务请求的响应时间
上图中,每个灰色的柱条代表了一次服务请求,它的高度代表了请求花了多少时间。大多数的请求是相当快的,但也有个别的出跳者,花的时间更长。也许速度慢的请求本质上就需要更大的开销,比如说处理更多的数据。但是即使在这样的情形下,你也应该认为所有的请求应该花费同样的时间,即使你还有如下的变化因素:
平均响应时间是进程能看到的 告。然而,如果你想知道“一般典型的”相应时间,平均值并不是一个好的测量手段,它不会告诉你到底有多少用户经历着延迟。
一般我们使用百分比。如果你有一个相应时间的列表,那就先从快到慢排序,然后正中间的点就是中位数,也就是如果中位数的相应时间是200ms,那么你的相应时间中的一半是小于200ms的,一般是大于200ms。
如果你想知道用户一般需要等待多久能得到相应,中位数就是一个好的测量标准:一般的用户不到200ms就得到回应了,一半的用户要花更长的时间。中位数也就是50%。记作p50。这里需要注意的是,中位数指的是单个请求。
为了得出你的出跳者到底有多糟糕,你应该先看看高的百分位。通常一般是p95,p99, p999(99.9%)。他们是位于95%, 99%和99.9%的响应时间的阀值。比如说,如果p95的响应时间是1.5s,那么也就是说95%的请求花了不到1.5秒,5%的请求花了多于1.5秒时间。
响应时间的高百分比,也被叫做tail latencies,因为直接对用户体验有影响,所以非常重要。例如,亚马逊以99.9%的百分比描述内部服务的响应时间要求,即使它仅影响1,000个请求中的1个。这是因为往往最慢请求的用户,在账户上有着最多的数据,而这是因为他们买的最多,因此他们是最有价值的用户。通过确保 站对他们的快速响应而使客户高兴是很重要的。
亚马逊还发现,响应时间增加100毫秒可减少销售额下降了1%,而其他 告则指出,速度降低1秒会使客户满意度指标降低16%
另一方面,优化第99.99个百分点(每10,000个请求中最慢的1个)被认为过于昂贵,并且无法为亚马逊带来足够的收益。很难在很高的百分位数上缩短响应时间,因为它们很容易受到您控制范围之外的随机事件的影响,并且收益正在减少。
例如,百分位通常用于服务级别目标(service level objectives,SLOs)和服务级别协议(service lebel agreement,SLA),这些定义了服务的预期性能和可用性。 SLA可以声明如果服务具有以下功能,则认为该服务已达标:
中值响应时间少于200毫秒,并且99%的相应时间小于1s(如果响应时间较长,则很可能会下降),并且至少99.9%服务需要达标。
这些指标为服务客户设定了期望,并允许客户在未达到SLA的情况下要求退款
队列延迟通常在较高的响应时间中占很大一部分。服务器只能并发的处理很小一部分事情(比方说,受限于CPU cores),因此只需要少量的慢速请求就能卡住后续请求的处理,这种影响被称为head-of-line blocking。即使后续的请求在服务器上处理的很快,但客户端,因为等待之前的请求完成,看到的是整体上的一个慢速的相应时间。因此,在客户端测试响应时间是非常重要的。
为了测试系统的 可扩展性,当人为的生成负载时,生成负载的客户端需要确保发送请求与响应时间无关。如果客户端在发送下一个请求之前等待上一个请求完成,这样的行为会人为的让测试中的请求队列小于真实情况,这会使测量值产生偏差。
处理负载的方法
适合于一个负载级别的架构不可能应付该负载的10倍。如果您正在开发快速增长的服务,则可能需要在每个数量级的负载增加(甚至可能更频繁)上重新考虑架构。
人们经常谈论二分法
在多台计算机之间分配负载也是被称为shared-nothing架构。运行于单台机器上的系统通常来说更简单,但高端的机器很贵,对于繁重的工作量来说scaling out是不可避免的。实际上,良好的体系结构通常包含务实的方法混合:例如,使用多个功能相当强大的计算机仍然比大量小型虚拟机更简单,更便宜。
一些系统具有弹性,这意味着它们在检测到负载增加时可以自动添加计算资源,而其他系统则是手动扩展的(人工分析容量并决定向系统中添加更多计算机)。当负载高度的不可预期时,弹性系统是有用的,但人为的扩展更简单,并且会有更少的操作上的惊喜。
虽然在多台机器之间分布无状态服务非常简单,但是将有状态数据系统从单个节点转移到分布式设置可能会带来很多额外的复杂性。因此,一般的做法是,保证你的数据库在单个机器上不停的scaling up,直到扩展的费用或是高可用性需求强制你将其分发。
随着工具和分布式系统的架构变得越来越好,这种一般的做法也在改变,至少一些应用是这样的。可以想象分布数据系统将在将来成为默认系统,即使对于无需处理大量数据或流量的用例也是如此
大规模运行的系统的架构通常是特定于应用程序的,没有通用的,一刀切的可扩展体系结构。问题可能是读取量,写入量,要存储的数据量,数据的复杂性,响应时间要求,访问模式,或者(通常)所有这些因素的混合体以及许多其他问题
比方说,一个被设计成每秒处理100000个请求,每个大小是1KB的系统与一个被设计成每分钟处理3个请求,单个请求大小是2GB的系统,看上去是不同的,即使两个系统有相同的数据吞吐量。
一种针对特定应用程序的良好扩展的架构是建立在假设基础之上的:
如果这些假设被证明是错误的,那么在规模扩展方面的工程努力就是浪费的,最糟糕的是适得其反。因此,在早期启动阶段或未经验证的产品中,通常重要的是能够快速迭代产品功能,而不是去因为一些假设的未来负载去做扩展。
尽管可扩展架构是特定于特定应用程序的,但是他们通常是由以熟悉的模式排列的通用构建块构建的。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!