一旦 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. 【基础知识】.Net基础加强09天

    委托: 1. 委托是一种数据类型,像类一样{可以声明委托变量类型} 2. deleate关键字定义委托 : public delegate void MethodDelegate();//这就是定义了 ...

  2. Entity Framework 4、5 多字段排序

    public interface IOrderByExpression<TEntity> where TEntity : class { IOrderedQueryable<TEnt ...

  3. 【转载】php中iconv函数使用方法

    原文:http://www.phpweblog.net/star65225692/archive/2011/03/23/7524.html     在选择用什么工具开发,唯一的指导标准就是:用最少的人 ...

  4. ASP.NET中的KRE是什么?

    KRE的英文全称是K Runtime Environment,其中K是ASP.NET vNext(后来叫ASP.NET 5)的项目代号. KRE是ASP.NET 5运行时环境,它是ASP.NET 5的 ...

  5. Flash矢量图与位图性能对比

    Flash中使用位图的性能要高于矢量图,究竟有多大区别呢?数据有最好的说服力,开始测试: 一.机器配置 二.测试过程 测试程序控制红色小球在舞台中不停匀速移动,通过改变小球数量控制实际帧率在24帧/秒 ...

  6. [异常] openCV安装和配置

    http://blog.csdn.net/mygis2005/article/details/10472717 >_<" 这个链接亲测可行,我试了很多次,找了很多个都不行,最后怀 ...

  7. [ACM_几何] UVA 11300 Spreading the Wealth [分金币 左右给 最终相等 方程组 中位数]

    Problem A Communist regime is trying to redistribute wealth in a village. They have have decided to ...

  8. LINQ-to-SQL那点事~利用反射在LINQ-to-SQL环境中实现Ado.net的CURD操作

    回到目录 对于linq to sql提供的CURD操作,给我们的感觉就是简单,容易使用,更加面向对象,不用拼SQL语句了,这些好处都表示在处理单条实体或者集合长度小的情况下,如果有一个1000条的集合 ...

  9. windows下安装mysql压缩包版[转]

    版本:5.6.17 1.将解压后的文件夹放到某个目录下,比如c:\software; 2.在环境变量中新建MYSQL_HOME=C:\software\mysql-5.6.17-winx64,然后在系 ...

  10. atitit.团队建设总结fx O622

    团队建设总结fx O622 大的title 2 建设团队文化 2 办公环境(3s+树+湖) 3 每人一个办公室 3 弹性工作制 3 重大的决定公投体制 3 每年一个kid经验日 3 做自己想到做的事. ...