没有解决方案的问题有什么好的?在《负载增长时悄然袭来的 42 个怪兽问题》一文中,我们讨论了一些问题;在这篇文章(早些时候有过一篇文章,我会重新组织一下内容)里,我们将讨论我所谓的聚合策略(aggregation strategies)。
请牢记,这些都是下层架构方面的一些建议,比如如何组织代码组件的结构,它们该如何交互。本文不会讨论大规模集群,但是会讨论你的应用程序在内部——在服务接口之下的深层——可能是什么样的。这个世界上除了事件架构之外,还有很多东西。
聚合,简单说来就是不用愚蠢的队列,我们的队列会很聪明。我们打心底里把队列视为工作内容的容器,该容器最终会呈现出整个系统是如何工作的。作为工作内容的容器,我们熟知队列里会有什么请求和数据,我们可以把这种情报作为我们巨大的优势。
合理排序工作内容
此处的关键理念是一个基本上必须要牢记于心的设计方法,有程序员将其视为一等概念——在工作创建时的方方面面里什么该做,为什么要做它,该什么时候做这三个问题的优先级。
避免级联故障
为什么合理排序工作内容如此重要?我们要避免的最不靠谱的情况就是级联故障。天真的系统没有合理排序的理念,在故障发生时,会让无用的控制层或数据层流量挤占必要的控制流量。
如果你需要向交换机发送一个请求,对路由进行重新编程以对流量做故障转移,如果此时低优先级的工作项造成了线端拥塞(head-of-line blocking),那么在不关闭整个系统的情况下,你将永远都无法获得系统的控制权。那些不了解情况的啰嗦的程序会不断发出低优先级的控制和数据流量,让系统一直处于繁忙状态,而做的事情却一点用都没有。这就好像代码中的垃圾食品。
一个了解优先级的系统会试着在确保高优先级的工作按时完成的同时避免无用的工作。用于控制的网络和数据网络是各自独立的,因此控制消息能自由通行。你会有智能重试策略,无用工作不会占据队列,作废的消息会从队列中剔除。在网络各处都要考虑缓存,确保不会看到老的版本。当最新的消息入主之时,可以用控制消息来暂停当前的工作,这样才能处理更高优先级的工作,它们的消息会立即发送出去,而非进入等待。为了让系统更强健,有很多非常酷的事情可做。
处理无限的工作负荷
排定优先级是处理无限工作负荷的关键思想,在《负载增长时悄然袭来的 42 个怪兽问题》里就讨论过这一点。传统观念里我们认为 100% CPU 使用率是一个不好的信号。作为补偿,我们制造了复杂的基础设施对工作进行负载均衡、状态复制、构建服务器集群。CPU 是不会疲劳的,所以我们可以将其榨干。当我们讨论排定优先级时,我们也在讨论系统在全负荷下如何做出漂亮的、可预见的反应。如果我们进行了合理的条件设置,这些都不成问题。只有对于那些架构天真的软件而言,这才会成为问题。
有意识的控制
合理排序的工作内容中说到开发者应该对以下内容进行有意识的控制:
- 什么工作该做,什么工作该扔
- 处理工作的顺序
- 处理工作的任务优先级
- 给予工作的资源量:
- CPU 时间
- 内存
- 队列空间
- 磁盘
- 网络
- 锁
你可能会注意到典型的编程环境里,你对这些内容的控制力都不强。必须改变这一点。
找出优先级
合理排序工作内容也是很多其他领域的可扩展性解决方案里的常见做法。排定优先级需要对系统正在发生什么,以及你希望系统里正在发生什么有一个深刻的理解。优先级取决于:
- 客户
- 请求类型
- 尝试提供公平的服务
- 请求数
- 已使用的资源量
- 需要的资源量
- 合约中的截止日期
SLA
除了避免级联故障之外,将拥抱优先级作为基本概念的另一个关键原因是为了保证 SLA。
你有一个高优先级的客户,他为了获得优于普通客户的服务而付费。为了实现他们的 SLA,系统中的所有组件都必须习惯于理解优先级,同时还不能让其他客户处于饥饿状态,为他们提供周到的服务。
这和 OS 任务调度以及网络流量调度有很多共同之处。其中的思想是一样的,但开发者需要在系统里运用这些思想。
合并聚合
合并聚合就是把单独的数据和 / 或命令合并到一起。其理念就是 _ 按固定限定成比例地使用资源 _。
例如,假设一个对象有如下命令序列:
- 新建
- 更新
- 更新
这三个独立的请求能被合并到一个请求里,而非三个。要是这组命令重复 100 次,那么队列里仍然始终只有一个请求。
另一个例子是属性变更事件。独立的多个事件可以合并为一个变更事件。
想想这有多强大啊。无论发生了多少事件,队列都不会超过对象个数。
你的通信子系统需要同应用程序合作才能实现这一切。
删除聚合
删除聚合就是在任何可能的时候删除工作内容。比方说,以下述操作为例:
- 新建
- 删除
其结果是新建和删除操作能在消息发出前被删除。在发生大量新建和删除操作的重定路由或抖动输入(flapping inputs)风暴中,这能省下相当数量的资源。
批量聚合
通过定时分析(timing analysis),将许多数据打包到一起能极大地提升性能。一个总比一堆好。一个挨着一个发送操作会比把所有操作放在一个批次里发送要慢很多。
理想情况下,应用程序不应该手动来处理分批操作。打个比方,应该让框架来替你处理这些事。
变更聚合
在这个策略中,很多变更被收缩成了一个变更。这和合并聚合有所不同,在变更聚合中,某些东西改变了,我们会保持对象状态,无论它变了多少次,我们都只会当它改了一次。
我们不会记下每次变更,但是我们会把值发给客户端,告诉它有东西变了。
在一个大系统中,我们没办法保存系统中发生变更的所有东西的历史。
集成聚合
在这个策略里,只有当事件在取消前经过了一段时间后才能存在。
最常见的例子是报警,只有当它经历了一段集成期后它才真正成立。否则,我们会在硬件故障或者一阵阵的条件触发时制造出报警风暴。
在告知系统其他部分有事发生前,先让事件“等”一会儿,这能避免很多无用功。
信号集成
我们后续会再进一步讨论这个话题,但这里的思想是要考虑系统有自己的情报网,监控系统正在监控整个网络内发生的所有事情。所有部署在系统里的设施会将它们收集到的情报汇报给总部,这样就可以采取协同行动来应对各种情况发展。
这个情报系统会产生一个恒定的信号流,可以用它来控制系统其他部分的行为。聚合策略对系统中正在发生的事非常敏感。为此,你需要配置自己的情报操作,管理自己的操作,甚至设计自己的计数机关。所有的传感器都会说谎。
查看英文原文: Low Level Scalability Solutions - The Aggregation Collection
评论