负载大逃亡:四十二路怪兽联军及七条逃生法则(很喜欢)

本文探讨了在负载增加时程序可能出现的42种问题,包括对象管理、资源使用、同步问题等,并提供了7条设计建议来提升程序的扩展性和可靠性。

负载大逃亡:四十二路怪兽联军及七条逃生法则

摘要:横向扩展与纵向扩展帮助我们处理了大量的高负载问题,然而优秀的程序设计仍然是不可忽视的。一个有设计缺陷的应用程序在低负载情况下可能表现不出来,然而随着负载的增加,各种各样的问题随之而来。Todd Hoff展示了负载加重后出现的42个“怪兽”级问题,并展示了7条逃生法则。

尽管你很精心的“烹制”你的应用程序,但是随着负载的增加,所有灾难都将降临。当然你可以使用横向扩展或纵向扩展,但是你同样可以更好的进行编程,让你的系统可以支撑更大的负载。这会给你节约成本,因为可以减少所添加的服务器数量,同样还可以提高整个应用程序的可靠性和响应速度。同时,这也应该是优秀工程师的分内之事。

下面一睹Tod Hoff在 High Scalabilty上带来的42怪兽军团峥嵘:

大量的对象

一旦对象数量太多,我们都会面临扩展问题。显然随着对象数量的剧增,可以为各种类型对象使用的资源将愈加捉襟见肘。

故障得不到恢复会导致无限的事件流

在大型网络故障的情景下,不会存在任何时间做系统恢复,系统将一直处于重负之下。

大量的高优先级工作

举个例子,路由的重定向就是个高优先级活动。如果存在大量既不可以被卸载又不可以被降级的路由重定向,资源将不断的被消耗,用于支撑这些高优先级工作。

数据流增大

随着数据体积的增大,系统负载将加重。随着请求源的增多,系统负载将加重。

功能蔓延(Feature Creep)

随着更多超过预期的特性添加,系统中的漏洞将会出现。

客户端的剧增

更多客户端意味着更多资源的占用。更多的线程被创建用于驱动事件。更多的内存在客户端请求队列上被占用,更多网络带宽被用于通信,每个客户端数据更需要专门的维护。

不完善的设计决策

不完善的设计可能会导致扩展隐患。

  • 设计不足以处理大量对象
  • 缺乏终端到终端的应用级流控制(flow control)
  • 应用级重试导致消息的再分配和发送
  • 常用内存的占用比例
  • 不是真正可靠的发布
  • 特殊数据结构的过度内存消耗
  • 消息协议不能覆盖所有错误场景
  • 使用硬盘作为存储
  • 磁盘同步中不使用块复制
  • 应用级协议还可以做的更好
  • 低功率CPU应对更多功能带来的负载增加
  • 操作系统不支持过程体系结构(process architecture)
  • 硬件缺少对操作的支持,甚至是简单的单消息推送
  • 常见网络问题,比如:当负载增加时,ARP故障

假设是无效的

大量无效的假设,比如:预期中的内存使用、某个操作/处理会持续多长时间、什么地方会产生响应超时、某个时间点会消耗多少资源、会发生什么类型的失败、系统中不同点的延时、请求队列的长度等等。

内存不足

随着负载的增加:系统的基本使用内存和峰值使用内存都会增加,这可能会导致OOM。

CPU饥饿(CPU Starvation)

更多的对象需要更长的时间去处理,因为它们必须要对更多的对象进行操作。这将降低CPU的有效性,同时其它的操作也可能会饿死。一个地方的饥饿会导致另一个地方也发生类似的情况。也可能会出现没有足够的CPU去处理需要立刻执行的工作,这可能是因为工作数量太多或者是特定情况下需要做大量优先级高的工作。

原始的资源使用增加

更多的对象占用更多的资源。如果有人希望支持1000万个对象,你可能就无法实现,因为你根本没有足够的内存。

隐式的资源使用量增加

大部分特性都需要耗费原始资源之外更多的资源,比如:对比之前你只将对象储存在一个表中,你现在将对象存入了两个表,那么耗费的资源将是之前的两倍。同样还可能会在以下方面耗费更多的资源:队列的长度、磁盘空间、二次数据拷贝增加的时间、将数据加载到应用程序中的时间、用于处理这些操作的CPU使用率、引导时间等等。

只知道疯狂的压榨CPU,对系统无真正的认知

无限工作流在许多新型系统中都有实现。Web服务器和应用程序服务器支撑着非常大的用户群体,不断的新工作会带来无限的事件流。基于7×24小时永无止境的新工作,CPU使用率很容易的就会被推倒100%。

