LeastShardAllocationStrategy
是 Akka Cluster Sharding 默认使用的分片分配策略,主要实现负载均衡和动态再平衡功能。
下面我将重点分析其核心实现,特别是 allocateShard
和 rebalance
方法,以及关键参数的作用。
1. 构造方法参数分析
class LeastShardAllocationStrategy(
rebalanceThreshold: Int = 10,
maxSimultaneousRebalance: Int = 3
) extends ShardAllocationStrategy with Logging
关键参数:
-
rebalanceThreshold
(默认=10):- 触发再平衡的阈值差
- 当某个节点的分片数比最少分片节点多出这个数量时,触发再平衡
- 值越大,再平衡频率越低
-
maxSimultaneousRebalance
(默认=3):- 同时进行的最大再平衡操作数
- 控制再平衡的并发度,避免网络和资源过载
2. allocateShard
方法实现分析
override def allocateShard(
requester: ActorRef,
shardId: ShardId,
currentShardAllocations: Map[ActorRef, IndexedSeq[ShardId]]
): Future[ActorRef] = {
// 1. 找出当前分片数最少的节点
val (minNode, minCount) = currentShardAllocations
.map { case (n, shards) => n -> shards.size }
.minBy { case (_, count) => count }
// 2. 如果有多个最少分片节点,随机选择一个
val candidates = currentShardAllocations.collect {
case (n, shards) if shards.size == minCount => n
}.toVector
// 3. 随机选择(避免总是选择第一个)
val selected = candidates(ThreadLocalRandom.current().nextInt(candidates.size))
Future.successful(selected)
}
关键点:
-
纯负载均衡策略:
- 只考虑各节点当前持有的分片数量
- 不考虑分片大小、资源使用率等其他因素
-
随机选择:
- 当多个节点具有相同最少分片数时,随机选择
- 避免总是选择同一个节点
-
同步返回:
- 直接返回
Future.successful
,没有异步操作 - 分配决策是轻量级的
- 直接返回
3. rebalance
方法实现分析
override def rebalance(
currentShardAllocations: Map[ActorRef, IndexedSeq[ShardId]],
rebalanceInProgress: Set[ShardId]
): Future[Set[ShardId]] = {
// 1. 计算各节点分片数
val allocations = currentShardAllocations
.map { case (n, shards) => n -> shards.size }
.toMap
// 2. 找出最少和最多分片的节点
val (minNode, minCount) = allocations.minBy { case (_, count) => count }
val (maxNode, maxCount) = allocations.maxBy { case (_, count) => count }
// 3. 检查是否需要再平衡
if (maxCount - minCount > rebalanceThreshold) {
// 4. 计算可迁移的分片数(考虑并发限制)
val numberOfShards = math.min(
maxCount - minCount - rebalanceThreshold,
maxSimultaneousRebalance - rebalanceInProgress.size
)
if (numberOfShards > 0) {
// 5. 选择要迁移的分片(排除正在迁移的)
val candidateShards = currentShardAllocations(maxNode)
.filterNot(rebalanceInProgress)
.take(numberOfShards)
.toSet
Future.successful(candidateShards)
} else {
Future.successful(Set.empty)
}
} else {
Future.successful(Set.empty)
}
}
关键点:
-
阈值触发机制:
- 只有当
maxCount - minCount > rebalanceThreshold
时才触发 - 避免频繁的再平衡操作
- 只有当
-
并发控制:
maxSimultaneousRebalance
限制同时迁移的分片数- 计算方式:
maxSimultaneousRebalance - rebalanceInProgress.size
-
安全选择分片:
- 过滤掉正在迁移的分片 (
filterNot(rebalanceInProgress)
) - 从最多分片的节点选择分片
- 过滤掉正在迁移的分片 (
-
保守迁移策略:
- 每次只迁移足够消除阈值差的分片数
- 不会一次性平衡到完全平均
4. 策略特点总结
特性 | 说明 |
---|---|
分配策略 | 始终选择当前分片最少的节点 |
再平衡触发 | 基于固定阈值差 (rebalanceThreshold) |
并发控制 | 限制同时迁移数 (maxSimultaneousRebalance) |
优点 | 实现简单,资源开销小 |
局限 | 不考虑分片负载差异,仅基于计数 |
5. 调优建议
-
增大
rebalanceThreshold
:- 当集群规模大且分片迁移成本高时
- 减少不必要的再平衡操作
-
调整
maxSimultaneousRebalance
:- 在高速网络环境中可以增加
- 在资源受限环境中减少
-
监控指标:
// 监控分片分配差异 val maxDiff = allocations.values.max - allocations.values.min // 监控再平衡队列 val rebalanceQueueSize = rebalanceInProgress.size
这种策略适合大多数均匀负载的场景,但如果您的分片有显著不同的资源需求(如您之前提到的大内存Actor),则需要像您之前实现的 WeightedShardAllocationStrategy
这样的自定义策略。