当前位置: 首页 > news >正文

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

架构模式 1:Embedded + Remote Store(嵌入式缓存 + 远程存储)

这种模式下,Keycloak 节点有本地嵌入式缓存,同时配置了远程存储(Remote Store)连接到外部 Infinispan 集群。

/** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ @ClientListener public class RemoteCacheSessionListener<K, V extends SessionEntity> {

清理机制:

  1. 本地缓存自动过期
    • 当会话数据写入本地嵌入式缓存(DefaultSegmentedDataContainer)时,会同时设置lifespanmaxIdle参数
    • Infinispan 的内置过期机制会自动清除过期条目

  1. 远程缓存事件同步
    • 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 won't update it back cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES) .remove(key); }); } }
  1. 重要限制
    • Infinispan 不发送过期事件(Expiration Event)给 Hot Rod 客户端监听器!
    • 远程 Infinispan 中的条目过期时,不会主动通知 Keycloak 节点
    • 本地缓存的清理完全依赖于本地 Infinispan 的自动过期机制

架构模式 2:Remote 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 的过期事件不会通知本地缓存,在以下情况下可能存在短暂的数据不一致

  1. 远程条目已过期被清除
  2. 但本地缓存的过期时间还没到
  3. 此时本地可能返回一个"幽灵会话"

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;
http://www.cnnetsun.cn/news/1634217.html

相关文章:

  • (全网最全)分享8款AI工具,毕业论文AIGC率速降至5%!
  • Qwen3-14B开源模型实战:跨境电商多平台产品文案批量生成
  • 2026年最全互联网大厂最全 Java 面试八股文题库
  • 3大核心功能解放明日方舟玩家双手:MAA自动化助手全攻略
  • Phi-3 Forest Laboratory 技能拓展:创建自定义Skills智能体应对复杂任务
  • 全志T113 G2D硬件加速实战:在Cdroid框架下实现UI图层高效Blit与FillRect
  • 使用HunyuanVideo-Foley为开源项目添加音效:以STM32智能硬件项目为例
  • AUTOSAR RTA-OS计数器配置避坑指南:从MAXALLOWEDVALUE到Seconds Per Tick的五个关键参数详解
  • 别再傻傻分不清HIL和SIL了!用NI PXI和Simulink手把手教你搭建第一个测试环境
  • 掌握5个核心配置技巧:OpenCore-Configurator从入门到专家
  • 避坑指南:GitLab中文社区版15.5.3安装时你一定会遇到的5个配置问题(含rb文件详解)
  • ChanlunX缠论插件:技术原理与实战应用指南
  • 两步搞定!阿里云OpenClaw一键部署+免费Token领取指南
  • Dramatron:AI驱动的结构化内容生成技术革命
  • 3步构建音乐资源聚合引擎:多平台无损音乐获取解决方案
  • Qwen2.5-Coder-1.5B应用案例:自动生成Bash脚本处理日志文件
  • jsontop.cn:JSON 格式化 + 全场景开发工具集,免安装无广告,效率直接拉满
  • 3步实现HTML到Word的智能转换:html-to-docx技术深度解析
  • 当今互联网安全的基石 - TLS/SSL
  • 告别重复劳动:用快马定制高效openclaw命令,自动化你的文件抓取流程
  • Phi-4-mini-reasoning Chainlit前端深度定制:支持LaTeX渲染与数学公式交互
  • 三菱PLC GXWorks2实战:基于SFC的红绿灯控制系统设计与优化
  • 从MATLAB桌面到STM32板子:手把手教你搭建Simulink硬件在环(HIL)测试环境
  • Adams导入SOLIDWORKS模型“隐身”难题:从Parasolid格式到视图显示的完整排查指南
  • 2026国内企业AI公司排名(权威榜单验证
  • 从‘黑鬼’到‘紫苯’:聊聊中文仇恨言论检测里的那些坑与优化思路
  • 从零搭建CarSim与Matlab/Simulink联合仿真环境:一个分布式驱动控制的实践案例
  • 别再只会用AT指令了!用GD32F103驱动ESP8266实现MQTT连接阿里云(附完整源码)
  • EasyAnimateV5-7b-zh-InP在AI艺术创作中的算法优化实践
  • Python MCP服务端从零到上线(含插件下载→证书配置→热重载安装全流程),附赠可审计的Docker Compose生产级模板: