# g2.tkmind.cn 负载均衡（Studio + 105）

> 2026-06-17 上线。把 g2 H5 的请求按权重分到两台机器：Studio（主）和 105（灰度副）。
> Goose **只在 103 / Studio 跑**，105 是无状态前端孪生，通过固定公网地址把请求代理回 Studio 的 goosed。
> 2026-06-22 已确认：Studio 上 Goose 不是单实例，而是双实例负载：`18006` 主、`18007` 备用/第二实例；105 仍然不跑 Goose。

## 流量拓扑

```
用户 → 阿里云解析 → 105 服务器公网入口
                         ├─ 转发到 103 / Studio 服务器 → Studio portal 127.0.0.1:8081
                         └─ [SSH 正向隧道 127.0.0.1:18080] → 105 portal :8080
                                                                                                   │
                       105 / Studio 两个 portal ── 都连 ──→ Studio goosed 58.38.22.103:18006（主）
                                                  └────→ Studio goosed 58.38.22.103:18007（备用/第二实例）
                       发布页文件 ── 都读 ──→ Studio canonical /Users/john/Project/Memind/MindSpace
                                              （105 经反向隧道 + rclone 挂载到本地同路径）
```

- **为什么不用旧的 Cloudflare 直连**：当前入口已经迁到阿里云解析，外网流量先到 105，再由 105 转发到 103 / Studio。
- **为什么直接固定公网 IP**：当前服务器互联和部署链路统一使用固定公网地址 `120.26.184.105` / `58.38.22.103`，不再依赖内网地址、域名别名或临时回退链路。

## 105 / 103 联通约束（硬性要求）

**要求：域名只做用户入口；服务器互联、健康检查、部署同步统一走固定公网 IP。**

- 105 运维地址：`120.26.184.105`。
- 103 / Studio 运维地址：`58.38.22.103`。
- 如果 SSH 别名未更新，直接写 `root@120.26.184.105` / `john@58.38.22.103`。
- Studio / goosed 目标直接写 `58.38.22.103`，不要写 `g2.tkmind.cn` 或 105 域名。
- Studio 对 105 的联通入口是 `127.0.0.1:18080`，不是 `105.tkmind.cn`。

快速自检（每次操作 105 前）：

```bash
ping -c 1 120.26.184.105
ping -c 1 58.38.22.103
ssh root@120.26.184.105 'echo ssh-ok-105'
ssh john@58.38.22.103 'echo ssh-ok-103'
curl -s http://127.0.0.1:18080/api/status   # 应返回 ok
```

- 禁止：用 `105.tkmind.cn` 做 API 健康检查或同步链路入口。
- 禁止：把 105 公网 IP 当成业务上游写进 Caddy / goosed 目标；公网 IP 只保留给外部入口或应急 SSH。
- 应急：如果 SSH 别名或本地缓存配置仍残留旧地址，显式设置 `H5_DEPLOY_HOST=root@120.26.184.105`，Studio 目标改为 `john@58.38.22.103`。

## 调灰度比例（最常用）

编辑 **103 / Studio** 上的入口配置，调整流量分配（第一个=Studio :8081，第二个=105 :18080）：

| 配置 | Studio | 105 |
|------|------|-----|
| `weighted_round_robin 19 1` | 95% | 5%（默认上线值） |
| `weighted_round_robin 9 1`  | 90% | 10% |
| `weighted_round_robin 4 1`  | 80% | 20% |
| `weighted_round_robin 1 1`  | 50% | 50% |

改完**零停机生效**：

```bash
ssh john@58.38.22.103
cd ~/Project/Memind/scripts
caddy validate --config g2-lb.Caddyfile   # 可选，先校验
caddy reload  --config g2-lb.Caddyfile     # 零停机热重载
```

> `caddy` 路径若不在 PATH：用 `/opt/homebrew/bin/caddy`。

## 一键回滚（把流量全部收回 Studio）

把 `to` 那行的 `127.0.0.1:18080` 删掉（只留 `127.0.0.1:8081`），或直接恢复备份，然后 reload：

```bash
cd ~/Project/Memind/scripts
ls g2-lb.Caddyfile.bak-*          # 找最近备份
cp g2-lb.Caddyfile.bak-XXXX g2-lb.Caddyfile
caddy reload --config g2-lb.Caddyfile
```

## 验证 / 观测

每个 g2 响应都带 `X-Memind-Upstream` 头，标记实际命中的上游（`127.0.0.1:8081`=Studio，`127.0.0.1:18080`=105）：

```bash
# 打 N 次看分流比例
for i in $(seq 1 40); do
  curl -s -D - -o /dev/null https://g2.tkmind.cn/api/status \
    | grep -i x-memind-upstream | awk '{print $2}' | tr -d '\r'
done | sort | uniq -c

# 看入口反代两个上游健康状态
curl -s http://127.0.0.1:2019/reverse_proxy/upstreams   # 在 103 / Studio 上执行
```

健康检查会每 10s 打一次各上游的 `/api/status`，连不上或非 200 就自动摘除该上游、流量全转到健康节点；恢复后自动加回。

## 涉及的组件与配置位置

| 组件 | 位置 | 作用 |
|------|------|------|
| 入口反代 | 103 / Studio `scripts/g2-lb.Caddyfile`（LaunchAgent `cn.tkmind.g2-lb`，:8090） | 入口转发 + 健康检查 |
| 正向隧道 | 103 / Studio `scripts/memind-fwd-tunnel.sh`（LaunchAgent `cn.tkmind.memind-fwd-tunnel`） | Studio `127.0.0.1:18080` → 105 `:8080` |
| 反向隧道 | 103 / Studio `scripts/memind-mac-tunnel.sh`（LaunchAgent `cn.tkmind.memind-tunnel`） | 105 经此挂载 Studio MindSpace 文件 |
| 105 portal | 105 systemd `goose-h5`（:8080），`/root/tkmind_go/ui/h5/.env` | 无状态前端，主 Goose 指向 `https://58.38.22.103:18006`，第二 Goose 指向 `https://58.38.22.103:18007` |
| 105 文件挂载 | 105 systemd `.mount` → `/mnt/memind-shared`（rclone over 反向隧道） | 发布页/工作区共享只读 |

## 关键约束（改动前必读）

- **105 永不跑 goose**。goosed 只在 103 / Studio；当前是双实例 `18006` / `18007`。105 只是代理 + serve。
- 105 的 portal 必须设 `MEMIND_WORKSPACE_MAINTENANCE=0`、`MINDSPACE_AGENT_JOBS_ENABLED=false`——工作区维护守护（缩略图/资产同步 watcher）只该在本地跑，否则 105 对 rclone 挂载树做递归 fs.watch 会占满 libuv 线程池导致 portal 启动卡死。
- 105 必须与 103 / Studio 跑**同一份代码**（用 `sync-to-105.sh` 从 Studio 同步）和**相同的 `TKMIND_SERVER__SECRET_KEY`**（否则连不上 goosed / 解不开共享 RDS 里的加密设置）。
- 调权重只动 `g2-lb.Caddyfile`，不要动 105 的 systemd 单元或 .env。