通常情况下100%的CPU使用率会被当作一个不好的标志。为了应对,我们建立了复杂的基础设施、机器集群、冗余用于负载的平衡。

因为CPU不会说它很累,所以你可能一直让其处于满载状态。在服务器领域中,我们为了得到需要的响应时间通常会尽可能的压榨CPU;想法就是:如果我们不让CPU最高效率的运转,新的工作可能就会得不到理想的延时,旧的工作也不可能尽快的完成。

然而一直将CPU压榨到100%真的没问题吗?真正的问题就在于我们使用CPU有效性和任务优先级的方法:在系统架构中我们只对系统做简单的认知,而不是弄懂系统的低等级工作流,然后使用这个信息制定合适的调度决策。

除了基于负载平衡服务器做笨拙的架构决策,以及猜测会使用到的线程数量及这些线程的优先次序,我们并没有使用工具做任何对架构有益的事。

扩展一个系统首先要对其做深刻的认知,而当前的框架很少有说明应用程序的运行机制。

延时增加

随着负载增加(规模变大),延时可能和你想象的完全不同。CPU饥饿是导致这个问题的主要原因。

错误的任务优先级

低负载下制定的优先级方案可能完全不适用于高负载情况。这种情况在差的流程机制下尤为明显:给高优先级的任务指定一个低的优先级,会导致速度下降以及增加内存使用,因为低优先级任务得到运行的机会很小。

队列长度不够

大量的对象意味着更多的同步操作,从而队列的长度将远过于前。

引导时间变长

更多的对象将需要更长的时间从磁盘加载到内存中,因此引导时间必然变长。

同步时间变长

对象越多,应用程序越需要更长的时间去完成相互之间的同步。

大场景下的测试不够

随着结构变大,测试步骤所需要的开销将越来越大,所以在大型结构下测试的时间很少。在开发过程中你不可能使用到大型系统,就如同开始时你的系统并没有扩展。

操作需要更长的时间

如果某个操作是针对所有对象的,那么随着对象增多其必然会花费更长的时间。表格同样会变大,所以在之前查询很快速的表格,对象变多后也会变慢。

更多的随机错误

可能会出现正常操作中不会遇见的某些错误。ARP请求、文件系统,消息、响应等都会出现你预期不到的故障。

为错误打开了更大的窗户

规模的变大意味着错误有更多的发生机会,因为所有操作都要花费更长的时间。小数据集的数据交换可能会很快结束,这就意味着超时或重启只有很小的概率发生;然而在你对系统进行扩展的同时,你还扩宽了错误进入的大门,所以一些错误将首次进入你的眼帘。

超时适应不了扩展

任何设定在小数据集上的超时随着数据集变大都将失去作用。加上CPU饥饿问题,你的代码甚至得不到任何机会运行,初始的超时设定就更加无效了。

重试适应不了扩展

在错误发生之前没有任何途径给应用程序选择一个合适的重试次数,因为没有足够的信息让你做这项决策。1秒4次的重试真的有用?为什么不是20次重试?

优先级继承

长时间使用大范围锁将出现优先级继承问题。

内存消耗模式的改变会耗尽节点资源

在一种规模下,你可以取得发生器中的所有数据;而在另一种规模下,你可能就会耗尽队列空间或者是内存。举个例子:在轮询下一个队列之前,使用轮询器轮询一个远程队列中所有数据源;在队列中排队项很少时,这会工作的很好;但是一个特性的改变就可以增加远程队列中的排队项数量,而轮询器就会耗尽一个节点上的所有资源。

监控器超时

100% CPU使用可能会导致监控器超时。在低负载系统上可能会很少出现,然而高负载系统上则会经常出现。

内存泄露加速

随着系统扩展度增加,你不曾重视的缓慢内存泄露可能会增加到你不会相信的速度。

被遗忘的锁再次出现

放错位置的锁,在低扩展系统下可能不会引起注意,然而在接到指令前这个可能永远占用着CPU的线程在高扩展情况下就可能产生问题。在高扩展系统中可能会出现更多的抢占,这就意味着不同线程同时访问数据的机会增多。

死锁的可能性增加

不同的调度模式通过不同的路径使用代码,这就给死锁的出现创造了更多的机会。举个例子:某个文件系统会因为CPU的使用率过高而得不到运行,恰好的是这个文件系统在运行过程中莫名其妙的中断并且占用了100%的CPU使用率,这样的话它永远都不会得到运行。

