写点什么

可伸缩性的最差实践

  • 2008-12-18
  • 本文字数:4321 字

    阅读完需:约 14 分钟

引言

在扩展大量大型的分布式系统期间,我有机会观察(并实践)了一些最差实践。这些最差实践中的大部分在开始时都没有危害,但如果疏忽大意,它们就会对系统的发展和可伸缩性构成危害。很多文章都聚焦于最佳实践,以确保拥有一个易于维护和可伸缩的系统,但在本文中,我主要强调的则是一些应该规避的最差实践。

技术

没有任何一种技术或架构能实现所有的需求。了解何时该反思现有的方法、如何拓宽视野以超越局部范围、或如何进行依赖的有效控制,这些都是可伸缩性的关键特性。让我们进一步分别研究一下。

金锤子

金锤子起源于一条古老的谚语:如果你只有一把锤子,那么任何东西在你眼里都是一枚钉子。很多开发人员都局限在仅使用一种技术的观念中——其代价是不得不使用选定的技术来构建和维护基础设施,即便已经存在另一种技术更适用于特定问题域的功能和抽象。强行把一种技术用在它所不擅长的方面,有时会适得其反。

举例来说,持久化键—值对问题的常见解决方案是使用数据库。之所以常常这样选择是因为组织或开发者有坚实的数据库实践,针对许多问题自然而然就会沿用同样的解决途径。当数据库的特性(关系完整性、锁、连接和方案)成为瓶颈或阻碍了其伸缩扩展时,问题也就出现了。这是因为应用基于数据库的解决方案要发展,其成本通常要比使用其它可用技术更为昂贵。随着键—值存储访问率的增加,数据库并发模式的性能就开始降低,而数据库具备的高级特性却被闲置。许多传统关系数据库的替代方案都是针对这些缺点的,比如 CouchDB、SimpleDB 或 BigTable。

另一个常见的“锤子”就是总利用线程来进行并发编程。尽管线程确实是针对并发的,但它们也带来了成本,这些成本包括代码复杂性的增加、以及由于目前线程的的锁定和访问模型造成的组件编排(composability )方面的固有不足。由于如今最流行的编程语言都使用线程处理并发,因此数千行代码都含有竞态条件、潜在的死锁和不一致的数据访问管理。有些正在成长的社区提出了另一些并发方案,这些方案不存在线程的可伸缩性问题,也就是由 Erlang 或 Stackless Python 提倡的并发模型。即便不在实际生产中选择那些语言,研究一下它们的概念(比如消息传递或异步 I/O)仍然是一种不错的实践。

资源滥用

小范围的问题开发者们一般都能处理得得心应手:使用分析工具、了解算法的空间和时间复杂度、或者了解哪种场合应该用哪种列表实现。但并非每个人都善于认识到大型系统的约束条件,比如识别共享资源的性能要求、了解服务的各种客户、或发掘数据库的访问模式。

应用程序实现伸缩性的普遍方法是不断横向部署冗余的、无状态的、彼此不共享内容的服务,以此作为最理想的体系架构。但以我的经验看来,这种扩展往往会忽视新增服务对共享资源的影响。

比如说,如果一个特定的服务使用数据库作为持久存储,它通常通过一个线程池来管理数据库连接。使用池是不错的方法,有助于避免进行过多的数据库连接处理。然而数据库仍然是共享资源,除了单个池配置,还必须对所有池从总体上进行管理。下面两个实践就会导致失败:

  1. 持续增加服务数,但并不减小池的最大数。
  2. 增大单个池的大小,而不减小服务数量。

以上两种情况中,除了按性能要求配置应用之外,连接的总数也必须加以管理。此外,还要持续监控数据库的容量,以保持连接均衡。

处理共享资源的可用性至关重要,准确的说,这是因为它们一旦失效,由于其“共享”的本质,失效会对系统造成全面的影响,而非孤立存在。

大泥球

