系统架构设计(3)-可扩展性

简介: 即使系统现在可靠,不代表将来一定可靠。发生退化的最常见原因是负载增加:并发用户从最初的10,000 增长到 100,000或系统目前处理数据量超出之前很多倍。

即使系统现在可靠,不代表将来一定可靠。发生退化的最常见原因是负载增加:并发用户从最初的10,000 增长到 100,000或系统目前处理数据量超出之前很多倍。


可扩展性,描述系统应对负载增加的能力。它不是衡量一个系统的一维指标, 谈论“系统X是可扩展 ”或“不扩展”无太大意义。相反,讨论可扩展性通常得考虑:“若系统以某种方式增长,应对措施有啥”, “该如何添加计算资源来处理额外的负载”


3.1 描述负载


先得简洁描述系统当前的负载,才能更好讨论后续的增长问题(例如负载加倍,意味啥?)。负载可以用称为负载参数的若干数字来描述。参数的最佳选择取决于系统的体系结构,它可能是:


服务器的请求处理次数/s

数据库中写入的比例

聊天室的同时活动用户数量

缓存命中率

有时平均值很重要,但有时系统瓶颈来自少数峰值(大促时期尤为明显)。比如Twitter两个典型业务操作:


发推文:用户可快速推送新消息到所有粉丝,平均大约4.6k request/s, 峰值约12k requests/sec

页时间线(Home timeline)浏览:平均300k requests/s 查看关注对象的最新消息

仅处理峰值12k的消息发送看起来不难,但扩展性挑战重点其实不在消息大小,而是巨大的扇出( fanout,电子工程的术语,描述输入的逻辑门连接到另一个输出门的数量。输出需提供足够电流驱动所有连接的输入。事务处理系统中,用来描述为了服务一个输入请求而需要做的请求总数)结构:每个用户会关注很多人,也会被很多人圈粉。


对此有如下的


处理方案

方案一:关系型数据模型

将发送的新推文插入全局的推文集合。当用户查看时间线,首先找所有的关注对象,列出这些人的所有推文,以时间为序来排序合并。若以如下的采用关系型数据模型来支持时间:

16.png



可执行SQL:


SELECT tweets.*, users.*

FROM tweets

JOIN users ON tweets.sender_id = users.id

JOIN follows ON follows.followee_id = users.id

WHERE follows.follower_id = current_user

方案二:数据流水线万式推送

15.png


对每个用户的时间线维护一个缓存 ,类似每个用户一个推文邮箱。当用户推送新推文,查询其关注者,将推文插入到每个关注者的时间线缓存中。因为已预先将结果取出,之后访问时间就是线性性能,很快。


Twitter最初使用方案一,但发现主页时间线的读负载压力与日俱增,系统优化之路曲折,于是转向方案二,实践证明更好,因为时间线浏览推文的压力几乎比发布推文要高出两个数量级,基此,在发布时多完成一些事情可加速读性能。其实方案二的缺点也明显,在发布tweet时增加大量额外工作。考虑平均75个关注者和4.6k/s的tweet,则需每秒4.6*75 = 345k的速率写入缓存。但75这个平均关注者背后还隐藏其他事实,即关注者其实偏差巨大,例如某些用户拥有超过3000w的追随者。这就意味着峰值情况下一个tweet会导致3000w次写入!而且要求尽量快, Twitte目标是5s内完成,成为巨大挑战!


每个用户关注者的分布情况(还能结合用户使用推特的频率进行加权)是该案例可扩展的关键负载参数,因为它决定了扇出数。你的应用可能具有不同特性,但能采用类似原则研究具体负载。


Twitter案例最后是方案二得到稳定实现, Twitter正在转向结合两种方案。大多数用户的推文在发布时继续以一对多写入时间线,但少数大V用户除外,对这些用户采用类似方案一,其推文被单独提取,在读取时才和用户的时间线主表合井。这种混合方案能提供始终良好表现。