被破坏的时钟同步

时钟同步并不具备很高的优先级,所以当CPU和网络资源变得紧张时,不同点上的时钟将不再保持一致。

记录器(Logger)丢失数据

如果队列的长度不足以支撑负载或者是CPU没有时间分给记录器去发送记录数据,那么将会出现记录器丢失数据的情况。取决于队列的类型和长度,这可能导致OOM。

无法完成在预期的时间启动计时器(timer)

一个繁忙的系统可能无法在预期的时间启动计时器,这可能在系统的其它部分造成一连串的延时。

ARP失效

在高CPU使用率或者高网络负载情况下,ARP可能会在机器之间失去作用。这意味着表格在修改之前(可能永远都不会),数据包可能发给了错误的机器。

文件描述(File Descriptor)符限制

通常情况下,每台主机上的描述符数量都存在一个固定的限制,而设计的限制数量必须低于主机默认的限制数量。如果发生描述符池泄露,一个拥有大量连接(ftp、booting、clients等)的设计将会出现问题。随着负载增加,描述符的数量有可能会达到一个峰值,描述符池泄露可能耗尽描述符池的可用性。

Socket缓冲限制

每个Socket都拥有预分配大小的缓冲空间,大量的Socket会减少可用内存的数量。随着负载的增加,可能会出现空间不足的情况。这同样与优先级有关,因为一个任务可能没有足够的优先级去读取Socket内的数据;同样在发送方,一个高优先级的任务可能会淹没在一个低优先级任务的不断消息请求中。

启动镜像服务限制

每个节点同时服务的镜像节点数量是有限制的,FTP服务器基础设施必须限制服务的节点数量,否则将出现CPU饥饿。

无序的信息

重压下的消息系统可能会发送无序的消息,这可能会促成非幂等性操作,从而造成大量的问题。

协议相关

必须谨慎的定制你的应用程序协议,否则随着程序的扩展,将会带来大把问题。

连接限制

将某种服务器作为中心服务器:在连接10台客户端时,可能会有优异的表现;然而连接的客户端数量变成100时,可能就会适应不了对响应的需求。这种情况下,响应时间可能会随客户端数量的增加呈线性增高,我们称之为O(N)复杂度,同样也可能会出现其它复杂性的问题。比如,我们需要一个网络中的N个节点都可以相互通信:如果我们将它们都连接到一个通信枢纽的话,需要O(N)条电缆;但是如果我们把它们做相互连接的话,则需要O(N^2)条电缆。

分层架构

这里有一个很好的总结,所以此处只做简单的叙述:基于分层的架构永远都与低延时、高吞吐量的程序绝缘,问题在于分层架构本身就是用于处理历史数据。在客户端的时代到互联网时代的过渡中,分层架构确实是解决可扩展性的不二选择。

先前问题的关键是如何对应用程序进行扩展,让其可以支撑数十万的用户。当然现在我们已经知道这个问题的答案就是N层架构,扩展性通过表示层上的负载均衡器实现。事实上,它确实解决了这个问题;然而随着问题的衍变,当下许多行业需要考虑的不仅是扩展用户体验,还必须考虑数据的体积问题。

多重处理器的性能问题

当处理器被要求在巨量不相干任务中切换时,硬件缓存加速可能会失效。


负载加重的笼罩下的“怪兽军团”已现,开发者又该如何完成程序的设计,才能让程序既易于扩展又具备高可靠性?下面来看一下High Scalability上的一篇姐妹文—— “7条法则以应对负载怪兽的袭击”,当然这都是在程序低扩展等级编写下需要注意的事项:

1. 限制资源使用的比例

这在实现扩展的应用程序中可能是最重要的规则,可以这么认为:

  • 将资源限制到你认为可以支撑应用程序的等级,比如:保证可以在内存中处理一定数量的对象。所以如果我们一直按比例添加资源,那么就可以防止资源枯竭情况的出现。
  • 针对个体资源试用不同的设计方式

一些例子:

  • 我们需要保存一个订单列表,订单中商品的金额大于20美元(随便多少)。这种情况下内存中保存的绝不能是这些商品,因为商品的数量可能远大于订单的数量。你可以对比列表中订单的数量和资源的使用率,这样你就清楚了你的系统可以支撑多少个订单。
  • 使用Merge Aggregation:同一个对象说的所有操作应该整合到一个请求中,而不是为每个操作分别做请求。比如,一个create和一个update就可以整合进一个请求。

2. Merge Aggregation

