# 为什么要做 Redis 会话外置 + 粘性路由 ## 背景 当前 LSP 网关的核心能力是: - 把编辑器请求转成 LSP(JSON-RPC over stdio) - 为每个 `language + sessionId` 维护一个长生命周期会话(对应语言服务器进程与文档状态) 单实例时,这套方案天然稳定。 一旦进入微服务部署(多副本 + 负载均衡),如果没有额外机制,会出现严重一致性问题。 ## 不做会发生什么 ### 1. 会话状态丢失 同一个用户会话的请求可能先到实例 A、再到实例 B。 但 B 没有 A 的内存态(didOpen/didChange 版本、LSP 上下文),会导致: - 补全质量波动 - 诊断/跳转不一致 - 重复初始化语言服务器 ### 2. 成本放大 会话不稳定会触发频繁建进程,带来: - 更高 CPU/内存 - 更高请求尾延迟(P95/P99) - 更多瞬时失败 ### 3. 故障不可控 无统一会话归属时,问题难定位(到底是哪台实例持有上下文、何时丢失)。 ## 为什么要 Redis 会话外置 Redis 不是替代 LSP 进程,而是做“会话目录(Session Directory)”: - 记录某个 `language + sessionId` 当前归属哪个实例 - 记录实例心跳与可回源地址 - 给会话绑定 TTL,支持自动过期与回收 它带来的价值: - 多副本下会话归属可见、可控 - 实例重启/扩容/缩容时路由行为可预测 - 可以做统一治理(观测、告警、自动修复) ## 为什么要粘性路由 LSP 天然是“有状态协议”:同一会话必须持续命中同一实例才能复用上下文。 粘性路由的目标就是保证这一点。 网关当前策略: 1. 请求到达后先在 Redis `claim` 会话归属 2. 如果归属自己:本地处理 3. 如果归属其他实例:返回 `409 + routeTo`(HTTP 头 `X-LSP-Route-To`) 4. 上游(前端或 Java 网关)据此重试到目标实例 这比纯随机 LB 更符合 LSP 工作方式。 ## 设计取舍 ### 收益 - 会话一致性显著提升 - 进程复用率提高,资源更稳 - 故障域清晰,便于排障 ### 代价 - 引入 Redis 依赖(网络与可用性要保障) - 路由逻辑更复杂(冲突重试、TTL 管理) - 需要对上游调用方约束:必须稳定传 `sessionId` ## 什么时候可以不做 可以暂不启用 Redis/粘性路由的场景: - 仅单实例部署 - 开发/演示环境 - 对补全一致性不敏感 但只要进入生产多副本,建议启用。 ## 适配 Java 微服务体系的意义 将 LSP 网关作为独立微服务后: - Java 业务服务无需关心各语言 LSP 细节 - 可以统一接入鉴权、限流、链路追踪 - LSP 能力扩展(Go/JS/TS/Java/Python)不会反复侵入业务服务 这就是把它做成独立 LSP 服务的核心原因:**把状态复杂性收敛在一个可治理的边界里**。 ## 当前实现对应点 - Redis 默认配置:`10.0.0.10:6379`, `DB=1`, 无密码 - 会话注册与认领:`internal/cluster/redis_registry.go` - 会话管理与归属检查:`internal/completion/manager.go` - 路由冲突返回:`internal/api/handler.go`, `internal/api/ws_handler.go` - 启动配置入口:`cmd/server/main.go`