依赖是很多系统讨厌却又必不可少的东西,不积极地处理好依赖及其版本会损害灵活性和可伸缩性。

代码的依赖管理有多种不同的模式:

  • 同时编译整个代码集
  • 基于已知版本选取构件和服务
  • 发布的模型和服务所有变更都向后兼容

让我们看看这些情形。首先,在大泥球模式下,整个系统作为一个单元编译和部署。这种模式拥有明显的优势,也就是将依赖管理交给编译器处理,并能提前捕获一些问题,但它会因每次都部署整个系统(包括测试、交付和大范围变化引起的风险)而引发可伸缩性的问题。在这种模式下,会更难隔离系统的变化。

在第二种模式中,依赖都是按需挑选的,但是变化经过依赖传递之后依旧出现第一种模式一样的难题。

第三种模式中,服务负责依赖的版本化,并向客户端提供向后兼容的接口。这明显减轻了客户端的负担,从而允许逐步升级到新的模型和服务接口。此外,当数据需要转换的时候,它是依靠服务而不是客户端完成的——这进一步稳固了隔离性。向后兼容的变更意味着打补丁、升级和回滚都不能干扰客户端操作。

采用变更能向后兼容的服务体系架构在最大程度上避免了依赖问题。它同时方便了在受控环境下进行独立测试,隔离了客户端和版本化数据的变化。这三个优点对隔离变化来说都很重要。最近发布的 Google Protocol Buffers 项目也在倡导向后兼容的服务模型和接口。

全部打包还是部分打包

处理依赖时要考虑的另一件事情是如何对应用内容打包。

在一些场景中,比如 Amazon Machine Images 或 Google AppEngine 应用,它们的整个应用和所有的依赖都一起打包发布。这种囊括一切的打包方法保持了应用的自包含,但它增加了包的总大小,而且应用中任何地方的一个小小改变,都会迫使系统重新部署整个应用包(甚至对同一台物理机器上许多应用使用的共享库也是如此)。

替代方案是将应用的依赖移出主机系统,令应用包只包含依赖图的若干部分。这控制了包的大小,但由于应用在能提供服务之前需要将特定的组件传递到每台机器上,所以增加了部署配置。依赖项目没有立即准备好、机器没有经常测试、抑或是依赖错误,由于以上种种,不将整个包部署为自包含的方式会制约将应用部署到异构的、非标准化的机器上。

后一种方案——分成不同范围(全局的、机器的、应用的)去处理依赖——必然会增加疏漏和复杂性。它减少了配置和依赖隔离,增加了操作的复杂性。一般而言,隔离能增加可伸缩性,所以尽可能选用囊括一切的方法,除非有例外情况。

无论在代码还是在依赖处理中,最差实践就是不清楚模块间的关系,没有规划好模块以便于对其进行管理。未能增强控制是可伸缩性的一大绊脚石。

忘记检查时间

在分布式系统中,通常的目标是尽可能地将开发者和负责分布式调用的复杂方法隔离开来。这使主要的开发工作集中于核心的业务逻辑上,而不用担心失效恢复、超时以及其它分布式系统必需的需求。但是,让远程调用看起来像本地调用一样就意味着开发者要像本地调用一样编码。

我常发现很多代码都期望所有的远程请求能及时完成,但这样的期望是不合理的。比如说,Java 在 JDK1.5 中仅为HTTPURLConnection类引入了读超时,而让开发者要么创建线程去杀死进程,要么天真地等待响应。

Java 中,另一个潜在的时间处理不合理的例子是 DNS 查找。在一个长时间运行的典型系统中,执行完最初的 DNS 查找之后,如果不进行明确的配置,结果会缓存在 JVM 的生命期内。如果外部系统更改了主机的 IP 地址,将不能正确处理该条目,而且在很多情况下,因为编程时没有设置连接超时时间,连接就会被挂起。

为了对系统进行合适的伸缩扩展,为请求处理分配好时间是极其重要的。有很多方法可以实现,有一些是语言内置的(像 Erlang),其它的则作为库的形式提供,比如 libevent 或 Java 的NIO。抛开实现语言或架构不谈,正确地管理操作等待时间是非常必要的。

