tumrs简要流程分析(二)—Session 会话的结构、存储与生命周期

模块二:Session 会话的结构、存储与生命周期

本模块深入 session 的存储生命周期 。session 的对象模型(UserSession / UserSessionWrapper / NetConnection 三层抽象)已在模块一第 5 节讲过,此处只补齐「结构」中模块一未覆盖的容器与索引部分,重点放在「存储」与「生命周期」。

涉及代码横跨 turms-gateway(本地会话管理)与 turms-server-common(集群状态镜像)。


1. 总览:两层存储 + 三段生命周期

turms 的 session 采用两层存储

  • 内存层 (本节点):持有带活跃连接的 UserSession 对象,仅供本 gateway 节点访问。
  • Redis 层 (集群全局):镜像每个 session 的状态与位置(用户在线否、在哪个节点、哪个设备),供集群任意节点查询。

生命周期分三段:创建(登录)-> 心跳保活 -> 销毁

flowchart LR subgraph 本节点内存[本 gateway 节点内存 - SessionService] A[&#34;userIdToSessionsManager<br/>userId -> UserSessionsManager&#34;] B[&#34;ipToSessions<br/>IP -> UserSession 队列&#34;] end subgraph Redis[集群全局 - UserStatusService] C[&#34;Hash per user<br/>$ / 设备类型 / nodeId&#34;] D[&#34;Hash userId:d<br/>设备详情&#34;] end A -.状态镜像.-> C A -.设备详情.-> D

一句话:活连接只在内存,状态/位置镜像到 Redis;任何节点问「某用户在线否、在哪」都查 Redis,真正发数据才走对应节点的内存 session。


2. Session 结构(承接模块一)

模块一讲过单设备会话 UserSession 与绑定层 UserSessionWrapper。这里补两个容器:

2.1 UserSessionsManager:每用户容器

UserSessionsManagerUserSessionsManager.java:43)是一个用户所有在线设备的容器:

字段 含义
userId 用户 ID
userStatus 用户状态(AVAILABLE/INVISIBLE/...)
deviceTypeToSession ConcurrentEnumMap<DeviceType, UserSession>每个设备类型一个 session

关键方法:addSessionIfAbsentputIfAbsent 语义,返回新 session 或 null)、closeSession(deviceType, closeReason)(移除并关闭)、pushSessionNotification(给某设备发 session 信息通知)、countSessionsgetLoggedInDeviceTypes

设计含义:一个用户最多每设备类型一个 session。同设备类型重复登录会走并发登录冲突处理(第 7 节)。

2.2 SessionService:两个内存索引

SessionService@ServiceSessionService.java:96)持有两张本地表:

索引 类型 用途
userIdToSessionsManager ConcurrentHashMap<Long, UserSessionsManager> 主索引:userId -> 其会话容器
ipToSessions ConcurrentHashMap<ByteArrayWrapper, ConcurrentLinkedQueue<UserSession>> 副索引:客户端 IP -> session 队列,用于按 IP 封禁/踢人

注释(SessionService.java:114-118)说明 ipToSessionsByteArrayWrapper 而非 long 是为了支持 16 字节的 IPv6 地址。两张表都纯内存、仅本节点可见UserSession 对象(含活跃连接)从不序列化。

SessionService 还实现了 RpcSessionService,暴露 closeLocalSession / getSessions 给其它节点经 RPC 调用(如并发登录时跨节点踢人)。


3. 存储:内存 + Redis 双层

3.1 Redis 数据模型

UserStatusService@ConditionalOnBean("sessionRedisClientManager")UserStatusService.java:79)是集群状态的唯一权威。它用每个用户一个 Redis Hash 存储(UserStatusService.java:98-123 Javadoc):

Hash 字段 含义
$ 用户状态数字 固定字段,存 UserStatus(AVAILABLE/INVISIBLE/...)
<设备类型数字> (0,1,2...) node ID 该设备的 session 归属哪个 gateway 节点
<nodeId> 心跳时间戳(秒) 该节点最后刷新时间,用于 TTL 与死节点检测

