为什么多个Akka Actor可以防止消息丢失?

时间:2019-03-29 17:47:23

标签: scala akka actor

我最近正在测试不同的Akka消息处理模块,发现了一个有趣的现象。基本上,我比较了两种情况:

1)具有未来的单个演员来处理消息。 2)多个参与者,每个参与者都有一个线程。

从性能的角度来看,我发现并没有太大的区别。但是我发现,如果邮箱容量非常有限,第一个解决方案丢失数据的机会就更大。例如,我在池中有8个线程,并行发送有16个消息,消息队列容量为1,对于第一个解决方案,我将从第二个8条消息中丢失大部分,但是对于第二个解决方案,8个参与者可以处理所有16条消息(有时只丢失1或2条消息)。

这是否意味着actor在处理当前消息时会缓存下一条消息?

import java.util.Calendar

import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, DeadLetter, Props, Terminated}
import akka.routing.{ActorRefRoutee, RoundRobinRoutingLogic, Router, RoutingLogic}
import com.typesafe.config.ConfigFactory

import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.duration._

object Example_6_Backpressure extends App{
    lazy val akkaSystemConfiguration = ConfigFactory.parseString(
        """
          |akka.actor.default-mailbox {
          |  mailbox-type = "akka.dispatch.UnboundedMailbox"
          |}
          |
          |akka.actor.bounded-mailbox {
          |  mailbox-type = "akka.dispatch.BoundedMailbox"
          |  mailbox-capacity = 1
          |  mailbox-push-timeout-time = 100ms
          |}
          |
          |akka.actor.default-dispatcher {
          |  type = Dispatcher
          |  throughput = 100
          |  executor = "fork-join-executor"
          |
          |  fork-join-executor {
          |    parallelism-min = 1
          |    parallelism-factor = 1    # 8 core cpu
          |    parallelism-max = 8
          |  }
          |}
        """.stripMargin)

    final case class PayLoad[T](msg:T)
    final case class Shutdown()

    object RouterActor {
        def apply(childProps: => Props, instance:Int = 1, rl: RoutingLogic = RoundRobinRoutingLogic() ) = {
            Props(new RouterActor(childProps, instance, rl))
        }
    }

    class RouterActor(childProps: => Props, instance:Int, rl: RoutingLogic ) extends Actor with ActorLogging {
        override def preStart() = log.debug(s"${self.path}: Pre-Start")
        override def postStop() = log.debug(s"${self.path}: Post-Stop")

        var router = Router(rl, Vector.fill(instance) {
            val actor = context.actorOf(childProps)
            addWatcher(actor)
            ActorRefRoutee(actor)
        })

        def addWatcher(actor:ActorRef): Unit = {
            val watcher = context.actorOf(Props(classOf[Watcher], actor))
            context.system.eventStream.subscribe(watcher, classOf[DeadLetter])
        }

        def receive: Actor.Receive = {
            case t:Shutdown =>
                router.routees.foreach { r =>
                    context.stop(r.asInstanceOf[ActorRefRoutee].ref)
                    router.removeRoutee(r)
                }
                context.system.terminate()
            case p:PayLoad[_] =>
                log.debug(s"${self.path}: route to child actor")
                router.route(p, sender())
        }
    }

    class Watcher(target: ActorRef) extends Actor with ActorLogging {
        private val targetPath = target.path

        override def preStart() {
            context watch target
        }

        def receive: Actor.Receive = {
            case d: DeadLetter =>
                if(d.recipient.path.equals(targetPath)) {
                    log.info(s"Timed out message: ${d.message.toString}")
                    // TODO: ...
                }
        }
    }

    object ChildActor{
        def apply() = Props[ChildActor]
    }

    class ChildActor() extends Actor with ActorLogging {
        override def preStart() = log.debug(s"${self.path}: Pre-Start")
        override def postStop() = log.debug(s"${self.path}: Post-Stop")

        override def receive: Receive = {
            case msg => {
                Future {
                    println(s"${Calendar.getInstance.getTimeInMillis} - [Thread-${Thread.currentThread.getId}] - ${self.path}: $msg")
                    Thread.sleep(1000)
                }(context.dispatcher)
            }
        }
    }

    object BackPressureExample {
        def apply(): Unit = {
            val system = ActorSystem("testSystem", akkaSystemConfiguration)
            val rootRef = system.actorOf(
                RouterActor( ChildActor().withMailbox("akka.actor.bounded-mailbox"), instance = 1), "actor-router"
            )
            rootRef ! PayLoad("Hello-1!")
            rootRef ! PayLoad("Hello-2!")
            rootRef ! PayLoad("Hello-3!")
            rootRef ! PayLoad("Hello-4!")
            rootRef ! PayLoad("Hello-5!")
            rootRef ! PayLoad("Hello-6!")
            rootRef ! PayLoad("Hello-7!")
            rootRef ! PayLoad("Hello-8!")
            rootRef ! PayLoad("Hello-9!")
            rootRef ! PayLoad("Hello-10!")
            rootRef ! PayLoad("Hello-11!")
            rootRef ! PayLoad("Hello-12!")
            rootRef ! PayLoad("Hello-13!")
            rootRef ! PayLoad("Hello-14!")
            rootRef ! PayLoad("Hello-15!")
            rootRef ! PayLoad("Hello-16!")
            Thread.sleep(5100)
            rootRef ! new Shutdown
            Await.result(system.terminate(), 10 second)
        }
    }

    BackPressureExample()
}

代码显示了具有多线程场景的单个actor,可以自由注释掉ChildActor中的“ Future”,并将RouterActor的instances参数设置为8,以体验多个actor。

1 个答案:

答案 0 :(得分:0)

您的测试有点奇怪,我无法理解您要在此处使用大小为1的邮箱进行什么测试。

每个actor一次只能处理一条消息,因此,场景(1)会丢失消息,因为当新消息到达时actor会忙于处理该消息,并且如果队列已满,它将丢弃消息。

您在方案1中的预期行为是什么?