3.2 描述性能


描述系统负载之后,接下来设想如果负载增加将会发生什么。有两种考虑方式:


负载增加,但系统资源(如CPU 、内存、网络带宽等)保持不变,系统性能会如何变化?

负载增加,要保持性能不变,需增加多少资源?

这些都要关注性能指标。


批处理系统如Hadoop ,通常关心吞吐量(throughput),即每秒可处理的记录数或在某指定数据集上运行作业所需总时间。而在线系统通常更看重服务响应时间( response time),即客户端从发送请求到接收响应之间的间隔。


延迟( latency )与晌应时间( response time)

容易混淆的概念,并不完全一样。:


响应时间是客户端看到的 :除了处理请求时间(服务时间, service time )外,还包括来回网络延迟和各种排队延迟


延迟,请求花费在处理上的时间


即使反复发送、处理相同的请求,每次可能都会产生略微不同的响应时间。由于系统要处理各种不同请求,响应时间可能变化很大。因此,最好不要将响应时间视一个固定的数字,而是可度量的一种数值分布。


大多数请求的确快,但偶有异常,需要更长时间。这些异常请求有的确实代价高,如数据大很多。但有时,即使所有请求都相同,也会由于其他因素而引入随机的延迟抖动,比如上下文切换和进程调度、网络数据包丢失和TCP重传、垃圾回收暂停、缺页中断和磁盘I/O ,甚至服务器机架的机械振动等。


平均响应时间

服务请求的平均响应时间不是合适指标 ,因为掩盖一些信息,不能说明多少用户实际经历了多少延迟。最好用百分数(percentiles) 。


百分数(percentiles)

若已搜集到响应时间信息,按最快到最慢排序,若中位数响应时间200ms ,那意味着有一半请求响应不到200 ms ,而另一半请求需更长时间。中位数指标适合描述多少用户需等待多长时间: 一半用户请求的服务时间少于中位数响应时间,另一半则多于中位数的时间。因此中位数也称为50百分位数,缩写为p50 。中位数对应单个请求,这也意味着若某用户发了多个请求(例如包含在一个完整会话过程中或因某页面包含了多个资源),那么它们中


至少一个比中位数慢的概率远远大于50%。为弄清楚异常值,需关注更大的百分位数,如常见的第95、99、99.9 (缩写为p95、p99、p999 )值,分别表示有95%、99%、99.9%的请求响应时间快于阈值。 即若95百分位数响应时间为1.5s ,表示100个请求中的95个请求快于1.5s,而5个请求则需要1.5或更长时间。


采用较高的响应时间百分位数( tail latencies,尾部延迟或长尾效应)很重要,因为它们直接影响用户总体服务体验。如亚马逊采用99.9百分位数定义内部服务的响应时间标准,或许它仅影响1000个请求中的1个。但考虑到请求最慢的客户往往是买了更多商品,因此数据量更大。换言之, 他们是最有价值的客户。让这些客户始终保持愉悦的购物体验不是非常重要吗?亚马逊还注意到,响应时间每增加l100ms ,销售额就会下降约1%,其他研究则表明, 1s延迟增加等价于客户满意度下降6%。


有人说,优化这99.99百分位数(10,000个请求中最慢那1个)ROI 太低,进一步提高响应时间技术上代价更大,很容易受到非可控因素,如随机事件的影响,累积优势会减弱。


例如,百分位数通常用于描述、定义服务质量目标( Service Level Objectives, SLO )和服务质量协议( Service Level Agreements, SLA ),这些是规定服务预期质量和可用性的合同。例如一份SLA合约,通常会声明响应时间中位数小于200ms,99%请求的响应时间小于1s,且要求至少99.9%的时间都要达到上述服务指标。这些指标明确了服务质量预期,并允许客户在不符合SLA的情况下进行赔偿。


