Akka Cluster Sharding

Akka Cluster Sharding

96 
DeepLearningZ 
 0.1 2018.03.22 10:57* 字数 7911 阅读 449评论 1

  Sharding分片这个名词从P2P时代开始早已有之,对于分布式存储意味如何决定数据存到哪台节点机去、对于分布式计算意味如何决定计算分布到哪台节点机上,Akka中的分片兼而有之,用于将实体(带状态actor,既有数据属性也有作计算的微服务属性)分布到多个节点机以实现负载接近均衡的分布式微服务,全部实体仍然属于一个统一集群。分布基于一个肯定而简单的分片规则,Akka分片规则是一个算法/纯函数:输入实体ID、输出通常是这个实体所在数据分片的shard ID(分片和ShardRegion片区之间有映射、片区和节点机之间有映射),实体ID是实体核心要素,必须有,utf-8编码字符串。html

  重点在于:由于它是纯函数,随时随地能够调用无需依赖外界:无论在什么地方、何时调用必定获得一个惟一的肯定结果:在实体初始分片时调用、在查找定位实体物理位置时调用,惟一须要保证的是实体ID是稳定的,无需分布式协调便可随时随地知道数据应该发给谁、应该找谁要。典型分片规则算法:A simple sharding algorithm that works fine in most cases is to take the absolute value of the hashCode of the entity identifier modulo number of shards.java

  ActorSelection虽然能够按物理路径查询actor但须要指定搜索哪一台节点机:node

    val selection = context.actorSelection("akka.tcp://actorSystemName@10.0.0.1:2552/user/actorName")git

  这没多大屁用,可是加上一个简单的分片规则,使得actor的分布和定位自动化,实用价值立马爆棚,关键在于自动化,自动化定位使得分布式摆脱了物理地址的束缚,实现使用逻辑名便可与actor交互,往远了说云也不过是运维自动化——actor位置透明+经过ShardRegion转发,使一切变得简单github

  Sharding具有一个分布式分片管理层,推荐草原老师在InfoQ的演讲,当时的lightBend测试博客。注意演讲是2014年(Akka刚推出2.三、ShardRegion的shardID提取器叫Resolver、shard叫region),其余参考Akka分片集群。sharding叫作分片以区别集群的网络分区故障,分片上下级结构是:redis

    集群(多台节点机)  —>  每台节点机(1个片区)  —>  每一个片区(多个分片)  —>  每一个分片(多个实体)算法

  ShardRegion是在集群每台参与分片的成员节点机上部署的负责管理分片的actor咱们叫它片区,它是一种Stub驻点服务、long Running的系统actor,您有关于集群的问题?找它就对了,并且不限于分片、集群问题哦,它比你了解的更强大:后端

  全部片区组成分布式分片管理层,这也是一个分布式路由层,携带实体ID的消息直接发给本机片区,分片管理层就能够为你路由,根据消息的实体ID路由到相应节点机上的实体。所以,Sharding要求你必须提供从消息抽取分片和实体ID的函数、在这里分片ID抽取函数之因此也要提供,由于分片ID是从实体ID根据分片规则运算获得、这个分片规则是可插拔的,由你定制由你提供。缓存

 

  Akka文档把实体类比为DDD聚合根:It could for example be actors representing Aggregate Roots in Domain-Driven Design terminology. Here we call these actors “entities”. These actors typically have persistent (durable) state.网络

  针对数据变化,把一组相关的对象划分为一个单元来考虑,即为聚合。聚合使用边界将内部和外部的对象划分开,每一个聚合有一个根,这个根是一个实体而且,它是外部能够访问的惟一对象。根能够保持对任意聚合对象的引用,聚合内的对象也能够互相持有,但外部对象只能持有根对象的引用,显而易见,外部想要变动聚合内的对象必须/只能经过聚合根,根拥有全局标识符,而且有责任管理不变量。Akka想告诉咱们的是actor能够建立、持有大量其余的actor,固然,这些对象都必须和该actor的业务定位相关,都是为这个actor所用的;而后,这些对象不能“逸出”、不能抛头露面。最后,因为在Akka世界中是一切皆actor模式,因此事实上根actor持有的是一些其余的actorRef,这就好理解了,说它是职责分派、worker替身模式均可以。

  实体必须有本身的惟一ID、分片也同样,分片ID用于一致性哈希分片。实体相似于集群单例Cluster Singleton,若是你只用获得少许的分片actor,一台机器的资源也能够承载,那么为了简单起见用Cluster Singleton就够了,在此也能够看出实体和单例的一个共同点是:集群保证在一个时刻,一个实体/单例只会运行在一台节点机上,能够说ShardRegion是ClusterSingletonManager(都是本地Stub)与ClusterSingletonProxy(都是分布式通信代理)的合体。

  实体actor的特色首先是彻底由分片集群托管、其建立及生命周期不受咱们的控制;其次是不能直接给实体发消息,而是必须经过本机片区和实体所在片区转发;最后是实体必须具有全局(整个集群)惟一ID.

  分片集群能够提供相似send分布式发布订阅、集群客户端功能,由于片区也是分片集群的消息路由器,消息的发送者不须要知道目标实体实际位置,只要发给本机片区就好(只要携带目标实体的ID便可),再者,咱们可使用角色控制节点机的身份,指定哪些核心节点机参与分片,在一台非分片角色的节点机上你也能够建立片区ShardRegion,此时它属于proxy only mode,它能够向分片集群路由消息,但不参与分片。

  建立片区的代码就是加入分片集群的过程,示例代码中的实体同时继承了PersistentActor,虽然持久化不是必须的,仍是能看出来Akka仍是侧重分片集群搭配持久化总体解决方案,两者也确实关联颇多,在Akka2.3一块推出。不过Akka持久化稍显死板,由于要在恢复时重演的Events消息、必须在收到Command消息时先行持久化,若是你不想持久化到磁盘,能够考虑使用redis插件等分布式缓存,但这又引入了第三方进程间通信,并且Akka本身的ddata是不适用大数据量高并发业务数据的。建立片区你必须提供两个关键函数:

      1.extractEntityId:从消息当中提取实体ID的函数;

      2.extractShardId:从消息当中提取分片ID,实际上分片ID是从实体ID经过分片规则算出来的,默认实现就够用,参见:ShardRegion.HashCodeMessageExtractor;

  在系统初始化完成后、片区接收到第一条消息时会向ShardCoordinator中央协调者询问shard位置,协调者决策由哪个片区来持有特定shard分片,随后该片区会建立Shard分片actor、分片再建立独立实体,后面再来的消息再也不查询协调者直接路由:ShardRegion —> Shard —> Entity.

  消息能够发送给任意一台节点机、而后最多通过一跳到达目标节点机,咱们只用Akka理论上能够构建一个小规模、半自动的云,负载均衡没那么好、通用性仅限于JVM,这是专有场景下够用的私有云,一个properly适当的云。

 