在Merge Aggregation中,独立的数据和/或命令聚合到一处,用的是规则1的思想。

举个例子,如果一个对象中包含了以下几个命令序列:

  • Create
  • Update
  • Update

这三个分开的请求,可以融合到一处。如果有个循环运行了这个请求100次,那么我们的队列中始终只有一个请求。

另一个例子是属性修改事件,独立的改变也可以融合到一处。想象一下这样做的益处有多大,队列的长度永远都不会超过对象的数量,不管触发了多少事件。完成这一点需要通信子系统的配合,所以必须确保相对智能。

3. Delete Aggregation

在Delete Aggregation中,数据和/或请求在允许的情况下将会被删除。比如,做以下两个操作:

  • Create
  • Delete

在这个聚合中,许多create和delete操作将会被删除,大量的资源将被释放。

4. Batch Aggregation

定时分析的批处理将会把大量的数据整合在一起,从而大幅度的提高性能。逐个的进行操作永远比批量的处理来的慢。理念上应用程序不需要手动的做批处理业务,比如:框架会帮助你完成这个聚合。

5. Change Aggregation

在Change Aggregation中,所有的改变都将会被聚合到一处。当然这与Merge Aggregation不同,在Change Aggregation中我们注意到的是对象/数据改变的状态,而不是它被改变了多少次;取代为每次改变做记录,我们将把它的值发送给一个客户端,然后告诉它事情已经改变了。因为在大型系统中,我们不可能为事物的每次改变做记录。

6. Integration Aggregation

在Integration Aggregation中,事件只有在它存在过一个周期被关闭后才会被建立。

最常见的例子。一个警报只有在聚合周期结束后才被设置。当硬件发生问题等其它情况,我们可以建立一个alarm storm。

7. 卸载

卸载同样是个扩展方案,在这里工作将会被拒绝直到有足够的资源去运行它。

举个例子,在一个呼叫处理系统(call processing system)中,呼叫的数量将会被限制。任何在限制之后的出现的呼叫都会被拒绝,这将会给现有的呼叫足够的处理时间。

一些其它的卸载例子:

  • 限制单节点FTP上的Session数量
  • 在忙碌时将改服务器上的请求转发到另一个服务器
  • 公共电话系统在提示所有的呼叫正忙去阻止新呼叫的接入

写在最后

当然一篇文章不可能包括负载加重后出现的所有问题,也不可能包括所有的应对方案。但是可以肯定是对自己系统做足够的认知,基于充足的信息做好设计决策,才能减少系统在扩展后所带来的问题。(文/仲浩 审校/王旭东)