另外,设备详情(deviceToken 等)存在独立的 Hash <userId>:d,有自己的 TTL(DEVICE_DETAILS_TTL),与主 Hash 解耦。

TTLdeviceStatusTtlSeconds = closeIdleSessionAfterSeconds + 1UserStatusService.java:156)。每次注册/刷新都对主 Hash 执行 EXPIRE,所以用户若停止心跳,整个 Hash 会在约 181s 后自动过期。

缓存userIdToStatusCache(Caffeine,受 userStatus.cacheUserSessionsStatus 开关控制,UserStatusService.java:129)缓存在线与离线状态,减少 Redis 读。注意 fetchUserSessionsStatus 绕过缓存直读 Redis--登录注册时必须拿最新状态,防止缓存陈旧导致并发登录误判。

3.2 五个 Lua 脚本

所有 Redis 写操作都用 Lua 脚本保证原子性 (check-and-set 不能被中间打断),脚本位于 turms-server-common/src/main/resources/redis/session/

脚本 作用 返回
try_add_online_user_with_ttl.lua 原子注册「用户+设备+本节点」 '0' 冲突/未加 / '1' 由离线变在线 / '2' 加入已在线用户
update_users_ttl.lua 批量刷新本节点用户的 TTL 与心跳时间戳 不存在的 userId 列表(需关本地 session)
remove_user_statuses.lua 移除本节点拥有的设备项,清理空 Hash 是否删过设备
update_online_user_status_if_present.lua 存在则更新 $ 状态字段 是否更新
get_users_device_details.lua 批量取设备详情 userId -> details

3.3 核心脚本 try_add_online_user_with_ttl.lua

这是登录注册的原子操作,逻辑(try_add_online_user_with_ttl.lua):

  1. HMGET userId <device> $ 取出该设备现有的归属节点 existing_node_id 与现有状态。
  2. 冲突判定 (返回 '0'):
    • existing_node_id == node_id(本节点已拥有)-> '0';
    • 若已有归属节点且其心跳仍在 DEVICE_STATUS_TTL 内、且不满足 CAS 条件 -> '0'(不能踢)。
  3. CAS 条件 :脚本接收 expected_existing_node_idexpected_device_timestamp 两个期望值。只有当 Redis 里的实际值匹配期望值时才允许踢掉旧 session。这是乐观锁--「我先前读到的是这个节点这个时间戳,现在仍是它,我才能安全替换」,避免两个客户端同时登录互相踩踏。
  4. 写入HMSET device->nodeId, $->status, nodeId->nowEXPIRE userId ttl
  5. 清理旧节点项 :若被替换的旧 nodeId 已无其它设备引用,HDEL 它的心跳时间戳字段,避免残留。
  6. 设备详情 :若有,HMSET userId:d ...EXPIRE
  7. 返回码HLEN == 3(只有 $ + 设备 + nodeId,即首个设备)-> '1';否则(已有其它设备)-> '2'。

Java 侧(UserStatusService.addOnlineDeviceIfAbsent:633)把 '1'/'2' 都映射为 true(注册成功),'0' 映射为 false(冲突拒绝)。

3.4 死节点检测

节点宕机未必能清理 Redis。turms 用两道防线判定「某设备所属节点是否还活着」(UserStatusService.handleUserSessionsStatusEntries:348):

  1. 心跳时间戳 TTL :若 now - nodeId的心跳时间戳 > deviceStatusTtlMillis,标记该设备 inactive。
  2. discovery 成员检查 :对非本节点的 active 节点,fetchNodeStatus(nodeId) -> discoveryService.checkIfMemberExists(nodeId)(结果缓存 15s,UserStatusService.java:81:483)。若 discovery 说该节点已不存在,标记 inactive。

若某用户所有设备都 inactive -> 该用户判为 OFFLINE。这保证了节点崩溃后,其残留的 Redis 记录不会永远挡住用户重新登录


4. 生命周期之一:创建(登录)

4.1 完整链路

CREATE_SESSION_REQUESTgateway 本地处理(不下发 service),链路如下:

sequenceDiagram participant C as 客户端 participant CD as ClientRequestDispatcher participant SCC as SessionClientController participant SS as SessionService participant Auth as SessionIdentityAccessManager participant USS as UserStatusService(Redis) participant USM as UserSessionsManager C->>CD: CREATE_SESSION_REQUEST CD->>SCC: handleCreateSessionRequest SCC->>SS: handleLoginRequest(version,ip,userId,pwd,device,...) SS->>SS: 校验 version==1 / 设备类型未禁用 SS->>Auth: verifyAndGrant(NOOP/HTTP/JWT/PASSWORD/LDAP + 插件) Auth-->>SS: UserPermissionInfo(code, permissions) SS->>SS: tryRegisterOnlineUser SS->>USS: fetchUserSessionsStatus(绕过缓存) alt 用户离线 SS->>USS: addOnlineDeviceIfAbsent(Lua 原子注册) else 在线 & 同设备本地连接已断(UDP恢复) SS-->>SCC: 返回本地 session(复用) else 在线 & 冲突 SS->>SS: closeSessionsWithConflictedDeviceTypes(RPC 踢其它节点) SS->>USS: addOnlineDeviceIfAbsent(带 CAS expected) end USS-->>SS: wasSuccessful SS->>USM: addSessionIfAbsent(建 UserSession) SS->>SS: ipToSessions 索引 + 位置 SS-->>SCC: UserSession SCC->>SCC: sessionWrapper.setUserSession(接通 notificationConsumer) SCC->>SS: onSessionEstablished + goOnline 插件 SCC-->>C: OK

4.2 逐步对照源码

  1. 入口SessionClientController.handleCreateSessionRequest:92):若 wrapper 已有 session 返回 CREATE_EXISTING_SESSION;否则解析请求,调 sessionService.handleLoginRequest(...)。成功后取消建立超时任务,若连接仍连通则 sessionWrapper.setUserSession(session)(这一步把 session 绑到连接、触发回调设置 notificationConsumer,接通推送通道),再 onSessionEstablished + goOnline 插件。若建立超时已触发或连接已断 -> 以 LOGIN_TIMEOUT 关闭。

  2. 登录校验SessionService.handleLoginRequest:229):

    • version != 1 -> UNSUPPORTED_CLIENT_VERSION
    • 设备类型被禁用(userSimultaneousLoginService.isForbiddenDeviceType)-> LOGIN_FROM_FORBIDDEN_DEVICE_TYPE
    • sessionAuthenticationManager.verifyAndGrant(...) 认证;返回 OK 才进 tryRegisterOnlineUser
  3. 认证SessionIdentityAccessManager.verifyAndGrant:107):按 gateway.session.identityAccessManagement.type 选策略--NOOP / HTTP / JWT / PASSWORD / LDAP(构造时 switch,:85)。若 IAM 关闭 -> 直接授予全部权限;admin 请求者 ID 一律拒绝。还支持插件 UserAuthenticator(顺序执行、首个命中),先于默认策略。

  4. 注册核心SessionService.tryRegisterOnlineUser:612)--这是并发登录与 UDP 恢复的枢纽:

    • fetchUserSessionsStatus(userId)必须取最新(绕过缓存),处理 Redis 崩溃重启、本节点与 Redis 失联等导致本地与 Redis 不一致的边缘情况。
    • Step 1 :若本地有该用户的 session,但 Redis 显示某设备已归属其它节点 且 active -> 关掉本地那个 session(DISCONNECTED_BY_OTHER_DEVICE)。清理本地陈旧数据。
    • 若用户离线 -> addOnlineDeviceIfAbsent 直接注册。
    • 若用户在线
      • 若同设备类型已 active:
        • 若本地存在该 session 且其连接已断(isClosedSessionOnLocal)-> 这是 UDP->TCP 恢复:返回本地 session,由下游用新连接替换旧连接(模块九详述);可顺带更新状态与位置。
        • 否则若策略为「踢登录方」(shouldDisconnectLoggingInDeviceIfConflicts)-> 报 SESSION_SIMULTANEOUS_CONFLICTS_DECLINE
      • 否则(该设备无冲突):先 closeSessionsWithConflictedDeviceTypes(按冲突表 RPC SetUserOfflineRequest 踢其它节点上的冲突设备),再 addOnlineDeviceIfAbsent 带 CAS 期望值注册。
  5. 建本地 sessionSessionService.addOnlineDeviceIfAbsent:867):先 userStatusService.addOnlineDeviceIfAbsent(Lua 原子注册)成功后,userIdToSessionsManager.computeIfAbsent 建/取容器,manager.addSessionIfAbsentUserSession,加入 ipToSessions 索引,按需写位置。

