Akka-CQRS(6)- read-side
前面我们全面介绍了在akka-cluster环境下实现的CQRS写端write-side。简单来说就是把发生事件描述作为对象严格按发生时间顺序写入数据库。这些事件对象一般是按照二进制binary方式如blob存入数据库的。cassandra-plugin的表结构如下:
CREATE KEYSPACE IF NOT EXISTS akka
WITH REPLICATION = { 'class' : 'SimpleStrategy','replication_factor':1 }; CREATE TABLE IF NOT EXISTS akka.messages (
used boolean static,
persistence_id text,
partition_nr bigint,
sequence_nr bigint,
timestamp timeuuid,
timebucket text,
writer_uuid text,
ser_id int,
ser_manifest text,
event_manifest text,
event blob,
meta_ser_id int,
meta_ser_manifest text,
meta blob,
message blob,
tags set<text>,
PRIMARY KEY ((persistence_id, partition_nr), sequence_nr, timestamp, timebucket))
WITH gc_grace_seconds =864000
AND compaction = {
'class' : 'SizeTieredCompactionStrategy',
'enabled' : true,
'tombstone_compaction_interval' : 86400,
'tombstone_threshold' : 0.2,
'unchecked_tombstone_compaction' : false,
'bucket_high' : 1.5,
'bucket_low' : 0.5,
'max_threshold' : 32,
'min_threshold' : 4,
'min_sstable_size' : 50
};
事件对象是存放在event里的,是个blob类型字段。下面是个典型的写动作示范:
val receiveCommand: Receive = {
case Cmd(data) =>
persist(ActionGo) { event =>
updateState(event)
}
}
这些事件描述的存写即写这个ActionGo时不会影响到实际业务数据状态。真正发生作用,改变当前业务数据状态的是在读端read-side。也就是说在另一个线程里有个程序也按时间顺序把这些二进制格式的对象读出来、恢复成某种结构如ActionGo类型、然后按照结构内的操作指令对业务数据进行实际操作处理,这时才会产生对业务数据的影响。做个假设:如果这些事件不会依赖时间顺序的话是不是可以偷偷懒直接用一种pub/sub模式把reader放在订阅subscriber端,如下:
//写端
import DistributedPubSubMediator.Publish
val mediator = DistributedPubSub(context.system).mediator val receiveCommand: Receive = {
case Cmd(data) =>
persist(DataUpdated) { event =>
updateState(event)
mediator ! Publish(persistentId, event,sendOneMessageToEachGroup = true)
}
} //读端
val mediator = DistributedPubSub(context.system).mediator
mediator ! Subscribe(persistentId, self)
def receive = {
case DataUpdated: Event ⇒
updateDataTables()
}
这种pub/sub模式的特点是消息收发双方耦合度非常松散,但同时也存在订阅方sub即reader十分难以控制的问题,而且可以肯定的是订阅到达消息无法保证是按发出时间顺序接收的,我们无法控制akka传递消息的过程。因为业务逻辑中一个动作的发生时间顺序往往会对周围业务数据产生不同的影响,所以现在只能考虑事件源event-sourcing这种模式了。es方式的CQRS是通过数据库表作为读写间隔实现写端程序和读端程序的分离。写端只管往数据库写数据操作指令,读端从同一数据库位置读出指令进行实质的数据处理操作,所以读写过程中会产生一定的延迟,读端需要不断从数据库抽取pull事件。而具体pull的时段间隔如何设定也是一个比较棘手的问题。无论如何,akka提供了Persistence-Query作为一种CQRS读端工具。我们先从一个简单的cassandra-persistence-query用例开始:
// obtain read journal by plugin id
val readJournal =
PersistenceQuery(system).readJournalFor[CassandraReadJournal](CassandraReadJournal.Identifier) // issue query to journal
val source: Source[EventEnvelope, NotUsed] =
readJournal.eventsByPersistenceId("user-1337", 0, Long.MaxValue) // materialize stream, consuming events
implicit val mat = ActorMaterializer()
source.runForeach { pack =>
updateDatabase(pack.event)
}
eventsByPersistenceId(...)构建了一个akka-stream的Source[EventEnvelope,_]。这个EventEnvelope类定义如下:
/**
* Event wrapper adding meta data for the events in the result stream of
* [[akka.persistence.query.scaladsl.EventsByTagQuery]] query, or similar queries.
*/
final case class EventEnvelope(
offset: Offset,
persistenceId: String,
sequenceNr: Long,
event: Any)
上面这个event字段就是从数据库读取的事件对象。EventEnvelope是以流元素的形式从数据库中提出。eventsByPersistenceId(...)启动了一个数据流,然后akka-persistence-query会按refresh-interval时间间隔重复运算这个流stream。refresh-interval可以在配置文件中设置,如下面的cassandra-plugin配置:
cassandra-query-journal {
# Implementation class of the Cassandra ReadJournalProvider
class = "akka.persistence.cassandra.query.CassandraReadJournalProvider"
# Absolute path to the write journal plugin configuration section
write-plugin = "cassandra-journal"
# New events are retrieved (polled) with this interval.
refresh-interval = 3s
...
}
以上描述的是一种接近实时的读取模式。一般来讲,为了实现高效、安全的事件存写,我们会尽量简化事件结构,这样就会高概率出现一个业务操作单位需要多个事件来描述,那么如果在完成一项业务操作单元的所有事件存写后才开始读端的动作不就简单多了吗?而且还比较容易控制。虽然这样会造成某种延迟,但如果以业务操作为衡量单位,这种延迟应该是很正常的,可以接受的。现在每当完成一项业务的所有事件存写后在读端一次性成批把事件读出来然后进行实质的数据操作,应当可取。akka-persistence-query提供了下面这个函数:
/**
* Same type of query as `eventsByPersistenceId` but the event stream
* is completed immediately when it reaches the end of the "result set". Events that are
* stored after the query is completed are not included in the event stream.
*/
override def currentEventsByPersistenceId(
persistenceId: String,
fromSequenceNr: Long,
toSequenceNr: Long): Source[EventEnvelope, NotUsed] = ...
我们可以run这个stream把数据读入一个集合里,然后可以在任何一个线程里用这个集合演算业务逻辑(如我们前面提到的写端状态转变维护过程),可以用router/routee模式来实现一种在集群节点中负载均衡式的分配reader-actor作业节点。
下一篇准备对应前面的CQRS Writer Actor 示范里的akka-cluster-pos进行rCQRS-Reader-Actor示范。
Akka-CQRS(6)- read-side的更多相关文章
- Akka系列(三):监管与容错
前言...... Akka作为一种成熟的生产环境并发解决方案,必须拥有一套完善的错误异常处理机制,本文主要讲讲Akka中的监管和容错. 监管 看过我上篇文章的同学应该对Actor系统的工作流程有了一定 ...
- Akka系列(二):Akka中的Actor系统
前言......... Actor模型作为Akka中最核心的概念,所以Actor在Akka中的组织结构是至关重要,本文主要介绍Akka中Actor系统. 1.Actor系统 Actor作为一种封装状态 ...
- Akka系列(八):Akka persistence设计理念之CQRS
前言........ 这一篇文章主要是讲解Akka persistence的核心设计理念,也是CQRS(Command Query Responsibility Segregation)架构设计的典型 ...
- CQRS学习——最小单元的Cqrs(CommandEvent)[其一]
[说明:博主采用边写边思考的方式完成这一系列的博客,所以代码以附件为准,文中代码仅为了说明.] 结构 在学习和实现CQRS的过程中,首要参考的项目是这个[http://www.cnblogs.com/ ...
- Akka系列(九):Akka分布式之Akka Remote
前言.... Akka作为一个天生用于构建分布式应用的工具,当然提供了用于分布式组件即Akka Remote,那么我们就来看看如何用Akka Remote以及Akka Serialization来构建 ...
- Akka系列(六):Actor解决了什么问题?
前言..... 文档来源于 : What problems does the actor model solve? Actor解决了什么问题? Akka使用Actor模型来克服传统面向对象编程模型的 ...
- akka开发(一)HelloWorld
package com.hfi.helloakka; import akka.actor.ActorRef; import akka.actor.Props; import akka.actor.Un ...
- Akka系列(十):Akka集群之Akka Cluster
前言........... 上一篇文章我们讲了Akka Remote,理解了Akka中的远程通信,其实Akka Cluster可以看成Akka Remote的扩展,由原来的两点变成由多点组成的通信网络 ...
- Akka系列(七):Actor持久化之Akka persistence
前言.......... 我们在使用Akka时,会经常遇到一些存储Actor内部状态的场景,在系统正常运行的情况下,我们不需要担心什么,但是当系统出错,比如Actor错误需要重启,或者内存溢出,亦或者 ...
- Akka系列(四):Akka中的共享内存模型
前言...... 通过前几篇的学习,相信大家对Akka应该有所了解了,都说解决并发哪家强,JVM上面找Akka,那么Akka到底在解决并发问题上帮我们做了什么呢? 共享内存 众所周知,在处理并发问题上 ...
随机推荐
- JavaSE基础知识(5)—面向对象(5.6 static关键字)
一.说明 static属于一种修饰符,可以用于修饰 属性.方法.初始化块.内部类用static修饰的成员,称为静态成员不用static修饰的成员,称为普通成员 二.静态属性的特点 1.生命周期 静态属 ...
- SpringMVC 使用PUT请求遇到的问题小结
最近在使用REST风格的URL进行CURD操作的学习过程中 发现使用PUT请求时候提交表单进行修改操作 报错:Request method 'PUT' not supported 在网上查找资料发现是 ...
- 28. pt-table-sync
前提:得有checksum表 pt-table-sync --replicate=pt.checksum \h=192.168.100.101,P=3306,u=admin,p=admin \h=19 ...
- CAS SSO单点登录框架学习
1.了解单点登录 SSO 主要特点是: SSO 应用之间使用 Web 协议(如 HTTPS) ,并且只有一个登录入口. SSO 的体系中有下面三种角色: 1) User(多个) 2) Web 应用( ...
- HDU-6060 RXD and dividing
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6060 多校的题目,每次只能写两道SB题,剩下的要么想不到,要么想到了,代码不知道怎么实现,还是写的 ...
- 偏差(Bias)和方差(Variance)——机器学习中的模型选择zz
模型性能的度量 在监督学习中,已知样本 ,要求拟合出一个模型(函数),其预测值与样本实际值的误差最小. 考虑到样本数据其实是采样,并不是真实值本身,假设真实模型(函数)是,则采样值,其中代表噪音,其均 ...
- 软件测试---测试模型(V、W、H)
一.V测试模型 1.V模型示意图: 单元测试:又叫模块测试,针对软件设计中的最小单位—>程序模块 集成测试:又叫组装测试,通常在单元测试的基础上,将所有程序模块进行有序.递增测试. 系统测试:把 ...
- spring深入学习(二)-----bean的生命周期、IOC容器bean装配
bean的生命周期 1.实例化Bean对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBea ...
- input checkbox复选框取值
<table> <!--列表表头 开始 --> <tr class="ui-widget ui-state-hover" style="he ...
- 公用表表达式 (CTE)、递归、所有子节点、sqlserver
指定临时命名的结果集,这些结果集称为公用表表达式 (CTE).公用表表达式可以包括对自身的引用.这种表达式称为递归公用表表达式. 对于递归公用表达式来说,实现原理也是相同的,同样需要在语句中定义两部分 ...