运行时

建立一个符合成本效益的可扩展方案、处理好依赖、预先考虑到失效都是创建优秀架构的各方面要求。而在生产环境中,系统易于部署和运维的能力也同等重要。这里同样有很多不利于系统可伸缩性的最差实践。

英雄模式

运维问题普遍的解决方案是有一个“英雄”(关键性人物),他能处理、并经常处理大部分的操作需求。在小规模环境中,当某个人有天赋和能力熟悉整个系统(包括保持系统正常运行的许多细节之处),英雄模式可以正常运行。尽管这是最常见的实施方案之一,但对拥有许多组件的大型系统而言,这种方法就不能进行伸缩扩展了。

在没有形式说明的情况下,“英雄”往往要理解服务依赖,牢记如何开、关特性,或了解其他人已经遗忘了的系统。“英雄”虽然至关重要,但他不应该是一个个体。

我认为英雄模式最好的解决方案是自动化。如果组织的情况允许,让个人在团队之间轮换也有帮助。在银行里,休假有时是强制性的,好让“你这里不行,要到我的机器上做”之类的问题及时暴露出来。

非自动化

系统过度依赖于人工干预往往是存在“英雄”的后果,这面临着可重复生产能力的问题和“英雄”出现意外情况带来的问题。能重现特定的构建、部署和环境很重要,而明确定义的元数据控制下的自动化是实现可重复能力的成功关键。

在一些开源项目中,工件的发布过程依赖于个体开发者在自己工作站上构建工件,没有任何措施保证产生出来的工件版本能实际对应到源码控制系统中的某个分支。在这些情况下,完全有可能发布软件,其代码从未被提交到源码控制系统。

综上所述,“英雄”的活动应该由自动化取代,从而确保个人(或许多人)可以相对容易地替换其他人。自动化的替代方案是增加流程—— Clay Shirky 为流程给出了一个有趣的定义:

流程是对先前蠢行的内在反应。

先前的蠢行在所难免——自动化应该吸取教训。

监控

当时间紧迫时,监控(比如测试)往往是第一个牺牲的环节。有时,在我问及有关组件的运行时表现方面的细节问题时,总没有答案。缺乏对运行系统内部的深入了解和迅速切入问题的能力,不利于对从哪里入手和着手做什么做出正确攸关的决策。

Orbitz 很幸运地拥有久经考验的监控软件,它们既能提供服务调用的细粒度详细信息,也能精确显现出问题域的数据。来自监控基础设施的可用度量数据有利于快速有效地解决问题。

总结

在不久前 Amazon 的 S3 出现服务中断之后, Jeff Bezos 说道:

遇到问题的时候,我们知道直接原因,我们从那里入手分析并找到了根本原因,然后从根本上进行了修复,又向前迈进了一步。

软件和系统的开发是一个迭代的过程,在这个过程中,失败和成功的机会并存。简单但较难伸缩的解决方案有其一席之地,特别是计划或应用尚处于不成熟的阶段。“好”和“完美”不是对立的。但随着系统的日臻完善,应该除去其中的那些最差实践,这样,成功也就是理所当然的了。

非常感谢 Monika Szymanski 对本文初稿提出的建议。

关于作者

Brian Zimmer 是旅游业新创企业 Yapta 的架构师,是一位受人尊敬的开源社区成员,也是 Python 软件基金会的成员。他之前作为高级架构师服务于 Orbitz。他的博客在 http://bzimmer.ziclix.com

查看英文原文: Scalability Worst Practices


译者简介:张兵,有 Web 应用开发、XML 技术、消息中间件和企业服务总线等方面的开发经验,对 SOA 领域比较熟悉,关注软件架构技术和有效的项目管理。

志愿参与 InfoQ 中文站内容建设,请邮件至 editors@cn.infoq.com 。也欢迎大家到 InfoQ 中文站用户讨论组参与我们的线上讨论。

