)
文档教程【免费下载链接】ddia《Designing Data-Intensive Application》DDIA 第一版 / 第二版 中文翻译项目地址https://gitcode.com/gh_mirrors/dd/ddia点击查看免费下载本文基于本仓库《Designing Data-Intensive ApplicationsDDIA第二版》中文翻译的第 2 章含英文原稿对照整理而成。功能需求回答“系统要做什么”而非功能性需求回答“系统应该有多好”。一个慢到无法忍受、或经常出错的系统几乎等同于不存在——本章正是要帮你把“快、稳、能扩、好维护”这四个目标从模糊的直觉变成可度量的工程指标。读完本文你将掌握用响应时间分位数与吞吐量刻画性能、用 SLO/SLA 约定服务水平、区分故障与失效并设计容错机制、评估三种扩展架构以及用可运维性/简单性/可演化性衡量长期维护成本。本章在全书中的位置第 2 章属于全书“第一部分数据系统基础”完整目录中紧随第 1 章数据系统架构中的权衡。本章提出的四个核心概念构成了后续所有章节的概念底座性能performance如何定义与衡量参见本章“描述效能”可靠性reliability出了问题时仍能继续正确工作参见“可靠性、故障与容错”可伸缩性scalability负载增长时如何高效增加计算能力参见“可伸缩性”可维护性maintainability让系统在长期使用中更易维护参见“可维护性”。其中许多术语如扇出、物化、尾延迟、故障注入在后面的复制、分片、流处理等章节中都会被反复引用因此本章既是方法论也是全书术语表的起点可对照术语表。案例研究社交网络首页时间线为了不流于抽象本章以一个“类 X原 Twitter的社交网络”案例开场。它远没有真实服务复杂但足以说明大规模系统会遇到的核心矛盾读多写少场景下如何在“按需查询”与“预先计算”之间做取舍。数据模型与一条昂贵的 SQL假设所有数据放在关系型数据库中一张表存用户一张表存帖子一张表存关注关系如图 2-1 所示static/fig/ddia_0201.png。主要读操作是首页时间线home timeline——展示你所关注的人最近发布的帖子。其实现就是一个典型的连接查询SELECT posts.*, users.* FROM posts JOIN follows ON posts.sender_id follows.followee_id JOIN users ON posts.sender_id users.id WHERE follows.follower_id current_user ORDER BY posts.timestamp DESC LIMIT 1000数据库先用follows表找出current_user关注的所有人再查询这些用户最近的帖子、按时间戳排序取最新 1,000 条。书中给出的量级估算很有冲击力假设每天发布 5 亿条帖子平均每秒约 5,700 条偶发峰值可达每秒 150,000 条平均每位用户关注 200 人、也有 200 名关注者实际分布极广多数人只有寥寥几名关注者而名人如巴拉克·奥巴马拥有超过一亿名关注者帖子讲究时效发帖后希望关注者在5 秒内看到。若客户端每 5 秒轮询polling一次同时在线并已登录的用户有 1,000 万就意味着每秒要执行 200 万次时间线查询更糟的是这条查询本身开销大关注 200 人就要分别取 200 人的近期帖子再合并。200 万次查询/秒意味着数据库每秒要按发帖者查询近期帖子4 亿次——这还只是平均情况关注数万账号的用户成本更高、更难以保证速度。物化时间线与扇出fan-out更好的做法有两步其一由服务器主动推送新帖子给在线关注者而非客户端轮询其二预先计算查询结果让首页时间线请求直接由缓存cache提供。设想为每位用户维护一个数据结构装着他关注的人最近的帖子每次有人发帖就找出他的所有关注者把帖子插入每个关注者的时间线——如同把信投进一个个邮箱。用户登录时直接取走预计算好的时间线新帖通知则通过订阅流入的帖子流实现。这就是扇出fan-out一个初始请求引发多个下游请求时用“扇出系数”表示请求数量被放大的倍数见 static/fig/ddia_0202.png。按每秒 5,700 条帖子、平均送达 200 名关注者扇出系数 200计算每秒要做略多于 100 万次时间线写入。数字依然很大但相比“每秒 4 亿次按发帖者查询近期帖子”已经省下了巨量工作。发帖速率骤增时也不必立刻完成所有投递把投递任务放入队列接受帖子暂时晚一点出现在关注者时间线上即使负载高峰时间线依然快速加载因为读请求只访问缓存。这种“预计算并不断更新查询结果”的过程称为物化materialization时间线缓存就是一个物化视图materialized view的实例后续在第 12 章“维护物化视图”还会深入讨论。物化视图加快读取、加重写入对多数用户来说写入成本不高但必须考虑两类极端情况关注了非常多账号、且这些账号发帖频繁的用户其物化时间线的写入速率很高但该用户多半不会读完所有帖子因此可以丢弃一部分时间线写入只展示所关注账号帖子的一小部分样本拥有海量关注者的名人发帖要为数百万粉丝的首页时间线各插入一条帖子丢弃写入不可接受。解决办法是把名人帖子单独存储读取时再与物化时间线合并——即便这样承载名人账号仍可能需要大量基础设施。描述效能响应时间与吞吐量讨论软件性能通常看两类主要指标指标定义计量单位响应时间response time从用户发出请求到收到所需响应的经过时间秒 / 毫秒 / 微秒吞吐量throughput系统每秒处理的请求数或数据量对给定硬件存在最大吞吐量maximum throughput上限“每秒多少个……”在社交网络案例中“每秒帖子数”“每秒时间线写入数”是吞吐量指标“加载首页时间线所需时间”“帖子送达关注者的时间”则是响应时间指标。两者存在典型关系见 static/fig/ddia_0203.png吞吐量低时响应时间短负载增大响应时间随之上升。原因是排队queueing请求到达高负载系统时CPU 很可能正在处理前一个请求新请求只能等待。当吞吐量逼近硬件处理极限时排队延迟会急剧增加。当过载系统无法恢复时亚稳态故障系统濒临过载时可能陷入恶性循环请求排长队 → 响应时间增长到客户端超时 → 客户端重发请求 → 请求速率进一步上升 → 问题愈演愈烈即重试风暴retry storm。即使负载随后下降系统也可能一直停留在过载状态直到重启或重置这种“系统效率越来越低、因而更加过载”的现象称为亚稳态故障metastable failure可能造成严重的生产事故。应对手段包括客户端采用指数退避exponential backoff——逐渐延长并随机扰动连续重试间的等待时间用熔断器circuit breaker或令牌桶token bucket算法暂时停止向最近出错或超时的服务发送请求服务器在接近过载时主动拒绝请求负载卸除load shedding并在响应中要求客户端降速背压backpressure排队算法与负载均衡算法的选择同样有影响。在各指标中用户最关心响应时间吞吐量则决定所需计算资源如服务器数量进而决定成本。若吞吐量可能增长到超出当前硬件能力就需要扩容——如果增加计算资源能显著提高最大吞吐量我们就称该系统可伸缩scalable。延迟与响应时间四个术语的严格区分“延迟”与“响应时间”经常被混用但本书采用精确含义见 static/fig/ddia_0204.png响应时间客户端看到的时间包含系统各处产生的全部延误服务时间service time服务真正用于处理用户请求的时间排队延迟queueing delay可能在流程多处发生——请求到达后要等 CPU 空闲出站网络接口被其他任务占用时响应数据包也会先在缓冲区等待延迟latency泛指请求没有得到实际处理的时间即请求处于“潜伏latent”状态的时间其中网络延迟/网络时延network latency/delay指请求与响应在网络中传输的时间。即使反复发送同一请求每次响应时间也可能相差很大上下文切换、网络丢包与 TCP 重传、垃圾回收暂停、缺页读盘、甚至服务器机架的机械振动都会带来随机额外延迟后续在第 9 章“超时和无界延迟”会深入讨论。响应时间的波动很大一部分来自排队延迟。服务器能并行处理的任务数有限如受 CPU 核数限制因此只要少数几个慢请求就足以阻挠后续请求——这就是队头阻塞head-of-line blocking即使后续请求服务时间很短客户端看到的总体响应时间依然很长。排队延迟不属于服务时间所以必须在客户端侧测量响应时间。平均值、中位数与分位数响应时间不是单一数字而是一个分布distribution。多数请求很快偶尔出现耗时长得多的异常值outlier网络延迟的变化也叫抖动jitter。见 static/fig/ddia_0205.png100 次服务请求的响应时间样本。平均值mean/ 算术平均值arithmetic mean所有响应时间相加除以请求数。平均值有助于估算吞吐量上限但作为“典型响应时间”并不好——它没告诉你多少用户真正经历了这样的等待中位数median把响应时间从快到慢排序后位于正中间的值也叫第 50 分位数p50。例如中位数 200ms意味着一半请求不到 200ms、另一半更慢——想知道“用户通常要等多久”中位数是很好的指标高分位数第 95 / 99 / 99.9 分位数缩写p95 / p99 / p999对应“95% / 99% / 99.9% 的请求快于该阈值”。例如 p95 为 1.5 秒表示每 100 个请求有 95 个不到 1.5 秒、5 个达到或超过 1.5 秒。高分位数也称为尾延迟tail latencies直接影响用户体验。例如 Amazon 用第 99.9 分位数描述内部服务的响应时间要求尽管只影响每 1,000 个请求中的一个因为响应最慢的客户往往是在账户中积累最多数据的人——购买最多的最有价值客户。反过来针对第 99.99 分位数每 10,000 个请求中最慢的一个做优化则被 Amazon 认为成本过高、收益不足极高分位数易受不可控随机事件影响越往后收益越小。响应时间对用户的影响“快服务更受欢迎”直觉上显然但量化数据出人意料地难以获得且常被引用的数据并不可靠Google 2006 年报告响应时间从 400ms 增至 900ms 与流量和收入下降 20% 相关而 Google 2009 年另一项研究显示延迟增加 400ms 只使日均搜索次数减少 0.6%同年 Bing 发现加载时间增加 2 秒使广告收入减少 4.3%。Akamai 较新研究声称响应时间增加 100ms 使电商转化率最多下降 7%但同一研究也显示加载非常快的页面往往是 404 等无内容页与较低转化率相关且未区分页面内容与加载时间的影响结论意义有限。Yahoo 的一项研究在控制搜索结果质量后对比快慢响应的点击率发现相差 1.25 秒以上时快速搜索获得 20%30% 更多点击。响应时间指标的应用SLO 与 SLA当一次终端用户请求需要后端多次调用时高分位数尤其重要。即使这些调用并行发出终端用户请求仍要等最慢的一次完成——只需一个慢调用就能拖慢整个请求见 static/fig/ddia_0206.png。即使后端调用中只有小比例较慢一次请求所需的调用越多其中出现慢调用的概率越大最终有更高比例的用户请求变慢即尾部延迟放大tail latency amplification。分位数常出现在服务级别目标SLO与服务级别协议SLA中。例如一个 SLO 可以规定中位响应时间低于 200ms、第 99 分位数低于 1 秒、且至少 99.9% 的有效请求返回非错误响应。SLA 则是合同未达到 SLO 时怎么办例如客户有权退款。基本思路如此但实践中为 SLO/SLA 定义良好的可用性指标并不简单。计算分位数要在监控仪表板上持续计算响应时间分位数可以维护一个滚动窗口如最近 10 分钟所有请求的响应时间每分钟计算窗口内数值的中位数与各分位数并绘图。最简单的实现是保存窗口内所有响应时间的列表、每分钟排序一次若效率不足可用算法以极低 CPU 与内存开销算出相当准确的分位数近似值开源库包括 HdrHistogram、t-digest、OpenHistogram 与 DDSketch。注意对分位数取平均值如降低时间分辨率或合并多台机器数据在数学上没有意义——聚合响应时间数据的正确方法是把直方图相加。可靠性、故障与容错人们对可靠软件有典型的期望应用表现出用户期望的功能允许用户犯错或以出乎意料的方式使用在预期的负载与数据量下性能足够能防止未授权访问与滥用。把这些合起来是“正确工作”那么可靠性reliability可粗略理解为“即使出了问题也能继续正确工作”。为精确描述“出了问题”需要区分两个概念故障fault系统某个部分停止正常工作——如单块硬盘故障、单台机器崩溃、所依赖的外部服务中断失效failure整个系统停止向用户提供所需服务即系统没有达到服务级别目标SLO。二者其实是同一件事、只是观察层次不同一块硬盘停止工作我们说硬盘“失效”了若系统只有这一块硬盘整个系统也停止了服务。但若谈论包含许多硬盘的系统单块硬盘失效只是整个系统视角下的一项“故障”——只要数据在另一块硬盘上还有副本系统就可能容忍它。容错面向特定数量、特定类型如果某些故障发生时系统仍能继续提供所需服务就称系统容错fault-tolerant。系统无法容忍其故障的部分叫单点故障SPOF——它一旦出问题就会升级为整个系统的失效。回到社交网络案例扇出过程中负责更新物化时间线的某台机器崩溃或不可用要让这个过程容错就必须保证另一台机器能接手任务既不漏掉任何本应投递的帖子、也不重复投递——这个思想叫恰好一次语义exactly-once semantics第 13 章“数据库的端到端原则”会详细讨论。容错能力总是针对特定类型、特定数量的故障例如最多容忍两块硬盘同时失效、或三个节点中有一个崩溃。要求容忍任意数量的故障没有意义——如果所有节点都崩溃任何办法都无济于事书中调侃若整个地球连同服务器都被黑洞吞噬要容忍这项故障就得把网站托管到太空。反直觉的是在这类系统中故意触发故障以提高故障率有时是合理的——例如毫无预警地随机杀死某个进程称为故障注入fault injection。许多严重缺陷源于糟糕的错误处理故意制造故障可以让容错机制不断演练、检验增强“故障自然发生时系统能正确处理”的信心。混沌工程chaos engineering正是通过故障注入等实验增强对容错机制信心的学科。不过“预防优于容忍”的场景也存在安全即是如此——攻击者已攻破系统并窃取敏感数据时这件事无法撤销。硬件故障在规模面前成为常态人们首先容易想到硬件故障书中的量化数据值得记住每年约2%5%的机械硬盘故障10,000 块硬盘的存储集群平均每天约有一块失效每年约0.5%1%的 SSD 故障少量位错误会自动纠正但每块硬盘每年仍约发生一次无法纠正的错误即使相当新、磨损很少此错误率高于机械硬盘电源、RAID 控制器、内存模块等也会故障只是不如硬盘频繁约每 1,000 台机器中有一台的某个 CPU 核心偶尔算出错误结果很可能源于制造缺陷有时导致崩溃有时只是返回错误结果RAM 数据可能因宇宙射线等随机事件或永久物理缺陷而损坏即使采用 ECC 纠错内存一年内仍有超过 1% 的机器遇到无法纠正的错误通常导致崩溃并需更换内存条某些病态内存访问模式很可能导致位翻转整个数据中心可能不可用停电、网络配置错误甚至被永久摧毁火灾、洪水、地震太阳风暴在长距离导线中感应强电流可能破坏电网与海底电缆。这类大规模失效虽罕见但若服务不能容忍整个数据中心的丢失后果可能是灾难性的。在小系统中只要故障硬件容易更换这些事件不必过分担心但在大规模系统里硬件故障发生得足够频繁已成为系统正常运行的一部分。通过冗余容忍硬件故障对不可靠硬件的首轮反应是增加冗余磁盘组成RAID数据分散到同一机器多块磁盘单块失效不致丢数据服务器配双路电源、可热插拔 CPU数据中心备电池与柴油发电机。这些措施常能让一台机器连续运行多年。冗余在组件故障彼此独立时最有效一项故障的发生不改变另一项故障的概率但实践表明组件失效间常存在显著相关性——整个机架乃至数据中心不可用比我们希望得更常见。硬件冗余提高单台机器正常运行时间但如第 1 章“分布式与单节点系统”所述分布式系统还有其他好处例如容忍整个数据中心中断。因此云系统往往不那么强调单机可靠性而是在软件层面容忍节点故障实现高可用。云提供商用可用区availability zone标明物理上共处的资源——同一地点的资源比地理分散的资源更可能同时失效。本书讨论的容错技术旨在容忍整台机器、整个机架或整个可用区的丢失一个数据中心内的机器可在另一数据中心的机器故障或不可达时接替工作第 6 章复制、第 10 章一致性等多处讨论。能容忍整机丢失的系统还有运维优势单服务器系统重启装补丁必须安排停机多节点容错系统则可逐节点重启完成滚动升级rolling upgrade不影响服务第 5 章编码与演化会进一步讨论。软件故障高度相关更难对付硬件失效虽可弱相关大体仍相互独立软件故障则往往高度相关——许多节点运行同一套软件、带有同样的缺陷更难预见也更容易造成系统失效。例如一个软件缺陷在特定情况下使所有节点同时失效2012 年 6 月 30 日闰秒触发 Linux 内核缺陷许多 Java 应用同时挂起大量互联网服务中断又如固件缺陷使某些型号 SSD 在恰好运行 32,768 小时不到 4 年后突然全部失效、数据无法恢复失控进程耗尽 CPU 时间、内存、磁盘空间、网络带宽或线程等共享有限资源客户端库缺陷可能产生远超预期的请求量所依赖的服务变慢、失去响应或开始返回内容损坏的响应不同系统间互动产生涌现行为——各自单独测试时都不会出现级联失效一个组件的问题导致另一组件过载变慢进而拖垮下一个组件。这类缺陷往往潜伏很久直到一组不寻常条件触发暴露出软件对运行环境所作的某个“通常成立、最终不再成立”的假设。软件的系统性故障没有速效药但许多小措施都有帮助认真思考系统中的假设与交互、彻底测试、进程隔离、允许进程崩溃并重启、避免重试风暴之类的反馈环路见上文“亚稳态故障”、在生产环境中度量监控与分析系统行为。人类与可靠性软件由人设计、构建与运维。与机器不同人的长处是创造力与随机应变但也会带来不可预测性——即使出发点是好的人也会犯错并导致系统失效。一项针对大型互联网服务的研究发现运维人员修改配置是服务中断的首要原因硬件故障服务器或网络只在 10%25% 的中断中起作用。把问题归结为“人为错误”并幻想用更严格的流程与规则约束人往往适得其反所谓“人为错误”并非事故的根本原因而是人技术共同构成的社会技术系统sociotechnical system出问题的症状——身处其中的人只是在竭尽所能地完成工作。复杂系统还常有涌现行为组件间意外交互同样可能引发失效。可减小人为失误影响的技术手段包括彻底测试手写测试 用大量随机输入进行的属性测试property-based testing提供回滚机制迅速撤销配置变更逐步发布新代码提供详细清晰的监控与可观测性工具参见第 1 章“分布式系统的问题”精心设计界面使“做正确的事”更容易、“做错误的事”更困难。但这些措施要投入时间与金钱组织在现实压力下往往优先收入型工作而非增强抗失误能力。若必须在“更多功能”与“更多测试”间选择许多组织选择功能也不难理解——既然如此当本可避免的错误发生时再去责怪犯错的人便毫无道理问题在于组织如何设定优先级。越来越多的组织因此采用无责复盘blameless postmortem文化事故后鼓励参与者毫无保留讲清经过、不必担心惩罚让其他人从中学习。复盘可能发现业务优先级需调整、被长期忽视的领域需投入、激励需改变等系统性议题。调查事故时应警惕过分简单的答案“鲍勃部署时应该更小心”无助于解决问题“我们必须用 Haskell 重写后端”同样如此。可靠性有多重要可靠性不只属于核电站与空中交通管制商业应用缺陷降低生产率数字报错还有法律风险电商网站中断造成巨额收入损失并损害声誉。对许多应用中断几分钟乃至几小时尚可容忍但永久丢失或损坏数据是灾难。英国邮局 Horizon 丑闻是不靠谱软件伤人的极端案例1999–2019 年间数百名邮局网点经营者因会计软件显示的“账目短缺”被判盗窃或欺诈最终发现许多短缺源于软件缺陷大量判决被撤销。这场英国历史上最大的司法不公之所以发生是因为英格兰法律假定计算机正确运行、计算机产生的证据可靠。软件工程师也许觉得“软件没有缺陷”可笑但被错误定罪、监禁、宣告破产甚至自杀的人无法从中得到安慰。有时我们为降低开发成本牺牲可靠性如为未验证市场做原型但必须清醒意识到何时在走捷径、并牢记后果。可伸缩性系统今天可靠不意味着将来也可靠——负载可能从 1 万并发用户涨到 10 万、从 100 万涨到 1,000 万。可伸缩性scalability描述系统应对负载增长的能力。有人会说“你又不是 Google 或 Amazon别担心规模用关系型数据库就好”——这句话是否适用取决于你构建的究竟是哪一类应用。如果你在构建一个用户不多的新产品如初创公司的新业务压倒性的工程目标通常是尽可能简单、灵活以便随对客户需求的了解轻松修改调整。在这种环境里担心未来或许才需要的假想规模往往适得其反往好里说是白费力气与过早优化往坏里说会把你困在不灵活的设计中。可伸缩性不是一维标签“X 可伸缩”或“Y 不可伸缩”没有意义。真正要问的是“如果系统按某种方式增长我们有哪些应对选项”“如何增加计算资源承载额外负载”“按当前增长预期什么时候会撞到现有架构的极限”应用真正大受欢迎、负载不断增长后你会逐渐知道瓶颈在哪里、需要沿哪些维度扩展——到那时再认真考虑可伸缩性技术也不迟。描述负载首先要用吞吐量指标简明描述当前负载每秒请求数、每天新增多少 GB 数据、每小时完成多少次购物车结账有时关心某个变量的峰值如案例中的同时在线用户数。负载还有其他统计特征影响访问模式与伸缩需求数据库读写比、缓存命中率、每位用户的数据项数如关注者人数。有时平均情况最重要有时瓶颈由少数极端情况主导——一切都取决于应用细节。描述好负载后可从两个角度考察增长负载增加而资源CPU、内存、网络带宽不变性能受什么影响负载增加而性能不变需要增加多少资源目标通常是在满足 SLA 性能要求的同时尽量降低成本。如果资源加倍就能在性能不变下处理两倍负载称线性可伸缩性linear scalability通常很好偶尔因规模经济或峰值负载分布更均匀不到两倍资源也能处理两倍负载更常见的是成本增长快于线性——例如数据量很大时即使写请求大小相同处理一次写入的工作也可能多于数据量小时。共享内存、共享磁盘与无共享架构纵向上扩vertical scaling / scaling up迁移到更强机器。单核速度已不再显著提升但仍可买到更多核心、更大 RAM 与磁盘的机器。单机上用多进程/多线程获得并行同进程线程共享同一块 RAM因此也叫共享内存架构shared-memory architecture。问题在于成本增长快于线性硬件翻倍的高端机器价格往往远不止两倍且受各种瓶颈限制规模翻倍的机器常处理不了两倍负载。共享磁盘架构shared-disk architecture多台机器各有独立 CPU 与 RAM数据存在共享的一组磁盘阵列上通过高速网络连接如NAS或SAN。传统上用于本地部署的数据仓库工作负载但资源争用与加锁开销限制了其可伸缩性。无共享架构shared-nothing architecture也叫水平扩展 / scaling out采用多节点分布式系统每节点有自己的 CPU、RAM、磁盘节点间一切协调都在软件层通过普通网络完成。优势有望线性伸缩可用性价比最好的硬件云端尤其如此负载增减时更容易调整资源可分布到多个数据中心与地域获得更强容错。缺点必须显式分片第 7 章分片并承受分布式系统的全部复杂性第 9 章分布式系统的麻烦。一些云原生数据库系统把存储与事务执行拆分成不同服务参见第 1 章“存储与计算的分离”让多个计算节点共享同一存储服务。这个模型与共享磁盘架构有几分相似但避开了老系统的伸缩问题存储服务提供的不是文件系统NAS或块设备SAN抽象而是针对数据库具体需求设计的专用 API。可伸缩性原则大规模系统架构通常高度依赖具体应用不存在放之四海皆准的可伸缩架构俗称“万能伸缩秘方”。例如处理每秒 100,000 个请求、每个 1 kB 的系统与每分钟 3 个请求、每个 2 GB 的系统看起来截然不同——尽管二者数据吞吐量同为 100 MB/s。适合某个负载水平的架构多半应付不了十倍负载快速增长的服务的架构往往每隔一个数量级就要重新考虑提前为一个数量级之后的伸缩做规划通常不值得。两条通用原则值得记住把系统拆分成能大体独立运行的较小组件——这是微服务第 1 章“微服务与无服务器”、分片、流处理第 12 章流处理与无共享架构背后的共同原则。真正的挑战在于判断哪些该合、哪些该分不要把事情弄得比必要更复杂如果单机数据库能完成任务它很可能优于复杂的分布式配置。自动伸缩系统很酷但负载可预测时手动伸缩的系统运维中可能更少意外参见第 7 章“运维自动/手动再平衡”。由 5 个服务组成的系统比由 50 个服务组成的更简单——优秀架构通常务实混合多种方案。可维护性软件不会磨损、不会材料疲劳不会像机械那样损坏但需求经常变化、运行环境变化依赖与底层平台、总有缺陷要修。软件的大部分成本不在开发而在持续维护修缺陷、保持运行、调查失效、适配新平台、为新的使用场景修改、偿还技术债、加新功能。成功运行多年的系统可能仍在使用如今没多少工程师理解的过时技术大型机、COBOL随着人员离开关于“系统为何如此设计”的组织知识可能丢失计算机系统还常与支撑它的组织紧密交织——维护遗留legacy系统既是人的问题也是技术问题。我们今天构建的每个系统只要足够有价值能长期存续终有一天都会成为遗留系统。为减轻后来者的痛苦设计时就应考虑维护问题。本书特别关注三项广泛适用的原则可运维性operability便于组织保持系统平稳运行简单性simplicity采用人们熟知且前后一致的模式与结构避免不必要的复杂度让新工程师也能轻松理解可演化性evolvability便于工程师将来修改系统、适应事先没预料到的使用场景。可运维性让运维更轻松第 1 章“云时代的运维”讨论过可靠运维中人的流程至少与软件工具同等重要。有观点认为“良好的运维往往能绕开糟糕或不完整软件的局限但即使软件很好糟糕的运维也无法让它可靠运行”。成千上万台机器的系统纯靠人工维护成本高得难以承受自动化的确必不可少但自动化是双刃剑总会有边缘情况如罕见故障场景需要人工干预而自动化处理不了的恰恰是最复杂的问题所以自动化程度越高反而越需要技能更强的运维团队。而且自动化系统一旦出错往往比依赖人工操作的系统更难排查——对可运维性而言并非自动化越多越好最佳平衡点取决于具体应用与组织。数据系统可通过多种方式简化日常工作允许监控工具检查关键指标、支持可观测性工具深入了解运行时行为避免依赖任何单台机器机器可下线维护而系统不间断运行提供良好文档与易理解的操作模型“如果我做 X就会发生 Y”提供良好默认行为、同时允许管理员必要时覆盖在适当时候自动修复、也允许管理员手动控制系统状态表现出可预测的行为、尽量避免意外。简单性管理复杂度小项目可以有简单讨喜的代码项目变大后往往变得复杂难懂拖慢每个需要在系统上工作的人、推高维护成本。陷入复杂泥潭的项目有时被称为大泥球big ball of mud。复杂度使维护变难时预算与进度经常超支修改复杂软件也更容易引入缺陷——系统越难理解推理隐藏假设、无意的后果、意外的交互越容易被忽略。反过来降低复杂度能极大提高可维护性因此简单性应成为构建系统的关键目标。是否简单往往是主观品味问题没有客观标准一个系统把复杂实现藏在简单界面后另一个实现简单却暴露更多内部细节——哪个更简单人们曾把复杂度分为本质复杂度essential complexity应用问题领域固有与偶然复杂度accidental complexity工具局限所致但这条分界线会随工具演进而变化。管理复杂度最好的工具之一是抽象abstraction把大量实现细节藏在干净易懂的外观之后且可广泛复用于不同应用。复用抽象不仅比反复重新实现类似功能高效还能带来更高质量的软件——抽象组件质量的改进惠及所有使用它的应用。例如高级编程语言隐藏机器码、CPU 寄存器与系统调用SQL 隐藏复杂的磁盘/内存数据结构、其他客户端的并发请求、崩溃后的不一致。为降低应用代码复杂度可借助设计模式design pattern与领域驱动设计DDD等方法构建抽象本书讨论的不是这类应用专用抽象而是数据库事务、索引、事件日志等通用抽象——你可以在它们之上构建应用。可演化性让变化更容易需求几乎必然变化新事实、未预料的使用场景、业务优先级改变、用户要新功能、新平台取代旧平台、法律监管变化、增长迫使架构变化。组织流程方面敏捷Agile为适应变化提供了框架敏捷社群还发展了 TDD、重构等技术工具。本书则在“由多个特性各异的应用或服务组成的系统”这一层面上寻找提高敏捷性的办法。修改数据系统以适应变化有多容易与简单性、抽象密切相关松耦合、简单的系统通常比紧耦合、复杂的系统更容易修改。这个概念太重要值得用专门术语可演化性evolvability。大型系统中某些操作不可逆因此必须极为谨慎——例如数据库迁移如果新系统出问题却无法切回旧系统风险远高于能轻松回退的情况。尽量减少不可逆性可以提高系统的灵活性。总结本章考察了几种非功能性需求性能、可靠性、可伸缩性与可维护性并从社交网络首页时间线案例入手说明规模增大时出现的挑战。核心要点性能用响应时间分位数p50/p95/p99/p999衡量“用户实际感受到的快慢”用吞吐量决定所需资源SLO/SLA 是把这些指标固化为契约的手段可靠性区分故障局部与失效整体用冗余与容错技术让系统在组件故障时继续服务硬件故障大体独立、软件故障高度相关、人为失误则需从社会技术系统视角系统性缓解无责复盘、回滚、可观测性可伸缩性先描述负载再谈增长线性伸缩是理想无共享架构是主流方向但不存在万能架构——拆分组件 不过度复杂化是通用原则可维护性可运维性、简单性管理复杂度与偶然复杂度、可演化性松耦合、减少不可逆操作三者共同决定长期维护成本。实现这些目标没有简单答案但一个屡试不爽的做法是用人们熟知、能提供实用抽象的构件搭建应用。本书余下部分将逐一介绍这些经过实践检验的构件——数据模型与查询语言第 3 章、存储与检索第 4 章、复制第 6 章、分片第 7 章、事务第 8 章、分布式系统第 9–10 章、批处理与流处理第 11–12 章。想要继续深入可直接在本仓库中阅读第 2 章完整原文及其英文对照版或从完整目录按顺序研读全书。赞分享文档教程【免费下载链接】ddia《Designing Data-Intensive Application》DDIA 第一版 / 第二版 中文翻译项目地址https://gitcode.com/gh_mirrors/dd/ddia点击查看免费下载相关推荐颠覆传统方案基于计算机视觉的智能游戏辅助系统深度解析颠覆传统方案基于计算机视觉的智能游戏辅助系统深度解析 原神自动化助手Genshin Impact Assistant简称GIA是一款基于计算机视觉和模拟计算机视觉RPA深入理解数据密集型应用设计的三大基石可靠性、可伸缩性与可维护性深入理解数据密集型应用设计的三大基石可靠性、可伸缩性与可维护性 在当今互联网时代数据密集型应用已成为主流。与计算密集型应用不同这类应用的核心挑战不在于CP文档教程mold可维护性代码的可读性和可维护性mold可维护性代码的可读性和可维护性 引言现代链接器的工程典范 在当今软件开发中构建时间已成为影响开发效率的关键因素。moldModern Linke开发工具构建工具系统编程上一篇突破语言壁垒多语言处理工具让跨语言阅读效率提升300%下一篇终极Flask安全方案Flask-Security新手入门到精通教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考