Akka源码分析-Extension
一个设计优秀的工具或框架,应该都有一个易用、强大的插件或扩展体系,akka也不例外。
akka的扩展方法非常简单,因为只涉及到两个组件:Extension、 ExtensionId。其中Extension在每个ActorSystem中只会加载一次,然后被akka管理。你可以在ActorSystem启动的时候以编程的方式加载,也可以通过配置的方式自动加载。由于Extension是在ActorSystem层面的扩展,所以需要开发者自己处理线程安全的问题。ExtensionId可以理解为Extension的一个唯一标志,ActorSystem会根据它来判断Extension是否被加载过,以确保Extension只能加载一次。
/**
* The basic ActorSystem covers all that is needed for locally running actors,
* using futures and so on. In addition, more features can hook into it and
* thus become visible to actors et al by registering themselves as extensions.
* This is accomplished by providing an extension—which is an object
* implementing this trait—to `ActorSystem.registerExtension(...)` or by
* specifying the corresponding option in the configuration passed to
* ActorSystem, which will then instantiate (without arguments) each FQCN and
* register the result.
*
* The extension itself can be created in any way desired and has full access
* to the ActorSystem implementation.
*
* This trait is only a marker interface to signify an Akka Extension.
*/
trait Extension
上面是Extension的定义,可以看出它非常简单,简单到就是一个trait,没有任何字段和方法。也就是说我们实现的对akka的扩展可以是任意形式的类,而且会被保证加载一次,那么是如何保证只会加载一次的呢?ExtensionId也许可以回答这个问题。
/**
* Identifies an Extension
* Lookup of Extensions is done by object identity, so the Id must be the same wherever it's used,
* otherwise you'll get the same extension loaded multiple times.
*/
trait ExtensionId[T <: Extension] { /**
* Returns an instance of the extension identified by this ExtensionId instance.
*/
def apply(system: ActorSystem): T = {
java.util.Objects.requireNonNull(system, "system must not be null!").registerExtension(this)
} /**
* Returns an instance of the extension identified by this ExtensionId instance.
* Java API
* For extensions written in Scala that are to be used from Java also,
* this method should be overridden to get correct return type.
* {{{
* override def get(system: ActorSystem): TheExtension = super.get(system)
* }}}
*
*/
def get(system: ActorSystem): T = apply(system) /**
* Is used by Akka to instantiate the Extension identified by this ExtensionId,
* internal use only.
*/
def createExtension(system: ExtendedActorSystem): T override final def hashCode: Int = System.identityHashCode(this)
override final def equals(other: Any): Boolean = this eq other.asInstanceOf[AnyRef]
}
ExtensionId也很简单,首先这是一个trait,且有一个类型变量T,要求T是Extension的子类。其中有一个apply,通过system返回一个T的实例。createExtension没有实现。那需要继续深入registerExtension的代码。
/**
* Registers the provided extension and creates its payload, if this extension isn't already registered
* This method has putIfAbsent-semantics, this method can potentially block, waiting for the initialization
* of the payload, if is in the process of registration from another Thread of execution
*/
def registerExtension[T <: Extension](ext: ExtensionId[T]): T
通过registerExtension的定义来看,官方注释写的也很清楚,它就是在注册一个extension,并且创建一个实例。如果这个extension已经注册过,就不再注册。
@tailrec
final def registerExtension[T <: Extension](ext: ExtensionId[T]): T = {
findExtension(ext) match {
case null ⇒ //Doesn't already exist, commence registration
val inProcessOfRegistration = new CountDownLatch(1)
extensions.putIfAbsent(ext, inProcessOfRegistration) match { // Signal that registration is in process
case null ⇒ try { // Signal was successfully sent
ext.createExtension(this) match { // Create and initialize the extension
case null ⇒ throw new IllegalStateException(s"Extension instance created as 'null' for extension [$ext]")
case instance ⇒
extensions.replace(ext, inProcessOfRegistration, instance) //Replace our in process signal with the initialized extension
instance //Profit!
}
} catch {
case t: Throwable ⇒
extensions.replace(ext, inProcessOfRegistration, t) //In case shit hits the fan, remove the inProcess signal
throw t //Escalate to caller
} finally {
inProcessOfRegistration.countDown //Always notify listeners of the inProcess signal
}
case other ⇒ registerExtension(ext) //Someone else is in process of registering an extension for this Extension, retry
}
case existing ⇒ existing.asInstanceOf[T]
}
}
我们来看看registerExtension的具体实现,它首先通过findExtension查找对应的ExtensionId是否已经注册,如果已经注册,则直接返回找到的结果,否则就进行创建。在case null分支中,有一个CountDownLatch。我们有必要简要介绍一下这个类的作用和使用方法。
“CountDownLatch典型用法1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n new CountDownLatch(n)
,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown()
,当计数器的值变为0时,在CountDownLatch上 await()
的线程就会被唤醒”
也就是说registerExtension是会保证线程安全的,以保证Extension只被加载一次。extensions会通过putIfAbsent方法插入ExtensionId与inProcessOfRegistration的键值对,当然了extensions是一个ConcurrentHashMap。如果key不存在,即第一次注册的时候,则把键值对插入并返回null。所以第一次注册会命中case null,然后把当前ActorSystem传给createExtension方法创建Extension实例。如果创建成功,就会替换extensions中ExtensionId对应的value为新创建的Extension实例(替换之前是inProcessOfRegistration这个CountDownLatch),最后执行countDown,计数器变成0。如果创建失败呢?会抛出一个IllegalStateException异常或其他异常,收到异常后,会把ExtensionId对应的value变成对应的Throwable信息。那么如果putIfAbsent插入失败呢,也就是ExtensionId已经有对应的value了,会递归执行registerExtension重新注册,既然有值了为啥还要重新注册呢?因为对应的值有三种情况:Extension实例、Throwable、CountDownLatch。所以需要重新注册。
另外CountDownLatch一定会有await,那么啥时候await呢。别急,还有findExtension没有分析呢。
/**
* Returns any extension registered to the specified Extension or returns null if not registered
*/
@tailrec
private def findExtension[T <: Extension](ext: ExtensionId[T]): T = extensions.get(ext) match {
case c: CountDownLatch ⇒
c.await(); findExtension(ext) //Registration in process, await completion and retry
case t: Throwable ⇒ throw t //Initialization failed, throw same again
case other ⇒
other.asInstanceOf[T] //could be a T or null, in which case we return the null as T
}
很显然,findExtension会对查询到的结果进行判断,如果是CountDownLatch就调用await进行等待,等待其他线程的registerExtension执行完毕,然后递归调用findExtension;如果其他线程注册完了返回异常,则此处也简单的抛出异常;如果返回其他类型的数据,则把它转化成T的一个实例,也就是我们自定义的Extension,那如果返回null呢?那就返回null喽。
至此registerExtension分析完毕,它以线程安全的方式保证Extension被加载一次,也就是createExtension方法只被调用一次。那么如何根据ActorSystem创建我们自定义的Extension就非常灵活了。
我们来看一下官网的例子。
class CountExtensionImpl extends Extension {
//Since this Extension is a shared instance
// per ActorSystem we need to be threadsafe
private val counter = new AtomicLong(0) //This is the operation this Extension provides
def increment() = counter.incrementAndGet()
}
上面是我们自定义的一个Extension,它非常简单,就是一个计数器,且increment()保证线程安全。
object CountExtension
extends ExtensionId[CountExtensionImpl]
with ExtensionIdProvider {
//The lookup method is required by ExtensionIdProvider,
// so we return ourselves here, this allows us
// to configure our extension to be loaded when
// the ActorSystem starts up
override def lookup = CountExtension //This method will be called by Akka
// to instantiate our Extension
override def createExtension(system: ExtendedActorSystem) = new CountExtensionImpl /**
* Java API: retrieve the Count extension for the given system.
*/
override def get(system: ActorSystem): CountExtensionImpl = super.get(system)
}
上面是一个ExtensionId,还继承了ExtensionIdProvider,ExtensionIdProvider源码如下,其实就是用来查找ExtensionId的,这样就能够通过配置文件自动加载了。
/**
* To be able to load an ExtensionId from the configuration,
* a class that implements ExtensionIdProvider must be specified.
* The lookup method should return the canonical reference to the extension.
*/
trait ExtensionIdProvider {
/**
* Returns the canonical ExtensionId for this Extension
*/
def lookup(): ExtensionId[_ <: Extension]
}
可以看出createExtension就是new了一个CountExtensionImpl,没有把ExtendedActorSystem传给CountExtensionImpl。其实在稍微复杂点的Extension里面是可以接收ExtendedActorSystem参数的,有了对ExtendedActorSystem的引用,我们就可以调用ExtendedActorSystem的所有公开的方法。如果你要问我ExtendedActorSystem都有哪些公开的方法或者说,有了ExtendedActorSystem可以做什么,我是拒绝回答的。有了ExtendedActorSystem你还不是想干啥就干啥?哈哈。
Akka源码分析-Extension的更多相关文章
- Akka源码分析-Cluster-Metrics
一个应用软件维护的后期一定是要做监控,akka也不例外,它提供了集群模式下的度量扩展插件. 其实如果读者读过前面的系列文章的话,应该是能够自己写一个这样的监控工具的.简单来说就是创建一个actor,它 ...
- Akka源码分析-Cluster-Distributed Publish Subscribe in Cluster
在ClusterClient源码分析中,我们知道,他是依托于“Distributed Publish Subscribe in Cluster”来实现消息的转发的,那本文就来分析一下Pub/Sub是如 ...
- Akka源码分析-Persistence
在学习akka过程中,我们了解了它的监督机制,会发现actor非常可靠,可以自动的恢复.但akka框架只会简单的创建新的actor,然后调用对应的生命周期函数,如果actor有状态需要回复,我们需要h ...
- Akka源码分析-Cluster-ActorSystem
前面几篇博客,我们依次介绍了local和remote的一些内容,其实再分析cluster就会简单很多,后面关于cluster的源码分析,能够省略的地方,就不再贴源码而是一句话带过了,如果有不理解的地方 ...
- Akka源码分析-Akka Typed
对不起,akka typed 我是不准备进行源码分析的,首先这个库的API还没有release,所以会may change,也就意味着其概念和设计包括API都会修改,基本就没有再深入分析源码的意义了. ...
- Akka源码分析-Akka-Streams-概念入门
今天我们来讲解akka-streams,这应该算akka框架下实现的一个很高级的工具.之前在学习akka streams的时候,我是觉得云里雾里的,感觉非常复杂,而且又难学,不过随着对akka源码的深 ...
- Akka源码分析-Cluster-Singleton
akka Cluster基本实现原理已经分析过,其实它就是在remote基础上添加了gossip协议,同步各个节点信息,使集群内各节点能够识别.在Cluster中可能会有一个特殊的节点,叫做单例节点. ...
- Akka源码分析-local-DeathWatch
生命周期监控,也就是死亡监控,是akka编程中常用的机制.比如我们有了某个actor的ActorRef之后,希望在该actor死亡之后收到响应的消息,此时我们就可以使用watch函数达到这一目的. c ...
- Akka源码分析-Persistence Query
Akka Persistence Query是对akka持久化的一个补充,它提供了统一的.异步的流查询接口.今天我们就来研究下这个Persistence Query. 前面我们已经分析过Akka Pe ...
随机推荐
- Quadtrees(四分树)
uva 297 Quadtrees Time Limit: 3000MS Memory Limit: Unknown 64bit IO Format: %lld & %llu Subm ...
- xe的debug怪现象
死活有问题,而且不能重新编译生成文件. 查网上说明:在删除项目xxx.dproj文件后,然后打开dpk文件,会自动生成.dproj文件,再然后一切OK. 的确如此,但莫名其妙.
- Git——跟踪或取消跟踪文件
在Git是用过程中,可能遇到以下情况: 1.被跟踪文件里面有不想跟踪的文件. 2.每次用git status查看状态时总是列出未被跟踪的文件. 解决方法: 1.当被跟踪的文件里面有不想跟踪的文件时,使 ...
- UVALive 6511 Term Project
Term Project Time Limit: 3000ms Memory Limit: 131072KB This problem will be judged on UVALive. Origi ...
- Leetcode 86.分隔链表
分隔链表 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的初始相对位置. 示例: 输入: head = 1-> ...
- [bzoj4033][HAOI2015]树上染色_树形dp
树上染色 bzoj-4033 HAOI-2015 题目大意:给定一棵n个点的树,让你在其中选出k个作为黑点,其余的是白点,收益为任意两个同色点之间距离的和.求最大收益. 注释:$1\le n\le 2 ...
- jconsole远程连接 jmx配置注意事项
由于在测试程序时需要收集程序运行时的内存,CPU等消耗情况.选择了jconsole这个jdk自带工具来观察.为了不影响程序运行状态,用远程连接的方式来具体观察. 首先,程序是放在ubutun系统服务器 ...
- CentOS设置程序开机启动程序/服务的方法(转)
注意:CentOS 6下基本没什么问题,CentOS 7估计不一定能行. 在CentOS系统下,主要有两种方法设置自己安装的程序开机启动. 1.把启动程序的命令添加到/etc/rc.d/rc.loca ...
- ubuntu 网络监控 nethogs
***网络监控ubuntu自带的 netstat -an 查看当前网络状况 sudo netstat -anp 查看当前网络状况带对应进程号 traceroute 追踪路由 ***我比较喜欢用Neth ...
- JAVA 并发编程-线程池(七)
线程池的作用: 线程池作用就是限制系统中运行线程的数量. 依据系统的环境情况.能够自己主动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数量,其 ...