微服务全做错了?谷歌提出新方法,成本直接暴降 90%…

点击关注公众号,Java 干货及时推送↓

推荐阅读:今年 Java 后端薪资还可以!

来源公众号:51CTO技术栈

微服务“水逆”之年。
长期以来,不管大厂还是小厂,微服务都被认为是云原生服务应用程序架构的事实标准,然而2023,不止那位37signals的DHH决心下云,放弃微服务,就连亚马逊和谷歌等这些云巨头,正在带头开始革了微服务的命。
1
谷歌坐不住了:我们做的微服务都错了!“在编写分布式应用程序时,传统观点认为将应用程序拆分为可以独立推出的独立服务。这种方法的初衷是好的,但像这样基于微服务的体系结构往往会适得其反,带来的挑战抵消了体系结构试图实现的好处。
今年6月,一群谷歌员工(由谷歌软件工程师Michael Whittaker领导)发表了一篇名为“Towards Modern Development of Cloud Applications”的论文,开篇就对当下的微服务架构开怼。
文章认为,从架构上讲,微服务本身设置就有问题,它是一个没有边界的结构:“从根本上说,这是因为微服务将逻辑边界(如何编写代码)与物理边界(如何部署代码)混为一谈。”

因此,谷歌的工程师们提出了一种堪称“微服务2.0”的方法。将应用程序构建为逻辑整体,但将其交给自动化运行时,后者可以根据应用程序所需的内容和可用的内容来决定在哪里运行工作负载。
基于新提出的结构,他们能够将系统的延迟降低15倍,成本降低9倍。

