多层依赖:如何避免落入数据服务接口的陷阱?

开发 前端
我们先看看熟悉的传统单体架构设计思路,重点关注一下它有什么缺点,这有助于你理解为什么会出现 CQRS 这种模式。这里我们继续沿用前面课程里用户中心的案例,请看下图。图里面展示了单体服务状态下的用户中心,这时候高频和低频的读写服务放在了一起。

前面,我们讨论了不同类型系统(如读多写少、强一致、写多读少和读写密集)的优化方法。但在很多复杂的业务系统中,读写逻辑往往相互交织、互相制约,这让优化工作变得更具挑战。遇到这样的情况,不妨尝试一种更为高级的拆分模式——CQRS。你或许已经听说过 CQRS,却因为它看似复杂而犹豫不决。不过,今天的课程会通过一些实例来展示 CQRS 与传统单体服务架构的不同之处。学完后,你会深入理解 CQRS 的设计思路,找到避免数据服务接口陷阱的方法,并理解一些“反直觉”的设计选择,比如在微服务架构中为何会把 5 个项目拆分成 200 个。

传统单体架构缺点

我们先看看熟悉的传统单体架构设计思路,重点关注一下它有什么缺点,这有助于你理解为什么会出现 CQRS 这种模式。这里我们继续沿用前面课程里用户中心的案例,请看下图。图里面展示了单体服务状态下的用户中心,这时候高频和低频的读写服务放在了一起。

图片图片

在我们的印象中,用户中心读并发流量大。但实际上用户中心里,不是所有功能都是读多写少类型的,你可以参考后面表格列出的例子。

图片图片

可以看到,除了读多写少的服务功能外,还有很多其它类型的功能(事实上这取决于调用方的场景)。

图片图片

这张图展示了传统单体架构的优化方式,我们可以清晰地看到在读写操作优化上的差异。首先,关于高并发场景下的写优化,通常需要使用队列作为缓冲,以保证数据一致性。对于一些特殊的业务场景,为了确保分布式事务的一致性,写操作的服务器数量不能过多,因为参与事务协调的机器越多,响应速度就越慢。至于读优化,主要是通过增加服务器的部署来分担请求压力,常见做法是对高频访问的数据进行多副本缓存。由于读操作占用大量内存,并且需要更多的数据层连接,且请求压力较大,因此我们的部署还必须支持服务和数据层的动态扩展。

通过这些分析,我们发现,在单体架构中,同时优化读写操作时,很难在成本和性能之间找到理想的平衡。此外,单体架构的数据库层也存在一些问题。正如图中所示,读写操作都依赖同一个数据库,这意味着数据层的性能上限也受到集群性能的限制。而且,由于数据库和代码层紧密耦合,一旦业务流量增长,我们就需要扩容或更换数据层,而这个过程会变得非常复杂。

图片图片

既然单体服务读写混合部署这么复杂,有没有更简单的解决方案呢?这时 CQRS 就能派上用场了。

CQRS 的读写拆分策略

CQRS,全称是命令查询责任分离(Command Query Responsibility Segregation),它是一种将应用程序中的命令(Commands)和查询(Queries)职责分开的方式。如果你对 CQRS 的详细理论感兴趣,可以课后自行深入学习。今天我们主要聚焦于读写拆分的部分。CQRS 最吸引我的地方就是它将读和写拆分成不同的项目,这种方式可以说非常符合“微服务”的理念。接下来,我们继续通过用户中心的案例来对比和分析。

图片图片

在示意图中可以看到,我们将用户中心的业务根据高并发写入和高并发读取两个类别,分拆成了独立的项目部署。这种拆分让读写优化更加灵活,投入成本也更为精准。拆分后,我们可以根据流量调整服务器和基础服务的规模,显著降低运维成本、减少服务压力。此外,读写拆分有助于提升数据库性能,扩大我们在数据层的选择,比如可以使用 ElasticSearch、ClickHouse、NoSQL 等特殊的数据服务。如果细心观察,你会发现许多流行的分布式数据服务中也存在类似的读写分离实现。

总结来说,CQRS 允许我们将读写操作分别优化和部署,最大化利用读写优化的混合优势,同时为系统提供灵活的扩展能力。在此基础上,我们还可以将常见的读写操作封装成标准模块,提升优化效率,让业务在复用这些模块时能直接具备动态扩展能力。

不过,读写分离也有一定代价,因此除非在核心业务中,一般不建议大规模应用。这些优化技巧对运维有较高要求,通常需要运维来配合修改配置和服务调整。比如在数据同步方面,我们一般使用队列(如 binlog 数据同步队列或业务变更广播)来实现。这类同步通常不需编写额外代码,而是依赖基础服务,但如果没有自动化工具,每次扩展都需要手动调整配置,既繁琐又易出错。

此外,一些业务场景可能需要读强一致性,拆分后的架构可能难以满足。这里建议引入一个类似 Raft 的读强一致性 Proxy,来确保业务轻松获得最新数据。

数据服务接口导致的多层依赖

高并发的业务常常很复杂,而 CQRS 比较适合拆分和优化数据接口,但对于复杂的业务接口我们还要做更多处理。想要理解这一点,先要审视一下我们的编程习惯。很多人认为,把业务接口写成数据接口这个方式是衡量服务是否灵活的标准,甚至催生出 RESTful 这种 API 设计思路,我们看一个例子。

图片图片