无论这个世界有没有权威,在你内心不能有权威

  片区管理着一组shard(A shard is a group of entities that will be managed together.),相似HBase的RegionServer管理的region数据分区,片区就是“Shard Home”;片区还具有远程路由能力,对集群的其余片区、分片和实体的分布了然于胸;面对集群你没必要茫然,分片集群每台节点机都有完整一致的路由能力,你只要找你本机片区就够了,片区SR就是你的首席集群大管家

 
由于是本机驻点服务,片区大管家尽职尽责随叫随到,你能够随时经过ClusterSharding.shardRegion召见大管家,不能再贴心了

  对extractShardId函数的惟一硬性要求是实体ID和分片ID之间必须是一对一或者多对一关系:For a specific entity identifier the shard identifier must always be the same.  extractShardId是经过分片算法从EntityID算出来的ShardID,你能够自定义分片算法,设计目标是:首先假设以实体做为负载均衡最小单元、在此假设基础之上力求全部分片具有相同数量的实体,这里隐含了对实体的一个设计原则:

         实体尽可能能够预期工做负载而且全部实体工做负载尽可能近似

  这就是Akka的properly、半自动的负载均衡,它不能像云同样根据运行时机器实际负载情况去均衡、而是依赖于你的设计确切说是你对实体的划分是否均衡,Akka能作出的努力是基于你合理的设计能够把实体尽可能平均分布到全部分片上去。简单得体的分片算法是用实体ID与最大分片数numberOfShards求余获得分片ID,求余这种最简单的运算,能够将无限数字(实体ID)对分片数求余以后映射到一个小于该分片数的定值整数,对无限递增的实体ID求余获得的数字在{0, numberOfShards}这个区间振荡. i.e. 你始终能获得一个合法的分片shardID,这是个小学二年级学到的规律:余数小于除数。注意两点:

  一、numberOfShards必须固定不变,因此写在代码里也是无妨,numberOfShards和HBase的region分区数量的不一样在于,咱们能够自定义numberOfShards的值,只要不是太大或者过小都无妨,好比就设100,那么从2台到100台的集群都适用。

  二、实体ID最好具有随机性:它的随机性决定了实体分布的均衡性,因此能够遵循DDD将实体以中文命名,用名称的hashCode做为实体ID.

  最大分片数numberOfShards是你按照规划的集群规模所定义的你打算分多少片,最佳实践是分片数应该是规划的最多节点机数量的十倍大. i.e. 每台机10个shard,好比10台机则分100片。分片数少于节点机台数会形成一些节点机分不到shard. 过多的分片数会形成没必要要的分片管理负担,e.g.  形成再平衡分片负担、还会增长延迟,由于路由给shard第一条消息时会进行各类协做协调,这里隐含每台节点机超过一个数量级(10个)的分片可能会开始让你感受到一丢丢负担,节点机<=10个分片状况,没有明显的管理负担,大体估计每节点机10~100个分片都不是问题。

  集群节点机随着伸缩能够变,可是numberOfShards不能变,分片算法在集群全部节点也必须是一致的,分片算法的目的是Try to produce a uniform distribution 产生一个统一分布,两者都是不可滚动升级的,也就是说要改变numberOfShards或分片算法,必须停掉集群全部节点机。 As a rule of thumb, the number of shards should be a factor ten greater than the planned maximum number of cluster nodes.  举例来讲:10台机100个分片、宕掉9台、那么剩一台机也得本身跑100个分片,100个分片的集群最多扩容到100台机,101台就会有一台分不到分片运行,100分片就能够知足1~100台机的伸缩。

  除了负载均衡,Akka的“半自动”还体如今可伸缩方面,物联网领域咱们的集群规模通常是比较固定的,好比就几十台机的中小规模集群,那么Akka就用这几十台机,而不是像云同样在成千上万台机上去伸缩。在代码中,ShardRegion.ExtractEntityId是 “标记类型”Marker type of entity identifier (`String`). 在ShardRegion中酱紫定义的:

  type EntityId = String

  scala的类型还能够更加富有表达力,好比extractShardId也是一个类型:

  type ExtractShardId =Msg ⇒ShardId

 
