Infinispan 集群锁性能不会随着节点的增加而提高吗?
Posted
技术标签:
【中文标题】Infinispan 集群锁性能不会随着节点的增加而提高吗?【英文标题】:Infinispan clustered lock performance does not improve with more nodes? 【发布时间】:2022-01-14 22:25:05 【问题描述】:我有一段代码基本上是在嵌入式模式下使用 Infinispan 执行以下操作,使用版本为 13.0.0
的 -core
和 -clustered-lock
模块:
@Inject
lateinit var lockManager: ClusteredLockManager
private fun getLock(lockName: String): ClusteredLock
lockManager.defineLock(lockName)
return lockManager.get(lockName)
fun createSession(sessionId: String)
tryLockCounter.increment()
logger.debugf("Trying to start session %s. trying to acquire lock", sessionId)
Future.fromCompletionStage(getLock(sessionId).lock()).map
acquiredLockCounter.increment()
logger.debugf("Starting session %s. Got lock", sessionId)
.onFailure
logger.errorf(it, "Failed to start session %s", sessionId)
我将这段代码部署到 Kubernetes。然后我在分布在同一区域的六个节点上的六个 Pod 中运行它。该代码通过 API 公开了带有随机 Guid 的 createSession
。该 API 被调用并以 500 个块创建会话,在 Pod 前面使用 k8s
服务,这意味着负载在 Pod 上得到平衡。我注意到获取锁的执行时间随着会话的数量线性增长。一开始大约需要 10 毫秒,当有大约 20_000 个会话时大约需要 100 毫秒,并且趋势以稳定的方式继续。
然后我使用相同的代码并运行它,但这次是在 12 个节点上使用 12 个 Pod。令我惊讶的是,我发现性能特征与我有六个吊舱时几乎相同。我一直在深入研究代码,但仍然没有弄清楚为什么会这样,我想知道是否有充分的理由说明这里的 infinispan 在更多节点下似乎表现不佳?
为了完整起见,锁的配置如下:
val global = GlobalConfigurationBuilder.defaultClusteredBuilder()
global.addModule(ClusteredLockManagerConfigurationBuilder::class.java)
.reliability(Reliability.AVAILABLE)
.numOwner(1)
查看集群锁使用的代码DIST_SYNC
,它将缓存负载分散到不同的节点上。
更新:
上面代码中的两个计数器只是千分尺计数器。通过它们和 prometheus,我可以看到锁的创建速度是如何开始减慢的。
正确地观察到每个会话 id 创建一个锁,这是我们想要的设计。我们的用例是我们希望确保会话至少在一个地方运行。无需深入细节,这可以通过确保我们至少有两个 pod 尝试获取相同的锁来实现。 Infinispan 库很棒,因为它直接告诉我们锁持有者何时死亡,而 pod 之间没有任何额外的闲聊,这意味着我们有一种“廉价”的方式来确保在删除一个 pod 时会话继续执行。
在深入挖掘代码后,我在核心库的CacheNotifierImpl
中发现了以下内容:
private CompletionStage<Void> doNotifyModified(K key, V value, Metadata metadata, V previousValue,
Metadata previousMetadata, boolean pre, InvocationContext ctx, FlagAffectedCommand command)
if (clusteringDependentLogic.running().commitType(command, ctx, extractSegment(command, key), false).isLocal()
&& (command == null || !command.hasAnyFlag(FlagBitSets.PUT_FOR_STATE_TRANSFER)))
EventImpl<K, V> e = EventImpl.createEvent(cache.wired(), CACHE_ENTRY_MODIFIED);
boolean isLocalNodePrimaryOwner = isLocalNodePrimaryOwner(key);
Object batchIdentifier = ctx.isInTxScope() ? null : Thread.currentThread();
try
AggregateCompletionStage<Void> aggregateCompletionStage = null;
for (CacheEntryListenerInvocation<K, V> listener : cacheEntryModifiedListeners)
// Need a wrapper per invocation since converter could modify the entry in it
configureEvent(listener, e, key, value, metadata, pre, ctx, command, previousValue, previousMetadata);
aggregateCompletionStage = composeStageIfNeeded(aggregateCompletionStage,
listener.invoke(new EventWrapper<>(key, e), isLocalNodePrimaryOwner));
锁库在入口修改事件上使用集群监听器,并且这个使用过滤器仅在修改锁的键时通知。在我看来,核心库仍然需要在每个注册的监听器上检查这个条件,随着会话数量的增加,这当然会变成一个非常大的列表。我怀疑这就是原因,如果是这样的话,如果核心库支持一种键过滤器,这样它就可以为这些侦听器使用哈希图,而不是遍历所有侦听器的整个列表,那将是非常棒的。
【问题讨论】:
【参考方案1】:我相信您正在为每个会话 ID 创建一个集群锁。这是你需要的吗?获得的LockCounter是什么?我们即将弃用“lock”方法,而使用带有超时的“tryLock”,因为如果从未获得集群锁,lock 方法将永远阻塞。您是否曾经在另一段代码中解锁过集群锁?如果您共享完整的代码复制器,将对我们非常有帮助。谢谢!
【讨论】:
您好 Karesti,感谢您的快速回答!我在上面的帖子中添加了答案,因为我无法将其放入评论中。另外,请不要弃用 lock 方法:)。能够拥有这些长时间运行的锁并能够无限期地等待它们是一个非常强大的特性(特别是因为它们无论如何都不会线程阻塞),看到这个特性丢失将是一种耻辱。 关于复制器,我会看看我能做什么。因为初始设置是在 Kubernetes 上完成的,所以我无法仅使用 github 项目来重现它,但我会看看是否可以在本地项目中简单地重现时间的线性增长 集群锁使用监听器能够在拓扑或锁被释放时同步锁,所以你正在运行我相信内存问题会导致你的应用程序随着集群锁大小的增长而变慢.集群锁并不打算以这种方式使用,而是用于 infinispan 节点之间的同步。因此,我们无法提供您要求的监听器功能。 不使用锁,为什么不使用缓存和集群监听器呢? 抱歉不清楚。我们的场景是两段或多段代码竞争同一个资源(一个会话)。一次只允许一个人拥有它,因此锁绝对是该场景的合适构造。核心问题是通知更新是每个节点上的 O(n) 操作(其中 n 是侦听器的数量),无论这些侦听器是通用的(想要所有事件)还是只侦听特定的键。这也是为什么我们不能滚动自己的缓存的原因,我们仍然需要集群监听器,它也会有这个性能限制。以上是关于Infinispan 集群锁性能不会随着节点的增加而提高吗?的主要内容,如果未能解决你的问题,请参考以下文章