2008-12-18 00:302051

评论

发布
暂无评论
发现更多内容

选择合适的BI工具,解决中国式报表难题

对不起该用户已成仙‖

熹乐科技范维肖CC:基于开源 YoMo 框架构建“全球同服”的 Realtime Metaverse Application

声网

框架 #开源

如何用 30s 讲清楚什么是跳表

飞天小牛肉

redis 面试 社招 校招 秋招

下一代架构?从组装式企业到组装式应用

华为云开发者联盟

云计算 后端 数字化 华为云 12 月 PK 榜

IAA品类洞察:扫描品类加快变现,如何抓住增长机遇?

易观分析

广告业 IAA

【合作案例】科协基地预约小程序 | 闵行区科普资源地图

天天预约

演讲实录|姚延栋:终止“试点炼狱”,智能汽车时代数字化转型与实践

YMatrix 超融合数据库

车联网 海量数据 超融合数据库 智能网联 YMatrix

Tapdata 携手阿里云,实现数据平滑上云以及毫秒级在线查询和检索能力

云布道师

阿里云

2023年ha软件采购就选Skybility HA!6大优势看这里!

行云管家

高可用 ha 双机热备

如何在滑至页面底端添加提示?

Towify

微信小程序 无代码

VoneBaaS与飞腾CPU完成产品兼容性互认证

旺链科技

区块链 产业区块链 VoneBaaS 12 月 PK 榜

浅析静态应用安全测试

华为云开发者联盟

测试 开发 华为云 12 月 PK 榜

2023年中国企业数字化技术应用十大趋势

易观分析

企业 数字化

【服务故障问题排查心得】「内存诊断系列」Docker容器经常被kill掉,k8s中该节点的pod也被驱赶,怎么分析?

洛神灬殇

Docker Linux 12 月 PK 榜 容器内存问题

OpenMLDB 贡献者任务第六期 | 暖冬时节,活力继续

第四范式开发者社区

人工智能 机器学习 数据库 开源 特征

喜讯+1!袋鼠云数栈技术团队获“2022年度优秀开源技术团队”

袋鼠云数栈

开源

广告倒排服务极致优化

百度Geek说

架构 数据结构 后端 12 月 PK 榜

chatGPT实战之「基于你的数据库,为你智能生成SQL」

非喵鱼

Java MySQL sql openai ChatGPT

省会城市昆明分布式光伏项目落地 引领低碳化转型实践

Geek_2d6073

强化学习调参技巧二:DDPG、TD3、SAC算法为例:

汀丶人工智能

强化学习 深度强化学习 12月日更 12月月更

瓴羊Quick BI数据填报组件,实现智能化管理和高效挖掘利用

夏日星河

Flutter for Web 首次首屏优化——JS 分片优化

阿里巴巴终端技术

flutter 前端 Web 客户端

低碳正在成为春城的新名片

Geek_2d6073

从数据治理到数据应用,制造业企业如何突破数字化转型困境丨行业方案

袋鼠云数栈

数字化转型

了不起的程序员们,瞧,你的 2023 年度惊喜终于来了!

图灵社区

程序员

火山引擎DataTester:无需研发人力,即刻开启企业A/B实验

字节跳动数据平台

A/B测试

两步开启研发团队专属ChatOps|极狐GitLab ChatOps 的设计与实践

极狐GitLab

团队管理 DevOps ChatOps 极狐GitLab ChatGPT

ClickHouse 挺快,esProc SPL 更快

王磊

Kubernetes 跨集群流量调度实战

Flomesh

服务治理 Kubernetes 集群 流量管理

如何使用 Towify 在小程序中实现勾选用户协议后登录?

Towify

微信小程序 无代码

人工智能顶会AAAI 2023放榜!网易伏羲7篇论文入选

网易伏羲

人工智能

可伸缩性的最差实践_最佳实践_Brian Zimmer_InfoQ精选文章