- Add Nacos registry for service registration and deregistration. - Implement Redis registry for session management with heartbeat and session claiming. - Improve completion service with session handling and request validation. - Enhance WebSocket handling for completion requests with JSON-RPC support. - Add tests for new registry implementations and completion manager functionalities. - Refactor existing code for better readability and maintainability.
187 lines
5.3 KiB
Markdown
187 lines
5.3 KiB
Markdown
# Go LSP Gateway 接入 Java 微服务(Nacos)指南
|
||
|
||
## 1. 结论先说
|
||
|
||
可以。
|
||
你完全可以把当前 Go LSP Gateway 注册到 Nacos,然后由 Java 微服务通过服务名发现并转发请求。
|
||
|
||
但要注意:
|
||
- Nacos 解决的是「服务发现」问题。
|
||
- LSP 场景还需要「会话粘性」问题(同 `sessionId` 要持续命中同一实例)。
|
||
- 粘性在你现在的实现里由 Redis 会话目录 + `routeTo` 重试机制承担,Nacos 本身不替代这部分。
|
||
|
||
## 2. 当前 Go 服务能力(已具备)
|
||
|
||
对外接口:
|
||
- HTTP: `POST /api/v1/completions/{language}`
|
||
- WS: `GET /ws/completions`、`GET /ws/completions/{language}`
|
||
- 健康检查:`/health`、`/health/live`、`/health/ready`
|
||
|
||
多副本会话能力:
|
||
- Redis 会话外置(默认 `10.0.0.10:6379`, DB `1`)
|
||
- 会话归属冲突返回 `409`,并带 `routeTo` + `X-LSP-Route-To`
|
||
|
||
## 3. 推荐架构
|
||
|
||
1. 前端 -> Java Gateway/BFF -> Go LSP Gateway
|
||
2. Go LSP Gateway 多副本部署(Nacos 注册)
|
||
3. Redis 作为会话目录(`language + sessionId -> owner instance`)
|
||
|
||
## 4. Nacos 接入方式
|
||
|
||
你有两种落地方式:
|
||
|
||
1. 由部署系统注册(推荐)
|
||
指 K8s/运维平台在发布时自动调用 Nacos OpenAPI 注册实例,Go 程序本身不依赖 Nacos SDK。
|
||
2. Go 进程内注册
|
||
在 Go 服务启动时使用 `nacos-sdk-go` 注册/心跳/下线。
|
||
|
||
你当前明确要求走 SDK,本项目已支持方式 2。
|
||
|
||
## 5. Java 侧必须做的事
|
||
|
||
## 5.1 统一转发入口
|
||
|
||
Java 提供统一接口给前端,例如:
|
||
- `POST /editor/completions/{language}`
|
||
|
||
内部转发到:
|
||
- `http://{lsp-service}/api/v1/completions/{language}`
|
||
|
||
## 5.2 强制透传稳定 sessionId
|
||
|
||
请求体必须带稳定 `sessionId`,建议格式:
|
||
- `tenant:user:project:tab`
|
||
|
||
如果 `sessionId` 不稳定,会导致会话频繁重建、补全抖动。
|
||
|
||
## 5.3 处理 409 routeTo(关键)
|
||
|
||
当 Go 返回 `409` 时:
|
||
- 读取响应体 `routeTo`(或 header `X-LSP-Route-To`)
|
||
- Java 侧自动重试一次到 `routeTo`
|
||
|
||
这样可以跨实例正确命中会话拥有者。
|
||
|
||
## 5.4 透传请求头
|
||
|
||
- `X-Request-Id`(链路追踪)
|
||
- `X-API-Key`(若 Go 配置了 `LSP_API_TOKEN`)
|
||
|
||
## 6. Spring Cloud Alibaba(Nacos)示例
|
||
|
||
## 6.1 application.yml(服务发现)
|
||
|
||
```yaml
|
||
spring:
|
||
application:
|
||
name: editor-bff
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
server-addr: 10.0.0.20:8848
|
||
```
|
||
|
||
Go 服务在 Nacos 中注册名假设为:`lsp-gateway`
|
||
|
||
## 6.2 Go 侧与 Spring 配置映射(SDK 注册)
|
||
|
||
你的 Java 配置:
|
||
|
||
```yaml
|
||
cloud:
|
||
nacos:
|
||
discovery:
|
||
enabled: true
|
||
register-enabled: true
|
||
server-addr: 10.0.0.10:8848
|
||
username: nacos
|
||
password: nacos
|
||
```
|
||
|
||
对应 Go 环境变量:
|
||
|
||
- `ENABLE_NACOS_REGISTER=true`
|
||
- `NACOS_SERVER_ADDR=10.0.0.10:8848`
|
||
- `NACOS_USERNAME=nacos`
|
||
- `NACOS_PASSWORD=nacos`
|
||
- `NACOS_SERVICE_NAME=lsp-gateway`
|
||
- `NACOS_GROUP=DEFAULT_GROUP`
|
||
- `NACOS_IP=<当前实例可达IP>`
|
||
- `NACOS_PORT=8080`(或你的服务端口)
|
||
|
||
## 6.3 Gateway 路由示例(HTTP + WS)
|
||
|
||
```yaml
|
||
spring:
|
||
cloud:
|
||
gateway:
|
||
routes:
|
||
- id: lsp-http
|
||
uri: lb://lsp-gateway
|
||
predicates:
|
||
- Path=/lsp/api/**
|
||
filters:
|
||
- RewritePath=/lsp/api/(?<segment>.*), /api/$\{segment}
|
||
|
||
- id: lsp-ws
|
||
uri: lb:ws://lsp-gateway
|
||
predicates:
|
||
- Path=/lsp/ws/**
|
||
filters:
|
||
- RewritePath=/lsp/ws/(?<segment>.*), /ws/$\{segment}
|
||
```
|
||
|
||
## 6.4 BFF 转发逻辑(WebClient 伪代码)
|
||
|
||
```java
|
||
Mono<ResponseEntity<String>> proxyCompletion(String language, String body, HttpHeaders headers) {
|
||
return call("lb://lsp-gateway/api/v1/completions/" + language, body, headers)
|
||
.flatMap(resp -> {
|
||
if (resp.getStatusCodeValue() != 409) return Mono.just(resp);
|
||
|
||
String routeTo = extractRouteTo(resp); // from body.routeTo or X-LSP-Route-To
|
||
if (routeTo == null || routeTo.isBlank()) return Mono.just(resp);
|
||
|
||
String direct = routeTo + "/api/v1/completions/" + language;
|
||
return call(direct, body, headers); // retry once
|
||
});
|
||
}
|
||
```
|
||
|
||
## 7. Go 服务部署参数建议(生产)
|
||
|
||
- `ENABLE_REDIS_STICKY=true`
|
||
- `REDIS_ADDR=10.0.0.10:6379`
|
||
- `REDIS_DB=1`
|
||
- `REDIS_PASSWORD=`(空)
|
||
- `INSTANCE_ID`:每个实例唯一(建议 Pod 名)
|
||
- `INSTANCE_URL`:实例可回源地址(供 routeTo 使用)
|
||
- `ENABLE_NACOS_REGISTER=true`
|
||
- `NACOS_SERVER_ADDR=10.0.0.10:8848`
|
||
- `NACOS_USERNAME=nacos`
|
||
- `NACOS_PASSWORD=nacos`
|
||
- `NACOS_IP`:实例可达内网 IP(不要填 127.0.0.1)
|
||
- `NACOS_PORT`:实例监听端口
|
||
- `MAX_SESSIONS`:按机器资源评估(如 200~500)
|
||
- `SESSION_TTL`:建议 10~30 分钟
|
||
|
||
## 8. 常见误区
|
||
|
||
1. “用了 Nacos 就不需要 Redis 会话目录”
|
||
错。Nacos 只告诉你“有哪些实例”,不维护“某会话属于哪台实例”。
|
||
|
||
2. “随机 LB 也能跑”
|
||
能跑但体验会抖,LSP 上下文会丢。
|
||
|
||
3. “不传 sessionId 也没关系”
|
||
错。会话粘性依赖稳定 sessionId。
|
||
|
||
## 9. 联调 checklist
|
||
|
||
1. Nacos 中能看到 `lsp-gateway` 实例。
|
||
2. Java 转发 HTTP 可通。
|
||
3. 同一 `sessionId` 连续请求响应稳定。
|
||
4. 人为让请求打到非 owner 实例时,Java 能按 `routeTo` 自动重试成功。
|
||
5. WS 通道可建立,断线重连后会话仍可恢复。
|