Architecture-Microservices
由于工作的主要内容还是CRUD,对于软件架构设计鲜有接触,对相关概念的理解是欠缺的。这些欠缺在最近一次面试中暴露出来。虽然内心也在为面试造火箭,实际拧螺丝愤愤不平。很多架构的设计,对开发人员编码的影响是透明或极小的,因为这部分内容通常被框架完成或者进行了很好的封装,以让开发人员可以将注意力集中到业务逻辑上。但冷静后还是觉得,有必要去学习和实践,就算这些新玩意不一定用得到。这是一个拥抱进步的程序员该有的态度,也能帮助自己站在更高一层维度审视程序软件设计。
所以准备在闲暇时间学一学现代高并发、高性能、高可用程序开发。这些特征通常和一些关键词和技术绑定,例如微服务、分布式、SOA、DDD、SpringCloud等。对于这一堆概念,网上有各种介绍资料,质量也层次不齐。在似懂非懂看了一些后,总感觉大部分资料都在互相抄袭,引入一些列新的概念但又没有讲清楚。
因此转为阅读国外资料。虽然语言会影响阅读速度,但很多国外材料会回答得“更像人话”,反而更容易理解。因此开展一个系列,对国外一些好的文章进行解读或思考,作为阅读笔记。通常我会将文章先读1~2遍,然后再逐字写阅读笔记。阅读笔记的形式,更多是对原文内容的翻译和解读,不会像设计模式系列那样进行扩展和引起思考。
本次阅读的文章,是来自Martin Fowler关于微服务(本文不加区分使用微服务、微服务架构、微服务应用)的文章Microservices。接下来文本安排参照原文结构,对每一部分的关键内容总结并做一些解读。建议对照原文阅读本篇内容。
引言
作者在介绍微服务前,先叠了一层甲,提到微服务架构(Microservice Architecture)这个概念新起没多久。也没有十分明确的定义描述微服务,或指导如何实现。但作者仍尝试从各个方面,总结这些微服务应用的共同特点。总的来说,在
organization around business capability, automated deployment, intelligence in the endpoints, and decentralized control of languages and data.
围绕业务能力、自动部署、智能端点、语言和数据的分散控制这些方面有着共同特征。文章接下来的章节便是对这几方面做详细说明。
这篇文章发布于2014年3月25日。软件开发理论,更多是对于开发经验或规律的总结。这导致这个领域的理论发展,可能是先出现了某类事物,才会产生对这类事物的归纳总结。放眼现在,微服务概念在十年内变得更清晰,也有了一些最佳实践(best practice)指导。
简而言之,微服务架构风格是一种将单个应用程序开发为一套小型服务的方法,每个服务都在自己的进程中运行,并使用轻量级机制(如 HTTP)进行通信。这些服务围绕业务功能构建,可通过完全自动化的部署机制独立部署。这些服务的集中管理最低限度,可以用不同的编程语言编写,并使用不同的数据存储技术。
在介绍微服务之前,先介绍在微服务诞生之前的应用,主要采取的是单体风格(monolithic style)。在单体风格中,所有的功能都是一个整体,会被打包成一个应用程序。当单体中的任意一个部分发生改动时,都需要对整个应用重新构建部署。当需要对应用进行扩展时,需要对整个应用进行横向扩展。这些特点在现在越来越流行的云服务开发中,逐渐显现出不利。
微服务架构的特点
由于没有相对正式的定义,微服务架构的讨论更多为对其特点进行总结。
总的来说,微服务架构是将软件系统组件化(Componentization )。
组件(component)也是个难以定义的概念。Martin Fowler把组件定义为可独立更换和升级的软件单元。
component is a unit of software that is independently replaceable and upgradeable.
在java中,不同组件可以被打成不同的jar包。组件之间的通讯可以有多种方式,例如可以通过在内存中调用。采用这种通讯方式交互的组件也被称为库(libraries)。而微服务的主要特点就是将组件从库,转变为服务(service)。服务是通过如HTTP请求、RPC等方式进行通讯的组件。
服务的一个优点是可以独立部署。通常来说,如果一个应用是由一个主进程和多个库构成。当任意一部分发生变动时,需要将整个应用重新部署。而服务大部分情况下可以只部署发生变化的那些服务。只有当服务间的接口发生变动时,才需要重新部署涉及的多个服务。
使用服务的另一个优点是强制显示定义接口。通过接口定义可以更好地划分职责。服务间的调用只需遵循接口定义,而不需要了解其具体细节,促进了代码的解耦。
使用服务也有缺点,服务间的远程调用通常比内存中的调用更昂贵。因此在接口设计时,为了减少调用次数,会尽可能将接口设计得更通用。这也导致客户端使用接口会更复杂,可能需要额外的逻辑来解析或处理不必要的数据。
这一章最后,作者表述了服务和进程不一定是一一对应关系。例如服务可以包含一个应用进程,和一个仅被该服务使用的数据库进程。如果两者总是同时开发和部署,则可以被视为一个服务。
围绕业务能力组织
上一章提到,微服务架构的特点,是将一个大型软件系统,拆分成多个服务。那必然涉及到拆分的原则。
一种传统的拆分方式是根据技术层进行划分。例如 UI 团队、服务器端逻辑团队和数据库团队。这种划分方式会导致一些简单的改动,也会演变成跨团队的项目。每次更改都需要协调多个团队,增加沟通成本和项目复杂性。一个偷懒的解决方法,是将业务逻辑写在自己团队负责的代码中。例如UI团队直接在前端实现了一部分业务逻辑。这种方式虽然减少了短期内的沟通成本,但从全局来看会导致逻辑分散,系统变得难以维护。发生问题时,需要排查的范围变得更大。这就是康威定律(Conway’s Law )的一个实际例子。系统的设计结构会反映其组织的沟通结构。
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.
微服务提倡的拆分原则,是按照业务能力进行划分。一个团队涵盖软件的全栈实现,包括用户界面、持久存储和外部协作。团队是跨职能的,具备用户体验、数据库和项目管理等全方位技能。
围绕产品而不是项目
微服务强调从传统项目思维向产品思维转变。
传统的项目往往有明确的开始和结束时间,以完成特定的任务或达到特定的目标为导向,比如开发一个特定版本的软件系统并交付。当软件开发完成后,项目团队可能会解散,并将其维护工作移交给维护团队。
微服务则强调产品思维。产品思维注重的是为用户提供长期的价值和持续的服务。开发团队需要维护产品的整个生命周期。
单体应用当然也能采取这种产品思维,但微服务的小型自治团队,清晰的职责划分,更便于开发人员关注其产品。
智能端点 傻瓜管道
在跨进程间通讯时,请求从发起方到处理方,可能会经过其他系统的处理。例如发起方通过Enterprise Service Bus (ESB)调用服务时,ESB可以根据请求内容,对内容进行路由、转换,以及执行一些简单的业务规则。其中服务调用方和处理方称为端点,ESB在过程中是管道的角色。
微服务提倡智能端点,傻瓜管道。智能端点意味着端点有自己独立的业务逻辑和功能,能提供完整的服务。端点可以对数据进行处理和转换,使其符合自身业务需求以及与其他微服务交互的要求,而不需要在管道中进行。傻瓜管道则表示管道只负责在微服务之间传输数据,不具备任何业务逻辑处理能力。
基于这个原则,微服务通常采取简单的通讯协议。最常见的是使用HTTP请求和轻量级的MQ。
由于单体应用不同组件间,在内存中通过方法/函数互相调用,在将单体应用转为微服务时,需要更改通讯模式。直接将组件间的接口改为HTTP请求接口,会导致性能不佳。因此在调整接口时,还需要将接口调整为更粗粒度,泛用性更强的方法。通过减少调用次数以降低调用开销。
去中心化治理
围绕项目的单体应用倾向于集中化治理。在集中化治理模式下,应用通常会局限于某一技术平台/编程语言。但并非所有情况都能靠一种技术平台解决。微服务提倡去中心化治理,对于每一个场景都使用最合适的技术平台。当应用被拆分成多个服务,服务之间仅通过简单接口进行交互,每个服务可以自己决定其技术方案,只需保证可以实现相同的接口协议。虽然单体应用一定程度也可以使用不同语言,但这并不常见。
但去中心化治理不意味着所有类似的问题,都需要在每个微服务中重新解决一遍。对于制定标准,相对于使用文档这种传统书面方式去约束,微服务提倡开发实用工具,去解决相似问题,并通过开源在不同微服务之间共享这些工具。这段内容似乎和本章主题有些矛盾。去中心化治理,但又使用相同的工具。其实不然。去中心化强调的是,每个服务有自主选择方案的权力,不代表必须使用不同的方案。
服务契约(Service Contract)是定义服务提供者与服务消费者之间交互规则的一种约定,它包含了一系列规范,确保双方能够正确、高效地进行通信与协作。通常来说,服务契约包括接口定义、数据格式、协议规范、服务质量等。由于微服务中存在更多服务契约,因此会对其更加重视。微服务中常见的服务契约模式包括**“宽容读取者”、“消费者驱动契约”**等。消费者驱动契约指,由服务消费者根据自身的实际需求来定义期望从服务提供者那里获得的接口和数据格式等内容,即契约是由消费者 “驱动” 产生的。这与传统的服务提供者定义契约是相反的。这样的契约模式,减少服务之间的时间耦合,降低了对中央契约管理的需求。
去中心化数据管理
数据管理去中心化有多种形式。在最抽象层面,不同系统之间的数据概念模型是不同的。在对实体进行建模时,不同的系统会侧重不同的方面。例如销售部门对客户的看法与客服部门对客户的看法会有所不同。在销售视角中被称为 “客户” 的某些对象,在客服视角中可能根本不会出现。即便那些在两个视角中都存在的对象,也可能具有不同的属性。
微服务提倡采取使用领域驱动设计(Domain - Driven Design,DDD)中 限界上下文(Bounded Context)的概念。在复杂的业务系统中,一个领域可能包含多个不同的子领域,每个子领域都有其独特的业务规则、术语和工作方式。限界上下文就是将这些不同的子领域隔离开来,形成一个个相对独立的 “上下文”,每个上下文有自己明确的边界,避免不同子领域之间的概念和规则相互混淆。DDD将一个复杂的领域划分为多个限界上下文,并梳理出它们之间的关系。这个过程对于单体架构和微服务架构都很有用,不过在服务边界和上下文边界之间存在一种天然的关联,这有助于明确边界划分。
微服务还提倡数据存储去中心化。单体应用通常倾向于使用单个逻辑数据库来存储持久化数据。也就是说,整个单体应用的所有数据,不管是用户信息、订单信息还是商品信息等,都存储在同一个数据库中。在企业级应用场景中,甚至多个不同的单体应用也常常共用一个数据库。而微服务架构更倾向于让每个服务管理自己的数据库。每个微服务可以根据自身的业务需求和数据特点,选择合适的数据库技术。这些数据库可以是同一数据库技术的不同实例,也可以是完全不同的数据库系统,这种方法被称为多语言持久化。例如同时使用关系型数据库(MySQL、Oracle)和非关系型数据库(MongoDB、Redis)。
但是数据存储去中心化也会引入新的问题。对于数据的更新操作,以往处理更新操作的常用方法是在更新多个资源时使用事务来确保数据的一致性,这种方法在单体应用中经常被采用。但在微服务中使用事务时,会造成严重的时间耦合问题。分布式事务的实现难度是出了名的大。因此,微服务架构强调服务之间进行无事务协调,同时明确认识到一致性可能只能是最终一致性,并且通过补偿操作来处理出现的问题。
基础设施自动化
微服务提倡采取基础设施自动化技术。在这里,基础设施自动化技术是指持续交付(Continuous Delivery,CD)及持续集成(Continuous Integration,CI)。持续集成是指开发团队成员频繁地将代码集成到共享代码库中,并通过自动化构建和测试来验证代码的正确性;持续交付则是在持续集成的基础上,进一步实现软件的自动化部署,确保软件能够随时部署到生产环境。容器技术的良好发展也很好地支持了这项技术。
服务故障容忍设计
将服务用作组件会带来一个结果,应用程序需要被设计成能够容忍服务故障。由于服务提供者可能不可用,任何服务调用都有可能失败,客户端必须尽可能优雅地应对这种情况。与单体架构设计相比,处理服务故障会引入额外的复杂性。
由于服务随时都可能出现故障,因此能够快速检测到故障,并在可能的情况下自动恢复服务就显得至关重要。微服务应用十分注重对应用程序进行实时监控,既会检查架构层面的指标(例如数据库每秒接收多少个请求),也会关注与业务相关的指标(比如每分钟接收多少个订单)。语义监控能够在出现问题时提供早期预警,促使开发团队跟进并展开调查。
进化设计
不重要,略。