我最近正在测试不同的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。
答案 0 :(得分:0)
您的测试有点奇怪,我无法理解您要在此处使用大小为1的邮箱进行什么测试。
每个actor一次只能处理一条消息,因此,场景(1)会丢失消息,因为当新消息到达时actor会忙于处理该消息,并且如果队列已满,它将丢弃消息。
您在方案1中的预期行为是什么?