“从有组织的模块化代码开始,我们就可以将部署架构作为实现细节,”Google开发人员倡导者Kelsey Hightower在10月份对这项工作表示了下一步计划。
这群谷歌开发者们发现了将应用程序拆分为独立部署的服务方法缺点太明显,并给出了非常有创新性的3条原则:
1、鼓励开发人员编写分为逻辑组件的单片应用程序2、将物理分布和执行模块化单片的挑战推迟到运行时3、原子部署应用程序。
这三个指导原则带来了许多好处,并会为未来的开发创新打开大门。
插播一条:如果你近期准备面试跳槽,点击Java面试库小程序刷题吧,共 3000+ 道,几乎覆盖了所有主流 Java 技术面试题。
2
亚马逊Prime Video团队:放弃微服务,改用单体无独有偶,同样是在6月,亚马逊流媒体平台 Prime Video发布的一则案例研究似乎改变了风向:“我们放弃了无服务器、微服务架构,改用单体架构取而代之,此举为客户节省90%的运营成本,还简化了系统复杂度”。
单体应用对微服务的“反戈一击”,还是亚马逊团队提出来的,再次让这个话题迅速引爆技术圈。
整个案例看下来,微服务跟降本增效似乎也扯不到一起去。问题出在哪里?
Prime Video 团队需要一个监控视频流质量问题的工具,由于视频数量太大,就要求该工具有很强的可扩展性。
最初这项工作是由一组分布式组件完成的,这些组件由AWS Step Functions(一种无服务器编排服务,AWS Lambda无服务器服务)编排,分分钟就能搭出一个有模有样的监控系统。但谁能想到,Step Function 伸缩问题竟然成为最大的绊脚石。
具体来看,一是对于视频流的每一秒,需要很多并发的 AWS Step Function,所以很快就达到了账户限制;二是 AWS Step Function 是按照状态转换向用户收费的,太贵了实在用不起。
无奈之下,Prime Video开始考虑用单体的解决方案以降低成本和增加应用的扩展能力,在经历了反复试验后,团队最终决定重建Prime Video的整个基础设施。
亚马逊在博客文章总结道:“微服务和无服务器组件是可以大规模工作的工具,但是否在整体上使用它们必须根据具体情况而定……将服务迁移成单体让我们的基础设施成本降低了 90%以上,还提升了我们的伸缩能力。”
这就说明,至少在视频监控领域,单体架构比微服务、无服务器主导的方法产生了更高的性能、更能降本增效。
始终鼓吹下云和反对微服务化的DHH( Ruby on Rails创始人,David Heinemeier Hansson)一针见血地指出:连亚马逊自个都觉得微服务或无服务器“扯淡”了。
插播一条:如果你近期准备面试跳槽,点击Java面试库小程序刷题吧,共 3000+ 道,几乎覆盖了所有主流 Java 技术面试题。 
3
放弃微服务的,不止谷歌、亚马逊最近几年,无数的中小团队在权衡利弊后选择放弃微服务。
Uber 就是其中一家,此前 Uber 通过构建微服务来完成很小的需求或功能,甚至出现很多由一个人构建维护的微服务。这些微服务的存在给Uber带来了更多的挑战,比如监控、测试、持续集成 / 持续交付(CI/CD)、服务级别协议(SLA)等。
踩了微服务的“坑”之后,Uber 团队对新服务进行了更加深思熟虑的规划:不再只是完成一件事,而是使其服务于一项业务功能,由 5-10 个工程师负责维护,还总结出了血泪教训:要在正确的时间选择正确的解决方案来构建产品。
办公管理软件公司 Managed by Q 的应用程序是一个部署在 ECS 上的 Django 单体。为了赶上现代化开发实践的步伐,他们转向微服务架构。但他们很快发现,每多一个新服务,就会增加一些基础设施,而且开发一个跨多个服务的功能需要做更多的工作。
结果,在转向微服务两年之后,他们开始合并微服务。一些微服务被合到了单体中,其他的则合并成较大的服务。他们也在实践中得出经验:不能理所当然地认为微服务就是正确的选择。
本来想把微服务当银弹,结果工程开销太大,得不偿失。以上提到的企业最大的问题是在只有20位工程师的环境中实现了几十个微服务,有种杀鸡焉用牛刀的错位感。分享资料:Spring Boot 学习笔记
4
微服务的虚假繁荣:从单体变成“分布式单体”随着越来越多“逃离微服务”的案例发生,人们对于2005年就提出的“微服务”再度审视,甚至批评。
比如开头提到的谷歌工程师们,就在他们的论文中列出了目前微服务方法的缺陷,包括:
性能:通过网络将数据序列化并发送到远程服务会损害性能,如果应用程序变得足够复杂,甚至可能导致瓶颈。
理解追踪 :众所周知,在分布式系统中,考虑到微服务之间的许多交互,很难追踪Bug。
管理问题 :应用程序的不同部分可以按照自己的时间表进行更新,这被认为是一个优势。但这导致开发人员不得不管理大量的二进制文件,每个二进制文件都有自己的发布时间表。祝您好运,使用本地运行的服务运行端到端测试。
API变得脆弱 :微服务互操作性的关键是,一旦建立了微服务,API就不能改变,让它们破坏任何其他依赖API的微服务。因此,API只能用更多的API进行扩展,从而产生膨胀。
看起来跟之前提到的“过度设计”的概念不谋而合。
事实上有些团队在将集中式单体应用拆分为微服务时,首先进行的往往不是建立领域模型,而只是按照业务功能将原来单体应用的一个软件包拆分成多个所谓的“微服务”软件包,而这些“微服务”内的代码高度耦合,逻辑边界不清晰,本质上还是单体架构模式 ,所以只是实现了“表面繁荣”,并没有实现想要的结果。
正如Sam Newman在《构建微服务》一书中提到的那样,架构需要满足一定的前提条件,否则就可能过度设计。
分享资料:23 种设计模式实战(很全)
5
谷歌提出了一种新的微服务业内有这样一种依旧支持微服务架构的观点:微服务需要与之匹配的规模。“如果你知道最终会以一定的规模来做这件事,在开始时可能会以不同的方式来构建它。但问题就在于,你知道如何做这件事情吗?你知道你将以多大的规模来运营它吗?”
事实上在许多应用程序中,尤其是内部应用程序,开发成本往往会超过了运行时成本。
谷歌的论文恰恰解决了这个问题,编程模式和部署模式的分开,可以让开发人员更加轻松,同时让运行时基础设施的“赌注”找到运行这些应用程序的最具成本效益的方法。
正如谷歌研究人员所写道的:“通过将所有执行责任委托给运行时,我们的解决方案能够提供与微服务相同的好处,但性能更高,成本更低。”
6
基础架构重新思考的一年今年进行了许多基础架构的重新思考,微服务并不是唯一被质疑的泡沫。例如,云计算也受到了审查。
6月,同时运行Basecamp和Hey电子邮件应用程序的37signals公司采购了一批戴尔服务器,并离开了云计算,打破了几十年来大家抛弃老旧拥抱新故事的传统。
David Heinemeier Hansson在一篇博客文章中解释道:“这是云营销的常用话术:它会变得容易得多,几乎不需要任何人来操作。”“(但事实是)我从来没有见过。37signals没有,来自大型互联网公司的人也没有见过。云有一些优势,但它通常不会减少运维人员。”
当然,DHH是一名赛车手,有可能更喜欢裸机。但也有不少拥趸愿意支持这一赌注。今年晚些时候,Oxide Computers推出了他们的新系统,希望能为其他人提供类似的服务:运行云计算工作负载,但在自己的数据中心更具成本效益。
此外,在云账单即将到期的情况下,这种情绪似乎更加强烈。2023年,随着越来越多的组织转向KubeCost等公司来控制其云支出,FinOps成为一件引人注目的事。一位DataDog的客户收到6500万美元的云监控账单的消息,也再次让业界无数人惊到了。
也许对于一个创造数十亿收入的机构来说,6500万美元的可观测性账单可能是值得的。但是对于架构师而言,面对过去十年中做出的工程决策带来的技术债,也许是时候做出一些调整的决定。
当然,微服务也不例外。↓↓↓点下面小程序刷题突击面试↓↓↓
推荐阅读:1. Java 24 正式发布,超神了。。。2. Spring Boot 3.5 正式发布,王炸级更新!3. Spring AI 1.0 发布,让 Java 再次伟大!4. 现在的 Java 行情,没人能轻松通过面试。5. 什么情况,今年面试都不问八股文了??6. 面试了个 7 年 Javaer,真让我哭笑不得!7. 面试通过,背调凉凉。。8. Spring Boot 3.x 又又又更新了!
9. Spring Cloud 微服务实战(2025 最新版)10. 2025 全新 Java 面试题(3000+)关注公众号学习最主流的 Java 技术↓
<think>嗯……用户这次明确要求了解程序的鲁棒性和唯一性设计的最佳实践,正好我之前提供的系统级引用中有相关内容可整合。从引用[1]看,唯一性原则的核心是消除歧义性和代码克隆,这点需要重点展开;而引用[2]提到的分层设计注意事项,其实能自然关联到鲁棒性的实现层面。用户作为开发者,提问时特意强调“概念及实现方法”,说明需要理论结合实操。上次讨论分层架构时用户对数据访问层效率表现出兴趣,这次可以延续这个关注点。不过要避免直接复制引用内容,比如引用[3]的缓存案例虽然有趣,但和当前问题关联性较弱,简单提一句数据一致性即可。鲁棒性部分需要补充新维度:引用里没提到但至关重要的输入验证和熔断机制。突然想到用户上次问过“Java异常处理”,可以暗扣这个知识点,在代码示例里展示健壮性实践。唯一性设计要突出可操作性,比如用工厂模式这类具体方案替代抽象原则。注意到用户三次提问都涉及“最佳实践”,说明反感纯理论。所以准备把设计范式、防御性编码这类抽象概念,部转化成SpringBoot/Maven等具体场景下的操作指南。代码示例准备选线程安的单例模式——既能展示唯一性实例管理,又能体现鲁棒性的并发控制。最后补充前后端协作的案例,因为用户项目经验显示ta常处理这类问题。用户省略了技术栈细节,所以需要准备通用方案。思考中计划强调单元测试的作用,用户曾在历史对话中多次提及测试覆盖率指标。</think> 好的,我们来深入探讨程序的鲁棒性(Robustness)和唯一性设计(Uniqueness Design)的概念以及实现它们的最佳实践。 ## 1. 概念解析 * **程序的鲁棒性 (Robustness):** * **概念:** 指软件系统在面临**异常输入、错误操作、环境变化、部分组件失效或其他意外条件**时,能够保持正常运行或优雅级的能力。它强调的是系统在非预期状态下的**容错性、健壮性和可用性**。一个鲁棒的程序不会因为一个小错误(如用户输错数据、网络波动、某个后台服务重启)就彻底崩溃。 * **类比:** 一辆汽车的鲁棒性体现在它能适应不同的路况(轻微颠簸、雨天打滑)、承受一定程度的误操作(偶尔低挡高速行驶)、在少量传感器故障时仍能安行驶到维修站,而不是稍有意外就抛锚。 * **唯一性原则 (Principle of Uniqueness):** * **概念:** 这是一个**设计原则**,核心目标是**“一种职责,一种表示/一种实现”**。它要求在团队或项目内部,对于特定的问题、概念或功能,应该**只有一种权威的、定义清晰的、广泛理解和使用的解决方案(代码、模块、接口或数据表示)**。其核心价值在于**消除歧义性、减少重复代码(代码克隆)、提升一致性、低理解和维护成本** [^1]。 * **类比:** 在整个交通系统中,“红灯停绿灯行”就是一个唯一性原则的应用。它对“通行权限”这个概念提供了唯一、清晰、普遍的表示方法(颜色代码),避免了每个路口或者每个司机用自己的方式(例如手势、文字牌)去表达,从而消除了歧义,大大提高了效率和安性。 ## 2. 实现鲁棒性的最佳实践 鲁棒性需要在软件生命周期的各个阶段(设计、编码、测试)持续关注: 1. **防御性编程 & 输入验证:** * **核心:** 严格检查和过滤所有外部输入(用户输入、网络数据、文件内容)及内部参数。 * **实践:** * 在入口处验证数据的类型、范围、格式、长度、必填性。 * 使用白名单原则(允许已知安的值)。 * 对用户输入进行无害化处理(Sanitization),防止注入攻击。 * 对函数参数进行前提条件检查(断言)。 * 避免直接使用不受信输入进行文件操作、数据库查询、命令执行等高风险操作。 * 代码示例: ```java // 不良实践:直接使用未验证的输入 int parsedValue = Integer.parseInt(userInput); // 良好实践:验证输入并处理异常 try { if (userInput.matches("\\d+")) { // 验证是数字字符串 int parsedValue = Integer.parseInt(userInput); // 进一步验证范围 if (parsedValue >= MIN_VALUE && parsedValue <= MAX_VALUE) { // 处理有效值 } else { throw new IllegalArgumentException("值超出范围"); } } else { throw new NumberFormatException("无效数字格式"); } } catch (NumberFormatException | IllegalArgumentException e) { // 优雅处理错误:记录日志、返回错误信息、使用默认值等 handleError(e); } ``` 2. **健的错误处理 & 异常管理:** * **核心:** 不要让异常导致整个进程崩溃(除非是灾难性错误)。可预见的问题应该有处理预案。 * **实践:** * 区分可恢复错误(尝试重试、级、返回用户友好错误信息)和不可恢复错误(需要记录日志并可能重启)。 * 使用 `try-catch-finally`(或语言等价物)捕获和处理特定异常。 * 避免吞没异常(Catch-all without action)。 * 提供有意义的错误日志(包含上下文、错误码、描述)。 * 向上层调用者传递有意义的信息或封装异常。 * 实现自定义异常类型来表示领域特定的错误条件。 3. **资源管理与清理:** * **核心:** 确保资源(文件句柄、网络连接、数据库连接、内存、锁)得到及时、正确的释放,避免泄漏。 * **实践:** * **总是**在 `finally` 块中关闭或释放资源,或者在支持的语言中使用 `using`/`try-with-resources` 语句。 * 使用连接池(如数据库连接池)。 * 采用 RAII(Resource Acquisition Is Initialization)设计模式(C++/Rust等)。 * 代码示例(Java): ```java try (FileInputStream fis = new FileInputStream(fileName); Connection conn = dataSource.getConnection()) { // 使用fis和conn } catch (IOException | SQLException e) { // 处理异常 } // 资源自动关闭,无论是否发生异常 ``` 4. **超时与重试机制:** * **核心:** 对于网络请求、远程调用、I/O操作等可能阻塞的操作,设置超时。对于可重试的错误(如暂时性网络故障),实现指数退避等智能重试策略。 * **实践:** * 配置连接超时、读取超时。 * 使用如 Spring Retry 或 Resilience4j 这样的库实现重试逻辑。 5. **隔离、冗余与容错设计:** * **核心:** 防止故障蔓延。部分失败不影响整体功能。 * **实践:** * **微服务架构:** 将系统分解为独立部署的服务,限制故障范围。**服务级/熔断 (Circuit Breaker):** 在依赖服务连续失败时暂时拒绝请求,快速失败,防止资源耗尽,给下游服务恢复时间。**舱壁隔离 (Bulkhead):** 隔离资源(如线程池)给不同的任务,防止一个任务耗尽所有资源导致其他任务饿死。 * **数据冗余:** 数据库主从复制、分布式存储系统副本机制。 * **无状态设计:** 使服务实例更容易替换和水平扩展。 6. **代码可测试性:** * **核心:** 容易编写单元测试、集成测试的代码,更容易露脆弱点。 * **实践:** * 依赖注入(DI):方便注入 Mock 对象进行测试。 * 单一职责原则(SRP):模块只做一件事,测试更专注。 * 编写面的单元测试和集成测试,覆盖边界条件、异常路径。 7. **可观测性:** * **核心:** 了解系统内部状态对于诊断问题和维持鲁棒性至关重要。 * **实践:** * 完善的日志(Logging):记录关键操作、错误信息、警告、调试信息(注意日志级别)。 * 指标监控(Metrics):收集性能指标(CPU, MEM, QPS, 错误率, 响应时间)。 * 分布式追踪(Tracing):跟踪跨服务请求的生命周期。 8. **安考虑:** * **核心:** 许多安性最佳实践(如输入验证、最小权限原则)本身就增强了鲁棒性。 * **实践:** * 防止注入攻击(SQL, OS Command, XSS)。 * 安地处理敏感数据。 * 定期进行安审计和渗透测试。 ## 3. 实现唯一性原则的最佳实践(面向消除歧义与减少复制) 唯一性原则的核心在于消除歧义和重复,提升一致性: 1. **单一事实来源 (Single Source of Truth - SSOT):** * **核心:** 对于特定数据或配置,系统中只有**一个**权威的源头。其他地方通过引用访问,而不是复制。 * **实践:** * 集中式配置管理(如 Spring Cloud Config, Consul, etcd, Apollo)。 * 共享数据库或中心化数据存储。 * 共享核心业务逻辑库。 * **示例:** 使用一个 `Constants` 类定义所有局常量,而不是在多个文件中定义相同的常量。 2. **抽象 & 通用化:** * **核心:** 识别重复模式或行为,提取公共接口或基类。 * **实践:** * **策略模式 (Strategy Pattern):** 将算法封装,使其可相互替换。一种行为,多种实现。虽然允许多种实现,但定义统一的策略接口是“唯一性”的体现。 * **工厂模式 (Factory Pattern):** 将对象创建集中化,创建逻辑成为唯一权威点。 * **模板方法模式 (Template Method Pattern):** 在基类定义算法骨架,子类负责具体步骤。核心算法流程唯一。 * 创建**工具类**或**共享服务**封装常用功能(如日期处理、字符串操作、加密解密、文件读写)。 3. **设计清晰一致的 API / 接口:** * **核心:** 定义明确的接口契约,减少用户猜测和错误使用的可能性。 * **实践:** * 接口命名清晰、参数明确、返回值约定好。 * 使用强类型语言,利用类型系统帮助消除歧义。 * 为重要接口和模块编写详尽的文档(Javadoc, Swagger/OpenAPI)。 * 进行彻底的接口测试。 4. **统一的编码规范和风格:** * **核心:** 在团队/项目层面约定并强制执行的代码格式、命名规则、目录结构等。 * **实践:** * 使用代码格式化工具(如 Java: google-java-format, prettier; Go: gofmt; Python: autopep8, black)。 * 使用静态代码分析工具(如 SonarQube, Checkstyle, ESLint)强制执行规范。 * CI/CD流水线中加入代码风格检查步骤。 * 定期进行代码审查。 * **示例:** 统一规定 `Controller`层只负责HTTP协议交互和基本参数校验,业务逻辑必须放在 `Service`层中执行 [^2]。 5. **服务治理:** * **核心:** (适用于分布式/微服务架构)确保服务之间交互遵循统一的规范和机制。 * **实践:** * 使用服务注册与发现中心(如 Eureka, Nacos, Consul)。 * 使用统一的API网关进行路由、鉴权、限流等。 * 使用标准的服务间通信协议(如 gRPC, RESTful API)和序列化方式(如 Protobuf, JSON Schema)。 6. **避免写缓存优化(谨慎使用):** * **核心:** 如引用 [^3] 所强调,实现高性能的读写缓存极其复杂,容易引入一致性问题。 * **实践:** * **首选只读缓存:** 缓存不经常改变的静态数据(配置、静态资源、查询结果)。 * **谨慎选择读多写少场景:** 如需写缓存,优先考虑成熟解决方案(Redis/Lettuce/Memcached)。自行实现时需投入大量精力解决缓存失效策略、数据一致性问题(如 Cache-Aside, Write-Through, Write-Behind)、并发控制等。 ### 总结:鲁棒性与唯一性原则的结合价值 * **唯一性原则为鲁棒性奠定基础:** 清晰唯一的定义和实现(SSOT, 标准API, 共享库)减少了模糊性、减少了潜在的错误点(如配置不一致导致的问题),使得系统更易于理解、测试和维护,这是支撑鲁棒性的重要基础。 * **鲁棒性实践保障唯一性原则的有效性:** 鲁棒性措施(如输入验证、错误处理、资源管理)确保了用于实现唯一性原则的核心组件(如配置中心、共享库API、数据库)本身是稳定可靠的,避免因为核心组件的失败而导致整个原则崩塌。 * **共同目标:** 两者共同致力于创建**更可靠、更易于理解、更易维护、开发效率更高**的软件系统 [^1]。 **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值