打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 在Qt框架中,QSerialPort类被视为一个关键组件,用于执行与串行端口之间的通信任务,它具备多样化的功能,涵盖了串口的开启与关闭操作,以及波特率、数据位、停止位和奇偶校验等参数的设定,同时还包括数据的发送和接收功能。在标题和描述中提及的“Qt5的QSerialPort类通过信号槽实现串口读写”,这代表了一种在Qt编程中普遍采用的事件驱动策略,借助信号槽机制,能够便捷地管理串口数据的传输与接收。 1. **QSerialPort类的基础操作**: - 初始化阶段:必须构建一个QSerialPort实例,并为其指定串口名称,例如"/dev/ttyUSB0"。 - 参数配置:利用`setPortName()`、`setBaudRate()`、`setDataBits()`、`setParity()`、`setStopBits()`、`setFlowControl()`等方法,依据具体需求对串口参数进行配置。 - 串口开启/终止:借助`open()`方法启动串口,通过`close()`方法终止串口。务必验证`isOpen()`的返回状态,以确保操作的有效性。 2. **信号槽机制的应用**: - 信号的生成:QSerialPort类中定义了若干信号,诸如`readyRead()`表明有数据可读,`error()`指示出现错误,`bytesWritten()`显示数据已传输等。当这些事件发生时,将触发相应的信号。 - 槽函数的关联:相应地,可以将这些信号与自定义的槽函数相连接,比如,当`readyRead()`信号被激活时,可以调用一个用于处理读取数据的函数。 3. **串口数据...
内容概要:本文档聚焦于超宽带(UWB)技术的核心研究,系统探讨了干扰对齐与抵消机制、UWB单天线与多天线系统的建模与仿真,并提供了完整的Matlab代码实现方案。文档强调科研工作不仅需要严谨的逻辑与扎实的努力,更应注重“借力”思维与创新突破,建议读者按照知识体系循序渐进地学习,避免陷入碎片化理解的困境。除UWB专题外,文档还全面展示了基于Matlab/Simulink的多领域科研支持能力,涵盖智能优化算法、机器学习、电力系统、径规划、通信与信号处理、图像融合、雷达追踪、车间调度等多个前沿方向,形成了一套完整的科研方法论与技术生态体系。所有相关资源可通过指定公众号或百度网盘获取,便于快速复现与二次开发。; 适合人群:具备一定Matlab编程基础和通信系统理论知识,从事电子信息、通信工程、自动化、电力系统及相关交叉学科的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握UWB系统中干扰抑制与天线设计的关键技术原理;②利用配套Matlab代码完成算法仿真、性能验证与参数优化;③借鉴成熟的优化模型与仿真框架,拓展至自身研究课题如径规划、微电网调度、信号处理等;④通过复现高水平论文模型,提升科研实践能力与学术竞争力。; 阅读建议:建议严格按照文档的知识结构顺序阅读,优先聚焦与自身研究方向契合的内容模块,结合提供的Matlab代码动手实践,积极利用公众号“荔枝科研社”及百度网盘中的完整资源包,实现从理论理解到项目落地的高效转化。
已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 ### 批处理脚本实现指定文件夹内所有文件与子目录的移除 #### 简介 在Windows系统环境下,批处理脚本是一种极具价值的应用工具,它能够协助用户执行一系列预先设定好的指令,达成自动化处理的目的。本说明着重阐述如何借助批处理脚本移除特定文件夹内的全部文件及子文件夹,并对几种常用技巧的效果进行剖析。 #### 批处理脚本的基础知识 批处理脚本是一种基于DOS命令行环境构建的文本性文档,其文件后缀为`.bat`。借助编写批处理脚本,使用者可以完成复杂任务流程的自动化,例如文件复制、移动、清除等动作。 #### 第一种方法:运用`RD`指令 `RD`指令专用于移除目录(即文件夹)。该指令的标准格式如下所示: ```batch RD [drive:]path [parameters] ``` 其中,`[drive:]path`代表待清除的目录径,`[parameters]`为若干可选参数,常用的包括: - `/S`:递归式地移除目录及其所有嵌套子目录。 - `/Q`:执行静默模式,不进行确认提示。 ##### 示例1:直接运用`RD`指令 若采用`RD /S /Q c:\temp`指令来移除`C:\temp`目录中的所有文件及子文件夹,将连同`temp`目录本体一同被清除。 ```batch rd /s /q c:\temp ``` #### 第二种方法:灵活运用`RD`指令 为防止误删`temp`目录本身,可以通过先利用`RD`指令清空`temp`目录内的所有内容,随后重新构建`temp`目录的技巧来实现。 ##### 示例2:灵活运用`RD`指令 ```batch rd ...
已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 在“WEB前端-案例汇总”这一资源集合中,收录了大量的前端开发实践范例,其核心目的在于引导初学者逐步提升,并系统性地掌握前端开发所需的关键技能。这个广泛的案例合集几乎包罗了前端开发的所有重要范畴,对于渴望深入研究和理解Web前端技术的人来说,无疑是一份极具价值的参考资料。 1. HTML基础:HTML(超文本标记语言)是网页构建的根基,其涉及的基本构成要素包括标记、属性以及结构等。相关的实例可能涵盖基础的静态页面构建,例如个人履历、产品介绍页面等,通过这些范例,学习者可以领会到如何合理地安排网页的内容与结构。 2. CSS样式设计:CSS(层叠样式表)主要用于调控网页的布局与视觉呈现。相关的案例或许会涉及盒模型、选择器、浮动、定位以及响应式设计等,使学习者能够设计出既美观又能适应不同设备的页面。 3. JavaScript交互:JavaScript作为前端开发的核心,负责实现动态效果与用户交互功能。相关的实例可能包含事件管理、文档对象模型操作、异步JavaScript与XML请求、函数及对象的应用等,通过这些实例,学习者能够学会如何增强网页的互动性。 4. jQuery库的应用:jQuery简化了JavaScript的操作,提供了功能丰富的接口和插件。相关的案例或许会涉及动画效果、文档对象模型操作、事件管理等方面,使初学者能够迅速掌握并提高开发效率。 5. 响应式设计:随着移动设备的广泛使用,响应式设计已成为一项必备技能。相关的案例可能包括运用媒体查询、弹性盒模型或网格布局来达成不同屏幕尺寸下的适配效果。 6. 模块化与框架:在现代前端开发实践中,Vu...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值