本想通过了解一下Akka-actor工程中主要的类的概念,来看下Akka内部运作的机制。无奈里边的类的确太多,注释中对每个类的功能也没有足够的解释。所以还是通过debug的方式,找个入手点,看一下互相之间调用的关系。

最初的选择是看一下ActorSystem的实始化过程,但发现难度挺大,因为这个初始化工作是为以后的行为做准备,所以仅根据初始化的动作,难以了解其目的是什么。所以就试着从消息发送的过程了解起,发现这个逻辑更好理解。

下面来看一下其执行过程吧。代码如下,使用默认的配置。

object Main extends App{
val system = ActorSystem("football")
val actor = system.actorOf(Props[PrintActor])
actor ! "hello"
}

想要了解的是actor ! "hello"的执行过程。


首先,actor的类型是ActorRef,在默认的配置下,这个ActorRef的具体类型为RepointableActorRef。

它的!方法的定义为

def !(message: Any)(implicit sender: ActorRef = Actor.noSender) = underlying.sendMessage(message, sender)

underlying的定义为

def underlying: Cell = Unsafe.instance.getObjectVolatile(this, cellOffset).asInstanceOf[Cell]

Cell是ActorCell混入的一个特质,后者是Actor的实体部分(相对于ActorRef)。

对于underlying.sendMessage的调用,会调用ActorCell的sendMessage方法,这个方法实现于Dispatch这个trait。

def sendMessage(msg: Envelope): Unit =
try {
if (system.settings.SerializeAllMessages) {
val unwrapped = (msg.message match {
case DeadLetter(wrapped, _, _) ⇒ wrapped
case other ⇒ other
}).asInstanceOf[AnyRef]
if (!unwrapped.isInstanceOf[NoSerializationVerificationNeeded]) {
val s = SerializationExtension(system)
s.deserialize(s.serialize(unwrapped).get, unwrapped.getClass).get
}
}
dispatcher.dispatch(this, msg)
} catch handleException

虽然有些怪怪的,但是handleException是一个PartialFunction,所以语法是没问题的。

在if (system.settings.SerializeAllMessages)里的部分是根据需要把消息序列化后又反序列化了一遍,可能是为了副作用,因为这个流程中没有改变msg。

下面就走到了dispatcher.dispatch(this,msg)。

默认的Dispathcer实现是akka.dispatch.Dispatcher。其dispatch方法的实现为:

  protected[akka] def dispatch(receiver: ActorCell, invocation: Envelope): Unit = {
val mbox = receiver.mailbox
mbox.enqueue(receiver.self, invocation)
registerForExecution(mbox, true, false)
}

它把消息加入到receiver的mailbox中,然后调用registerForExecution。

  protected[akka] override def registerForExecution(mbox: Mailbox, hasMessageHint: Boolean, hasSystemMessageHint: Boolean): Boolean = {
if (mbox.canBeScheduledForExecution(hasMessageHint, hasSystemMessageHint)) { //This needs to be here to ensure thread safety and no races
if (mbox.setAsScheduled()) {
try {
executorService execute mbox
true
} catch {
case e: RejectedExecutionException ⇒
try {
executorService execute mbox
true
} catch { //Retry once
case e: RejectedExecutionException ⇒
mbox.setAsIdle()
eventStream.publish(Error(e, getClass.getName, getClass, "registerForExecution was rejected twice!"))
throw e
}
}
} else false
} else false
}

registerForExecution这个名字起得很到位,因为它的确只是"register",并不实际执行它。实际执行execution的是一个线程池,所以这个方法会调用executorService.execute(mbox)。

为什么能够执行mailbox呢?因为mailbox实现了Runnable方法。

  override final def run(): Unit = {
try {
if (!isClosed) { //Volatile read, needed here
processAllSystemMessages() //First, deal with any system messages
processMailbox() //Then deal with messages
}
} finally {
setAsIdle() //Volatile write, needed here
dispatcher.registerForExecution(this, false, false)
}
}

