一旦 shard coordinator(相当于分布式系统的 zookeeper) 启动,它就会启动一个定时器,每隔一定的时间尝试平衡一下集群中各个节点的负载,平衡的办法是把那些负载较重的 actor 移动到负载较轻的节点上。在这一点上,我以前的理解有误,我以为 shardRegion 是移动的最小单位。

val rebalanceTask = context.system.scheduler.schedule(rebalanceInterval, rebalanceInterval, self, RebalanceTick)

当 coordinator 收到 ReblanceTick 后,就开始尝试平衡系统负载

case RebalanceTick ⇒
if (persistentState.regions.nonEmpty) {
val shardsFuture = allocationStrategy.rebalance(persistentState.regions, rebalanceInProgress)
shardsFuture.value match {
case Some(Success(shards)) ⇒
continueRebalance(shards)
case _ ⇒
// continue when future is completed
shardsFuture.map { shards ⇒ RebalanceResult(shards)
}.recover {
case _ ⇒ RebalanceResult(Set.empty)
}.pipeTo(self)
}
}

上面的逻辑我看懂了,但是 Future 的用法没看明白。按照一般的写法,当 shardsFuture 返回 Failure 以后,应该直接执行 RebalanceResut(Set.empty).pipeTo(self),不知道为什么失败以后还要尝试等待 Future

allocationStrategy 提供了默认的实现,也可以自定义负载均衡策略。rebalance 函数返回的是 Set(ShardId),即那些要被移动的 shards

当 coordinator 收到 RebalanceResult 后,开始 启动 balance 逻辑

def continueRebalance(shards: Set[ShardId]): Unit =
shards.foreach { shard ⇒
if (!rebalanceInProgress(shard)) {
persistentState.shards.get(shard) match {
case Some(rebalanceFromRegion) ⇒
rebalanceInProgress += shard
log.debug("Rebalance shard [{}] from [{}]", shard, rebalanceFromRegion)
context.actorOf(rebalanceWorkerProps(shard, rebalanceFromRegion, handOffTimeout,
persistentState.regions.keySet ++ persistentState.regionProxies)
.withDispatcher(context.props.dispatcher))
case None ⇒
log.debug("Rebalance of non-existing shard [{}] is ignored", shard)
} }
}

rebalanceInProcess 是一个 Set,记录正在被移动的 shard,我想,在新一轮 balance 开始时, rebalanceInProcess 为空的情况只会发生在上次 balance 还没有做完。不知道这个时候,是应该报错还是继续 balance 更好,因为 balanceStrategy 应该不会考虑吧到 上一轮 balance 还没做完这种可能性。

然后, coordinator 启动 rebalanceWorker,也就是上篇提到的替身 actor。

private[akka] class RebalanceWorker(shard: String, from: ActorRef, handOffTimeout: FiniteDuration,
regions: Set[ActorRef]) extends Actor {
import Internal._
regions.foreach(_ ! BeginHandOff(shard))
var remaining = regions import context.dispatcher
context.system.scheduler.scheduleOnce(handOffTimeout, self, ReceiveTimeout) def receive = {
case BeginHandOffAck(`shard`) ⇒
remaining -= sender()
if (remaining.isEmpty) {
from ! HandOff(shard)
context.become(stoppingShard, discardOld = true)
}
case ReceiveTimeout ⇒ done(ok = false)
} def stoppingShard: Receive = {
case ShardStopped(shard) ⇒ done(ok = true)
case ReceiveTimeout ⇒ done(ok = false)
} def done(ok: Boolean): Unit = {
context.parent ! RebalanceDone(shard, ok)
context.stop(self)
}
}

akka 的逻辑是基于消息传递的,这种代码其实是很难去读的。在 rebalanceWorker 运行时,牵扯到很多个 actor。首先是,coordinator,其次是 shardRegion,也就是 host 待迁移 shard actor 的那个 region,然后是 shard actor 本身,最后是系统里所有的 shardRegion,他们也要参与进来。写到这里,我不禁把电脑屏幕竖了起来。

1. RebalanceWorker 首先给所有的 ShardRegion BeginHandOff 消息,告诉大家,hand off 开始,然后等待大家的回复

2. ShardRegion 收到 BeginHandOff 后,开始更新自己的知识库,将 HostShardRegion 和 shardActor 的记忆从自己的知识库中抹去

