从数据库读写分离到CQRS

发布时间 2024-01-10 17:28:41作者: Mr沈

1. 数据库读写分离

对于数据库的操作就四种:CRUD

我们把这四种操作,又划分为两类,读和写

 

当我们的系统并发量高的时候,自然会考虑到提高数据库性能,数据库读写分离,

 但是,实际测试下来,总是有各种不满意的地方。其中最麻烦的就是各种复杂查询的性能,写库有单点故障问题

2. CQRS

有了数据库层面的”归纳“:CRUD->读、写, 可以继续”归纳“CRUD->读、写 --> 查询、命令

将查询和命令分开,也就是我们要说的CQRS(Command Query Responsibility Segregation)模型,命令与查询职责分离

这样,我们在应用层面就可以抽象成如下模型:

 乍一看,这套东西其实和采用的数据库的读写分离是一样的,就是把读写给分开,但是这些并没有那么简单

其实是模型的不同,原来的数据库读写分离确实把读写的这两个行为分开了,但是它依然有一个重要的事情没有做,那就是职责的分开

 

什么叫职责的分开呢?就是读写双方不要搞同一套模型。而数据库读写分离的问题就在这里,它使用了同一个模型。

使用同一个模型在这里造成的问题是,这个模型由于既要考虑读取数据不能太困难,也要考虑写入数据不能太困难。

使用 CQRS 思想的话,写入不需要关心读取的问题,读取数据也不用关心写入的问题,那就可以做到真正的读写分离,提高性能

写存储可以用MySQL这样的关系型数据库,而查询存储则可以使用Elasticsearch作为存储。命令-查询职责分离(CQRS)模式是一种应用于这种场景的通用模型,它显式地将系统中的读(查询)和写(命令)进行分离

优点

1. 拆分了这两块的代码,使各自可以采用不同的技术栈,做针对性的调优。命令模型负责数据的变更,并把最新数据同步给查询模型。

2. 切分了流量,能够更灵活的做资源分配,处理数据逻辑的时候,查询模型根据自己的想法来安排数据,想怎么用就怎么用

缺点:

1. 引入 CQRS 的模式后,最大的问题在于引入了过度的复杂性, 由于需要读和写分开,那么我们开发的工作量无形中被加大了一倍。又引入 CQRS,这变得更复杂了, 查询想要更好的性能可能就得考虑开源的搜索引擎中间件。每引入一种都会增加开发成本、服务器成本,以及更多的复杂度

2. 最终一致性:如果分离读取和写入数据库,读取数据可能会过时

  a. 如果我们采用了CQRS模式,但是命令和查询两侧底层所依赖的数据模型并未分离,而是基于共享的数据存储和数据模型,命令和查询之间不需要额外的交互,命令侧的数据更新对查询侧实时可见。在这种架构模式下,两侧基于共享的数据已经天然的集成在一起,不需要额外机制进行通信,自然也无需引入消息了。

  b. 如果我们采用CQRS模式,并且命令和查询两侧进行了数据模型的分离,二者各自依赖独立的数据模型。同时,数据存储也分开部署。命令侧负责数据的更新,而查询侧只负责数据的查询,如何将数据的更新及时同步到查询侧是需要解决的问题。在这种架构模式下,使用消息模式作为两侧的通信机制是个不错的选择

 3 种主要的 CQRS 架构

1. 单数据库 CQRS

顾名思义就是command和query都是操作的同一个数据库

 2. 双数据库 CQRS

在“双数据库”方式中,我们需要两个数据库,一个用于写操作,一个用于读操作。命令端使用针对写操作优化的数据库。查询端使用针对读取操作优化的数据库

命令每改变一个状态,修改后的数据就必须从写数据库推送到读数据库中,或者作为一个跨两个数据库的分布式事务,或者使用最终一致性模型。
这种架构给软件的查询端带来了数量级的性能提升,这是有利的,因为一般系统在读数据上花费的时间一般比写数据要更多,但是要解决数据一致性问题

 3. 事件源 (EventSourcing) CQRS

最复杂的 CQRS 架构。与前面两种方式相比,事件源存储数据的思路完全不同。在事件源方法中,我们并不只存储实体的当前状态,而且将实体发生的每一个状态作为快照来存储。实体并不是以标准化数据的形式保存,而是通过事件的时间戳来保存它们的变更。

(可以参考Mysql的Binlog设计)这种记录的优点是可以根据回放,重现每一次状态变更的时间点以及变更轨迹。而查询则可以根据当前状态的快照来为查询提速