排队延迟往往在高百分数响应时间中影响大。由于服务器并行处理的请求有限(CPU核心数限制),正在处理的少数请求可能会阻塞后续请求,这种情况有时称为队头阻塞。即使后续请求可能处理简单,但它阻塞在等待先前请求的完成,客户端将会观察到极慢响应时间。因此,很重要的一点是要在客户端来测量响应时间。


所以,为了测试系统的可扩展性而人为地产生负载时,负载生成端要独立于响应时间来持续


发送请求。若客户端在发送请求之前总是等待先前请求的完成,就会在测试中人为缩短服务器端的累计队列深度,带来测试偏差。


3.3 应对负载增加的方案


现在真正讨论可扩展性了,当负载参数增加时, 如何继续保持良好性能呢。


实践中的百分位数

后台服务,若一次完整的服务包含多次请求调用,此时高百分位数指标尤为重要。 即使这些子请求是并行发送、处理,但最终用户仍然需等待最慢的那个调用完成。如下图 ,哪怕1个缓慢的请求处理,即可拖累整个服务。

14.png



即使只有很小百分比的请求缓慢,若某用户总是频 产生这种调用 ,最终总休变慢的概率就会增加(即长尾效应)。


最好将响应时间百分位数添加到服务系统监控 ,持续跟踪该指标。如设一个20min滑动窗口,监控其中的响应时间,滚动计算窗口中的中位数和各种百分位数,然后绘制性能图。一种简单的实现方案:在时间窗口内保留所有请求的响应时间列表,每分钟做1次排序。若这种方式效率太低,可采用一些近似法(如正向表减、t-digest或HdrHistogram)来计算百分位数,其CPU和内存开销很低。同时注意,降低采样时间精度或直接组合来自多台机器的数据,在数学上没有太大意义,聚合响应时间的正确方法是采用直方图。


针对特定级别负载而设计的架构不大可能应付超出预设目标10倍的实际负载。若目标服务处于快速增长阶段,则需要认真考虑每增一个数量级的负载,架构应如如何设计。


现在谈论更多的是如何在垂直扩展(升级更强大机器)和水平扩展(将负载分布到多个更小机器)之间取舍。在多台机器上分配负载也被称为无共享体系结构。在单台机器上运行的系统通常更简单,而高端机器昂贵,且扩展水平有限,所以无法避免需要水平扩展。好架构通常要做取舍,例如,使用几个强悍服务器仍可以比大量小型虚拟机来得更简单、便宜。


某些系统具有弹性特征,自动检测负载增加,然后自动添加更多计算资惊,而其他系统则得手动扩展(人工分析性能表现,之后再决定是否添加)。若负载高度不可预测,则自动弹性系统会更高效 ,但或许手动能减少执行期间的意外情况。


无状态服务分布然后扩展至多台机器相对比较容易


有状态服务从单节点扩展到分布式多机环境的复杂性会大大增加


因此,直到最近通常的做法一直是,将数据库运行在一个节点(采用垂直扩展策略),直到高扩展性或高可用性的要求迫使不得不做水平扩展。


而随分布式系统发展,至少对某些应用,上述通常做法或许会改变。乐观地说 ,即使应用可能并不会处理大量数据或流量,但未来分布式数据系统将成为标配。


超大规模系统往往针对特定应用而高度定制,很难有通用架构。背后取舍因素包括数据读取量、写入量、待存储的数据量、数据复杂度、 响应时间要求、访问模式等或更多的是上述所有因素叠加,再加上其他更复杂问题。


例如,即使两系统数据吞吐量折算后一样,但为每秒处理100,000 次请求(每个大小为1KB )而设计的系统,和为3个请求/min(每个大小2GB )设计的系统大不相同。


对特定应用来说,扩展能力好的架构通常会做出某些假设,然后有针对性地优化设计,如哪些操作最频繁,哪些负载是少数情况。若这些假设最终发现是错误的,则可扩展性的努力就白费了,甚至会出现与设计预期完全相反情况。对初创公司或尚未定型产品,快速迭代推出产品功能往往比技入精力来应对不可知的扩展性更重要。


