Scala---Akka Actor(三)Actor的持久化

之前的几部分Fault Tolerance,Dispatch,Mailbox是针对一些特定的应用场景,会需要用到不同的配置,这里先省略一下。至于Routing这一部分,应该是最接近分布式的一部分,放到之后在细细研究。FSM这一部分,需要一定的系统架构方面的知识,我就是看了个热闹。


(一)持久化需要什么?

说到持久化,一定会想到数据库,日志文件(journal),快照(snapshot)之类的。而Actor的持久化同样需要这些东西,在本文中,我们使用的是journal和snapshot来进行Actor的持久化操作。


我们使用Akka提供的持久化工具,需要添加依赖:

"com.typesafe.akka" %% "akka-persistence" % "2.5.6"

我们使用LevelDB来进行journal的保存,添加依赖:

"org.iq80.leveldb"            % "leveldb"          % "0.9"
"org.fusesource.leveldbjni"   % "leveldbjni-all"   % "1.8"

添加依赖之后,需要在application.conf文件中队其进行配置:

akka.persistence.journal.plugin = "akka.persistence.journal.leveldb"
akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"
akka.persistence.journal.leveldb.dir = "log/journal"
akka.persistence.snapshot-store.local.dir = "log/snapshots"
# DO NOT USE THIS IN PRODUCTION !!!
# See also https://github.com/typesafehub/activator/issues/287
akka.persistence.journal.leveldb.native = false  //因为我们本地并没有安装leveldb,所以这个属性置为false,但是生产环境并不推荐使用

(二)持久化的原理

在Akka中,支持持久化的Actor是PersistentActor。需要实现三个方法来完成持久化的操作。

override def persistenceId

这个方法是用来给当前的Actor设定指定的id,通过这个id,Actor将事件保存到指定的journal中,或者保存当前的snapshot。

override def receiveCommand

用于处理Actor收到的所有消息,类似于之前的receive方法。

override def receiveRecover

在Actor重新启动或再次启动的时候,从journal或者snapshot中回复状态。


此外我们还需要一个重要的方法,来将事件进行保存。

persist(Event)(Event handler)

这个函数有两个参数(柯里化),第一个参数表示一个Event(序列化之后保存在journal中),第二个参数是对之前事件参数的处理方法(一些操作)。这里需要强调一下,persist方法先将Event持久化之后,才会执行handler方法,因为handler方法可能会修改Event。这些操作是会阻塞当前Actor继续接受消息,所有到来的消息会被stash。知道persist完成。所以在这里stash可能会溢出,可以使用一些策略防止溢出发生。

akka.actor.default-mailbox.stash-capacity=10000

设置stash的最大容量。

akka.persistence.internal-stash-overflow-strategy=
  "akka.persistence.ThrowExceptionConfigurator"

设置stash的溢出策略。


我们来看一下示例代码

package persistent

import akka.actor.{ActorSystem, Props}
import akka.persistence._

// 定义命令
case class Cmd(data: String)
// 定义Event,保存到journal中(文件中)
case class Evt(data: String)

// 状态,保存在Actor内(内存中)
case class ExampleState(events: List[String] = Nil) {
  def update(evt: Evt): ExampleState = copy(evt.data :: events)

  def size: Int = events.length

  override def toString: String = events.reverse.toString()
}

class ExamplePersistentActor extends PersistentActor{
  // 状态(一个字符串链表)
  var state = ExampleState()

  def numEvents = state.size

  // Event handler(persist事件的回调函数)
  def updateState(event: Evt): Unit ={
    state = state.update(event)
  }

  // 不从snapshot中恢复,只读取journal中的前3条Event
  // override def recovery: Recovery = Recovery(fromSnapshot = SnapshotSelectionCriteria.None, toSequenceNr = 3L)

  // 不进行恢复(journal和snapshot)
  // override def recovery: Recovery = Recovery.none

  override def receiveRecover = {
    // 在完成恢复和开始接受消息之间,接受到RecoveryCompleted消息
    case RecoveryCompleted =>
      println("recovery complete")
    // 恢复Event
    case evt: Evt => updateState(evt)
    // 恢复Snapshot
    case SnapshotOffer(_, snapshot: ExampleState) => {
      println("recover from snapshot")
      state = snapshot
    }
  }

