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等产品进行快速整合如图所示。