ITPub博客

首页 > 数据库 > 数据库开发技术 > 命令查询责任分离模式(CQRS):分离读写模型以提升性能

命令查询责任分离模式(CQRS):分离读写模型以提升性能

原创 数据库开发技术 作者:灸哥漫谈 时间:2024-07-01 00:01:00 0 删除 编辑

点击上方蓝字,关注灸哥



大家好,我是灸哥,好久没聊点技术与架构主题的内容了,今天就来一起聊聊 CQRS~

在当今数字化和信息化的时代,应用程序的性能和响应速度至关重要。

用户期望在瞬间获得他们所需的信息,而系统需要快速处理大量的数据操作来保持良好的用户体验。在这种背景下,命令查询责任分离模式(Command Query Responsibility Segregation,简称 CQRS) 应运而生,成为一种提升系统性能的有效架构设计模式。

CQRS 是什么?

CQRS 是一种将数据修改操作(命令)和数据读取操作(查询)分离的架构模式。

传统的单一模型通常会将读写操作放在一起,导致复杂性和性能瓶颈。而 CQRS 则通过将命令和查询职责分开,简化了系统设计,并显著提升了系统性能。

CQRS 的核心理念

CQRS 的核心主要有两部分:

  1. 命令模型(Command Model):负责处理数据的写操作,例如创建、更新和删除操作。这部分逻辑通常比较复杂,因为它需要处理业务规则和数据验证。

  2. 查询模型(Query Model):负责处理数据的读操作,例如获取数据和展示数据。这部分逻辑通常比较简单,因为它只需要高效地检索和返回数据。

通过分离命令和查询模型,CQRS 可以针对不同的需求进行优化。

命令模型可以采用事务性数据库来确保数据一致性,而查询模型则可以采用高性能的缓存或索引机制来加速数据检索。

CQRS 的优劣

CQRS 的架构模式一般具备以下四个优点:

  1. 性能优化:读写分离后,查询模型可以单独优化,提高数据读取的效率。而写操作则可以独立处理,不受读操作的影响

  2. 可扩展性:通过分离读写操作,系统可以更容易地进行水平扩展。例如,可以单独扩展查询服务来处理大量的读请求,而不影响写操作的性能

  3. 简化复杂性:CQRS 将复杂的业务逻辑集中在命令模型中,而查询模型则保持简单。这种分离可以简化代码结构,提高可维护性

  4. 提高并发性:读写分离可以减少锁争用,提高系统的并发处理能力,尤其是在高并发场景下表现尤为显著

CQRS 的架构模式虽然有着以上四个优点,但同样也存在一些不足:

  1. 两个独立模型,会造成系统结构的复杂度高,维护和理解的成本增高

  2. 两个独立模型同样也会导致存在冗余代码,尤其是共享的业务逻辑

  3. 两个独立的流程就会涉及多次数据库操作,可能会导致性能问题

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 设计思想指导下,要求我们必须经过领域模型才能得到查询结果的话,正常的做法就是:

  1. 第一步,工时记录服务会调用 EffortRecordRepository,根据工时项 ID 取得一堆 EffortRecord (工时记录)实体

  2. 第二步,再根据每个工时记录实体中的员工 ID,调用 EmpRepository ,为每个工时记录取得相应的员工实体

  3. 第三步,把所有工时记录和对应的员工实体信息拼成相应的 EmpEffortRecordDto ,排序后组装成列表返回

这样一个在 DDD 设计思想要求下的实现方案,虽然保证了 DDD 的代码和模型一致的要求,但是代码编写太麻烦了,一条 SQL 就能解决的问题,搞得巨复杂。而且这种多步代码实现起来是很有可能带来性能问题的。

那我们应该怎么来解决呢?真的要完全按照 DDD 的要求来吗?查询的功能是否真的必须要经过领域模型吗?

很多人认为如果绕过领域模型,领域逻辑和数据就可能分散在代码的各个地方,无法保证数据的完整性和一致性,代码也很难理解和维护。增、删、改确实应该这么做,但是查询并不会改变数据,它不会造成数据的不完整和不一致。于是 CQRS 就提了出来,增、删、改称为 Command 命令,查询称为 Query,两种功能的职责不同,应该采用不同的方式来处理。这样的设计模式就是 CQRS:命令走领域模型,查询不走领域模型,可直接使用 SQL 和 DTO。

CQRS 在 DDD 项目中的落地策略

CQRS 的架构模式在 DDD 项目中一般有以下几种落地策略:

应用服务分离策略

这种策略从示意图中是可以很明显看出实现的策略的,数据库共享,而在应用服务层面进行读写服务分离,这种策略对系统来说,是非常容易横向扩展,但是代价就是微服务的数量会增加,对应的运维和治理成本增加。

当然,如果不想使用微服务的分离模式,我们可以把这个策略简化为代码结构的分离。

数据库实例分离策略

这种策略在实现的时候是把数据库实例分成两个,两者之间用 CDC (变更数据捕获,Change data capture)机制来同步。有多种开源和商业化产品可以选择,但底层原理都是基于数据库的日志变化来出发,然后同步数据,当然也可以在应用程序的层面来同步数据。

对于查询数据库实例,这里不一定非得是关系型数据库,NoSQL 数据库、内存数据库或者其他存储技术都可以。

数据库实例分离之后,查询数据库也可以横向扩展了,可以灵活地选择数据存储机制,可以解决数据库造成的性能瓶颈。但代价就是增加了数据同步的复杂性,数据同步的延迟也可能会带来数据不一致的问题。数据库实例的分离,也可以简化为数据库结构分离的策略。


命令查询责任分离模式(CQRS)通过分离读写操作,提供了一种提升系统性能和可扩展性的有效方法。

在高并发和大数据量的应用场景中,CQRS 可以显著提高系统的响应速度和处理能力。

通过合理设计命令模型和查询模型,并采用适当的数据同步和事务管理策略,CQRS 能够帮助企业构建高性能、可扩展的应用系统。



长按二维码识别关注

关注灸哥

了解更多


你的赞和在看,我统统都要

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

请登录后发表评论 登录
全部评论

注册时间:2023-11-26

  • 博文量
    368
  • 访问量
    173323