
点击上方蓝字,关注灸哥


点击上方蓝字,关注灸哥


大家好,我是灸哥,好久没聊点技术与架构主题的内容了,今天就来一起聊聊 CQRS~
在当今数字化和信息化的时代,应用程序的性能和响应速度至关重要。
用户期望在瞬间获得他们所需的信息,而系统需要快速处理大量的数据操作来保持良好的用户体验。在这种背景下,命令查询责任分离模式(Command Query Responsibility Segregation,简称 CQRS) 应运而生,成为一种提升系统性能的有效架构设计模式。
CQRS 是一种将数据修改操作(命令)和数据读取操作(查询)分离的架构模式。
传统的单一模型通常会将读写操作放在一起,导致复杂性和性能瓶颈。而 CQRS 则通过将命令和查询职责分开,简化了系统设计,并显著提升了系统性能。
CQRS 的核心主要有两部分:
命令模型(Command Model):负责处理数据的写操作,例如创建、更新和删除操作。这部分逻辑通常比较复杂,因为它需要处理业务规则和数据验证。
查询模型(Query Model):负责处理数据的读操作,例如获取数据和展示数据。这部分逻辑通常比较简单,因为它只需要高效地检索和返回数据。
通过分离命令和查询模型,CQRS 可以针对不同的需求进行优化。
命令模型可以采用事务性数据库来确保数据一致性,而查询模型则可以采用高性能的缓存或索引机制来加速数据检索。
CQRS 的架构模式一般具备以下四个优点:
性能优化:读写分离后,查询模型可以单独优化,提高数据读取的效率。而写操作则可以独立处理,不受读操作的影响
可扩展性:通过分离读写操作,系统可以更容易地进行水平扩展。例如,可以单独扩展查询服务来处理大量的读请求,而不影响写操作的性能
简化复杂性:CQRS 将复杂的业务逻辑集中在命令模型中,而查询模型则保持简单。这种分离可以简化代码结构,提高可维护性
提高并发性:读写分离可以减少锁争用,提高系统的并发处理能力,尤其是在高并发场景下表现尤为显著
CQRS 的架构模式虽然有着以上四个优点,但同样也存在一些不足:
两个独立模型,会造成系统结构的复杂度高,维护和理解的成本增高
两个独立模型同样也会导致存在冗余代码,尤其是共享的业务逻辑
两个独立的流程就会涉及多次数据库操作,可能会导致性能问题
CQRS 的架构模式在现在比较火的 DDD 设计模式中应用相对比较广泛,我们来看看在内部项目管理这样一个 DDD 驱动设计的系统中,CQRS 应该怎么用起来?
上图是一个需求内部项目管理的领域模型图,我们来看一个需求:给定一个工时项,要求查询出这个工时项下的所有工时记录,并显示在页面上。要求每条返回记录的字段中包括工号、姓名、日期、工时和备注,并根据工号和日期升序排列。
此时数据库中相关的表有员工表(emp)、工时记录表(effort_record)。
在没有 DDD 设计思想指导的时候,针对这个需求,我们一般使用 SQL 的方式来实现,代码如下:
select em.num, em.name, er.work_date, er.effort, er.notes
from effort_record as er
left join emp as em
on er.emp_id = em.id
and er.tenant_id = em.tenant_id
where er.tenant_id = ?
and er.effort_item_id = ?
order by em.num, er.work_date;
但当我们在 DDD 设计思想指导下,要求我们必须经过领域模型才能得到查询结果的话,正常的做法就是:
第一步,工时记录服务会调用 EffortRecordRepository,根据工时项 ID 取得一堆 EffortRecord (工时记录)实体
第二步,再根据每个工时记录实体中的员工 ID,调用 EmpRepository ,为每个工时记录取得相应的员工实体
第三步,把所有工时记录和对应的员工实体信息拼成相应的 EmpEffortRecordDto ,排序后组装成列表返回
这样一个在 DDD 设计思想要求下的实现方案,虽然保证了 DDD 的代码和模型一致的要求,但是代码编写太麻烦了,一条 SQL 就能解决的问题,搞得巨复杂。而且这种多步代码实现起来是很有可能带来性能问题的。
那我们应该怎么来解决呢?真的要完全按照 DDD 的要求来吗?查询的功能是否真的必须要经过领域模型吗?
很多人认为如果绕过领域模型,领域逻辑和数据就可能分散在代码的各个地方,无法保证数据的完整性和一致性,代码也很难理解和维护。增、删、改确实应该这么做,但是查询并不会改变数据,它不会造成数据的不完整和不一致。于是 CQRS 就提了出来,增、删、改称为 Command 命令,查询称为 Query,两种功能的职责不同,应该采用不同的方式来处理。这样的设计模式就是 CQRS:命令走领域模型,查询不走领域模型,可直接使用 SQL 和 DTO。
CQRS 的架构模式在 DDD 项目中一般有以下几种落地策略:
这种策略从示意图中是可以很明显看出实现的策略的,数据库共享,而在应用服务层面进行读写服务分离,这种策略对系统来说,是非常容易横向扩展,但是代价就是微服务的数量会增加,对应的运维和治理成本增加。
当然,如果不想使用微服务的分离模式,我们可以把这个策略简化为代码结构的分离。
这种策略在实现的时候是把数据库实例分成两个,两者之间用 CDC (变更数据捕获,Change data capture)机制来同步。有多种开源和商业化产品可以选择,但底层原理都是基于数据库的日志变化来出发,然后同步数据,当然也可以在应用程序的层面来同步数据。
对于查询数据库实例,这里不一定非得是关系型数据库,NoSQL 数据库、内存数据库或者其他存储技术都可以。
数据库实例分离之后,查询数据库也可以横向扩展了,可以灵活地选择数据存储机制,可以解决数据库造成的性能瓶颈。但代价就是增加了数据同步的复杂性,数据同步的延迟也可能会带来数据不一致的问题。数据库实例的分离,也可以简化为数据库结构分离的策略。
命令查询责任分离模式(CQRS)通过分离读写操作,提供了一种提升系统性能和可扩展性的有效方法。
在高并发和大数据量的应用场景中,CQRS 可以显著提高系统的响应速度和处理能力。
通过合理设计命令模型和查询模型,并采用适当的数据同步和事务管理策略,CQRS 能够帮助企业构建高性能、可扩展的应用系统。
往期回顾


长按二维码识别关注
关注灸哥
了解更多
你的赞和在看,我统统都要

原文作者:灸哥漫谈;如有侵权,请联系管理员删除。