4.3 跨节点踢人

closeSessionsWithConflictedDeviceTypesSessionService.java:794)按冲突设备类型分组到目标节点,对每个目标节点发 SetUserOfflineRequest RPC(SessionCloseStatus.DISCONNECTED_BY_CLIENT)。若 RPC 报 ConnectionNotFound

  • 目标节点是已知成员 -> 返回错误(可能网络问题,让客户端重试直到 TTL 过期);
  • 目标节点已不在 discovery -> 视为已离线,返回 true(让客户端能登录,改善体验)。

5. 生命周期之二:心跳保活

5.1 两条心跳路径

路径 触发 处理
TCP/WebSocket 空包(不可读 buffer) ClientRequestDispatcher.handleHeartbeatRequest -> sessionService.handleHeartbeatUpdateRequest(session) -> 仅更新本地 lastHeartbeatRequestTimestamp
UDP 14 字节 HEARTBEAT 信号 UdpRequestDispatcher -> sessionService.authAndUpdateHeartbeatTimestamp(userId, deviceType, sessionId)(校验 session 存在 + sessionId 匹配 + 未在恢复中)-> 更新时间戳 + 记录 UDP 地址

关键设计 :心跳只更新本地时间戳,不写 Redis。若每个心跳都写 Redis,海量在线用户下是性能灾难。

5.2 HeartbeatManager:后台巡检

HeartbeatManagerHeartbeatManager.java:57)用单线程 turms-client-heartbeat-refresher 每 1s(UPDATE_HEARTBEAT_INTERVAL_MILLIS=1000)跑一轮 updateOnlineUsersTtl()

对每个在线 session 执行 closeOrUpdateSession(session, now):200),三步检查:

  1. 切 UDP :UDP 已启用 + supportsSwitchingToUdp + 仍连接 + 非心跳请求空闲超 switchProtocolAfterNanos(默认 540s)-> connection.switchToUdp()(模块八)。
  2. 关空闲max(心跳时间, 请求时间) 空闲超 closeIdleSessionAfterNanos(默认 180s)-> closeLocalSession(HEARTBEAT_TIMEOUT)
  3. 刷 Redis :距上次刷新超 minHeartbeatIntervalNanos(默认 18s)客户端在此期间发过请求 -> 标记需刷新。

被标记的 userId 经 userStatusService.updateOnlineUsersTtl(批量 Lua update_users_ttl.lua)一次刷新:对每个 userId,若 HEXISTS userId nodeId(本节点仍拥有其设备)则 EXPIRE + HSET nodeId now;否则计入「不存在」列表。返回的不存在 userId 会被关掉本地 sessionDISCONNECTED_BY_OTHER_DEVICE)--因为 Redis 侧已过期,说明该用户在集群视角已离线。

