MongoDB 4.x——微服务入门
MongoDB 4.x微服务入门
- 1、微服务定义
- 1.1、什么是微服务
- 1.2、理解微服务
- 1.3、微服务的通用特性
- 1.4、微服务不是“银弹”
- 2、微服务基础设施
- 2.1、服务注册
- 2.2、服务发现
- 2.2.1、客户端发现
- 2.2.2、服务器端发现
- 2.3、API网关
- 2.4、服务容错
- 2.5、服务监控
- 2.6、配置中心
- 2.7、接口调用
- 2.8、容器化
- 3、CAP与BASE理论
- 3.1、CAP理论
- 3.2、BASE理论
- 4、为什么MongoDB适合微服务
- 4.1、灵活、可扩展
- 4.2、快速、持续的发布
- 4.3、数据治理、整合
1、微服务定义
1.1、什么是微服务
微服务(Microservice)是一种去中心化的应用架构方案。相对于单体式应用来说,微服务应用具有耦合性低、扩展性高、更灵活、能更高效交付的特点。
从名称上看,微服务的“微”涵盖了以下几层含义:
- 服务按功能进行了一定粒度的拆分,每一块都有独立的职责。
- 由于做了拆分,每一个微服务的开发都是独立进行的,因此这种架构的交付节奏可以更加灵活。
- 微服务应用的部署及运行都是隔离的,这保证了整个应用架构可以按需进行扩展。
1.2、理解微服务
微服务概念的提出,来自Martin Fowler于2014年3月发表的一篇论文。
在微服务整体理念的阐述中,大部分是围绕单体应用所遇到的瓶颈而展开的。为了更好地理解微服务,我们往往需要将其与传统应用进行比较。下图所示是一个典型的单体应用架构。
上图所示的架构是一个典型的电子商城应用,其中用户模块、购物车模块、商品模块都集中在一个商城后台服务中,每个功能模块都承担一定的业务逻辑。
- 用户模块:负责用户的登录、注册,包含用户资料的存取。
- 购物车模块:负责存取购物车中的商品信息。
- 商品模块:负责商品信息的查询、管理。
这样的架构在前期往往可以运行得很好,主要体现在如下几个方面。
- 开发简单,团队只需要使用一种技术框架,如基于Java EE的Web应用开发,使用统一的IDE,并集中在一个项目中进行开发。
- 部署简单,仅仅需要向Web容器中部署一个war包。
- 扩展简单,只需要一个负载均衡器,就可以水平地扩展系统的吞吐量,如图所示。
然而,这些便利不会持续很久,随着系统业务向上发展,各种问题也会接踵而来。
- 由于系统变得复杂而庞大,团队的开发成员也会相应增加。对于新成员来说,了解整个项目所要花费的学习成本变得很高,这样一来便降低了整体的开发速度。此外,由于所有人都可以修改项目的任一模块,便增加了“坏代码”的风险,此时很难评估一个代码变更所带来的质量风险。
- 用IDE编译、构建项目的速度更慢了,也降低了开发效率。
- 整个应用代码越庞大,其启动速度也会越慢,这样不利于做快速的部署。
- 持续集成变得困难,一个庞大的单体应用也需要频繁地部署,为了更新其中的某一个小部件必须重新部署整个应用,增加了失败的风险。
- 扩展变得困难,单体应用架构只能从单一的维度上进行扩展,即通过水平增加应用的部署实例来提升吞吐量。但不同的应用模块对于资源的需求是不同的,比如有些是重CPU计算,而有些是内存资源型的,这些应用模块在单体应用架构中将无法独立扩展。
- 部署上变得不灵活,在图7-2所示的架构中,对于商品模块的一个更新将导致整个应用都需要进行升级。
- 单体应用意味着需要长期维护一个技术栈,这样不利于团队对于新技术的学习与创新。比如使用Java Web开发,在某些情况下需要切换底层的技术框架,这将导致所有的模块都需要进行适配性的更新、验证及重新部署。
将单体应用架构按微服务的架构进行拆分,则可以解决上述多个问题。我们将这个电子商城应用转变成微服务化的架构,如图所示。
在新的微服务化架构中,用户、购物车、商品被划分为单独的服务,每个服务都拥有自己的数据库实例。服务之间的交互不再通过本地代码调用来完成,而是使用轻量级的接口进行通信。
这种架构具备的优点如下:
- 服务间的边界更加清晰,每个服务只需要关注自身的内部逻辑,这对于开发和维护来说变得更加容易。
- 单个服务的代码相对更少,因此项目的编译、构建及启动速度也更快。
- 微服务可以单独进行部署和升级,在运维上灵活性更高。
- 技术栈的选择更加灵活。不同的服务可以选择灵活的技术栈,比如在购物车服务中使用内存式KV数据库,而商品服务中使用检索数据库。
- 实现按需伸缩,根据需要对某些微服务进行单独的扩展,如发展的需要,可以为商品服务配置更高性能的服务器,或者增加更多的部署实例。
1.3、微服务的通用特性
在Martin Fowler的描述中,微服务架构风格具备以下一些通用特性,读者可以作为一些参考。
- 以微服务作为组件单元:在微服务架构中,最小的单元就是服务,一个服务在定义上等同于一个可独立部署、升级的组件。
- 按业务能力组织服务:微服务是按业务属性来拆分的,比如例子中的商品、购物车、用户都分属不同的业务模块。
- 产品而非项目模式:项目模式下的交付形式是水平割裂的,即团队严格按照职责来划分。开发团队只负责设计、编码,之后交给测试团队进行测试,最终由运维团队来部署上线。而在产品交付模式下,一个团队则负责整个微服务项目的全生命周期(包含设计、开发、测试、运维多个阶段)。
- 轻量级通信机制:服务器采用简单、易理解的RESTful HTTP协议进行交互,并适当配合异步的MQ通信机制。
- 去中心化治理:在技术工具层面不会产生依赖,每个微服务可使用最合适自身的编程语言、技术框架。在数据库层面不产生耦合。每个微服务拥有独立的数据库,可以采用不同的数据库技术。
- 基础设施自动化:采用持续集成、持续交付等工具链来降低微服务构建、部署、运维的难度及成本,并加速服务交付的效率。
- 为故障设计:架构上重点考虑微服务运行的失败容错机制,提供合理的监控、故障转移能力。
- 可演进的设计:架构功能要可演进,而非一开始就大而全。微服务系统的设计随业务的发展而变化,在这个过程中不断寻求更快的升级、变更、扩展方式。
1.4、微服务不是“银弹”
尽管微服务架构具备众多优点,但其不是“银弹”。在新的项目中使用微服务架构,或者将一个旧项目改造为微服务架构都需要付出一定的成本。主要体现在以下几个方面。
- 开发设计的复杂度提升:本质上微服务架构就是分布式系统,在系统设计、开发过程中势必要处理网络延迟、数据冗余及一致性等问题。
- 接口管理难度增加:微服务之间通过接口进行交互,这样会存在调用关系的依赖,一个接口的变更必然导致所有依赖该接口的微服务需要进行升级。
- 运维监控的成本提升:由于系统被拆分成多个微服务,因此运维需要管理的进程实例会变得更多,此外部署拓扑也更加复杂。与此同时,问题定界变得更难了,一个业务流程的调用链路变长了,因此很难快速地界定问题出现在哪里,这需要依赖复杂的调用链跟踪技术。
- 学习成本更高:微服务架构本身所涉及的技术栈及组件更多,对于初学者来说增加了难度。
2、微服务基础设施
微服务架构本质上是一种面向服务的分布式系统,为了解决分布式所带来的一系列管理问题,微服务通常需要依赖一些基础设施来保证架构的完整性。
2.1、服务注册
在一个微服务集群中,由于服务的种类、实例数量有很多,仅通过人工配置的方式会加大工作量。而且这些服务实例的信息可能随时会发生变化,比如我们可能需要对某个服务做在线的扩容,或是因为故障处理而隔离某些节点。因此,需要有一个自动化的服务注册组件来完成这件事情。服务注册通常需要记录当前可用的服务实例信息,并提供服务注册表API。服务的调用方可以通过API获得所需服务的实例信息,并实时订阅服务实例的变化。
通常服务注册的实现方式是心跳,即注册表与服务实例之间保持一个稳定的心跳检测,根据心跳的状态来判定服务实例是否存活。
2.2、服务发现
既然大量的微服务实例都记录到了服务注册表中,那么服务的调用方则应该通过服务发现组件来动态地获得可调用的服务实例信息。在微服务架构中,服务的发现有两种实现方式。
2.2.1、客户端发现
客户端发现是指由调用方来完成目标服务实例信息的发现,如图所示。
其中,调用方先通过服务注册表API获得目标服务的实例信息,接着直接对目标服务发起HTTP接口调用。这种方式在实现上比较简单,客户端的服务发现行为通常可以由统一的SDK完成封装。另外,考虑到性能的因素,通常在客户端会对目标服务实例的信息进行本地缓存,同时跟注册表保持订阅关系。每当订阅的目标服务发生变更时可以更新本地缓存。
2.2.2、服务器端发现
服务器端发现是一种代理式的架构,即服务器间的调用统一使用负载均衡器(Lood Balencer)来完成,如图所示。
这与客户端发现的差别就在于:服务实例的发现由负载均衡器来完成,并且所有的微服务接口调用都由该组件来代理。
这种方式的好处是可以屏蔽被调用服务的一些内部细节,并增加一些公共的能力,比如接口鉴权、流量控制、日志记录等。但是弊端也很明显,由于所有的接口调用都需要经过该负载均衡器,所以该组件便很容易形成瓶颈,一旦负载均衡器故障将会产生全局的影响。
服务发现的实现方式无论是客户端发现,还是服务器端发现,都离不开以下两点。
- 依赖服务注册表组件来发现可用的服务。
- 提供目标实例的路由,如何在多个实例中挑选合适的节点取决于路由的算法,常见的包括随机路由、轮询路由、动态压力路由等。
2.3、API网关
API网关是外部系统接入微服务集群的唯一入口。我们可以将微服务架构看作一个整体,其内部的微服务职责划分、服务间的交互调用对于外部来说是不可见的。当然,外部也不应该关注这些。那么为了对外提供体验一致的访问接口,微服务需要一个统一的API网关,所有外部系统对微服务的调用都经过API网关组件。
API网关组件通常具备的功能包括但不限于:
- 接入鉴权;
- 传输加密;
- 请求路由;
- 流量控制;
- 灰度发布;
2.4、服务容错
前面提到,微服务拆分带来了分布式系统都会遇到的问题:在系统的节点实例变多后,实例故障的概率会增加。而且一旦故障发生,服务间的调用关系会导致故障大面积“传染”,通过人工进行实例故障隔离的方式效率是较低的,这就需要微服务能自动地检测问题并自动做出应对。这种检测及应对能力通常由服务容错组件提供,一些手段如下。
- 请求重试:在某些关键业务出现问题时,尝试进行请求的重试。
- 流量控制:这需要先对系统的容量做出明确的规划,然后对服务实例上的流量进行实时监控,一旦发现超过阈值则拒绝请求,这样可以避免整个系统全面瘫痪。
- 服务熔断:根据一定的规则判断目标服务是否已经失效。规则的设计可以基于某个时间窗口的调用失败率进行计算,如果超过阈值则执行熔断(快速返回错误消息)。
服务的注册、发现机制在一定程度上也提供了容错的能力,当实例发生故障时,调用方可以通过注册表服务动态获得感知。
2.5、服务监控
对微服务实例保持足够的监控是非常重要的,而通常架构上需要对服务监控组件进行单独考虑。监控的目的是及时发现问题并采取一定的合理规避措施,以保证服务的SLA质量。通常在微服务监控服务中提供的功能如下。
- 业务日志采集:比如系统中用户注册、上下线等信息。
- 运行指标采集:比如CPU、内存占用、JVM堆内存大小,或是某些接口流量等。
- 监控告警:对业务日志、运行指标信息进行分析,根据结果做出一定的判断和处理,比如当接口流量超过警戒线时产生告警。
- 调用链跟踪:用于业务流程在分布式调用中出现问题时提供定位的手段,调用链需要借助一些特定的技术实现,比如服务埋点、跟踪树等。
2.6、配置中心
传统的服务实例配置是通过本地配置文件(XML/YAML/PROPERTIES)实现的,比如数据库连接池的大小、接口请求流量的阈值等。对于配置的一些改动往往需要重新发布并重启服务,在存在大量实例时情况变得很不乐观。想象一下对于某个配置项的调整,你可能需要做几十次的发布动作。
通过将这些配置信息注册到统一的配置中心服务,微服务通过配置中心获取其所需要的配置,这样便免去了各种繁冗的发布工作。此外,如果服务实现了配置的动态感知及自动更新,则还可以实现各种平滑的动作。比如在数据库连接池的大小设置发生了变化时,实例可以自动感知而不需要重启。
2.7、接口调用
微服务架构推崇采用轻量化的接口调用方式,比如使用HTTP/REST。在项目实践中,我们还应该做出更统一的规范定义,并形成公共的接口调用组件。这部分需要考虑的内容包括:
- 数据的传输,如是HTTP还是TCP。
- 数据的编码,如是JSON还是XML,或是二进制。
- 数据的内容,如是否采用固有的消息头定义。
- 数据的安全,如是否使用TLS/SSL实现加密,如何对接口权限进行校验等。
2.8、容器化
以Docker为代表的容器技术是微服务的最佳组合。通过使用容器作为基础设施,微服务能够实现快速部署、快速迭代的目标。Kubernetes是当今容器标准化平台的代表,其提供了强大的容器生命周期管理功能,可用于部署、扩展和管理所有的微服务容器,如图所示。对于实现微服务的自治、敏捷化管理来说,容器的无状态、弹性伸缩能力无疑是最契合的。
3、CAP与BASE理论
事务是数据库的基本能力,事务保证了数据的特性,分别是:
- 原子性(Atomic);
- 一致性(Consistency);
- 隔离性(Isolation);
- 持久性(Duration);
然而,在分布式领域,事务的实现及一些定义变得复杂,这里又不得不提到经典的CAP和BASE理论。
3.1、CAP理论
CAP理论又被称为CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性),三者不可得兼得,而最多只能同时拥有两者。
这几个特性的说明如下。
- 一致性(C):分布式系统中节点的数据,在同一时刻拥有同样的值。对于每一次读操作都能够读到最新写入的数据。
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求,即保持高可用。
- 分区容忍性(P):在出现网络分区(中断)以后,系统是否还能继续保持运作。分区相当于对于通信条件的要求,如果出现了分区的情况,则势必会影响数据的一致性,即同步出现时延。此时系统就必须在一致性和可用性上做出选择。
实际上,CAP理论中忽略了网络时延对于系统的影响,在现实中网络时延一定是真实存在的,也就是P一定是存在的。因此分布式系统如果选择了高可用(AP),那么就会造成访问节点之间的数据不一致(牺牲一致性)。如果选择了一致性(CP),那么必须淘汰数据的备节点,而只访问主节点(牺牲高可用性)。CA的场景是无法存在的,因为网络通信失败的情况一定会存在。
3.2、BASE理论
BASE理论可被看作是CAP理论的一个补充,主要来源于对大规模互联网系统分布式实践的总结。该理论由以下几个短语组成(BASE)。
- Basically Available(基本可用)。
- Soft State(软状态)。
- Eventually Consistent(最终一致性)。
实质上,BASE是对于一致性和可用性进行权衡的结果,其主要思想是在系统无法实现强一致性(StrongConsistent)的情况下,根据应用的业务特点来做出一些 权衡及补充,并使系统达到最终一致性(EventuallyConsistent)。在达到最终一致性之前,系统会处于一个中间状态,具备以下特性。
- 基本可用:即损失部分可用性,比如响应时间变长,或者部分服务被降级。
- 软状态:数据会存在中间状态(不一致),但该状态不会影响系统的基本使用。
在经过一段时间之后,系统应该能达到真正一致的状态,比如数据复制经过一段时间后真正完成同步。
相比CAP理论来说,BASE理论将一致性分成了强一致性和弱一致性,并在充分考虑网络时延、系统吞吐量的情况下选择了一种基本可用(弱一致性)的处理思路,这无疑更加适用于现有的分布式系统。
对MongoDB来说,数据会在主备节点之间进行传输,节点之间的数据本身就一定会存在时延,但是否选择CP还是AP,可以由用户来决定,比如:
- 如果Read Preference(读优先)选择Primary,只读写主节点,那么一致性能得到保证,但主节点宕机时会产生不可用,这是CP。
- 如果Read Preference(读优先)选择Secondary,写主节点,读备节点,那么可用性提高了,但一致性却降低了,这是AP。
- 指定Write Concern(写安全)=Majority来提供写入数据的强一致性,但这样写操作的可用性就会降低,比如在节点宕机后,写操作由于无法满足大多数写成功的条件将会失败。
实质上,这种权衡会一直存在,而MongoDB提供的默认选项就是强一致性的(读写Primary),同时通过复制、基于心跳的失效转移(failover)等机制来降低系统发生故障时产生的影响,从而提升系统整体的可用性。
4、为什么MongoDB适合微服务
微服务这种小而美的架构模式,在现今已经成为分布式服务的默认选择。轻量化、解耦、快速发布,几乎都是微服务天生所具有的优势。我们在大肆谈论微服务架构的同时,却鲜少提及微服务所依赖的数据库架构。一个显而易见的事实是,大多数的互联网服务都是数据密集型的。因此,在实施微服务模式时,团队将不得不应对构建灵活高效的数据架构带来的挑战。
MongoDB的灵活、高扩展等特性让它和微服务产生了很高的契合度。因此,在微服务模式下的数据库选型工作中,MongoDB往往可以表现出较强的竞争力。
4.1、灵活、可扩展
灵活的扩展性是微服务架构的最大优势,在《可扩展性的艺术》(The Art of Scalability)一书中,将系统的可扩展性划分为3个维度,这就是经典的Scale Cube模型,如图所示。
在这个模型中,X 轴是指服务实例的横向扩展,即通过在负载均衡器后运行应用的多个相同实例来达成扩展。Y轴是功能性的拆分,将不同职能的模块分成不同的服务,比如按业务模块、读写模式进行划分。Z 轴则是指数据的分区(Sharding),通常可以理解为数据的分库、分表。
在一个完整的微服务架构中,需要同时考虑这3个维度的扩展。微服务拆分更强调的是Y轴的能力,这解决了耦合问题,应用服务可以自治和独立扩展。那么在X 轴和Z 轴层面,则绕不开数据库的高可扩展的能力。
Z轴实现数据的分区,一般可考虑以下两种做法。
应用分区:即应用层面对数据的存储进行分区管理。例如,使用UserID哈希取模的方式,将不同用户记录的数据存储到不同的数据库实例上。应用分区通常要求在应用层设计数据的拆分规则和路由策略,实现上会比较复杂。数据库分区:由数据库进行数据分区管理,数据的拆分、路由分发对应用层是透明的,这种方式往往可以实现低成本的扩展。对此,MongoDB提供了开箱即用的分区能力,可以帮助应用快速地实现数据的拆分工作。
X 轴实现了应用实例的水平复制,目的在于提供更高的负载能力和更高的可用性。但从完整的调用链路看,应用实例还需要读写底层的数据库,因此,X 轴扩展仍然要求数据库具备读写的扩展能力。在读写分离的场景下,使用MongoDB的副本集可以将读负载分担到多个备节点以降低性能风险;当系统产生无法承载的写压力时,使用MongoDB分片机制可以利用多个分片节点的写入能力来共同提供服务。
4.2、快速、持续的发布
DevOps理念的一个重要目标是实现微服务的快速开发、上线,MongoDB的动态模式可以成为该目标的一个推力。在新版本发布之时,传统的关系型数据库要求对数据表模式的变更进行强制的模式升级,这个操作可能会带来非常高的成本,尤其在一些超级大表上实现这种变更时可能会导致业务的中断。相比之下,MongoDB采取非强制约束的模式,应用可以选择兼容性处理以保证线上业务的平滑升级。
MongoDB这种动态Schema模式具备快速升级的优点,但是在项目演进过程中,团队仍然需要进行有关Schema的审查工作,否则容易产生混乱的设计。
除了微服务本身的升级,对于MongoDB的版本变更也可以在不停服务的情况下进行。利用副本集的故障转移特性,可以先升级备节点,再通过主备节点倒换的方式实现滚动升级。这样能有效避免数据库变更时对业务服务质量产生影响。
MongoDB所使用的JSON模型已经广为开发者所接受,在面向对象语言中使用JSON数据模型是非常自然的,而且还应该注意到,在构建微服务的轻量级API时,基于JSON的Resultful风格接口也被广泛应用。综合来看,在微服务中使用MongoDB会让开发工作变得更加简单。
4.3、数据治理、整合
在微服务架构实践中,或许并不会只使用一种数据库。在许多互联网项目中,混合数据库方案往往比较常见。例如,为了实现高并发的计数器,使用Redis是比较理想的选择。在分词、全文检索领域,使用ElasticSearch则更为合适。在实现商品目录管理、元数据存储时,选用MongoDB文档型数据库。微服务架构为使用混合数据库提供了非常好的基础,不同的业务模块可以使用独立的数据存储技术。
当然,混合数据库方案常见于大型项目,它的开发、运维管理成本也是显而易见的。在中小型项目中,往往只需要基础的OLTP功能和少部分OLAP功能,使用MongoDB已经足够了。
微服务架构要求服务自治,自治的范畴也同样包括了数据本身。这意味着数据的访问需要通过服务接口提供(屏蔽了服务所用的数据库技术),原则上服务之间不允许直接相互进行数据访问。这种高度自治的数据开发模式必然产生数据孤岛,一种常见的需求是跨服务之间的数据同步。对此,MongoDB的变更流(ChangeStream)功能提供了一个简单易用的方案,如图所示。
在生态合作方面,MongoDB连接器(Connector)项目可以与Spark、BI等产品进行快速整合,如图所示。
