ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

微服务设计原则——高可用

2026/8/26 13:51:43 拓冰建站 浏览量
微服务设计原则——高可用 文章目录1.依赖故障1.1 降级1.2 冗余1.3 自研2.超时时间3.失败重试4.幂等设计5.限流6.过载保护何为过载保护为何要过载保护如何过载保护过载保护与限流的区别7.熔断何为熔断为何要熔断如何实现熔断8.快速失败9.无状态10.最少依赖11.简单可靠12.分散原则13.隔离原则14.故障自愈15.版本兼容向旧兼容向新兼容16.数据一致性数据同步与对账数据时效性17.防御性编程18.异常处理19.CAP 定理20.BASE 理论参考文献1.依赖故障在分布式系统中被调服务发生故障无法正常响应请求时常发生。为了保证高可用需要采取一系列策略和措施来减少依赖故障对自身服务的影响。1.1 降级降级是一种在面对特殊业务或异常情况时保持系统可用的策略。当被调服务不可用时退而求其次提供一些基本功能或返回预设的默认值以确保系统依然能够提供有限的功能或服务又或者某些特定活动场景例如双十一下优先保障计算资源投入到 业务倾向的服务降级边缘服务。大部分服务是如下结构既要给上游使用又依赖于下游提供的第三方服务中间又穿插了各种业务逻辑这里每一块都可能是故障的来源。如果第三方服务挂掉怎么办我们业务也跟着挂掉显然这不是我们希望看到的结果如果能制定好降级兜底的方案那将大大提高服务的可靠性。比如我们做个性化推荐服务时需要从用户中心获取用户的个性化数据以便代入到模型里进行打分排序但如果用户中心服务挂掉我们获取不到数据了那么就不推荐了显然不行我们可以在本地 cache 里放置一份热门商品以便兜底。又比如做一个数据同步的服务这个服务需要从第三方获取最新的数据并更新到 MySQL 中恰好第三方提供了两种方式一种是消息通知服务只发送变更后的数据。一种是 HTTP 服务需要我们自己主动调用获取数据。我们一开始选择消息同步的方式因为实时性更高但是之后就遭遇到消息迟迟发送不过来的问题而且也没什么异常等我们发现一天时间已过去问题已然升级为故障。合理的方式应该两个同步方案都使用消息方式用于实时更新HTTP 主动同步方式定时触发比如 1 小时用于兜底即使消息出了问题通过主动同步也能保证一小时一更新。1.2 冗余通过在不同的地理位置或服务器上部署多个冗余组件确保即使上游服务出现故障系统也能继续运作。可以使用多个数据源、服务器或服务实例以便在一个出现故障时其他实例仍然可以提供服务。冗余需要故障转移Failover的配合。在被调出现故障时自动或手动将流量转移到备用系统确保系统的持续运行。最好是实现自动化故障转移例如使用负载均衡器如 Nginx、HAProxy来监控后端服务的健康状态并自动将流量切换到可用的备份服务。1.3 自研在关键被调服务无法满足需求时自主开发和维护关键组件以降低对外部服务的依赖。构建自有的服务或组件以替代外部服务的功能确保系统在被调服务不可用时能够独立运行。2.超时时间对依赖的内部、外部系统和各种基础组件需要设置一个合理的超时时间。如果超时时间设置过长系统长时间不返回请求积压可能会导致服务过载系统挂掉。如果超时时间设置过短可能会出现大量请求超时系统的可用性会降低。此外如果你的服务超时时间设置得短,重试次数设置得多,会因为重试增加系统的整体耗时;如果超日时间设置得短,重试次数设置得也少那么这次请求的返回结果会不准确。所以需要为请求设置合理的超时时间根据业务需求和被调服务的性能进行调整防止请求无限等待或大于被调服务自身的耗时时间。【强制】主调用方必须设置超时时间且调用链路超时从上往下递减。建议超时时间的设置如下服务类型建议超时时间(ms)DB50-100Redis50-100MQ50-100公司内部服务500公司外部服务3000-5000其他参考P99耗时【强制】服务间访问必须通过命名服务方便服务发现控制下游节点和超时时间。【推荐】调研被依赖服务自身调用下游的超时时间是多少。调用方的超时时间要大于被依赖方调用下游的时间。3.失败重试被调失败了一定要重试吗如果不管三七二十一直接重试这样是不对的。比如有些业务返回的异常表示业务逻辑出错那么你怎么重试结果都是异常又如有些异常是接口处理超时这个时候就需要结合业务来判断了有些时候重试往往会给被调方造成更大压力雪上加霜。所有失败重试要有收敛策略必要时才重试并做好限流处理。注意接口重试的风险。当大系统被拆分成多个微服务微服务之间大量RPC调用经常可能因为网络抖动等原因导致RPC调用失败这时候使用重试机制可以提高请求的最终成功率减少故障影响让系统运行更稳定。虽然重试能够提高服务的稳定性但是一般情况下大家都不会转经易去重试或者说不敢重试主要是因为重试有放大故障的风险。首先重试会加大直接下游的负载。如下图假设 A 服务调用 B 服务重试次数设置为r包括首次请求当 B 高负载时很可能调用用不成功这时 A 调用失败重试 BB 服务的被调用量快速增大最坏情况下可能放大到 r 倍不仅不能请求成功还可能导致 B 的负载继续升高甚至被流量打挂。更可怕的是重试还会存在链路放大效应结合下图说明一下假设现在场景是 Backend A 调用 Backend BBackend B 调用 DB Frontend均设置重试次数为 3 。如果 Backend B 调用 DB Frontend 3 次都失败了这时 Backend B 会给 Backend A 返回失败。但是 Backend A 也有重试逻辑Backend A 重试 Backend B 3 次每一次 Backend B 都会请求 DB Frontend 3 次那么 DB Frontend 就会被请求 9 次实际是指数级扩大。假设正常访问量是 n链路一共有 m 层每层重试次数为 r则最后一层受到的访问量最大为n * r ^ (m - 1)。这种指数放大的效应很可怕可能导致链路上多层都被打挂整个系统雪崩。【强制】与被调方沟通接口在失败超时情况下能否重试最好由被调在回包告知是否可以重试。【强制】重试需设置最大重试次数一般是重试三次。【强制】重要操作进行重试时梳理请求处理的整个链路保证重试收敛在一个地方不然可能造成指数级放大。【建议】重试策略可以考虑带有退避策略如固定间隔重试、指数退避等避免频繁重试导致被调负载变大。4.幂等设计幂等Idempotence指对接口调用多次和调用一次所产生的结果一样。接口需要幂等设计的主要原因是确保在不同条件下的重复请求或意外的重复操作不会出现意外结果或数据损坏。重复请求很容易发生比如用户误触超时重试等。举个最简单的例子那就是支付用户购买商品后支付支付扣款成功但是返回结果时网络异常超时成功此时钱已经扣了用户再次点击按钮此时会进行第二次扣款返回结果成功用户查询余额发现多扣钱了流水记录也变成了两条这就没有保证接口的幂等性。幂等设计是分布式系统和服务设计中的一个重要原则有助于提高系统的可靠性、稳定性和一致性。在设计幂等接口时通常会使用一个唯一的事务标识符例如请求ID或幂等键来识别重复的请求。服务器可以跟踪已处理的事务 ID并对相同 ID 的重复请求返回相同结果。核心流程写接口设计的时候需要考虑幂等性的需求比如超时重试和异常的重复请求。幂等接口需要进行幂等性测试确保在重复请求的情况下接口的行为符合幂等性要求。5.限流限流Rate Limiting是对请求速率进行限制避免瞬时大量请求击垮系统。现实生活中处处都有限流的实际应用比如排队买票为了避免大量用户涌入购票厅而导致售票员无法处理。限流可用于保护下游服务。当下游服务的处理能力有限时限流可以避免主调方发送过多请求至被调方防止下游服务如数据库、微服务、第三方API崩溃或出现严重性能下降。限流亦可用于保护自身服务。同样地如果我们的服务资源有限、处理能力有限就需要对主调方的请求进行限制以防止自身服务由于资源耗尽而被迫停止服务。常见的场景有1请求限制。下游有严格的请求限制。比如银行转账接口微信支付接口等都有严格的接口限频。2保护下游。下游不支持高并发场景。比如提供异步计算结果拉取的服务并不需要考虑各种复杂的高并发业务场景不需要提供高并发流量的支持。每个主调方应该在拉取数据时缓存下来而不是每次业务请求都过来拉取将业务流量打到下游。3突发流量控制。例如在电商促销活动、抢购、票务抢购等场景用户请求量会在短时间内急剧增加导致服务器负载骤增。限流可以防止服务被过量请求压垮。常用的限流算法有漏桶算法和令牌桶算法。必要的情况下需要实现分布式限流。关于限流的实现方式参见服务高可用利器 —— 限流算法介绍与示例。6.过载保护何为过载保护过载保护Overload Protection是通过控制请求的处理速率限制系统在特定时间窗口内接收和处理的请求数量确保系统在超负荷时仍能提供降级服务避免完全崩溃。基于系统资源指标CPU 80%、内存不足、线程池满等比如数据库连接池耗尽时拒绝新连接。为何要过载保护当系统已经过载时那么需要做过载保护防止服务过载引发雪崩。相信很多做过高并发服务的同学都碰到类似事件某天 A 君突然发现自己的接口请求量突然涨到之前的10倍没多久该接口几乎不可使用并引发连锁反应导致整个系统崩溃。当流量过大服务过载时可以采取「拒绝」或者「引流」等措施。如何过载保护请求等待处理超时比如把接收到的请求放在指定的队列中排队处理如果请求等待处理超时了假设是 100ms这个时候直接拒绝超时请求。再比如队列满了之后清除队列中一定数量的排队请求保护服务不过载保障服务高可用。服务过载及早拒绝根据服务当前指标如 CPU、内存使用率、平均耗时等判断服务是否处于过载过载则及早拒绝请求并带上特殊错误码告知上游下游已经过载应做限流或熔断处理。引流当系统过载时自身只做轻量的处理逻辑然后将流量转发给性能更高的系统去做处理或者完全不处理只做一个代理转发。过载保护与限流的区别限流指控制流量速率防止系统过载。而过载保护指系统已经发生过载确保系统在超负荷时仍能提供降级服务避免完全崩溃。7.熔断何为熔断熔断Circuit Breaker是微服务架构中的一种自我保护机制。当一个服务频繁出错或响应过慢时熔断器会暂时切断对该服务的调用避免故障 cascading级联从而防止整个系统被拖垮。为何要熔断在分布式架构中一个服务通常会与多个外部服务进行交互这些外部服务可能是RPC接口、数据库、第三方 API 等。例如在支付过程中可能需要调用银联提供的 API而查询某个商品的价格则可能需要进行营销活动查询。然而除了自身服务外依赖的外部服务不可能 100% 可靠。当依赖的第三方服务出现异常情况时例如第三方服务过载会导致调用第三方服务的响应时间变长甚者形成级联效应。这样一来服务自身的请求可能会积压最终可能耗尽自身资源导致自身服务不可用。如何应对这种情况生活给了我们答案比如老式电闸都安装了保险丝一旦有人使用超大功率的设备保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的服务也需要装上“保险丝”以防止非预期请求压垮系统。服务的保险丝就是熔断器Circuit Breaker。熔断器可以帮助系统在依赖的下游服务出现问题时保证整个系统的高可用减少对下游的压力防止故障进一步扩散。同时也能在一段时间后重新尝试恢复正常操作。避免局部不稳定因素导致整个分布式系统雪崩。如何实现熔断熔断实现通常包括监控、熔断器、降级和自动恢复等机制。重点说一下熔断器的工作原理。熔断器分为三个状态关闭状态、开启状态和半开状态。关闭状态Closed允许调用远程服务记录调用的状态和延迟等信息。开启状态Open当调用失败率达到一定阈值后熔断器会进入开启状态停止调用远程服务直接返回失败的响应。半开状态开启状态下每隔一段时间熔断器会进入半开状态允许部分请求通过以测试远程服务的可用性。如果测试成功则熔断器进入关闭状态否则重新进入开启状态。一般不需要自己实现一个熔断器可以使用常见的熔断组件主要有奈飞的 Hystrix 以及阿里的 Sentinel两种互有优缺点可以根据业务的实际情况进行选择。8.快速失败遵循快速失败原则一定要设置超时时间。假设一个第三方接口正常响应时间是 50ms某天该第三方接口出现问题大约有15%的请求响应时间超过 2s。没过多久我们的服务负载飙升到 10 倍以上响应时间也变得很慢即第三方服务将我们的服务拖垮了。为什么会被拖垮没设置超时我们采用的是同步调用方式使用了一个线程池该线程池里最大线程数设置了50如果所有线程都在忙多余的请求就放置在队列里中。如果第三方接口响应时间都是 50ms 左右那么线程都能很快处理完自己手中的活并接着处理下一个请求但不幸的是如果有一定比例的第三方接口响应时间为 2s那么最后这 50 个线程都将被拖住队列将会堆积大量请求从而拖垮整服务。正确的做法是和第三方商量确定一个较短的超时时间比如 200ms这样即使他们服务出现问题也不会对我们服务产生很大影响。9.无状态尽可能地使微服务无状态。无状态服务可以水平扩缩容从而不会成为性能瓶颈。状态即数据。如果某一调用方的请求一定要落到某一后台节点使用服务在本地缓存的数据状态那么这个服务就是有状态的服务。我们以前在本地内存中建立的数据缓存、Session 缓存到现在的微服务架构中就应该把这些数据迁移到分布式缓存让业务服务变成一个无状态的计算节点。迁移后就可以做到按需动态伸缩微服务应用在运行时动态增删节点就不再需要考虑缓存数据如何同步的问题。10.最少依赖能不依赖的尽可能不依赖越少越好。减少依赖便可以减少故障发生的可能性提高服务可靠性。任何依赖都有可能发生故障即使其如何保证我们在设计上应尽可能地减少对第三方的依赖。如果无法避免则需要对第三方依赖在发生故障时做好相应处理避免因第三方依赖的抖动或不可用导致我们自身服务不可用比如降级兜底。11.简单可靠可靠性只有靠不断追求最大程度的简化而得到。乏味是一种美德。与生活中的其他东西不同对于软件而言“乏味”实际上是非常正面的态度。我们不想要自发性的和有趣的程序我们希望这些程序按设计执行可以预见性地完成目标。与侦探小说不同缺少刺激、悬念和困惑是源代码的理想特征。因为工程师也是人他们经常对于自己编写的代码形成一种情感依附这些冲突在大规模清理源代码的时候并不少见。一些人可能会提出抗议“如果我们以后需要这个代码怎么办”“我们为什么不只是把这些代码注释掉这样稍后再使用它的时候会更容易。”“为什么不增加一个功能开关”这些都是糟糕的建议。源代码控制系统中的更改反转很容易数百行的注释代码则会造成干扰和混乱那些由于功能开关没有启用而没有被执行的代码就像一个定时炸弹等待爆炸。极端地说当你指望一个Web服务7*24可以用时某种程度上每一行新代码都是负担。法国诗人 Antoine de Saint-Exupéry 曾写道“不是在不能添加更多的时候而是没有什么可以去掉的时候才能达到完美”。这个原则同样适用于软件设计。书写一个明确、简单的 API 是接口可靠的保证。我们向 API 消费者提供的功能和参数越少这些 API 就越容易理解。在软件工程上少就是多一个很小很简单的 API 通常也是一个对问题深刻理解的标志。软件的简单性是可靠性的前提条件。当我们考虑如何简化一个给定的任务的每一步时我们并不是在偷懒。相反我们是在明确实际上要完成的任务是什么以及如何容易地做到。我们对新功能说“不”的时候不是在限制创新而是在保持环境整洁以免分心。这样我们可以持续关注创新并且可以进行真正的工程工作。12.分散原则鸡蛋不要放一个篮子分散风险。比如一个模块的所有接口不应该放到同一个服务中如果服务不可用那么该模块的所有接口都不可用了。我们可以基于主次进行服务拆分将重要接口放到一个服务中次要接口放到另外一个服务中避免相互影响。再如所有交易数据都放在同一个库同一张表里面万一这个库挂了此时影响所有交易。我们可以对数据库水平切分分库分表。13.隔离原则控制风险不扩散不放大。不同模块之间要相互隔离避免单个模块有问题影响其他模块传播扩散了影响范围。比如部署隔离每个模块的服务部署在不同物理机上。再如 DB 隔离每个模块单独使用自身的存储实例。古代赤壁之战就是一个典型的反面例子铁锁连船导致隔离性被破坏一把大火烧了80W大军。隔离是有级别的隔离级别越高风险传播扩散的难度就越大容灾能力越强。例如一个应用集群由N台服务器组成部署在同一台物理机上或同一个机房的不同物理机上或同一个城市的不同机房里或不同城市里不同的部署代表不同的容灾能力。例如人类由无数人组成生活在同一个地球的不同洲上这意味着人类不具备星球级别的隔离能力当地球出现毁灭性影响时人类是不具备容灾的。14.故障自愈没有 100% 可靠的系统故障不可避免但要有自愈能力。人体拥有强大的自愈能力比如手指划破流血会自动止血结痂再到皮肤再生。微服务应该像人体一样当面对非毁灭性伤害故障时在不借助外力的情况下自行修复故障。比如消息处理或异步逻辑等非关键操作失败引发的数据不一致需要有最终一致的修复操作如兜底的定时任务失败重试队列或由用户在下次请求时触发修复逻辑。15.版本兼容向旧兼容向旧兼容Backward Compatibility也称为向后兼容是指在软件系统或接口更新时确保新版本的系统或接口能够正常运行旧版本的功能或与旧版本的客户端进行交互。向旧兼容性设计对于保持系统的稳定性和用户体验至关重要尤其是在大规模分布式系统中向旧兼容性可以防止新版本导致的业务中断或用户操作失败。以下是关于向旧兼容性设计的一些原则和方法1接口兼容性新增功能在接口更新时尽量以增加新功能的方式而不是修改或移除旧功能。例如在REST API中可以通过新增API路径或增加新的请求参数来实现新功能而不影响现有接口。默认值支持如果必须增加新的参数或字段确保它们具有合理的默认值使旧版本的客户端无需更改即可继续使用。版本控制引入版本控制机制如在API路径中包含版本号如 /api/v1/resource确保旧版本的客户端可以继续访问原有的API版本而不会受到新版本更改的影响。2数据格式兼容性结构化数据在数据格式如JSON、XML中新增字段时确保旧版本的客户端可以忽略未识别的字段而不出错。例如在JSON响应中增加新字段时旧版本的客户端应能忽略这些新字段。数据格式转化如果数据格式发生了变化提供兼容性的转换层确保旧版本的客户端仍能正确解析数据。保持字段的语义避免改变已有字段的语义和类型。例如不要将字符串字段改为数字字段这样可能会导致旧客户端的解析失败。3数据库兼容性数据库迁移在进行数据库结构变更时采用迁移脚本逐步演进数据库结构确保新旧版本的代码都能正常操作数据库。避免一次性大规模修改数据库表结构。保持旧字段新增字段或表时尽量保留旧字段或表直到所有旧版本的代码都不再依赖这些旧结构为止。可以在代码中标注即将废弃deprecate的字段但暂时不要移除。兼容性索引在数据库中创建新的索引时考虑到旧版本代码对索引的依赖避免移除旧的索引结构。4配置文件兼容性配置文件版本化在配置文件中增加版本号并且在新版本中保持对旧版本配置文件的兼容解析。例如如果增加了新的配置项确保在没有配置该项时使用默认值。配置项保留保留旧的配置项即使新版本不再使用也不要直接移除确保旧版本系统能够继续使用相同的配置文件。5兼容性测试回归测试在新版本发布前执行回归测试确保新版本的代码不会破坏旧版本的功能。自动化测试建立向后兼容性的自动化测试模拟旧版本客户端的行为验证其在新版本环境下的兼容性。灰度发布在新版本发布时采用灰度发布的方式逐步将流量引导到新版本并监控兼容性问题。6文档与沟通版本发布说明在发布新版本时提供详细的版本发布说明明确新版本的变更内容以及对旧版本的兼容性。Deprecation 通知当某个功能或接口即将被废弃时提前通过文档或公告通知用户给出合理的迁移周期。开发者指南为开发者提供向后兼容性设计的最佳实践和指南确保在系统设计与开发过程中保持兼容性意识。7向旧兼容的实际应用API 开发在开发REST API时通过版本控制、默认值、保持字段语义等手段确保API更新不会影响旧版本客户端。数据库结构调整在数据库结构调整中通过增量式迁移、保留旧字段、创建兼容性索引等方法确保旧版本代码能够正常运行。软件升级在企业系统中进行软件升级时通过灰度发布、自动化兼容性测试等手段减少升级对业务的影响。8小结向旧兼容性设计确保了在系统不断演进的过程中旧版本的功能和用户体验能够得到维护。通过版本控制、数据格式兼容、数据库迁移、配置文件管理等多种手段可以有效地避免新版本引入的问题对现有系统的影响从而提高系统的稳定性和用户的满意度。向新兼容向新兼容也称为向前兼容Forward Compatibility是指指系统、接口或协议在设计时考虑到未来的版本更新使得当前版本能够与未来的版本兼容。这意味着即使未来的版本发生变化当前的系统或客户端仍然能够正确地理解或处理这些变化而不需要进行重大修改。【强制】兼容上游扩展。账户类型、品类、字段取值等自身服务需要做到兼容并在非法输入时编写防御性代码来处理这些情况做好容错。【建议】兼容性字段在数据库设计中保留一些扩展字段或列用于未来可能的功能扩展。16.数据一致性数据同步与对账在分布式系统中数据一致性是指不同节点之间的数据在一段时间内保持同步和一致的状态。由于分布式系统中存在网络延迟、节点故障等问题如何保证数据的一致性成为一个重要的挑战。根据CAP理论分布式系统必须在一致性Consistency、可用性Availability和分区容忍性Partition Tolerance之间进行权衡。【强制】凡是数据同步必须有对账机制。建立自动化巡检工具定期执行对关键模块的审计和回顾。【强制】数据需要需要满足最终一致可通过兜底脚本来实现。【强制】关键数据状态要集中化管理避免多个系统分别维护相同状态如使用容器本地缓存导致不一致推荐使用中央数据库或分布式数据管理系统统一管理关键数据状态。【强制】采用可靠的消息队列如 Kafka、RabbitMQ或事件驱动架构确保关键数据状态的实时同步。【建议】数据最终一致需要根据业务场景考虑业务容忍度比如容忍数据不一致的时间长度和数据一致性对用户体验的影响。【建议】实施异常监控和预警机制及时发现和处理关键数据状态未更新/更新失败的问题。数据时效性数据时效问题通常指的是如何确保系统中数据在各个组件和系统之间保持最新和一致同时在延迟、数据同步等方面不会影响用户体验。【强制】数据缓存或兜底数据一定要有更新机制。如基于时间的 TTLTime-To-Live和基于事件的失效机制确保数据不过时。【建议】在系统启动或服务恢复时刷新缓存或兜底数据使数据有机会重新初始化。【建议】时效性监控。有主动发现数据已过期的机制并设置告警阈值及时发现和解决时效性问题。17.防御性编程防御性编程Defensive Programming 是软件开发中的一种编程思想和方法旨在通过在代码中引入额外的防护措施来提高软件的鲁棒性。通过防御性编程开发者可以预防或减少因意外输入、错误条件或不符合预期的环境引发的错误确保系统在各种情况下都能正常运行。防御性编程的核心原则和实践1输入验证检查输入的有效性对所有外部输入用户输入、文件、API调用等进行严格验证确保输入数据的格式、范围和类型符合预期。避免注入攻击通过验证和清理输入防止SQL注入、XSS攻击等安全漏洞。2异常处理捕获并处理异常使用try-catch块捕获可能的异常并提供合理的处理方案如记录日志、返回友好错误信息或执行补救操作。定义自定义异常为特定业务场景定义自定义异常使错误处理更加清晰和具有针对性。3边界条件检查边界检查在处理数组、集合、字符串等数据结构时检查边界条件防止越界访问。避免溢出在处理数值计算时检查是否可能发生溢出或下溢并采取相应措施防止意外结果。4容错设计使用默认值当输入或外部资源不可用时使用合理的默认值确保系统继续运行。降级处理在关键功能无法执行时提供简化版本或降级服务避免系统完全失效。5断言和预条件使用断言在关键路径或重要逻辑中使用断言来验证代码的前置条件确保程序状态符合预期。明确前后条件在函数或方法中确保输入参数前条件和输出结果后条件符合函数的设计约定。6设计时假设最坏情况悲观思维假设外部系统可能返回错误、不可靠的数据或在关键时刻不可用并设计系统以应对这些情况。资源管理确保对资源内存、文件句柄、网络连接等的正确分配和释放防止资源泄漏或死锁。7接口与契约明确接口契约定义和遵守API的输入、输出和异常处理契约确保调用方和实现方有一致的理解。封装和隔离通过封装和模块化设计减少各组件之间的依赖防止错误在系统中传播。8日志记录记录关键操作在程序中记录重要的操作、输入数据和异常情况以便后续调试和问题排查。日志级别控制合理设置日志级别如INFO、WARN、ERROR避免生产环境中记录过多无关信息影响系统性能。9慎用外部依赖验证外部依赖对第三方库、服务和组件进行严格验证确保其行为符合预期并能够处理其可能的错误情况。降级或备用方案当外部依赖不可用时提供降级方案或备用实现确保系统关键功能继续运行。10测试与验证单元测试通过全面的单元测试覆盖所有代码路径验证代码的正确性和鲁棒性。模拟与仿真通过模拟各种错误场景如网络故障、数据库不可用测试系统在异常情况下的行为。防御性编程的优点提高系统稳定性通过主动防范可能出现的错误和异常系统在各种情况下都能保持稳定。增强安全性防御性编程有助于防止常见的安全漏洞如注入攻击和未授权访问。便于维护和扩展通过清晰的契约、输入验证和异常处理系统的维护和扩展变得更加容易。防御性编程的挑战代码复杂性过度的防御性代码可能导致代码臃肿难以阅读和维护。因此需平衡防御性和代码简洁性。性能开销额外的检查和验证可能增加系统的性能开销需在设计时权衡性能与鲁棒性。防御性编程是一种思维方式它要求开发者在编写代码时始终考虑到系统可能遇到的各种异常情况并提前设计好应对措施以确保系统的稳定性和安全性。18.异常处理在系统开发中处理接口的异常分支是确保系统稳健性和用户体验的关键部分。异常分支处理不仅需要识别和响应错误还要确保系统能恢复正常状态避免数据不一致和其他问题。以下是接口异常分支处理的关键策略和步骤【强制】技术方案中要有依赖接口异常场景的处理预案明确接口字段异常的处理情况以便事前确认和事后对照。【强制】编码上采用不信任编码对依赖服务返回的数据进行校验确保数据的完整性和正确性包括检查空数据、未识别的枚举值、数据格式错误尽量等。常见的操作if分支一定要有elseswitch一定要有default。【强制】实现错误重试机制设置合理的重试次数和间隔时间避免频繁重试引发依赖服务更大的负载。注意重试的对象必须是幂等支持重试的接口且需要在系统中最靠近数据上游的地方重试以免造成重试风暴。【强制】建立监控系统配置异常告警实时监控请求的成功率、失败率、响应时间等指标增加关键指标的电话告警增强处理及时性。【强制】错误链路等异常分支必须至少有一条错误日志的详细信息打印包括异常类型、时间、请求参数等信息。【强制】对于接口使用的固定流程或特殊场景应当在协议中明确指出告知使用方可减少不必要的沟通和风险。【强制】数据兼容性问题应当在设计时充分考虑。防止需要使用或处理到历史数据时逻辑对应不上需要额外处理。【建议】实现降级兜底策略当依赖服务异常时根据接口是否为主流程强依赖决策当前请求的回包逻辑。如提供替代方案如兜底返回缓存数据、或默认值、或默认文案通知用户系统繁忙稍后重试等。注意默认值不适用于千人千面的接口如用户获取自己的持仓信息。【建议】实现熔断策略当依赖服务频繁出现异常时通过命名服务暂时停止对其请求避免对下游服务造成更大压力。【建议】定期演练依赖服务接口异常的各项场景增加对外部依赖不可用情况的故障注入回归验证流程。【建议】外部依赖变化时应主动检查自身应用的异常分支处理是否适配。19.CAP 定理2000 年加州大学伯克利分校的计算机科学家 Eric Brewer 在分布式计算原理研讨会PODC上提出了一个猜想分布式系统有三个指标一致性Consistency 可用性Availability 分区容错性Partition tolerance它们的第一个字母分别是 C、A、P。Eric Brewer 说这三个指标最多只能同时实现两点不可能三者兼顾这便是著名的布鲁尔猜想。在随后的 2002 年麻省理工学院MIT的 Seth Gilbert 和 Nancy Lynch 发表了布鲁尔猜想的证明使之成为一个定理即 CAP 定理。CAP 定理告诉我们如果服务是分布式服务那么不同节点间通信必然存在失败可能性即我们必须接受分区容错性P那么我们必须在一致性C和可用性A之间做出取舍即要么 CP要么 AP。如果你的服务偏业务逻辑对接用户那么可用性显得更加重要应该选择 AP遵守 BASE 理论这是大部分业务服务的选择。如果你的服务偏系统控制对接服务那么一致性显得更加重要应该选择 CP遵守 ACID 理论经典的比如 Zookeeper。总体来说 BASE 理论面向的是大型高可用、可扩展的分布式系统。与传统 ACID 特性相反不同于ACID的强一致性模型BASE 提出通过牺牲强一致性来获得可用性并允许数据段时间内的不一致但是最终达到一致状态。同时在实际分布式场景中不同业务对数据的一致性要求不一样因此在设计中ACID 和 BASE 应做好权衡和选择。20.BASE 理论在 CAP 定理的背景下大部分分布式系统都偏向业务逻辑面向用户那么可用性相对一致性显得更加重要。如何构建一个高可用的分布式系统BASE 理论给出了答案。2008 年eBay 公司选则把资料库事务的 ACID 原则放宽于计算机协会Association for Computing MachineryACM上发表了一篇文章Base: An Acid Alternative正式提出了一套 BASE 原则。BASE 基于 CAP 定理逐步演化而来其来源于对大型分布式系统实践的总结是对 CAP 中一致性和可用性权衡的结果其核心思想是即使无法做到强一致性但每个业务根据自身的特点采用适当的方式来使系统达到最终一致性。BASE 可以看作是 CAP 定理的延伸。BASE 理论指Basically Available基本可用基本可用就是假设系统出现故障要保证系统基本可用而不是完全不能使用。比如采用降级兜底的策略假设我们在做个性化推荐服务时需要从用户中心获取用户的个性化数据以便代入到模型里进行打分排序。但如果用户中心服务挂掉我们获取不到数据了那么就不推荐了显然不行我们可以在本地 cache 里放置一份热门商品以便兜底。Soft state 软状态软状态指的是允许系统中的数据存在中间状态并认为该状态不影响系统的整体可用性即允许系统在多个不同节点的数据副本存在数据延时。Eventual consistency最终一致性上面讲到的软状态不可能一直是软状态必须有时间期限。在期限过后应当保证所有副本保持数据一致性从而达到数据的最终一致性因此所有客户端对系统的数据访问最终都能够获取到最新的值而这个时间期限取决于网络延时系统负载数据复制方案等因素。参考文献微服务的4个设计原则和19个解决方案博客园.如何健壮你的后端服务高可用的本质CAP 定理的含义 - 阮一峰的网络日志CAP理论该怎么理解为什么是三选二为什么是CP或者AP面试题有哪些Base: An Acid Alternative如何优雅地重试 - 字节跳动技术团队