flowchart TD H1[&#34;TCP/WS: 空包 = 心跳<br/>-> handleHeartbeatUpdateRequest&#34;] H2[&#34;UDP: 14字节信号<br/>-> authAndUpdateHeartbeatTimestamp&#34;] H1 --> U[&#34;仅更新本地时间戳,不写 Redis&#34;] H2 --> U U --> HM[&#34;HeartbeatManager 单线程 1秒/轮&#34;] HM --> C1{每 session 三检查} C1 -->|非心跳空闲>540s 且支持UDP| S1[&#34;switchToUdp (模块八)&#34;] C1 -->|总空闲>180s| S2[&#34;closeLocalSession(HEARTBEAT_TIMEOUT)&#34;] C1 -->|距上次刷新>18s 且有新请求| S3[&#34;标记刷新&#34;] S3 --> R[&#34;updateOnlineUsersTtl 批量 Lua<br/>EXPIRE + HSET nodeId=now&#34;] R --> N{Redis 返回不存在的 userId} N -->|有| NC[&#34;closeLocalSession(DISCONNECTED_BY_OTHER_DEVICE)&#34;]

5.3 为何这么做

类注释(HeartbeatManager.java:46-56)列出三种候选策略:

  1. 每个心跳都刷 Redis -> 性能灾难(否决);
  2. MPSC 队列攒批定期发 -> 浪费内存、队列操作损耗性能(否决);
  3. (采用)不存请求,直接遍历内存在线表,每秒快照出需要刷新/下线的用户。

即用「单线程定期遍历内存 + 批量 Lua」替代「每心跳一次 Redis 写」,把 Redis 写频率从「每用户每心跳」降到「每用户每 18s 一次」。

5.4 关键配置(均热更新)

SessionPropertiesSessionProperties.java,全部 @GlobalProperty @MutableProperty):

配置 默认 含义
closeIdleSessionAfterSeconds 180 无任何请求多久后关 session(=Redis TTL 基准)
switchProtocolAfterSeconds 180*3=540 仅收心跳多久后切 UDP
minHeartbeatIntervalSeconds 180/10=18 刷 Redis 的最小间隔
clientHeartbeatIntervalSeconds 180/3=60 客户端心跳间隔(仅用于估算每轮刷新量,不改变客户端行为)
notifyClientsOfSessionInfoAfterConnected true 连接后是否推送 session 信息

SessionService 构造时注册了全局属性变更监听(:176),这些值改了即时生效,HeartbeatManager 的对应字段也被热更新。


6. 生命周期之三:销毁

6.1 统一入口与关键顺序

所有关闭路径最终汇聚到 SessionService.closeLocalSessions(userId, deviceTypes, closeReason, manager):491)。顺序至关重要 (注释 :502-504):

先删 Redis 状态,再关连接。若反过来,客户端在连接关闭后、Redis 状态未删前重新登录,会读到陈旧的 Redis 数据而误判冲突。

flowchart TD T[&#34;触发: DELETE_SESSION / 连接断 / 心跳超时 / Admin拉黑 / 关停&#34;] T --> A[&#34;closeLocalSessions(userId, deviceTypes, closeReason)&#34;] A --> B[&#34;① removeStatusByUserIdAndDeviceTypes (Lua)<br/>先删 Redis 设备项 + 清理空 Hash&#34;] B --> C[&#34;② manager.closeSession 关连接 + 移出 deviceTypeToSession&#34;] C --> D[&#34;③ 移出 ipToSessions 索引&#34;] D --> E[&#34;④ 移除位置(如启用)&#34;] E --> F[&#34;⑤ notifyOnSessionClosedListeners&#34;] F --> G[&#34;⑥ removeSessionsManagerIfEmpty + goOffline 插件&#34;]
  1. userStatusService.removeStatusByUserIdAndDeviceTypes(Lua remove_user_statuses.lua):HMGET 各设备字段,只 HDEL 值 == 本 nodeId 的(只删自己的设备);若剩余字段数 <= 2(只剩 $ + nodeId 心跳)则 DEL 整个 Hash,否则清理无设备引用的 nodeId 心跳字段。
  2. 逐设备 manager.closeSession -> 从 deviceTypeToSession 移除并 connection.close(closeReason)
  3. ipToSessions 移除(空则删 key)。
  4. 移除位置信息(如启用)。
  5. 通知 onSessionClosedListeners
  6. removeSessionsManagerIfEmpty:容器空则从 userIdToSessionsManager 移除;触发 goOffline 插件扩展点。

6.2 触发场景

场景 关闭状态码 入口
客户端主动 DELETE_SESSION_REQUEST DISCONNECTED_BY_CLIENT SessionClientController.handleDeleteSessionRequest
连接断开 UNKNOWN_ERROR(除非正在切 UDP) UserSessionAssembler.tryRemoveSessionInfoOnConnectionClosed
心跳超时 HEARTBEAT_TIMEOUT HeartbeatManager
Redis 侧已过期 DISCONNECTED_BY_OTHER_DEVICE HeartbeatManager(更新 TTL 返回不存在)
Admin 封禁/按 IP 踢 视调用方 closeLocalSessions(ip...) / closeLocalSessions(userIds...)
节点关停 SERVER_CLOSED destroy() -> closeAllLocalSessions

SessionService 构造时注册 CLOSE_SESSIONS 关闭钩子(:195),destroy() 先停 HeartbeatManagercloseAllLocalSessions(SERVER_CLOSED)

6.3 关闭状态码

SessionCloseStatusSessionCloseStatus.java:25)按区间分类:

区间 含义 示例
1xx 客户端行为异常/超时 100 ILLEGAL_REQUEST、110 HEARTBEAT_TIMEOUT、111 LOGIN_TIMEOUT、112 SWITCH
2xx 服务端行为 200 SERVER_ERROR、201 SERVER_CLOSED、202 SERVER_UNAVAILABLE
3xx 网络错误 300 CONNECTION_CLOSED
4xx 未知错误 400 UNKNOWN_ERROR
5xx 用户主动 500 DISCONNECTED_BY_CLIENT、501 DISCONNECTED_BY_OTHER_DEVICE
6xx 管理员主动 600 DISCONNECTED_BY_ADMIN
7xx 用户状态变更 700 USER_IS_DELETED_OR_INACTIVATED、701 USER_IS_BLOCKED

CloseReason(record,CloseReason.java:29)= (closeStatus, businessStatusCode, reason),按 closeStatus 池化;CloseReason.get(throwable) 能从异常映射出对应的 closeStatus(服务端错误->SERVER_ERROR,非法请求->ILLEGAL_REQUEST,等)。


7. 并发登录策略

UserSimultaneousLoginServiceUserSimultaneousLoginService.java:43)把「哪些设备类型互斥」做成可配置策略。核心是 deviceTypeToExclusiveDeviceTypes 映射(每个设备类型冲突的设备类型集合,每个设备类型都与自己冲突 ),由 SimultaneousLoginStrategy 推导:

策略 含义
ALLOW_ONE_DEVICE_OF_EACH_DEVICE_TYPE_ONLINE 每种设备类型各允许一个(仅与自己冲突)
ALLOW_ONE_DEVICE_FOR_ALL_DEVICE_TYPES_ONLINE 全设备类型只允许一个(全部互斥)
ALLOW_ONE_DEVICE_OF_DESKTOP_AND_ONE_DEVICE_OF_MOBILE_ONLINE 桌面一个 + 移动一个(ANDROID/IOS 互斥)
...DESKTOP_AND_BROWSER_AND_MOBILE... 桌面、浏览器、移动各一个
...DESKTOP_OR_BROWSER_AND_MOBILE... (桌面或浏览器)一个 + 移动一个
...DESKTOP_OR_MOBILE_ONLINE (桌面或移动)只一个

两个配套机制:

  • forbiddenDeviceTypes :某些策略下 BROWSER 被禁;UNKNOWN/OTHERS 默认禁用(除非 allowDeviceTypeUnknownLogin/allowDeviceTypeOthersLogin)。
  • LoginConflictStrategy :冲突时踢谁。shouldDisconnectLoggingInDeviceIfConflicts() 为 true 时踢登录方 (拒绝登录),否则踢已在线方 (默认,配合 closeSessionsWithConflictedDeviceTypes 跨节点踢旧 session)。

