Akka系列(八):Akka persistence设计理念之CQRS
前言........
这一篇文章主要是讲解Akka persistence的核心设计理念,也是CQRS(Command Query Responsibility Segregation)架构设计的典型应用,就让我们来看看为什么Akka persistence会采用CQRS架构设计。
CQRS
很多时候我们在处理高并发的业务需求的时候,往往能把应用层的代码优化的很好,比如缓存,限流,均衡负载等,但是很难避免的一个问题就是数据的持久化,以致数据库的性能很可能就是系统性能的瓶颈,我前面的那篇文章也讲到,如果我们用数据库去保证记录的CRUD,在并发高的情况下,让数据库执行这么多的事务操作,会让很多数据库操作超时,连接池不够用的情况,导致大量请求失败,系统的错误率上升和负载性能下降。
既然这样,那我们可不可借鉴一下读写分离的思想呢?假使写操作和同操作分离,甚至是对不同数据表,数据库操作,那么我们就可以大大降低数据库的瓶颈,使整个系统的性能大大提升。那么CQRS到底是做了什么呢?
我们先来看看普通的方式:

我们可以看出,我们对数据的请求都是通过相应的接口直接对数据库进行操作,这在并发大的时候肯定会对数据库造成很大的压力,虽然架构简单,但在面对并发高的情况下力不从心。
那么CQRS的方式有什么不同呢?我们也来看看它的执行方式:

乍得一看,似乎跟普通的方式没什么不同啊,不就多了一个事件和存储DB么,其实不然,小小的改动便是核心理念的转换,首先我们可以看到在CQRS架构中会多出一个Event,那它到底代表着什么含义呢?其实看过上篇文章的同学很容易理解,Event是我们系统根据请求处理得出的一个领域模型,比如一个修改余额操作事件,当然这个Event中只会保存关键性的数据。
很多同学又有疑问了,这不跟普通的读写分离很像么,难道还隐藏着什么秘密?那我们就来比较一下几种方式的不同之处:
1.单数据库模式
- 写操作会产生互斥锁,导致性能降低;
- 即使使用乐观锁,但是在大量写操作的情况下也会大量失败;
2.读写分离
- 读写分离通过物理服务器增加,负荷增加;
- 读写分离更适用于读操作大于写操作的场景;
- 读写分离在面对大量写操作的情况下还是很吃力;
3.CQRS
- 普通数据的持久化和Event持久化可以使用同一台数据库;
- 利用架构设计可以使读和写操作尽可能的分离;
- 能支撑大量写的操作情况;
- 可以支持数据异步持久,确保数据最终一致性;
从三种方式各自的特点可以看出,单数据库模式的在大量读写的情况下有很大的性能瓶颈,但简单的读写分离在面对大量写操作的时候也还是力不从心,比如最常见的库存修改查询场景:

我们可以发现在这种模式下写数据库的压力还会很大,而且还有数据同步,数据延迟等问题。
那么我们用CQRS架构设计会是怎么样呢:

首先我们可以业务模型进行分离,对不同的查询进行分离,另外避免不了的同一区间数据段进行异步持久化,在保证数据一致性的情况下提升系统的吞吐量。这种设计我们很少会遇到事务竞争,另外还可以使用内存数据库(当然如果是内存操作那就最快)来提升数据的写入。(以上的数据库都可为分布式数据库,不担心单机宕机)
那么CRQS机制是怎么保证数据的一致性的呢?
从上图中我们可以看出,一个写操作我们会在系统进行初步处理后生成一个领域事件,比如a用户购买了xx商品1件,b用户购买了xx商品2件等,按照普通的方式我们肯定是直接将订单操作,库存修改操作一并放在一个事务内去操作数据库,性能可想而知,而用CQRS的方式后,首先系统在持久化相应的领域事件后和修改内存中的库存(这个处理非常迅速)后便可马上向用户做出反应,真正的具体信息持久可以异步进行,当然若是当在具体信息持久化的过程中出错了怎么办,系统能恢复正确的数据么,当然可以,因为我们的领域事件事件已经持久化成功了,在系统恢复的时候,我们可以根据领域事件来恢复真正的数据,当然为了防止恢复数据是造成数据丢失,数据重复等问题我们需要制定相应的原则,比如给领域事件分配相应id等。
使用CQRS会带来性能上的提升,当然它也有它的弊端:
- 使系统变得更复杂,做一些额外的设计;
- CQRS保证的是最终一致性,有可能只适用于特定的业务场景;
Akka Persistence 中CQRS的应用
通过上面的讲解,相信大家对CQRS已经有了一定的了解,下面我们就来看看它在Akka Persistence中的具体应用,这里我就结合上一篇文章抽奖的例子,比如其中的LotteryCmd便是一个写操作命令,系统经过相应的处理后得到相应的领域事件,比如其中LuckyEvent,然后我们将LuckyEvent进行持久化,并修改内存中抽奖的余额,返回相应的结果,这里我们就可以同时将结果反馈给用户,并对结果进行异步持久化,流程如下:

可以看出,Akka Persistence的原理完全是基于CQRS的架构设计的,另外Persistence Actor还会保存一个内存状态,相当于一个in memory数据库,可以用来提供关键数据的存储和查询,比如前面说到的库存,余额等数据,这部分的设计取决于具体的业务场景。
阅读Akka Persistence相关源码,其的核心就在于PersistentActor接口中的几个持久方法,比如其中的
def persist[A](event: A)(handler: A ⇒ Unit): Unit def persistAll[A](events: immutable.Seq[A])(handler: A ⇒ Unit): Unit
等方法,它们都有两个参数,一个是持久化的事件,一个是持久化后的后续处理逻辑,我们可以在后续handler中修改Actor内部状态,向外部发送消息等操作,这里的模式就是基于CQRS架构的,修改状态有事件驱动,另外Akka还可以在系统出错时,利用相应的事件恢复Actor的状态。
总结
总的来说,CQRS架构是一种不同于以往的CRUD的架构,所以你在享受它带来的高性能的同时可能会遇到一些奇怪的问题,当然这些都是可以解决的,重要的是思维上的改变,比如事件驱动,领域模型等概念,不过相信当你理解并掌握它之后,你便会爱上它的。
Akka系列(八):Akka persistence设计理念之CQRS的更多相关文章
- Akka系列(十):Akka集群之Akka Cluster
前言........... 上一篇文章我们讲了Akka Remote,理解了Akka中的远程通信,其实Akka Cluster可以看成Akka Remote的扩展,由原来的两点变成由多点组成的通信网络 ...
- Akka系列(三):监管与容错
前言...... Akka作为一种成熟的生产环境并发解决方案,必须拥有一套完善的错误异常处理机制,本文主要讲讲Akka中的监管和容错. 监管 看过我上篇文章的同学应该对Actor系统的工作流程有了一定 ...
- Akka系列(二):Akka中的Actor系统
前言......... Actor模型作为Akka中最核心的概念,所以Actor在Akka中的组织结构是至关重要,本文主要介绍Akka中Actor系统. 1.Actor系统 Actor作为一种封装状态 ...
- Akka系列---什么是Actor
本文已.Net语法为主,同时写有Scala及Java实现代码 严肃的说,演员是一个广泛的概念,作为外行人我对Actor 模型的定义: Actor是一个系统中参与者的虚拟人物,Actor与Actor之间 ...
- Akka系列(九):Akka分布式之Akka Remote
前言.... Akka作为一个天生用于构建分布式应用的工具,当然提供了用于分布式组件即Akka Remote,那么我们就来看看如何用Akka Remote以及Akka Serialization来构建 ...
- Akka系列(四):Akka中的共享内存模型
前言...... 通过前几篇的学习,相信大家对Akka应该有所了解了,都说解决并发哪家强,JVM上面找Akka,那么Akka到底在解决并发问题上帮我们做了什么呢? 共享内存 众所周知,在处理并发问题上 ...
- 微软云平台windows azure入门系列八课程
微软云平台windows azure入门系列八课程: Windows Azure入门教学系列 (一): 创建第一个WebRole程序与部署 Windows Azure入门教学系列 (二): 创建第一个 ...
- SQL Server 2008空间数据应用系列八:基于Bing Maps(Silverlight)的空间数据存储
原文:SQL Server 2008空间数据应用系列八:基于Bing Maps(Silverlight)的空间数据存储 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft S ...
- java多线程系列(八)---CountDownLatch和CyclicBarrie
CountDownLatch 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线 ...
随机推荐
- Acwing-275-传纸条(DP)
链接: https://www.acwing.com/problem/content/description/277/ 题意: 给定一个 N*M 的矩阵A,每个格子中有一个整数. 现在需要找到两条从左 ...
- SQL Server自动备份
1.打开SQL Server Management Studio 数据库-管理-维护计划,右键,维护计划向导 2.在弹出页面右下方点击[更改],修改计划执行方案 根据需要,修改执行时间 3.修改完毕后 ...
- 【leetcode】1271. Hexspeak
题目如下: A decimal number can be converted to its Hexspeak representation by first converting it to an ...
- python re.search方法
re.search 扫描整个字符串并返回第一个成功的匹配. 函数语法: re.search(pattern, string, flags=0) 函数参数说明: 参数 描述 pattern 匹配的正则表 ...
- 探讨MySQL的各类锁
参考文档:http://blog.csdn.net/soonfly/article/details/70238902 锁是计算机协调多个进程或纯线程并发访问某一资源的机制.在数据库中,除了传统的计算机 ...
- 家谱(gen)x
家谱(gen) 时间限制 2S [问题描述] 现代的人对于本家族血统越来越感兴趣,现在给出充足的父子关系,请你编写程序找到某个人的最早的祖先. [输入格式]gen.in 输入文件由多行组 ...
- LU分解法求逆矩阵 C语言实现
最近在网上找了下,没有找到我想要的C语言版本,找到的也是错误的.故自己写了一个,并进行了相关测试,贴出来分享. 具体的LU分解算法就不细说了,随便找本书就知道了,关键是分解的处理流程,细节特别容易出错 ...
- 9.Python关键字(保留字)一览表
保留字是 Python 语言中一些已经被赋予特定意义的单词,这就要求开发者在开发程序时,不能用这些保留字作为标识符给变量.函数.类.模板以及其他对象命名. Python 包含的保留字可以执行如下命令进 ...
- HDU 5793 A Boring Question (找规律 : 快速幂+逆元)
A Boring Question 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5793 Description Input The first l ...
- summernote(富文本编辑器)将附件与图片上传到自己的服务器(vue项目)
1.上传图片至自己的服务器(这个官方都有例子,重点介绍附件上传)图片上传官方网址 // onChange callback $('#summernote').summernote({ callback ...