可扩展架构一般从通用模块逐步构建而来,背后往往有规律可循,所以我们会多讨论这些通用模块和常见模式。

目录
相关文章
|
负载均衡 Cloud Native 数据库
构建高可用的云原生微服务架构:实现弹性和可扩展性
随着云计算技术的快速发展,云原生微服务架构成为了现代应用开发领域中的一种重要范式。它通过利用云服务提供的弹性和可扩展性,为企业构建高可用的、面向未来的应用程序。本文将探讨云原生微服务的概念、核心原则以及一些关键技术,帮助您设计和构建具有弹性和可伸缩性的架构。
1094 1
|
6月前
|
存储 分布式计算 分布式数据库
【专栏】云计算与分布式系统架构在数字化时代的关键作用。云计算,凭借弹性、可扩展性和高可用性,提供便捷的计算环境
【4月更文挑战第27天】本文探讨了云计算与分布式系统架构在数字化时代的关键作用。云计算,凭借弹性、可扩展性和高可用性,提供便捷的计算环境;分布式系统架构则通过多计算机协同工作,实现任务并行和容错。两者相互依存,共同推动企业数字化转型、科技创新、公共服务升级及数字经济发展。虚拟化、分布式存储和计算、网络技术是其核心技术。未来,深化研究与应用这些技术将促进数字化时代的持续进步。
188 4
|
3月前
|
存储 监控 安全
|
3月前
|
存储 安全 关系型数据库
"揭秘!如何设计数据库架构,让信息系统心脏强健无比?一场关于数据效率、安全与可扩展性的深度探索"
【8月更文挑战第19天】数据库架构是信息系统的核心,关乎数据存储效率与安全及应用性能和扩展性。优秀设计需综合考量业务需求、数据模型选择、查询优化、事务处理、安全性和扩展性。首先,深刻理解业务需求,如电商系统需高效处理并增长商品、订单等数据。其次,基于需求选择合适的数据模型,如关系型或非关系型数据库。再者,优化查询性能与索引策略以平衡读写负载。同时,考虑事务处理和并发控制以保证数据一致性和完整性。最后,加强安全性措施和备份恢复策略以防数据风险。通过这些步骤,可以构建稳健高效的数据库架构,支持系统的稳定运行。
40 0
|
4月前
|
设计模式 消息中间件 监控
如何在Java项目中实现可扩展性架构
如何在Java项目中实现可扩展性架构
|
4月前
|
消息中间件 负载均衡 Kubernetes
构建可扩展性强的返利App后端服务架构
构建可扩展性强的返利App后端服务架构
|
4月前
|
设计模式 消息中间件 监控
如何在Java项目中实现可扩展性架构
如何在Java项目中实现可扩展性架构
|
5月前
|
消息中间件 存储 监控
通过将大型应用拆分成一系列小型、独立的服务,微服务架构为后端开发带来了更高的灵活性、可扩展性和可维护性
【6月更文挑战第10天】本文探讨了构建高效微服务架构的后端开发最佳实践。微服务的核心原则是服务独立、去中心化、自治和轻量级通信,优势在于可扩展性、独立性、技术灵活性和团队协作。实践中,应注意服务的拆分粒度,选择合适的通信协议(如RESTful、RPC、消息队列),处理数据一致性与分布式事务,实施服务治理和监控,以及确保安全性与权限控制。未来,微服务将结合服务网格、容器化和云原生技术,持续发展和优化。
119 0
|
6月前
|
存储 并行计算 网络协议
|
6月前
|
缓存 前端开发 JavaScript
云LIS系统源码 B/S架构,SaaS模式,可扩展性强
云LIS系统源码 B/S架构,SaaS模式,可扩展性强
105 0