case BeginHandOff(shard) ⇒
log.debug("BeginHandOff shard [{}]", shard)
if (regionByShard.contains(shard)) {
val regionRef = regionByShard(shard)
val updatedShards = regions(regionRef) - shard
if (updatedShards.isEmpty) regions -= regionRef
else regions = regions.updated(regionRef, updatedShards)
regionByShard -= shard
}
sender() ! BeginHandOffAck(shard)

最后,发送 BeginHandOffAck 消息,告诉 rebalanceWorker 自己准备完毕(这些 shardRegion 以后也没事干了)

3. 继续回到 rebalanceWorker,它发送 HandOff 告诉 Host shard actor 的 ShardRegion,你可以做自己的清理工作了。然后将自己的状态设置成 stoppingShard,等待 ShardStopped 消息,这个消息的来源有两个,一个是 HostShardRegion,另外一个是 shard actor

4. HostShardRegion 收到 HandOff 消息后

case msg @ HandOff(shard) ⇒
log.debug("HandOff shard [{}]", shard) // must drop requests that came in between the BeginHandOff and now,
// because they might be forwarded from other regions and there
// is a risk or message re-ordering otherwise
if (shardBuffers.contains(shard)) {
shardBuffers -= shard
loggedFullBufferWarning = false
} if (shards.contains(shard)) {
handingOff += shards(shard)
shards(shard) forward msg
} else
sender() ! ShardStopped(shard)

如果 HostShardRegion 已经不再含有 shard actor,那么直接返回 ShardStopped,否则 HandOff 这个 Set 加入 shard actor,并将 HandOff 传给 shard actor

5. 又看了一遍代码,发现 shard actor 和 entity actor 又是两种东西,shard actor 存在于 entity actor 和 shard region 之间

目前还不知道 entity actor 和 shard region 之间的关系

def getEntity(id: EntityId): ActorRef = {
val name = URLEncoder.encode(id, "utf-8")
context.child(name).getOrElse {
log.debug("Starting entity [{}] in shard [{}]", id, shardId) val a = context.watch(context.actorOf(entityProps, name))
idByRef = idByRef.updated(a, id)
refById = refById.updated(id, a)
state = state.copy(state.entities + id)
a
}
}

从这段代码来看, shard actor 与 entity actor 是一对多的关系。

def receiveCoordinatorMessage(msg: CoordinatorMessage): Unit = msg match {
case HandOff(`shardId`) ⇒ handOff(sender())
case HandOff(shard) ⇒ log.warning("Shard [{}] can not hand off for another Shard [{}]", shardId, shard)
case _ ⇒ unhandled(msg)
} def handOff(replyTo: ActorRef): Unit = handOffStopper match {
case Some(_) ⇒ log.warning("HandOff shard [{}] received during existing handOff", shardId)
case None ⇒
log.debug("HandOff shard [{}]", shardId) if (state.entities.nonEmpty) {
handOffStopper = Some(context.watch(context.actorOf(
handOffStopperProps(shardId, replyTo, idByRef.keySet, handOffStopMessage)))) //During hand off we only care about watching for termination of the hand off stopper
context become {
case Terminated(ref) ⇒ receiveTerminated(ref)
}
} else {
replyTo ! ShardStopped(shardId)
context stop self
}
}
def receiveTerminated(ref: ActorRef): Unit = {
if (handOffStopper.exists(_ == ref))
context stop self
else if (idByRef.contains(ref) && handOffStopper.isEmpty)
entityTerminated(ref)
}

从这段代码看, shard actor 与 entity actor 的关系是一对一,因为当 entity stop self 了以后, shard actor 也会 stop self。这让我想到 coursera reactive programming 的最后一道作业题,为什么也是类似于 一个 entity 有一个 shard actor 对应。