实现这个type;必须认可英语的抽象能力比汉语高得多,英语把复杂变简单,汉语白话文言是将简单变复杂;你的语言体现你的思想,而语言最终也会影响你的思想

How it works

for the Impatient一图顶千言,③节点N1N2N3 ③片区 ⑨分片 加一个协调者C:

 
消息路由

  由于一个entity必然属于一个特定shard,因此说分片Shard就是最重要的资源,SR托管Shard,SR和节点机绑定、Shard则能够在节点机间以SR为落脚点漂移(这就是为何必须有SR这个驻点角色,相似的作法在其余分布式系统都存在:片区SR相似于hadoop里的NodeManager、协调者C相似于ResourceManager.  因此说框架繁多,但只要诺依曼硬件架构和TCP不变、架构和通信协议就是长久不变的),Shard的漂移对SR来讲即所谓的handoff换手,也就是Shard在不一样的托管SR之间takeover交接。由此咱们看到,分布式系统一个常规架构,就是在各个节点机上会有一个Stub驻点程序,它负责节点机自己的资源管理、与其余驻点的通信协调,做为服务的托管者负责服务的启动、漂移、终止。

  分布式系统中要确保实体单例惟一性关键在于全部节点具备一致的shard分布视图(某个shard位于哪一台节点机,你们的见解都是一致的),因此初始的shard位置分派以及后期的调整,是由集中式的协调者C来决定,已经安排好位置的叫作resolved已知shard,片区会缓存全部resolved shard,即便你直接向一个片区发送不属于它的消息它也会转发给正确的片区,没有意外状况下,全部片区的resolved shard视图是一致的:全部节点具备一致的shards分布视图。

  若是集群有新加入成员则中央协调者就得作shards的再平衡:从一个节点迁移实体到新节点。再平衡过程当中,协调者首先通知全部片区一个 handoff换手 即将开始、全部片区会将发给该shard的消息在本地缓存、协调者对位置未定的处于再平衡阶段shard的请求不予答复直到换手完成。拥有shard的原片区会有序中止该shard下的全部实体:给他们喂handOffStopMessage(default PoisonPill) 毒丸、实体死光之后,协调者开始向新位置路由消息,全部片区缓存的消息都会路由到新位置,负责接手再平衡shard的片区会建立启动新的实体,注意这又是一次按需建立。可是这些新实体的状态若是须要恢复则须要你本身去使用Persistence. 综上所述,由于分片集群每每涉及服务迁移,而服务迁移又须要恢复服务状态,因此分片集群和persistence好基友是在Akka2.3一块发布的:For this reason Cluster Sharding and Akka Persistence are such a popular combination.

  shard的分配以及再平衡都由可插拔组件决定:ShardCoordinator.LeastShardAllocationStrategy最少分片优先分配策略,该策略会从当前拥有最多分片的片区上选取去作再平衡的分片,把它handoff转手给当前拥有最少分片的片区,通常也就是集群新成员,能够配置一个阈值,该阈值指定最大达到多大的差距(最多分片和最少分片的差)就必须开始再平衡。

  各个shard的位置信息保存在中央协调者中,这些信息就至关于中央协调者自身的状态了,为避免单点,中央协调者的状态默认采用Distributed Data 作容错,当中央协调者crashed,新的协调者将会接任并恢复状态,在此失效期间,各个片区缓存的shards保持可用、发给一些节点还不知道的shard的消息也会获得缓存、直到新的中央协调者恢复完毕、以后就所有走正常流程。shard位置信息是典型的运行时数据,它只在系统运行起来之后产生、而且也只在系统运行期间有价值,一旦整个集群系统都停掉了,这些信息也再也不有价值了。相比之下,实体记忆则是持久化的:The state of Remembering Entitiesis durable, i.e. it is stored to disk. The stored entities are started also after a complete cluster restart.

  只要通过同一个片区向同一个实体路由消息,则消息的顺序能够保障As long as a sender uses the same ShardRegion actor to deliver messages to an entity actor the order of the messages is preserved. 这等价于Akka的另外一个保障:两个肯定的actor之间的消息保证送达顺序,也就是消息顺序得以保障的上下文是the same sender–receiver pair.  关于消息投递可靠性保障,要作到at-least-once须要基于AtLeastOnceDelivery in Persistence.

