使用Akka持久化——持久化与快照
来源:互联网 发布:sql怎么安装 编辑:程序博客网 时间:2024/06/05 08:33
前言
对于Javaweb而言,一个用户的HTTP请求最终会转换为一条Java线程处理。HTTP本身是无状态的,具体的请求逻辑一般也是无状态的。如果进程奔溃或者系统宕机,用户会发觉当前网页不可用之类的错误。虽然会影响一些用户体验,但是只要服务重启了,用户依然可以完成他的请求并满足其需要。但是有些情况下则势必会造成混乱甚至恐慌,例如跨行转账。用户从自己A银行的账户转账1万元至自己在B银行的账户,如果转出的动作成功了,但是转入却失败了,用户的心情是可想而知的,自己的财产不翼而飞了!一种解决的方式是引入事务,在此场景下还必须是分布式事务。如果只是银行内部实现分布式事务多少还是可行的,但是不同银行之间要实现的成本是可想而知的,甚至不可行的。如果A银行转出时对用户的状态作持久化,B银行对收到的转入请求也进行持久化,那么恢复用户的损失才有可能。
以上啰里啰嗦说了这么多,无非就是抛出个引子,进而介绍Akka提供的持久化功能。
Akka的持久化架构
- UntypedPersistentActor: 是一个持久化、有状态的Actor。它能够将将事件持久化到日志并且以线程安全的方式重新执行这些事件。它能够被用于实现命令和事件源的Actor。当一个集成了UntypedPersistentActor的Actor启动或者重启时,日志化的消息被重新发送给此Actor,以便于通过这些消息回复内部状态。
- UntypedPersistentView: 视图是一个持久化、有状态的Actor,它接收由另一个持久化Actor写入日志的消息。视图本身不会日志化新的消息,它仅仅从一个持久化Actor的复制消息流来更新内部状态。
- UntypedPersistentActorAtLeastOnceDelivery: 用于实现向目标至少发送一次消息的语义,也适用于发送者和接收者的JVM进程奔溃。
- AsyncWriteJournal: 异步存储发送给持久化Actor的消息序列的日志。应用程序能够通过持久化Actor控制哪个消息被日志化,哪个消息不用被日志化。日志为每条消息维护一个不断增加的序列号。日志存储的底层实现是可插拔的。Akka的持久化扩展自带一个叫做"leveldb",向本地文件系统写入的日志插件。Akka社区里还有更多日志存储插件提供。
- Snapshot store: 快照存储对持久化Actor或持久化视图的内部状态的快照进行持久化。快照用于优化回复的时间。快照存储的底层是可插拔的。Akka持久化扩展自带一个向本地文件系统写入的“本地”快照存储插件。Akka社区里还有更多快照存储插件提供。
配置
有关Akka的日志持久化和快照持久化的配置如下:
- persistence {
- journal {
- plugin = "akka.persistence.journal.leveldb"
- leveldb.dir = "target/example/journal"
- leveldb.native = false
- }
- snapshot-store {
- plugin = "akka.persistence.snapshot-store.local"
- local.dir = "target/example/snapshots"
- }
- }
持久化Actor的例子
消息与状态
本例子中需要用到Cmd和Evt两种消息,Cmd代表命令,Evt代表事件。ExampleState代表我们例子中的状态。以上三个类的定义如下:
- public interface Persistence {
- public static class Cmd implements Serializable {
- private static final long serialVersionUID = 1L;
- private final String data;
- public Cmd(String data) {
- this.data = data;
- }
- public String getData() {
- return data;
- }
- }
- public static class Evt implements Serializable {
- private static final long serialVersionUID = 1L;
- private final String data;
- public Evt(String data) {
- this.data = data;
- }
- public String getData() {
- return data;
- }
- }
- public static class ExampleState implements Serializable {
- private static final long serialVersionUID = 1L;
- private final ArrayList<String> events;
- public ExampleState() {
- this(new ArrayList<String>());
- }
- public ExampleState(ArrayList<String> events) {
- this.events = events;
- }
- public ExampleState copy() {
- return new ExampleState(new ArrayList<String>(events));
- }
- public void update(Evt evt) {
- events.add(evt.getData());
- }
- public int size() {
- return events.size();
- }
- @Override
- public String toString() {
- return events.toString();
- }
- }
- }
持久化Actor的实现
在具体介绍本例中持久化Actor之前,先看看其实现,其代码清单如下:
- @Named("ExamplePersistentActor")
- @Scope("prototype")
- public class ExamplePersistentActor extends UntypedPersistentActor {
- LoggingAdapter log = Logging.getLogger(getContext().system(), this);
- @Override
- public String persistenceId() {
- return "sample-id-1";
- }
- private ExampleState state = new ExampleState();
- public int getNumEvents() {
- return state.size();
- }
- @Override
- public void onReceiveRecover(Object msg) {
- if (msg instanceof Evt) {
- state.update((Evt) msg);
- } else if (msg instanceof SnapshotOffer) {
- state = (ExampleState) ((SnapshotOffer) msg).snapshot();
- } else {
- unhandled(msg);
- }
- }
- @Override
- public void onReceiveCommand(Object msg) {
- if (msg instanceof Cmd) {
- final String data = ((Cmd) msg).getData();
- final Evt evt1 = new Evt(data + "-" + getNumEvents());
- final Evt evt2 = new Evt(data + "-" + (getNumEvents() + 1));
- persistAll(Arrays.asList(evt1, evt2), new Procedure<Evt>() {
- public void apply(Evt evt) throws Exception {
- state.update(evt);
- if (evt.equals(evt2)) {
- getContext().system().eventStream().publish(evt);
- }
- }
- });
- } else if (msg.equals("snap")) {
- // IMPORTANT: create a copy of snapshot
- // because ExampleState is mutable !!!
- saveSnapshot(state.copy());
- } else if (msg.equals("print")) {
- log.info(state.toString());
- } else {
- unhandled(msg);
- }
- }
- }
ExamplePersistentActor继承了UntypedPersistentActor,并覆盖实现了三个方法:
- persistenceId:持久化Actor必须有一个标识符,此标识符必须通过persistenceId方法定义;
- onReceiveRecover:此方法将在恢复期间被调用,并交由用户处理那些持久化的消息或者快照;
- onReceiveCommand:此方法用于处理正常的消息;
- 如果接收到的消息是Cmd时,则根据Cmd的data,构造两个新的Evt。例如:Cmd的data是test,那么两个Evt的内容分别是test-0,test-1。之后会调用UntypedPersistentActor提供的持久化方法persistAll,对两个生成的Evt持久化。最后,当持久化完成时会回调匿名内部类Procedure的apply方法将连个Evt按序更新到ExampleState中。
- 如果接收到的消息是snap,那么复制ExampleState中的缓存并调用UntypedPersistentActor提供的方法saveSnapshot生成快照。
- 如果接收到的消息是print,那么打印输出ExampleState中的缓存内容。
运行与验证
消息持久化验证
我们先使用一段代码向持久化Actor连续发送三个Cmd,内容分别是foo、baz、bar,最后再发送一条print消息,代码如下:
- final ActorRef persistentActor =
- actorSystem.actorOf(springExt.props("ExamplePersistentActor"), "examplePersistentActor");
- persistentActor.tell(new Cmd("foo"), null);
- persistentActor.tell(new Cmd("baz"), null);
- persistentActor.tell(new Cmd("bar"), null);
- persistentActor.tell("print", null);
这说明当前的程序逻辑准确无误。下面我们开始验证消息持久化的功效了。
我们将上述代码修改为代码清单1所示。
代码清单1
- final ActorRef persistentActor =
- actorSystem.actorOf(springExt.props("ExamplePersistentActor"), "examplePersistentActor");
- persistentActor.tell("print", null);
最后,将上述代码修改为代码清单2所示。
代码清单2
- final ActorRef persistentActor =
- actorSystem.actorOf(springExt.props("ExamplePersistentActor"), "examplePersistentActor");
- persistentActor.tell(new Cmd("buzz"), null);
- persistentActor.tell("print", null);
可以看到持久化的消息依然被“重播”,并且新打印出了我们最新发送的内容为buzz的Cmd。这些消息依然被有序的放入ExampleState的缓存。这是由于在恢复期间,新发送给持久化Actor的不会干扰到“重播”的消息,新的消息将被缓存直到恢复阶段完成之后再由持久化Actor接收。
持久化时间考量与快照
上面的例子我们只发送了4个Cmd消息,并对其恢复。一般而言这不会有什么问题,但是当系统中的消息频率很高时,那么通过一条一条消息“重播”的方式显然是低效的,假如应用本身能够每隔一段时间利用快照存储,会极大地缩短恢复过程所需要的时间。
我们对上面的例子先进行一些修改,加入快照的生成:
- final ActorRef persistentActor =
- actorSystem.actorOf(springExt.props("ExamplePersistentActor"), "examplePersistentActor");
- persistentActor.tell(new Cmd("foo"), null);
- persistentActor.tell(new Cmd("baz"), null);
- persistentActor.tell(new Cmd("bar"), null);
- persistentActor.tell("snap", null);
- persistentActor.tell("print", null);
我们再次执行代码清单2,其输出也仍然与图2一致。
总结
通过本文的介绍,发现使用Akka的持久化功能,类似于使用java多线程中的wait/notify,lock/unlock,将功能从语法层面解决,对程序员而言能更多地关注于自身业务。
其它Akka应用的博文如下:
- 《Spring与Akka的集成》;
- 《使用Akka的远程调用》;
- 《使用Akka构建集群(一)》;
- 《使用Akka构建集群(二)》;
- 《使用Akka持久化——持久化与快照》;
- 《使用Akka持久化——消息发送与接收》;
- 使用Akka持久化——持久化与快照
- 使用Akka持久化——持久化与快照
- 使用Akka持久化——持久化与快照
- 使用Akka持久化——消息发送与接收
- rdb快照持久化
- rdb快照持久化
- Redis快照持久化
- Actor Persistence&Snapshot 快照与持久化
- akka学习教程(十一) akka持久化
- Redis快照持久化+参数说明+快照持久化缺陷
- Redis rdb快照持久化
- 持久化与持久层
- Akka(14): 持久化模式:PersistentActor
- AAAakka学习教程(十一) akka持久化
- redis的持久化--快照持久化(SNAPSHOTTING)
- redis的rdb快照持久化
- [Redis]Redis持久化之RDB快照
- Redis之rdb快照持久化
- fragment进阶
- Mac OS 能前置窗口 de Afloat插件
- AAA云免费云主机推荐码
- 域名访问项目
- IOS10.2.1无法播放微信小视频的解决方案
- 使用Akka持久化——持久化与快照
- Phoenix使用指南
- 一分钟了解微服务的好处和陷阱
- 机器学习第三章复习(3)
- Android Handler消息机制源码分析——第二部分: Message与Handler
- 从零构建部署Node.js+Express+Bootstrap Web应用
- 利用axis请求webservice接口
- mongoDB——GridFS存储机制
- opencv激光点追踪代码