  // 设置取snapshot的间隔
  val snapShotInterval = 1000
  override def receiveCommand = {
    // 接收到一条命令(这里就是一个字符串)
    case Cmd(data) =>
      // 持久化Evnet,使用updateState作为Event handler
      persist(Evt(s"${data}-${numEvents}"))(updateState)
      // 使用自定义的Event handler
      persist(Evt(s"${data}-${numEvents + 1}")){
        event =>
          updateState(event)
          context.system.eventStream.publish(event)
          // 取快照
          // lastSequenceNr: Highest received sequence number so far or 0L if this actor hasn't replayed or stored any persistent events yet.
//          if(lastSequenceNr % snapShotInterval == 0 && lastSequenceNr != 0)
//            saveSnapshot(state)
      }
      // 不进行持久化,但是会调用 Evnet handler
      deferAsync(Evt(s"${data}-${numEvents + 2}")){e => println(e)}
    case "print" => println(state)
    case "snap" => saveSnapshot(state)
  }

  override def persistenceId = "sample-id-2"
}

object PersistentActorExample extends App{
  val system = ActorSystem("example")

  val persistentActor = system.actorOf(Props[ExamplePersistentActor], "persistentActor-4-scala")

  persistentActor ! "print"
  persistentActor ! Cmd("foo")
  persistentActor ! Cmd("baz")
  persistentActor ! Cmd("bar")
  persistentActor ! "snap"
  persistentActor ! Cmd("buzz")
  persistentActor ! "print"

  Thread.sleep(10000)
  system.terminate()
}



另外一个持久化方法是

persistAsync(Event)(Event handler)

这个方法会从持久化操作中立刻返回,新到的消息不会被stash

一个更加直接的实例:高throughput下的可持久化


需要注意一点:PersistentActor不能使用PoisonPill进行关闭。

详见:为什么不能使用PoisonPill


(三)一种Confirm Delivery

在之前对Actor的学习过程中,我们没有考虑Actor出现故障,网络通信受阻等情况,在实际场景下,有时候我们需要可信传输来保证数据的一致性。我们已经学习过了持久化的一些方法,我们就可以使用持久化+AtLeastOneDelivery来实现可信传输。

我们首先需要一个新的特性:

AtLeastOnceDelivery

这个特性为我们提供一个单调增加的deliveryId,我们就是用这个deliveryId来实现可信传输。

Actor通过deliver方法,将消息发送给一个ActorSelection,注意这里不是ActorRef。这个操作会在unconfirm列表中添加新的一项,当超过一定时间没有收到Comfirm消息的情况下,消息会重新发送。


我们看一下实例代码

package confirm

import akka.actor.{Actor, ActorLogging, ActorSelection, ActorSystem, Props}
import akka.persistence.{AtLeastOnceDelivery, PersistentActor}

case class Msg(deliveryId: Long, s: String)
case class Confirm(deliveryId: Long)

sealed trait Evt
case class MsgSent(s: String) extends Evt
case class MsgConfirm(deliveryId: Long) extends Evt

object MyPersistentActor2{
  def props(destination: ActorSelection): Props = {
    Props(new MyPersistentActor2(destination))
  }
}

class MyPersistentActor2(destination: ActorSelection)
extends PersistentActor with AtLeastOnceDelivery with ActorLogging{

  def updateState(evt: Evt): Unit = evt match{
    case MsgSent(s) =>
      log.info(s"sent msg $s to ${destination.pathString}")
      deliver(destination)(deliveryId => Msg(deliveryId, s))
    case MsgConfirm(deliveryId) =>
      log.info(s"receive confirm msg with id $deliveryId")
      confirmDelivery(deliveryId)
  }

  override def receiveCommand = {
    case s: String => persist(MsgSent(s))(updateState)
    case Confirm(deliveryId) =>
      updateState(MsgConfirm(deliveryId))
//      persist(MsgConfirm(deliveryId))(updateState)
  }

  override def receiveRecover = {
    case evt: Evt => updateState(evt)
  }

  override def persistenceId = "persistent-id"

}

class MyDestination extends Actor with ActorLogging {
  override def receive = {
    case Msg(deliveryId, s) =>
      log.info(s"receive message with $deliveryId and $s")
      sender() ! Confirm(deliveryId)
  }
}

object TestConfirmedDelivery extends App{
  val system = ActorSystem("example")

  val dest = system.actorOf(Props[MyDestination], "dest")

  val persistentActor = system.actorOf(MyPersistentActor2.props(system.actorSelection("/user/dest")))

  persistentActor ! "hello"

  Thread.sleep(10000)
  system.terminate()
}

在可信通信的情况下,我们不光要将发送的消息保存到journal,返回的Confirm消息也需要保存到journal。在恢复的过程中,就不需要再发送之前已经发送并确认过的消息了。(它们会逐一从journal中取出,并重新处理后,更新对应的deliverId在unconfirm表中的状态)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值