在上篇文章中,俺认为在设计Akka时,应该考虑提交给线程池的任务的粒度,在这个run方法中,实现上决定了这个粒度:ProcessAllSystemMessages以及processMailbox。

  @tailrec private final def processMailbox(
left: Int = java.lang.Math.max(dispatcher.throughput, 1),
deadlineNs: Long = if (dispatcher.isThroughputDeadlineTimeDefined == true) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0L): Unit =
if (shouldProcessMessage) {
val next = dequeue()
if (next ne null) {
if (Mailbox.debug) println(actor.self + " processing message " + next)
actor invoke next
if (Thread.interrupted())
throw new InterruptedException("Interrupted while processing actor messages")
processAllSystemMessages()
if ((left > 1) && ((dispatcher.isThroughputDeadlineTimeDefined == false) || (System.nanoTime - deadlineNs) < 0))
processMailbox(left - 1, deadlineNs)
}
}

这个方法会调用actor.invoke(next)来处理消息。

  final def invoke(messageHandle: Envelope): Unit = try {
currentMessage = messageHandle
cancelReceiveTimeout() // FIXME: leave this here???
messageHandle.message match {
case msg: AutoReceivedMessage ⇒ autoReceiveMessage(messageHandle)
case msg ⇒ receiveMessage(msg)
}
currentMessage = null // reset current message after successful invocation
} catch handleNonFatalOrInterruptedException { e ⇒
handleInvokeFailure(Nil, e)
} finally {
checkReceiveTimeout // Reschedule receive timeout
}

对于AutoReceivedMessage会走autoRecieveMessage这条路,其它的走receiveMessage(msg)这条路。

final def receiveMessage(msg: Any): Unit = actor.aroundReceive(behaviorStack.head, msg)
protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = receive.applyOrElse(msg, unhandled)

至此,可以看到我们在actor中定义的receive方法返回的Actor.Receive这个PartialFunction被应用于msg。也就是说,至此,我们定义的处理消息的逻辑被执行。


可见,Akka处理消息的过程,实际上是就是把消息加入了actor的mailbox中,然后生成一个任务(即Mailbox的run方法中的动作)提交给线程池。

当然这其中的很多细节决定了Akka的正确工作。比如每次调用processMailbox处理多少消息;比如equeue一个消息到mailbox的动作是否阻塞,阻塞多久;比如,在Mailbox的receiveMessage方法中,behaviorStack的行为。

