时间:2026-01-30 13:19
人气:
作者:admin
在 Keycloak 分布式部署(使用外部独立部署的 Infinispan)的架构下,sessions 和 clientSessions 的过期清理涉及两种不同的部署模式,机制略有不同:
这种模式下,Keycloak 节点有本地嵌入式缓存,同时配置了远程存储(Remote Store)连接到外部 Infinispan 集群。
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ClientListener
public class RemoteCacheSessionListener<K, V extends SessionEntity> {
清理机制:
DefaultSegmentedDataContainer)时,会同时设置 lifespan 和 maxIdle 参数
RemoteCacheSessionListener 通过 Hot Rod Client Listener 机制监听远程缓存事件@ClientCacheEntryRemoved 事件: @ClientCacheEntryRemoved
public void removed(ClientCacheEntryRemovedEvent event) {
K key = (K) event.getKey();
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
this.executor.submit(event, () -> {
// We received event from remoteCache, so we won't update it back
cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES)
.remove(key);
});
}
}
这种模式下,Keycloak 不维护本地会话缓存,所有会话数据都直接存储在外部 Infinispan 集群中。
@Override
public void removeAllExpired() {
//rely on Infinispan expiration
}
@Override
public void removeExpired(RealmModel realm) {
//rely on Infinispan expiration
}
清理机制:
DefaultSegmentedDataContainer,因为不使用嵌入式缓存无论哪种模式,会话的过期时间都通过 SessionTimeouts 计算:
public static long getUserSessionLifespanMs(RealmModel realm, ClientModel client, UserSessionEntity userSessionEntity) {
return getUserSessionLifespanMs(realm, false, userSessionEntity.isRememberMe(), userSessionEntity.getStarted());
}
public static long getUserSessionLifespanMs(RealmModel realm, boolean offline, boolean rememberMe, int started) {
long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(offline, rememberMe,
TimeUnit.SECONDS.toMillis(started), realm);
if (offline && lifespan == IMMORTAL_FLAG) {
return IMMORTAL_FLAG;
}
lifespan = lifespan - Time.currentTimeMillis();
if (lifespan <= 0) {
return ENTRY_EXPIRED_FLAG;
}
return lifespan;
}
DefaultSegmentedDataContainer 对象的清理方式| 场景 | 清理机制 |
|---|---|
| 本地条目自然过期 | Infinispan 嵌入式缓存的内置过期 Reaper 线程自动清理 |
| 远程条目被删除 | 通过 RemoteCacheSessionListener 的 @ClientCacheEntryRemoved 事件同步删除本地条目 |
| 远程条目自然过期 | 不会主动通知!本地条目依赖自身的过期时间自动失效 |
| Failover 事件 | 触发 onFailover 回调,清空整个本地缓存(ispnCache::clear)以保证一致性 |
由于远程 Infinispan 的过期事件不会通知本地缓存,在以下情况下可能存在短暂的数据不一致:
Keycloak 的解决方案:
Expiration.isExpired() public boolean isExpired() {
return maxIdle == SessionTimeouts.ENTRY_EXPIRED_FLAG || lifespan == SessionTimeouts.ENTRY_EXPIRED_FLAG;
}
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ClientListener
public class RemoteCacheSessionListener<K, V extends SessionEntity> {
@ClientCacheEntryRemoved
public void removed(ClientCacheEntryRemovedEvent event) {
K key = (K) event.getKey();
if (shouldUpdateLocalCache(event.getType(), key, event.isCommandRetried())) {
this.executor.submit(event, () -> {
// We received event from remoteCache, so we won't update it back
cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES)
.remove(key);
});
}
}
@Override
public void removeAllExpired() {
//rely on Infinispan expiration
}
@Override
public void removeExpired(RealmModel realm) {
//rely on Infinispan expiration
}
public static long getUserSessionLifespanMs(RealmModel realm, ClientModel client, UserSessionEntity userSessionEntity) {
return getUserSessionLifespanMs(realm, false, userSessionEntity.isRememberMe(), userSessionEntity.getStarted());
}
public static long getUserSessionLifespanMs(RealmModel realm, boolean offline, boolean rememberMe, int started) {
long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(offline, rememberMe,
TimeUnit.SECONDS.toMillis(started), realm);
if (offline && lifespan == IMMORTAL_FLAG) {
return IMMORTAL_FLAG;
}
lifespan = lifespan - Time.currentTimeMillis();
if (lifespan <= 0) {
return ENTRY_EXPIRED_FLAG;
}
return lifespan;
}
public boolean isExpired() {
return maxIdle == SessionTimeouts.ENTRY_EXPIRED_FLAG || lifespan == SessionTimeouts.ENTRY_EXPIRED_FLAG;
}
作者:仓储大叔,张占岭,
荣誉:微软MVP
QQ:853066980
支付宝扫一扫,为大叔打赏!
