一文详解:企业数字化的未来基石——微服务
微服务架构(microservicearchitecture)是一种架构设计理念,旨在通过将功能分解到各个分散的服务中来实现对解决方案的解耦。服务化是将企业资源以业务能力的形式组织起来,通过一定的技术架构对这些业务能力进行封装,形成易于消费的服务,从而实现业务能力粒度的复用、组装、维护和管理,以灵活迅捷地构筑特定业务目的的企业应用,助力企业实现数字化转型。
微服务架构特点
技术架构的目标是实现对业务发展提供有力支持,比如业务敏捷、应用弹性、持续交付。相比单体架构,微服务架构具有如下特点。
每个微服务应用独立部署、独立运行(独立的进程),可以针对每一个微服务应用独立扩缩容。
一个微服务应用内包括若干服务,服务粒度无特别标准,综合考虑复用性以及功能完整性,保持组织内部理解一致即可。
微服务之间通过轻量级通信协议进行交互(基于 HTTP,而不是 SOAP)。协议风格分为 RPC 和 REST 两种。
微服务应用之间通过标准的接口以及契约进行交互,每个微服务应用可以采用不同的技术栈实现。
每个微服务应用都可以独立发布、独立升级,而不会影响其他的微服务应用,这种方式更容易实现对业务需求的快速响应。
去中心化架构,服务注册中心只在应用启动时用以注册及推送服务,服务调用过程无须通过服务注册中心,而采用更高效的点对点调用(或通过第三方的服务网关路由)。
微服务设计原则
微服务设计以服务识别出的服务目录为基础,针对服务粒度、依赖组合关系做合理调整(更新服务目录和服务关联),并对服务接口(输入/输出异常)、服务应用分组以及部署方式,采用契约先行的方式并更好地支持可伸缩高可用(如无状态原则),为目标做进一步的设计,同时在性能、管控、安全等非功能性需求方面对接下来的服务实现阶段提出明确的要求(服务定义)。
在服务设计阶段,针对服务模型(服务目录+服务关联+服务定义)的设计是自上而下、由粗到细的,微服务设计有以下一些参考原则。
服务化松耦合原则
实现的松耦合:即服务消费端不需要依赖服务契约的某个特定实现,这样服务提供端的内部变更就不会影响消费端,而且消费端未来还可以自由切换到该契约的其他提供端。
时间的松耦合:典型的是异步消息队列系统,因为有中介者(broker),所以生产者和消费者不必在同一时间都保持可用性及相同的吞吐量,而且生产者也不需要立即得到回复。
位置的松耦合:典型的是服务注册中心,消费端完全不需要直接知道提供端的具体位 置,而通过服务注册中心来查找服务提供端的地址来访问。服务提供端应用扩容时,也由服务注册中心实时通知消费端新的服务地址。
版本的松耦合:消费端不需要依赖服务契约的某个特定版本来工作,这就要求服务的契约在升级时尽可能地提供向下兼容性。
服务依赖原则
业务层:对依赖的聚焦迫使我们将流程合理化并进行精简。业务流程需要能够追溯到组织机构的愿景、组织的任务和为执行组织机构战略所需的广泛功能(产品管理、工程、市场、销售等)。
应用层:需要更加静态地对操作进行分组,那些属于一类的操作应该归到一起,并且在不同的系统间仅保留低限度的联系。
数据层:任何系统的“内部”数据模型又被称为“领域数据模型”,它们将永远不会对其他系统可见。与之相对应,系统的“外部”数据模型被称为“接口数据模型”,它们永远对外暴露并与其他系统分享。要区分好内外部数据模型。
技术层:服务之间的交互依赖面向接口以及契约,而非具体的实现。为了降低系统之间的耦合性,应尽可能减少直接依赖(如同步服务调用),更多地采用间接依赖(如异步消息)。
服务设计原则
优化远程调用:分析服务调用场景,选择较优的调用模式。多路复用 TCP 长连接,多服务复用 TCP 长连接,以减少 TCP 建立连接的时间消耗并减少服务器的端口占用。
消除冗余数据:尽量避免留有当前业务用例不需要的冗余字段,从而减小序列化和传输的开销。
粗粒度契约:在保证一定复用性的前提下,尽可能提供完整业务含义的服务粒度。
通用契约:为了支持不同语言和平台的客户端,服务契约中不能出现某些语言独有的高级特性,参数和返回值也必须是被广泛支持的较简单的数据类型。
隔离变化:隔离内外系统,把内部系统变化对外部的冲击减少到较小的程度。
契约先行:详细规定双方合作的内容、合作的形式等,对双方形成强有力的约束和保障。
稳定和兼容的契约:由于用户范围的广泛性,在公开发布之后就要保证相当的稳定性,不能随便被重构,即使升级也要考虑尽可能地向下兼容。
服务命名原则
建议使用服务使用者专业领域内有意义的名称,优先选用业务概念,而不是技术概念。应使用名词对服务进行命名,使用动词对操作进行命名。
服务粒度原则
综合考虑复用性以及功能完整性,尽量保持一个团队内部对于服务粒度的一致理解。服务应该是内聚而完整的。
内聚:创建功能内聚的接口,一组操作由于其功能相关而聚合到一起。从服务使用者角度看待服务粒度的划分更加有效。
完整性:服务应该为可复用的,因此需要考虑将来的使用者的可能需求。如果不知道使用者需求,则很难提供正确的功能,因此就有可能存在将开发和测试工作浪费在提供将不会使用的操作上的风险。
服务无状态原则
无状态的服务可以轻易地被替代,如果一个服务出现异常,系统可以随时拉起一个新的服务来接管原有服务的工作;而有状态的服务伸缩起来非常复杂,可以通过将服务的状态外置到数据库、分布式缓存中,从而使服务变成无状态的。
持续演进原则
应逐步划分、持续演进,避免服务数量的爆发性增长。这就类似于应用的灰度发布效果,先将几个不太重要的功能拆分到一个服务做实验,万一有问题也可以缩小故障影响范围。另外,除了业务服务数量的增加,还需要准备持续交付的工具、微服务框架等。随着分布式微服务的改造,系统的全链路追踪监控也要及时跟上,以便出问题时进行快速定位排查。
服务管理原则
业务管理垂直化:以垂直业务划分开发、测试、运维团队,明确跨团队服务消费需求并定义服务契约,方便背对背模式。
数据归属单一化:主数据规则(可写)存储归属于单一业务域,跨域数据规则读写必须通过所属域的服务,不能直接访问对方的数据库。
系统组织层次化:划分服务的系统逻辑层次,大量复用的服务尽可能下沉,对服务依赖做逻辑层次上的限制。
服务契约原则
合适粒度原则:平衡可维护性与易用性。可提供普遍适用的粗粒度业务逻辑片段,根据需求增加精细化的服务接口。
强描述性原则:服务名(名词)及方法(动词)意义明确,表意精准,服务模式明确(同步、异步)。
内聚完整原则:从服务消费者的角度出发,考查服务提供业务能力的完整性,即功能组合的自洽。
语义接口原则:接口信息使用语义化封装,内外隔离,接口信息对象独立于服务内部的实现信息对象定义。
接口普遍适用原则:接口信息基本属性使用跨语言的基本类型(字符串、日期、数值等)。
扩展兼容原则:在保证易用性的前提下,尽可能考虑未来扩展,避免未来频繁更改接口。
微服务治理
微服务治理的好坏决定了企业实施分布式微服务架构的成败。总体来说,微服务治理包括服务目录、服务路由、服务管控、服务监控 4 个部分的内容。服务治理的手段是从不同角度来确保服务调用的成功率。
服务目录
服务治理领域最重要的问题就是服务发现与注册。绝大部分微服务框架中引入了一个服务注册中心的概念,服务的注册与发现主要依赖这个服务注册中心。服务注册中心还需实时监听所有服务节点的健康状态,以及节点的上下线消息,及时更新推送服务地址信息。
服务路由
根据配置中心配置的路由规则进行服务的路由指向,既可以在调用方本地路由,也可以由专门的组件(如负载均衡)进行路由处理。服务调用并不总是一定成功的,可能的原因包括服务提供者节点自身死机、进程异常退出或者服务消费者与提供者之间的网络出现故障等。对于服务调用失败的情况,需要有手段自动恢复,来确保调用成功。
服务管控
提供不同环境(如开发、测试、生产)的服务隔离机制,以及不同租户之间的服务隔离。限流和降级是保护自身服务正常运行的两方面的手段。限流指的是当外部请求量太大时,进行主动拒绝(或者放入缓冲队列),确保系统不被压垮;降级指的是当依赖的第三方系统性能异常(或不可用)时,为了不被第三方系统拖垮。
服务监控
记录每一次服务请求的审计日志,以不同维度生成服务报表(调用量、调用时长、状态分布、错误率等)。服务追踪系统可以跟踪记录一次用户请求发起了哪些调用,经过哪些服务处理,并且记录每一次调用所涉及的服务的详细信息,通过日志快速定位是调用失败的环节。
微服务框架
HSF
HSF 是一个分布式 RPC 框架,联通各种不同的分布式系统,以服务的方式调用。其核心有服务注册中心(ConfigServer)、RPC 框架和服务治理(包括服务寻址、路由、限流等)。图 6-5 所示是 HSF 微服务框架,以及各个核心模块之间的交互流程。
HSF 底层的通信基于 Netty 设计实现高性能的服务器之间远程调用,支持多种序列化方式(java、hessian1、hessian2、kryo、fastjson 和 customized 等)和多种 RPC 协议(HSF、Dubbo 等),高性能得益于快速高效的二进制协议,包括二进制序列化和 TCP。
HSF 对开发最大的价值是透明地将本地调用转换为远程调用,而无须改动业务代码,客户端通过代理模式,支持多种调用方式,分别是同步调用、future 并行调用、callback 异步回调、泛化调用,以及 HTTP 调用。
HSF 服务治理包括服务发布和寻址,以及通过各种规则来实现诸如限流、白名单、授权、路由、同机房优先、单元化、动态分组、权重路由、降级等功能,配置和规则放在 Diamond 集中管理,有专门的 ops 页面进行维护,所有客户端和服务器端都会订阅这些配置和规则。
Dubbo
Dubbo 是一款高性能、轻量级的开源 Java RPC 框架。它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo 最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo 采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务。
Dubbo 采用的是 JavaDevelopment Kit(JDK)标准的串行外设接口(Serial Peripheral Interface,SPI)扩展机制,微核心+插件式,平等对待所有的第三方插件,扩展性极佳。
Spring Cloud
Spring Cloud 为最常见的分布式系统模式提供了简单易用的编程模型,帮助开发人员构建弹性、可靠和协调的应用程序。
Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,比如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控、链路追踪等。
Spring Cloud 采用的是基于 HTTP 的 REST 方式。RPC 一般面向接口引用来调用远端服务,对开发人员更加友好,且 RPC 主要是基于 TCP/IP 的,调用参数采用二进制进行序列化,网络传输更加精简高效;RPC 的缺点是调用双方存在技术栈的强绑定。REST 比 RPC 更灵活,天然具有跨语言的优势,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级的强依赖,这在强调快速演进的微服务环境下,显得更加合适。
gRPC
gRPC 是一个基于 HTTP/2 通信协议和 Protobuf 序列化的 RPC 实现,其前身是谷歌内部大量使用的 Stubby 服务间通信产品。gRPC 已被 CNCF 所收录,作为云原生架构的一个进程之间服务调用参考。
在 gRPC 中,客户端应用可以像调用本地对象的方法一样直接调用部署在其他机器上的服务器端应用的方法。gRPC 降低了构建分布式应用和服务的难度。和其他 RPC 系统一样,gRPC 的核心也是服务定义,定义可以被远程调用的方法的入参和返回值。gRPC 客户端和服务器端既可以在多种不同的环境下运行并相互通信,也可以使用 gRPC 支持的语言实现。
gRPC 并不算是一个完整的微服务框架,而只是一个轻量级远程服务调用技术。通常我们不会单独使用 gRPC,而是将 gRPC 作为一个组件进行使用。这是因为生产环境中在面对大并发的情况时,需要使用分布式系统来处理,而 gRPC 并没有提供分布式系统相关的一些必要组件。
Service mesh
service mesh 是一种非侵入式架构,用于处理服务到服务通信的专用基础设施层。它负责通过复杂的服务拓扑来可靠地传递请求。
service mesh 帮助应用程序在海量服务、复杂的架构和网络中建立稳定的通信机制。业务所有的流量都转发到 service mesh 的代理服务中,支撑应用之间透明的网络调用,可以确保应用的调用请求在复杂的微服务应用拓扑中可靠地穿梭。
service mesh 通常由一系列轻量级的网络代理组成,这通常被形象地称为边车(sidecar)模式,与应用程序部署在一起,但应用程序不需要知道它们的存在。以旁路的方式为应用系统提供服务发现、路由、负载均衡、健康检查和可观察性来帮助管理流量。
service mesh 承担了微服务框架的所有功能,包括服务注册发现、负载均衡、熔断限流、认证鉴权、缓存加速、运行监控等。可以说,service mesh 是云原生下的微服务治理方案。微服务架构强调去中心化、独立自治、跨语言,service mesh 通过独立进程的方式进行了隔离,可以低成本实现微服务框架这些特性。
当下主流的 service mesh 框架有:Linkerd、Envoy、Istio、SOFAMesh。有兴趣的读者可以去各个官网上查看相关信息。
微服务通信
一个完整的微服务调用框架包含 3 个部分。
1)通信框架:主要解决客户端和服务器端如何建立连接、管理连接以及服务器端如何处理请求的问题。2)通信协议:主要解决客户端和服务器端采用哪种数据传输协议的问题。
3)序列化和反序列化:主要解决客户端和服务器端采用哪种数据编解码的问题。
远程服务调用是分布式服务框架的基础和核心功能。目前主流的分布式服务框架使用两种传输协议。
1)RPC 协议:以 HSF、Dubbo、Thrift、gRPC、Motan 为代表的框架使用的协议。
2)HTTP REST 协议:以 Spring Cloud 为代表的框架使用的协议。
RPC
远程过程调用(Remote Procedure Call,RPC)框架作为架构微服务化的基础组件,能大大降低架构微服务化的成本,提高服务调用方与服务提供方的开发效率,屏蔽跨进程调用函数(服务)的各类复杂细节。
RPC 调用的四个关键细节
客户端如何找到服务器端地址
多数 RPC 产品采用本地负载均衡策略,即调用方启动时从注册中心拉取服务地址信息后,在本地缓存,运行过程中真正发起调用时,直接从本地缓存读取服务地址信息,根据一定的负载均衡算法选取某一个地址,发起点对点的直接调用。当服务器端地址信息发生变更时(如节点上下线),由注册中心实时推送变更信息到所有的调用方同步更新。
服务器端如何处理请求
服务器端 I/O 的方式通常分为 3 种处理方式:
同步阻塞(Blocking I/O,BIO)方式—— 一次请求需要创建一个线程处理。
同步非阻塞(Non-blocking I/O,NIO)方式——多个连接共用一个线程。
异步非阻塞(Asynchronous I/O,AIO)方式——当进行读写操作时,AIO 只需直接调用 API 的 read 或 write 方法。
序列化和反序列化
客户端和服务器端交互时将参数或结果转化为字节流在网络中传输,那么将数据转化为字节流或者将字节流转换成能读取的固定格式时,就需要进行序列化(数据编码)和反序列化(数据解码),序列化和反序列化的速度也会影响远程调用的效率。
常用的序列化方式分为两类:文本类(如 XML、json 等)、二进制类(如 Hessian、Thrift 等)。而具体采用哪种序列化方式。主要取决于 3 个方面的因素:支持数据结构种类的丰富度、跨语言支持、性能。
如何进行网络传输
多数 RPC 框架会选择 TCP 作为传输协议,也有部分会选择 HTTP(如 gRPC 使用 HTTP/2)。不同的协议各有利弊,TCP 更加高效,而 HTTP 在实际应用中更加灵活。
RPC 的优点:
对于服务器端的开发人员而言,容易设计、开发。
对于消费者而言,调用非常简单。便于做集中的监控。
基于 socket 的二进制 RPC 协议,建立连接延迟低、网络传输效率高。
支持有状态的长连接,可进行双向通信,实时性好。
在各个企业的使用较为成熟,许多企业都有自己的 RPC 实践,并已广泛应用在生产环节。
RPC 的缺点:
紧耦合:API 一旦发布,就难以再做改动。客户端必须使用特定的框架,而且需要引入 API 包。
没有统一的设计风格:增加了客户端开发人员的学习成本。难以实现通用的客户端库,每个 RPC 框架都有各自的协议。通常以动词的形式设计 API,一个功能就增加一个 API,设计时很少考虑领域模型。
掩盖网络的复杂性:开发人员很容易混淆远程调用与本地调用。实际上网络调用与本地调用是完全不同的,RPC 的调用方式,让使用者很难意识到是在进行网络调用,忽略了针对网络复杂性的处理。这样会损害用户(客户端)可感知的性能。
RESTful
RESTful 是一种网络应用程序 API 的设计风格和开发方式,基于 HTTP,可以使用 XML 格式定义或 json 格式定义。最常用的数据格式是 json。由于 json 能直接被 JavaScript 读取,因此以 json 格式编写的 REST 风格的 API 具有简单、易读、易用的特点。
REST 风格协议的特点如下:
1)每个统一资源标识符(Uniform Resource Identifier,URI)代表一种资源。任何事物只要有被引用的必要,它就是一个资源;要让一个资源可以被识别,需要有一个唯一标识,在 Web 中这个唯一标识就是 URI。2)客户端使用 GET、POST、PUT、DELETE 这 4 个表示操作方式的动词对服务器端资源进行操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。3)通过操作资源的表述形式来操作资源,资源在外部的具体呈现可以有多种表述形式(例如文本资源可以采用 HTML、XML、json 等格式,图片可以使用 PNG 或 JPG 格式展现出来),在客户端和服务器端之间传送的也是资源的表述,而不是资源本身。
4)客户端与服务器端之间的交互在请求之间是无状态的,从客户端到服务器端的每个请求都必须包含理解请求所必需的信息。这里说的无状态通信原则,并不是说客户端应用不能有状态,而是指服务器端不应该保存客户端状态。
RESTful API 的优点:
使用 HTTP 作为应用层协议(注意:不是传输层),没有耦合性。
可以使用浏览器扩展(如 Postman)或者 curl 之类的命令行来测试 RESTful API。
客户端使用任意支持 HTTP 的工具即可。使用类似 Netflix Feign 这样的专门设计的工具,可以做到接近 RPC 的调用方式。
可以方便地发布到外网环境。HTTP 对防火墙友好,可以设置各种安全策略。
基于资源的设计思想,强迫设计人员抽象资源,思考模型,使设计人员加深对业务模型的理解。
不需要中间代理,简化了系统架构。
RESTful API 的缺点:
由于 HTTP 是应用层协议,本身比 TCP 慢一些;加之数据本身是自描述的文本格式,需要占用更多的带宽,因此相比于 RPC,RESTful API 的速度会稍慢一些。
并不是所有业务场景都适合采用 RESTful API。也不是在设计 API 时就一定要遵守所有规则,如何取舍还要看具体业务需求,适合的才是最好的,毕竟架构也是伴随业务的发展而不断演进的。
微服务化实践指导
服务管理指导
服务依赖指导:重要的服务不能依赖非重要服务。
团队拆分指导:系统拆分时,团队规模大小以一个系统 10 个左右开发人员维护为宜(两个比萨原则)。
服务拆分指导:服务拆分时,往往先拆分数据服务层,因为数据服务层往往是复用性高的一层;同一个业务领域内不要拆分。
合并同类项:在服务设计过程中,要避免同类服务由不同服务单元提供;每个业务领域的通用规则需要沉淀成服务。
版本向下兼容:服务要做到向前兼容。如果无法做到,则需要在管控机制上确保服务消费者能强制升级。
组织架构适应:服务化架构的变化要使组织的架构能适应这种变化。
服务部署分离:在部署服务单元时,读服务和写服务分离,核心服务和非核心服务分离,以确保整个服务单元的稳定性和可靠性。
灵活可扩展:在服务接口定义时,要考虑服务需求的多样性和灵活性。
安全先行:服务化时要首先考虑安全。
动静分离:静态资源可以实现服务化,实现静态资源与动态资源分离,从而提高性能。
服务埋点:通过在外层系统埋点,可以实现面向终端用户服务的精细管理,比如服务的容量、服务的性能等。
服务实现指导
超时保护:要提供超时控制。
快速失败:只要发生错误,就立刻返回,避免服务调用时间过长。
结果可预期:任何服务的调用结果一定是成功、失败、未知三者之一,如果是未知,则需要再次检查。
服务无状态:在实现设计上确保服务无状态,避免让服务维护跨服务的会话上下文。
可重入幂等:在实现设计上确保服务操作的幂等效果。
异步解耦:能异步调用的服务尽量使用异步调用,从而提高系统响应速度并降低系统之间的耦合性。
避免单点:服务的实现不能有单点。
乱序可容忍:分布式架构会使任务、数据被处理的顺序可能不同于其产生的顺序,需要在实现设计上消除乱序对业务一致性的影响,要么让处理对顺序不敏感,要么以牺牲性能可扩展性为代价,强制实现处理的顺序性
高并发性能优化:高压场景下的服务调用链路需要特殊处理,可通过将链路缩短、异步化、批量化、缓存预热等多种提升性能的综合手段和处理方式。
版权声明: 本文为 InfoQ 作者【穿过生命散发芬芳】的原创文章。
原文链接:【http://xie.infoq.cn/article/187418005ea77ce04159e3d73】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论