场景
假设我们正在使用微服务架构模式开发一个在线商店应用。大多数的服务都需要持久化数据到某些数据库中。例如,订单服务存储订单信息,用户服务存储用户信息。
问题
微服务应用中的数据库架构是什么样的?
约束条件
服务必须是松耦合的,这样才能够独立地开发、部署和扩展这些微服务。
必须保持某些跨多个服务的业务事务的不变性。例如,下订单用例必须验证新订单不会超出用户信用额度限制。某些业务事务必须更新由多个服务拥有的数据。
某些业务事务需要查询由不同服务拥有的数据。例如,查看可用信用额度用例必须查询用户服务来查找信用额度限制以及订单服务来计算未完成订单总数量。
某些查询必须关联由多个服务拥有的数据。例如,查找属于特定区域的用户以及他们最近一段时间的订单,需要关联用户和订单。
有时候为了扩展,数据库必须可以被复制和共享。
不同服务有不同的数据存储要求。对于某些服务,关系型数据库是最好的选择。其它服务可能需要NoSQL数据库比如MongoDB(擅长存储复杂、非结构化数据)或Neo4J(能高效地存储以及查询图形数据)。
解决方案
保持每个微服务的持久化数据是私有的并且只能通过该服务的API访问。下图展示了该模式的结构:
服务的数据库实际上作为该服务实现的一部分。它不能直接地被其它服务访问。
保持服务持久化数据的私有有一些不同的方式。为每个服务提供一个数据库服务器不是必要。例如,如果使用关系型数据库,可以使用下面的三种方式:
Private-tables-per-service —— 每个服务拥有一个必须只能被该服务访问的表集
Schema-per-service —— 每个服务都有一个私有的数据库schema
Database-server-per-service —— 每个服务都有自己的数据库服务器
Private-tables-per-service和schema-per-service的开销是最低的。使用schema-per-service比较有吸引力,因为该方式让所有权比较清楚。某些高吞吐量服务可能需要它们自己的数据库服务器。
创建一些边界壁垒来加强模块化是个好主意。例如,可以给每个服务分配不同的数据库用户ID,并且使用数据库访问控制机制比如授权。没有某种边界壁垒来实施封装,开发人员总会被诱惑去绕过某个服务的API而去直接访问它的数据。
影响
每个服务一个数据库的好处:
有利于确保服务是松耦合的。某个服务的数据库发生改变不影响其它服务。
每个服务能够使用最合适它们需求的数据库类型。例如,某个服务处理文本搜索,可以使用ElasticSearch。处理社交图数据的服务可以使用Neo4j。
弊端:
实现跨不同服务的事务很复杂。由于CAP理论,最好避免分布式事务。此外,很多现代(NoSQL)数据库不支持分布式事务。最好的解决方案是使用Saga模式(事件履历模式)。当服务更新数据时,服务发布事件。其它的服务订阅这些事件并且更新它们的数据作为回应。
目前在多个数据库中实现关联查询是很有挑战的。
一些解决方案:
API组合 —— 应用处理关联而不是数据库。例如,每个服务(或者API网关)可以检索某个用户和他的订单,首先从用户服务检索用户,然后查询订单服务,返回用户最近一段时间的订单。
命令与查询职责分离(CQRS)—— 维护一个或多个包含不同服务的数据的物化视图(materialized view)。这些视图由订阅事件的服务保有。例如,通过维护一个关联用户和订单的视图,在线商店能够执行某个查询去找出某个特定区域的用户和他们最近一段时间的订单。该视图由订阅用户和订单事件的服务更新。
管理不同SQL和NoSQL数据库的复杂性