观察上面的网址,可以发现 RESTful 是围绕数据实体来设计的数据服务。这么做虽然方便快速迭代,但很容易让上层业务依赖底层的数据结构,而且接口的隔离性很差,修改的时候影响范围很大。

不仅如此,围绕数据实体去设计接口还会带来更多的问题。下图是一个常见的在线商城服务,可以看到每个具体业务应用(蓝色方块)都是通过拼合多个 Service 服务实现的。

图片图片

这种结构日常运转没什么问题,但一旦流量增大,对 QPS 要求更高的时候,这个结构就会有大量的网络损耗,接口的请求耗时链路分析如下图。

图片图片

我们目前接口的总返回时间是 420ms,而这个接口依赖了两个 Service 服务,而这两个 Service 又各自依赖了其他服务。显然,这种依赖关系消耗了大量的时间。在这种情况下,使用缓存来减少底层服务接口的调用次数可以提高接口性能,但这种方法也有局限性。它主要适用于优化那些不需要强一致性的读取服务,而且可能会导致缓存不一致等维护问题。

再想想我们在写项目时,为何通常会自觉地为每个功能多封装一层 Service,并且将 Model 层设计成多层目录?其实,原因很简单,因为多层依赖的情况非常常见,而实体之间的关系又往往比较复杂,一层 Model 很难涵盖所有的逻辑和关系。所以,我们通常会不自觉地通过这种方式来进行优化,确保代码结构更加清晰、灵活,也更易于维护。

图片图片

如果是大型系统,业务实体关系更多、更复杂,那么反映在代码上就会有更多的层级。当我们的系统复杂到一定程度,就会出现多层项目依赖问题,呈现出下图所示的“搭乐高”状态。

图片图片

可以说,这种用数据实体思路设计接口的方法确实存在一些缺点,也暴露了贫血模型的问题。为了避免这种“搭积木”式的依赖关系,在项目初期,我们通常尽量将依赖逻辑直接在 Model 层实现。不过,如何在 Model 层内部进行分层,功能如何拆分,并没有统一的参考标准,因此团队之间的实现往往各不相同,很难形成一致规范,这也导致了后续的优化和维护成本居高不下。

为了解决多层依赖的问题,我们可以考虑充血模型作为一种替代方案。接下来,我们结合接口实现的实例来直观对比一下这种方法,从而帮助你更好地理解充血模型的优点和应用场景。

图片图片

可以看到,和贫血模型直接写增删改查不同,充血模型通过拼合多个实体的功能直接提供业务服务。同时,为了更好地拆分复杂的依赖实体关系,充血模型按业务领域划分了 Model 层,每个领域会包含更多的子领域,这会导致模型内实现有更多的分层。但是因为模型内的实体实现都是业务操作,这样项目之间接口只有业务依赖关系,而不是数据依赖。业务依赖关系的接口之间只有流程和事务的拼接,访问频率也不会那么高,可以很好地缓解系统压力。

微服务的平层设计

不过,即使我们使用了充血模型,仍然会有一些公共数据存在多层依赖的情况。为此,微服务做出了新尝试,直接去掉所有服务的层级,设计成大平层的架构。如果你曾经翻过一些微服务资料,可能会疑惑微服务的设计为什么和我们的习惯差异这么大?比如几个小接口就能封装成一个项目,再比如服务之间的依赖不是树形而是星状。

图片图片

图片图片

这是因为,在微服务里一个 Service 就是一个独立部署的项目,同层 Service 服务之间没有上下层级关系,可以相互调用(虽然不推荐)。这和我们以往的横切习惯有很大不同,属于纵切拆分。横切是按照依赖关系把服务划分为内网服务、外网服务,纵切可以理解成按业务服务切分,具体效果如图。

图片图片

结合之前的知识,不难发现,横切服务适用于项目的初期验证阶段,具有较高的复用性,但同时也带来了复杂的依赖关系,性能优化较为困难。而纵切服务则更适合复杂业务场景,没有多层依赖的负担,性能更优。但由于依赖关系需在充血模型内部实现,因此还要解决数据共享的问题,数据同步的难度较高,且业务逻辑的梳理也较为复杂。

尽管纵切结构并不常见,但近年来微服务和领域驱动设计(DDD)都选择了这种方法。

责任编辑:武晓燕 来源: 二进制跳动
相关推荐

2023-05-17 10:53:43

AICIO

2021-04-22 11:22:12

云计算数据迁移混合云

2021-03-01 15:52:14

开源开源软件陷阱

2017-10-20 10:19:49

Kotlin语言陷阱

2018-06-11 15:26:47

云计算企业云陷阱

2019-02-11 10:00:23

云网络云平台微服务

2013-01-06 10:15:02

大数据分析数据分析师大数据

2016-09-08 23:47:17

大数据大数据服务

2024-11-01 10:37:31

2021-02-28 13:19:42

大数据IT数据管理

2016-10-10 23:01:48

安全认证云供应商安全评估

2022-09-19 09:19:24

云存储TCO云服务

2021-05-21 14:19:45

数据服务API技术

2022-08-26 05:12:29

IT运营工具蔓延

2022-12-05 11:57:10

2014-07-11 10:23:54

2011-06-07 15:34:15

2015-06-02 11:10:20

2019-10-29 14:15:25

云存档数据服务技术

2012-04-28 10:00:42

点赞
收藏

51CTO技术栈公众号