登录时 getConflictedDeviceTypes(deviceType) 返回需要被踢的设备类型集合,驱动第 4.3 节的跨节点 RPC。


8. 关键设计要点(Takeaways)

  1. 两层存储分工:活连接只在内存(本节点),状态/位置镜像 Redis(集群全局)。任何节点查「在线否、在哪」走 Redis,真正下发才路由到归属节点的内存 session。
  2. 心跳不写 Redis :心跳只更新本地时间戳;HeartbeatManager 单线程每秒巡检,批量 Lua 刷新 Redis TTL(每用户最快 18s 一次)。把 Redis 写从「每心跳」降到「每 18s」。
  3. 销毁先 Redis 后连接:避免「连接已关、Redis 未删」窗口内重登录读到陈旧状态而误判冲突。
  4. 原子注册 + CAStry_add_online_user_with_ttl.lua 用 expected nodeId/timestamp 乐观锁,保证并发登录不互相踩踏。
  5. 死节点双保险:心跳时间戳 TTL + discovery 成员检查,节点崩溃后其 Redis 残留不会永久挡住用户重登。
  6. UDP 恢复复用 sessionisClosedSessionOnLocal 分支让 UDP->TCP 重连复用原 session,只换连接不重建会话(模块九详述)。

9. 关键文件索引

关注点 文件
本地会话管理(主) turms-gateway/src/main/java/im/turms/gateway/domain/session/service/SessionService.java
每用户容器 turms-gateway/src/main/java/im/turms/gateway/domain/session/manager/UserSessionsManager.java
心跳巡检 turms-gateway/src/main/java/im/turms/gateway/domain/session/manager/HeartbeatManager.java
登录入口(CREATE/DELETE_SESSION) turms-gateway/src/main/java/im/turms/gateway/domain/session/access/client/controller/SessionClientController.java
认证 turms-gateway/src/main/java/im/turms/gateway/domain/session/service/SessionIdentityAccessManager.java
并发登录策略 turms-gateway/src/main/java/im/turms/gateway/domain/session/service/UserSimultaneousLoginService.java
Redis 状态镜像 turms-server-common/src/main/java/im/turms/server/common/domain/session/service/UserStatusService.java
状态 BO turms-server-common/src/main/java/im/turms/server/common/domain/session/bo/UserSessionsStatus.javaUserDeviceSessionInfo.java
关闭状态 turms-server-common/src/main/java/im/turms/server/common/domain/session/bo/SessionCloseStatus.javaCloseReason.java
会话配置 turms-server-common/src/main/java/im/turms/server/common/infra/property/env/gateway/session/SessionProperties.java
Redis 脚本 turms-server-common/src/main/resources/redis/session/*.lua

下一模块03-mongodb-schema.md MongoDB 表结构(实体、集合、分片键、索引、分层存储)。

相关推荐
9527出列1 小时前
tumrs简要流程分析(四)—Push 流程
开源
智_永无止境1 小时前
开源免费+AI运维,Netcatty全功能解析与上手教程
开源·netcatty
TunerT_TQ3 小时前
Valhalla 静态工程审阅 #024|蚂蚁集团Ant Design 源码证据驱动评测【大厂开源基础设施特辑】
开源·github·蚂蚁集团·#推荐系统·#typescript·#antdesign
魔镜前的帅比3 小时前
(开源项目)x-claw (设计)
python·ai·rust·开源
奈斯先生Vector3 小时前
2026 大模型 API 路由与基础设施效能深度评测:从开源中继方案到企业级智能调度选型指南
开源
9527出列4 小时前
tumrs简要流程分析(一)—Gateway 启动流程
开源
ClouGence6 小时前
CloudDM:开源免费!一站式数据库访问、SQL审核、权限与脱敏管控平台
数据库·sql·开源
tedcloud1237 小时前
Kimi-K3 部署指南:大模型应用开发环境搭建实践
linux·运维·服务器·开源·音视频