keycloak~分布式部署中会话过期清理机制

张开发
2026/4/3 12:16:15 15 分钟阅读
keycloak~分布式部署中会话过期清理机制
架构模式 1Embedded Remote Store嵌入式缓存 远程存储这种模式下Keycloak 节点有本地嵌入式缓存同时配置了远程存储Remote Store连接到外部 Infinispan 集群。/** * author a hrefmailto:mposoldaredhat.comMarek Posolda/a */ ClientListener public class RemoteCacheSessionListenerK, V extends SessionEntity {清理机制本地缓存自动过期当会话数据写入本地嵌入式缓存DefaultSegmentedDataContainer时会同时设置lifespan和maxIdle参数Infinispan 的内置过期机制会自动清除过期条目远程缓存事件同步RemoteCacheSessionListener通过 Hot Rod Client Listener 机制监听远程缓存事件当远程 Infinispan 中条目被删除时会触发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 wont update it back cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES) .remove(key); }); } }重要限制Infinispan 不发送过期事件Expiration Event给 Hot Rod 客户端监听器远程 Infinispan 中的条目过期时不会主动通知 Keycloak 节点本地缓存的清理完全依赖于本地 Infinispan 的自动过期机制架构模式 2Remote Only纯远程模式这种模式下Keycloak不维护本地会话缓存所有会话数据都直接存储在外部 Infinispan 集群中。Override public void removeAllExpired() { //rely on Infinispan expiration } Override public void removeExpired(RealmModel realm) { //rely on Infinispan expiration }清理机制完全依赖远程 Infinispan 的过期机制Keycloak 本地 JVM 中没有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; }总结本地 JVM 中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 hrefmailto:mposoldaredhat.comMarek Posolda/a */ ClientListener public class RemoteCacheSessionListenerK, 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 wont 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;

更多文章