[Akka]发送一条消息的内部流程的更多相关文章

  1. 【转】C# 重写WndProc 拦截 发送 系统消息 + windows消息常量值(1)

    C# 重写WndProc 拦截 发送 系统消息 + windows消息常量值(1) #region 截获消息        /// 截获消息  处理XP不能关机问题        protected ...

  2. RabbitMQ中,exchange1绑定exchange2,exchange1和exchange2都绑定queue1,此时消息发送给exchange1,queue1中有几条消息

    如题: 存在两个交换器 exchange1,exchange2 存在一个队列 queue1 存在三个绑定关系:exchange1绑定exchange2 ,exchange1绑定queue1,excha ...

  3. [3] MQTT,mosquitto,Eclipse Paho---怎样使用 Eclipse Paho MQTT工具来发送订阅MQTT消息?

    在上两节,笔者主要介绍了 MQTT,mosquitto,Eclipse Paho的基本概念已经怎样安装mosquitto. 在这个章节我们就来看看怎样用 Eclipse Paho MQTT工具来发送接 ...

  4. C# 重写WndProc 拦截 发送 系统消息 + windows消息常量值

    接收拦截+发送消息 对于处理所有消息.net 提供了wndproc进行重写 WndProc(ref Message m)protected override void WndProc(ref Mess ...

  5. 使用Python发送、订阅消息

    使用Python发送.订阅消息 使用插件 paho-mqtt 官方文档:http://shaocheng.li/post/blog/2017-05-23 Paho 是一个开源的 MQTT 客户端项目, ...

  6. 掌握Rabbitmq几个重要概念,从一条消息说起

    RabbitMQ 是功能强大的开源消息代理.根据官网称:也是使用量最广泛的消息队列.就像他的口号“Messaging that just works”,开箱即用使用简单,支持多种消息传输协议(AMQP ...

  7. PHP版微信公共平台消息主动推送,突破订阅号一天只能发送一条信息限制

    2013年10月06日最新整理. PHP版微信公共平台消息主动推送,突破订阅号一天只能发送一条信息限制 微信公共平台消息主动推送接口一直是腾讯的私用接口,相信很多朋友都非常想要用到这个功能. 通过学习 ...

  8. 源码分析Kafka 消息拉取流程

    目录 1.KafkaConsumer poll 详解 2.Fetcher 类详解 本节重点讨论 Kafka 的消息拉起流程. @(本节目录) 1.KafkaConsumer poll 详解 消息拉起主 ...

  9. nsq - 一条消息的生命周期(一)

    经过前面几篇的学习,相信大家对nsq已经有了一个大概的了解,我在写这篇文章的时候也看了很多其他人写的教程,发现大家对于分析系统每个点写的很不错,但是都很少有整体串起来一起走一遍,所以,我打算分成2-3 ...

随机推荐

  1. Apache配置多端口多站点

    配置httpd.conf 监听多个端口 复制代码 代码如下:# Listen: Allows you to bind Apache to specific IP addresses and/or # ...

  2. 第二十八篇、自定义的UITableViewCell上有图片需要显示,要求网络网络状态为WiFi时,显示图片高清图;网络状态为蜂窝移动网络时,显示图片缩略图

    1)SDWebImage会自动帮助开发者缓存图片(包括内存缓存,沙盒缓存),所以我们需要设置用户在WiFi环境下下载的高清图,下次在蜂窝网络状态下打开应用也应显示高清图,而不是去下载缩略图. 2)许多 ...

  3. Cocos开发中性能优化工具介绍之使用Windows任务管理器

    说到Windows平台,我们很快就想到了Visual Studio 2012,然而Visual Studio 2012在这方面没有很好的工具.如果我们只是想知道大体上内存.CPU等在某一事件前后变化情 ...

  4. 【转载】应读者强烈要求给出《超容易的Linux系统管理入门书》一书的主要知识点

    刚开始了一篇连载,收到广大Linux爱好者的反馈,非常欣慰.大家对Linux学习感到很迷茫,不知道学哪些内容,如何学习? <超容易的Linux系统管理入门书>一书是腾讯Linux专家在腾讯 ...

  5. cplusplus解析

    经常在头文件包含代码里面看到如下代码 #ifndef MAC_API_H #define MAC_API_H #ifdef __cplusplus extern "C"{ #end ...

  6. linux同步

    1. 内核同步 主要是防止多核处理器同时访问修改某段代码,或者在对设备驱动程序进行临界区的保护. 1 .互斥量 头文件 #include<linux/mutex.h> 初始化方法: 静态初 ...

  7. TIMAC 学习笔记(二)

    昨天大体上熟悉了TIMAC自带的CC2530的示范例程,今天先从演示抓包入手,分析四种不同的配置工程在空中传输的差异.随后,会按照扫描.组网.入网等MAC层接口函数入手,结合IEEE 802.15.4 ...

  8. 用于主题检测的临时日志(594fb726-af0b-400d-b647-8b1d1b477d72 - 3bfe001a-32de-4114-a6b4-4005b770f6d7)

    这是一个未删除的临时日志.请手动删除它.(f8322da6-c527-47fb-98d2-f12a7d814d19 - 3bfe001a-32de-4114-a6b4-4005b770f6d7)

  9. poj 2154 Color

    这是道标准的数论优化的polya题.卡时卡的很紧,需要用int才能过.程序中一定要注意控制不爆int!!!我因为爆intWA了好久=_=…… 题目简洁明了,就是求 sigma n^gcd(i,n):但 ...

  10. Java中的队列:java.util.Queue接口

    队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作. Queue接口与List.Set同一级别,都是继承了Collection接口.Linked ...