akka cluster sharding source code 学习 (2/5) handle off的更多相关文章

  1. akka cluster sharding source code 学习 (1/5) 替身模式

    为了使一个项目支持集群,自己学习使用了 akka cluster 并在项目中实施了,从此,生活就变得有些痛苦.再配上 apache 做反向代理和负载均衡,debug 起来不要太酸爽.直到现在,我还对 ...

  2. akka cluster sharding

    cluster sharding 的目的在于提供一个框架,方便实现 DDD,虽然我至今也没搞明白 DDD 到底适用于是什么场合,但是 cluster sharding 却是我目前在做的一个 proje ...

  3. StreamSets学习系列之StreamSets支持多种安装方式【Core Tarball、Cloudera Parcel 、Full Tarball 、Full RPM 、Docker Image和Source Code 】(图文详解)

    不多说,直接上干货! Streamsets的官网 https://streamsets.com/ 得到 https://streamsets.com/opensource/ StreamSets支持多 ...

  4. Classic Source Code Collected

    收藏一些经典的源码,持续更新!!! 1.深度学习框架(Deep Learning Framework). A:Caffe (Convolutional Architecture for Fast Fe ...

  5. spark source code 分析之ApplicationMaster overview(yarn deploy client mode)

    一直不是很清楚ApplicationMaster的作用,尤其是在yarn client mode和cluster mode的区别 网上有一些非常好的资料,请移步: https://blog.cloud ...

  6. Learning English From Android Source Code:1

    英语在软件行业的重要作用不言自明,尤其是做国际项目和写国际软件,好的英语表达是项目顺利进行的必要条件.纵观眼下的IT行业.可以流利的与国外客户英文口语交流的程序猿占比并非非常高.要想去国际接轨,语言这 ...

  7. Steps of source code change to executable application

    程序运行的整个过程,学习一下 源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 汇编程序 (assembler) → 目标代码 (o ...

  8. UI5 Source code map机制的细节介绍

    在我的博客A debugging issue caused by source code mapping里我介绍了在我做SAP C4C开发时遇到的一个曾经困扰我很久的问题,最后结论是这个问题由于Jav ...

  9. Akka系列(十):Akka集群之Akka Cluster

    前言........... 上一篇文章我们讲了Akka Remote,理解了Akka中的远程通信,其实Akka Cluster可以看成Akka Remote的扩展,由原来的两点变成由多点组成的通信网络 ...

随机推荐

  1. WPF快速入门系列(3)——深入解析WPF事件机制

    一.引言 WPF除了创建了一个新的依赖属性系统之外,还用更高级的路由事件功能替换了普通的.NET事件. 路由事件是具有更强传播能力的事件——它可以在元素树上向上冒泡和向下隧道传播,并且沿着传播路径被事 ...

  2. Java Config 下的Spring Test方式

    用了三种方式: 1.纯手动取bean: package com.wang.test; import com.marsmother.commission.core.config.MapperConfig ...

  3. 更改MySql表和字段区分大小写

    数据库和表名在 Windows 中是大小写不敏感的 ,而在大多数类型的 Unix 系统中是大小写敏感的Windows 版的 MySQL 默认继承 os 的大小写习惯,即使 SQL中有区分,在导入的时候 ...

  4. CentOS升级MySQL到5.5

    centOS的yum安装的MySQL是5.1版本,可通过官方的rpm包安装5.5版本 # 查看安装的相关项 rpm -qa|grep -i mysql # 停止服务 service mysqld st ...

  5. [安卓] 4、CheckBox、RadioButton和Toast简单用法

      和按钮类似,这里采用cb1.setOnCheckedChangeListener(this);方法分别对3个CheckBox进行CheckChange事件绑定,然后在onCheckedChange ...

  6. ueditor样式过滤问题

    1.4.3版本样式过滤处理如下: if (domUtils.isEmptyNode(me.body)) {    //alert("xx");    //me.body.inner ...

  7. paip.sqlite 管理最好的工具 SQLite Expert 最佳实践总结

    paip.sqlite 管理最好的工具 SQLite Expert 最佳实践总结 一般的管理工具斗可以...就是要是sqlite没正常地关闭哈,有shm跟wal文件..例如ff的place.sqlit ...

  8. 解决pageControl页面设置无效问题

    废话不多说,先上代码 1.添加pageViewControl - (void)addPageControl { UIPageControl *pageControl = [[UIPageControl ...

  9. php类自动装载、链式操作、魔术方法

    1.自动装载实例 目录下有3个文件:index.php load.php tests文件夹 tests文件夹里有 test1.php <?php namespace Tests; class T ...

  10. fine-uploader 5.11.8 (最新版) 使用感受

    以前测试和使用过 3.X 因为够用,所以一直没升级,今天闲来无聊测试了一下最新版.和3.X比性能上好了很多,但不支持了移动设备的多文件上传. 关于3.X版 可以看这里:http://www.cnblo ...