分布式数据 vs Persistence持久化

  节点机无论是伸仍是缩(包括有计划地减机器和意外Crash),都会涉及到实体的迁移,那么,实体的状态怎么恢复?两种方式:ddata(Distributed Data)或persistence.

  两者的功用同样,没有优劣之分,都能实现集群容错,协调者和片区的状态保持默认依靠分布式数据。若是除了作容错你的actor没有其余地方用到持久化的话,为了方便起见能够只用分布式数据,这样你就不用再安装、维护和操做第三方外部存储了。ddata有一个闪亮亮的特性:All data entries are spread to all nodes = 高可用集群支持n-(n-1)超级容错

  持久化特性从一开始设计就应该固定下来,由于这个特性在整个集群必须统一,i.e. 不可能滚动升级该特性。咱们在作实时系统时,第一直觉仍是分布式数据更好,不过实际上持久化作存储的时候使用的是异步actor,因此也没太差。另外在持久化Actor中还有另外两个概念JournalSnapshot,前者用于保存日志流水,后者用于持久化快照,二者在Actor survive failures的时候都起到了相当重要的做用,Journal也即event stream事件流(The event stream can be queried and fed into additional processing pipelines (an external Big Data cluster for example) or alternate views(like reports). 知足这个条件的事件流持久化可用于实体的恢复,持久化还能够用于实现消息可靠投递、实现CQRS system.  Akka社区有HBase持久化插件也有redis的,HBase插件能够保存journal和snapshot,基于openTSDB的底层组件asynchbase,这玩意是openTSDB公司基于本身的异步hbase操做库开发的,不过一是咱们要去作异步持久化能够直接基于舱壁模式去作、二是HBase官方客户端也看到原生HBase只有同步客户端不太好、也在慢慢加入异步feature.  最后即便HBase很快,但对并发量数据量比较高的时序业务数据仍是力不从心的。

  分布式数据feature是默认开启的:akka.cluster.sharding.state-store-mode = ddata/persistence则为持久化,协调者的状态基于它保持,是一种WriteMajority/ReadMajority多数读写一致性。协调者的状态并无默认保存到磁盘,当整个集群全部节点机宕机或停掉,状态会永久丢失,事实上也再也不须要了。

  Remembering Entities实体记忆是持久化到磁盘的,它会持久化每一个shard所拥有的实体名单,即便是整个集群彻底重启,依然能够恢复重启以前每一个shard的实体。设置rememberEntitiesflag = true能够开启,该设置在调用 ClusterSharding.start时、在ClusterShardingSettings上设置。同时确保你写的shardIdExtractor分片ID提取器具有代码Shard.StartEntity(EntityId). 该代码直接从一个EntityId映射到一个 ShardId,示例:

    val extractShardId: ShardRegion.ExtractShardId = {

      case EntityEnvelope(id, _)                 ⇒ (id % numberOfShards).toString

      case Get(id)                                          ⇒ (id % numberOfShards).toString

      case ShardRegion.StartEntity(id) ⇒  // StartEntity is used by remembering entities feature

            (id.toLong % numberOfShards).toString //该代码在集群重启时从持久状态恢复自动调用

}

  配置为实体记忆后,Shard再平衡到另外一个节点上时、或者从Crash恢复时,总会从新建立以前所拥有的全部实体,这是它默认的、全自动的行为,要彻底停掉实体,须要发送Passivate消息给实体actor的父actor,不然实体总会被从新建立,配置为rememberEntitiesis=false的话,Shard在再平衡或Crash恢复后不会自动重建实体,实体只会在第一条属于它的消息到达Shard时被建立一次,也就是依然是按需建立creating on demand.

  分片使用本身的分布式复制子Replicator,在每一个节点上都有,以这种方式,你能够指定对某些实体类型分配到某些节点上、另外的实体类型则分配到另一些节点上. Each such replicator has a name that contains the node role and therefore the role configuration must be the same on all nodes in the cluster, i.e. you can’t change the roles when performing a rolling upgrade.

  ddata配置项:akka.cluster.sharding.distributed-data.

  实体记忆的性能代价仍是有点高的,shard再平衡时,性能消耗随着实体数量增加而增加,当前版本的AK,若是每一个shard的实体数量超过1w个的话,咱们不推荐使用该特性。

Startup after minimum number of members

  使用akka.cluster.min-nr-of-members 或 akka.cluster.role..min-nr-of-members. 能够指定集群开始分片的最少启动成员节点机数量,系统直到达到该数量的片区启动上线才会开始shard分配,这能够避免过多shard在启动阶段分配到第一个片区、在后续节点机陆续启动后又再次再平衡rebalance.

Proxy Only Mode

  片区能够是纯代理模式,此时它不会建立任何实体, i.e. 只作消息的分布式路由,这时它更像一个纯路由器、属于分布式路由层但不参与分片、像是分片集群的旁观者。用ClusterSharding.startProxy专用方法能够建立纯代理Shard。好比说,作流数据处理的时候,有一些前置接收数据的节点机、还有一批后端组成分片集群的节点机,就能够这么干。再者,还能够经过角色划分达到一样目的,在调用ClusterSharding.start启动ShardRegion actor时,若是本机角色和ClusterShardingSettings指定的角色不符,则本机启动的ShardRegion就处于纯代理模式。这个feature和集群客户端、分布式发布订阅的各自特色多是:

  一、纯代理片区和发布订阅相比只至关于Send不能作Pub;

  二、集群客户端则是容许一个集群向另外一个集群发送消息、或者是由于某些缘由不能加入集群的机器向集群发送消息;

  三、分布式发布订阅使用更灵活功能更丰富,容许订阅者动态变化、支持针对主题的publish、针对path的send和SendToAll;

  综上,若是不采用热备方式,也就是说一个实体就只有一个,能够采用一、2;若是采用热备能够考虑3,同一份数据消息发给不止一个如出一辙的功能实体。

Passivation

  钝化,若是实体较长时间内再也不用了,能够停掉他们减小内存占用。还能够定义消息接收超时receive timeout (context.setReceiveTimeout). 能够自动钝化。可是送达钝化actor的消息会被删除,要不丢消息,也就是优雅/有序钝化,实体能够先通知本身的父替本身代收消息、发送ShardRegion.Passivate消息给它的父也就是Shard actor.:我要钝化掉了、归隐磁盘,个人消息您帮我留着。那么Shard就会缓存它的消息 between reception of Passivate and termination of the entity. 实体是实例,它钝化实际上就是死亡,等到有它的消息来了,须要复活它来处理,算是克隆吧,但确实不是一个实例了,因此缓存消息最终会由该实体的一个新的incarnation化身也就是新实例来处理。这种用法适合移动互联网:用户离开/关闭app时actor钝化掉。

监管体系

  若是你想对实体应用自定义监管策略supervisorStrategy来代替默认的重启策略,你得写一个全部实体的直接父actor,在里面定义自定义策略:Escort是父、counter是子

class Escort extends Actor {

    val counter = context.actorOf(Props[Counter], "theCounter")

    override val supervisorStrategy = OneForOneStrategy() { //默认监管策略

        case _: IllegalArgumentException ⇒ SupervisorStrategy.Resume

        case _: ActorInitializationException ⇒ SupervisorStrategy.Stop

        case _: DeathPactException ⇒ SupervisorStrategy.Stop

        case _: Exception ⇒ SupervisorStrategy.Restart

    }

    def receive = { case msg ⇒ counter forward msg  }

}

  那么,咱们可让这个Escort负责全部实体的建立,若是实体之间还有上下级关系,那么就在建立下级实体以后将其actorRef以消息形式发给上级实体,上级实体保持持有全部下级实体actorRef便可。这样,只有Escort做为全部实体的监管者,而实体间的上下级关系也具有,分离监管层次和业务上下级层次,完美。Escort就像普通实体同样建立启动便可:

ClusterSharding(system).start(

    typeName = "Escort",

    entityProps = Props[Escort],

    settings = ClusterShardingSettings(system),

    extractEntityId = extractEntityId,  extractShardId = extractShardId

)

注意中止的实体,当有它的消息来到时会自动从新启动——克隆新化身。

优雅Shutdown

  给片区发送GracefulShutdown消息来优雅地手动中止它,它的shard将会迁移,期间属于它的消息缓存和再平衡过程同样。

  警告:不要把Cluster Sharding和Automatic Downing联合使用。自动下线配置容许集群分裂为两个独立小集群,这会致使多个分片和实体同时运行,这会破坏Akka集群元数据,可能致使集群没法重启。若是还用了persistence那么还可能会破坏你的业务数据。

若是已经形成Akka集群元数据破坏致使集群没法重启,使用以下Main命令执行程序清除损坏的Akka集群元数据:

java  -classpath  jarFiles

    akka.cluster.sharding.RemoveInternalClusterShardingData

    -2.3 entityType1 entityType2 entityType3

  该程序包含在akka-cluster-sharding jar包文件,最好使用一致的classpath 和配置,可使用sbt或Maven.  entity type实体类型和ClusterSharding.start中的一致。

  -2.3指定了删除Cluster Sharding in Akka 2.3.x存储的数据,由于这个版本使用了不一样的persistenceId.

全部配置:Configuration

  想到一个actor模型的缺陷,那就是彷佛作不到精确的定时任务,由于和actor对话的方式只有一种那就是发消息给它,可是消息必须先进消息队列,进队列就可能会延迟,作不到在一个精确时间点上作某一件事,好比保存一个时刻的大量actor的快照断面,延伸想到actor是否能够容许多个邮箱,按照优先级分类好比加急邮箱。可贵挑出actor模型的毛病,它几乎就是OO的理想国:面向对象系统是由对象及其相互之间的消息构成。

  不那么精确的、可用于大批量数据的定时任务有个内置支持scheduler,使用步骤:

  一、在一个actor中能够用:context.system.scheduler来获得一个定时器,同时须要一个隐式传递的ExecutionContext,咱们知道它基本就是一个线程池,scheduler须要它提供的线程来执行定时任务,通常状况下只要import context.dispatcher也就是直接使用当前的dispatcher.

  二、scheduler.scheduleOnce(time, actorRef, message)方法能够把message消息调度给一个future 、以time为定时、消息会被发送一次、给actorRef也能够是self.  因此说即便是本身的事也得用消息通知,相似记事帖,要是到了时间本身很忙呢,就可能滞后。scheduleOnce方法返回值是一个Cancellable,若是一次性的任务按时完成了,能够用它取消timer.

  其它参考:替身模式、任务actor代码示例

相关文章
相关标签/搜索