feat(切换): 支持并行试跑,新站先挂 oj2.xuyue.cc 跑几天
设计文档写的是「不双跑不灰度」,这次改主意了。理由:「只换前后端」形态下新栈
本来就不碰数据库进程,双跑的增量风险只剩「两个后端同时写同一个库」这一条;
换来的是**正式切换退化成改一行 NPM 上游**,比停机切换更稳,回滚也不用停任何容器。
## 两个新变量
WEB_PORT 对外端口。8080 还被旧 backend 占着(机房是 81)
JUDGE_STATE_DIR 判题机运行状态目录
JUDGE_STATE_DIR 是必须的:不设的话新旧两个 judger 会同时往
`data/judge_server/{run,log}` 里写。默认值用嵌套写法跟着 DATA_DIR 走
(`${JUDGE_STATE_DIR:-${DATA_DIR:-../data}/judge_server}`,compose 支持嵌套默认值,
试过),所以一次性切换那条路径完全不受影响。
test_case 和 public/upload 仍然共享 —— 那是故意的,测试点和题面图片两边必须
看到同一份。
## 手册
新增第四节「并行试跑」,后面章节顺移(原四~九 → 五~十),两处交叉引用一并改了。
第五节拆成两条路径:试跑过的只需改 NPM 上游 + 事后停旧栈;没试跑的走原来那套。
第七节回滚同理。
试跑那节写明了三件容易踩的:NPM 的 Websockets Support 必须打开(漏了的话页面
一切正常,唯独「判题中…」永远不动,而刷新一下结果就出来,自测很难发现)、
两边登录态不互通、以及双写的是真实数据不是沙盒(别在 oj2 上办正式比赛)。
## 验证
试跑形态 `config` 解析:判题机目录落到 judge_server_oj2、端口 8090、test_case
仍指向共享的那份;不设新变量时默认值一个没变(judge_server / 8080)。
四份 compose `config -q` 全通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -118,6 +118,9 @@ Drizzle schema 是从生产库 `drizzle-kit pull` 出来的,**不写迁移**
|
||||
- **只换前后端**(上线用这个):设 `DATA_DIR` / `DB_HOST` / `REDIS_HOST`,
|
||||
沿用旧栈已经在跑的 postgres 和 redis,只起 api / worker / web / judge。
|
||||
- **自带数据**(本机、演练):不设那几个变量,起栈时加 `--profile local-data`。
|
||||
- **并行试跑**(上线前先挂 `oj2.xuyue.cc` 跑几天):在「只换前后端」基础上再加
|
||||
`WEB_PORT`(8080 被旧 backend 占着)和 `JUDGE_STATE_DIR`(两个判题机不能共用运行目录)。
|
||||
这种形态下旧栈一个容器都不用停,正式切换退化成改一行 NPM 上游。
|
||||
|
||||
⚠️ `DATA_DIR` 默认值 `../data` 是 **`OJ2/data`**,不是部署目录的 `data/`。
|
||||
沿用旧数据却忘了设它,会静默起一套空数据(空库、没测试点、图片 404),
|
||||
|
||||
@@ -47,6 +47,25 @@ DB_PORT=
|
||||
REDIS_HOST=
|
||||
REDIS_PORT=
|
||||
|
||||
# --- 并行试跑(oj2.xuyue.cc)才需要 ---
|
||||
#
|
||||
# 新站和旧站同时跑的时候,这两个必须设,否则会跟旧栈抢资源。
|
||||
#
|
||||
# 对外端口。旧 backend 占着 8080(机房 81),新栈换一个,NPM 那边把
|
||||
# oj2.xuyue.cc 指到这个端口。**NPM 的记录要打开 Websockets Support**,
|
||||
# 否则学生那边「判题中…」永远不动。
|
||||
WEB_PORT=
|
||||
|
||||
# 判题机的运行状态目录。不设的话是 `$DATA_DIR/judge_server`,和旧判题机同一份 ——
|
||||
# 试跑期间两个 judger 都在跑,必须分开,例如:
|
||||
#
|
||||
# JUDGE_STATE_DIR=/root/OJDeploy/data/judge_server_oj2
|
||||
#
|
||||
# 建完记得 `mkdir -p .../{log,run}`。
|
||||
# `test_case` 和 `public/upload` 仍然共享,那是**故意的** —— 测试点和题面图片
|
||||
# 两边必须看到同一份。
|
||||
JUDGE_STATE_DIR=
|
||||
|
||||
# --- 只有机房那套需要 ---
|
||||
# 机房走 http 直连 IP,没有 TLS,必须 false,否则 Cookie 发不回来。
|
||||
COOKIE_SECURE=false
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
#
|
||||
# 后者要在 env 里设 `DB_HOST` / `REDIS_HOST` / `DATA_DIR`,见 `.env.example` 末尾。
|
||||
#
|
||||
# **并行试跑**(新站挂在 oj2.xuyue.cc、旧站原样不动)是「只换前后端」再加两个变量:
|
||||
# `WEB_PORT`(8080 被旧 backend 占着)和 `JUDGE_STATE_DIR`(两个判题机不能共用运行目录)。
|
||||
# 这种形态下旧栈一个容器都不用停。见手册第四节。
|
||||
#
|
||||
# ⚠️ `DATA_DIR` 默认值 `../data` 解析出来是 **`OJ2/data`**,不是部署目录的 `data/`。
|
||||
# 旧栈用的是 `<部署目录>/data/`,两者不是一个地方 —— 直接用默认值切过去,postgres 会在
|
||||
# 空目录上初始化一个全新的空库,测试点和题面图片也全都不在。**用旧数据就必须设 `DATA_DIR`。**
|
||||
@@ -83,8 +87,11 @@ services:
|
||||
- /tmp
|
||||
volumes:
|
||||
- ${DATA_DIR:-../data}/backend/test_case:/test_case:ro
|
||||
- ${DATA_DIR:-../data}/judge_server/log:/log
|
||||
- ${DATA_DIR:-../data}/judge_server/run:/judger
|
||||
# 判题机的运行状态目录。默认跟着 DATA_DIR 走,和旧栈是同一份 ——
|
||||
# 一次性切换时无所谓(旧判题机已经停了),但**并行试跑时必须单独设 JUDGE_STATE_DIR**,
|
||||
# 否则新旧两个 judger 同时往一个 run/log 目录里写。
|
||||
- ${JUDGE_STATE_DIR:-${DATA_DIR:-../data}/judge_server}/log:/log
|
||||
- ${JUDGE_STATE_DIR:-${DATA_DIR:-../data}/judge_server}/run:/judger
|
||||
environment:
|
||||
SERVICE_URL: http://oj-judge:8080
|
||||
# 心跳路径跟着新后端改了:judge_server_heartbeat/ → judge-server/heartbeat
|
||||
@@ -164,5 +171,6 @@ services:
|
||||
# Caddy 只往这里写访问日志,静态资源在镜像里
|
||||
- ${DATA_DIR:-../data}/backend/log:/data/log
|
||||
ports:
|
||||
- "0.0.0.0:8080:8000"
|
||||
# 并行试跑(oj2.xuyue.cc)时换一个端口,8080 还被旧 backend 占着。
|
||||
- "0.0.0.0:${WEB_PORT:-8080}:8000"
|
||||
mem_limit: 256m
|
||||
|
||||
@@ -44,8 +44,9 @@ services:
|
||||
- /tmp
|
||||
volumes:
|
||||
- ${DATA_DIR:-../data}/backend/test_case:/test_case:ro
|
||||
- ${DATA_DIR:-../data}/judge_server/log:/log
|
||||
- ${DATA_DIR:-../data}/judge_server/run:/judger
|
||||
# 并行试跑时必须单独设 JUDGE_STATE_DIR,否则新旧两个 judger 共用一个运行目录
|
||||
- ${JUDGE_STATE_DIR:-${DATA_DIR:-../data}/judge_server}/log:/log
|
||||
- ${JUDGE_STATE_DIR:-${DATA_DIR:-../data}/judge_server}/run:/judger
|
||||
environment:
|
||||
SERVICE_URL: http://oj-judge:8080
|
||||
BACKEND_URL: http://oj-api:3000/api/judge-server/heartbeat
|
||||
@@ -111,5 +112,6 @@ services:
|
||||
volumes:
|
||||
- ${DATA_DIR:-../data}/backend/log:/data/log
|
||||
ports:
|
||||
- "81:8000"
|
||||
# 并行试跑时换一个端口,81 还被旧 backend 占着
|
||||
- "${WEB_PORT:-81}:8000"
|
||||
mem_limit: 256m
|
||||
|
||||
@@ -98,7 +98,7 @@ WS 经 Caddy upgrade: 已连接
|
||||
|
||||
### ⚠️ 演练没暴露的一个坑:两套 compose 的数据目录不是同一个地方
|
||||
|
||||
演练时是把生产 dump 恢复进 `OJ2/data/postgres` 的(见第九节),所以这件事被盖住了。
|
||||
演练时是把生产 dump 恢复进 `OJ2/data/postgres` 的(见第十节),所以这件事被盖住了。
|
||||
真实部署目录 `/root/OJDeploy` 的实际情况:
|
||||
|
||||
| | 旧栈(`docker-compose.yml`) | 新栈默认值 |
|
||||
@@ -187,7 +187,7 @@ docker exec oj-postgres pg_dumpall -U onlinejudge > ~/oj-before-cutover-$(date +
|
||||
```
|
||||
|
||||
**不是为了迁移**(不需要 DDL、不需要迁数据),是为了出事有退路。真要用它重建,
|
||||
先读第七节第 1 条 —— 这份备份会把角色口令覆盖回备份当时的值。
|
||||
先读第八节第 1 条 —— 这份备份会把角色口令覆盖回备份当时的值。
|
||||
|
||||
### 4. 确认 `DATA_DIR` 指对了
|
||||
|
||||
@@ -211,13 +211,94 @@ docker compose -f OJ2/docker/compose.debian.yml --env-file OJ2/docker/.env confi
|
||||
- [ ] **两边的 `DATA_DIR` 都指向旧数据目录**,`config | grep source:` 核对过
|
||||
- [ ] 两边镜像都构建完了
|
||||
- [ ] 备份做了
|
||||
- [ ] 端口没变(服务器 8080、机房 81),前面的 Nginx Proxy Manager 不用动
|
||||
- [ ] 决定好走哪条路:先并行试跑(第四节,推荐),还是直接切(第五节)
|
||||
|
||||
## 四、切换步骤(当天,两边一起切)
|
||||
## 四、并行试跑(oj2.xuyue.cc)
|
||||
|
||||
正式切换之前,先让新栈挂在一个独立域名上跑几天,**旧站原样不动、一个容器都不用停**。
|
||||
|
||||
这是双跑 —— 设计文档原本明确否掉过(「不双跑不灰度」)。改主意的理由是:
|
||||
「只换前后端」形态下新栈本来就不碰数据库进程,双跑的增量风险只剩「两个后端同时写同一个库」
|
||||
这一条;而换来的是**正式切换退化成改一行 NPM 上游**,反而比停机切换更稳。
|
||||
代价见下面「试跑期间要知道的」,不是零。
|
||||
|
||||
### 1. env 比「只换前后端」多两个
|
||||
|
||||
```
|
||||
WEB_PORT=8090
|
||||
JUDGE_STATE_DIR=/root/OJDeploy/data/judge_server_oj2
|
||||
```
|
||||
|
||||
`WEB_PORT` 是因为 8080 还被旧 backend 占着。`JUDGE_STATE_DIR` 是因为**两个判题机不能
|
||||
共用运行目录** —— 不设的话新旧两个 judger 会同时往 `data/judge_server/{run,log}` 里写。
|
||||
|
||||
`test_case` 和 `public/upload` 仍然共享,**那是故意的**:测试点和题面图片两边必须看到同一份。
|
||||
|
||||
### 2. 起
|
||||
|
||||
```bash
|
||||
mkdir -p /root/OJDeploy/data/judge_server_oj2/log /root/OJDeploy/data/judge_server_oj2/run
|
||||
cd /root/OJDeploy
|
||||
docker compose -f OJ2/docker/compose.debian.yml --env-file OJ2/docker/.env up -d
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8090/ # 期望 200
|
||||
```
|
||||
|
||||
旧站这时候完全没受影响,8080 上照常服务。
|
||||
|
||||
### 3. NPM 加一台 proxy host
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| Domain | `oj2.xuyue.cc`(先把 DNS 解析加上) |
|
||||
| Forward | `<宿主机 IP>` : `8090` |
|
||||
| **Websockets Support** | **必须打开** |
|
||||
| SSL | 签一张证书,`COOKIE_SECURE=true` 依赖 https |
|
||||
| client_max_body_size | `200M`,和 Caddyfile 里的 `200MiB` 对齐(上传测试用例压缩包) |
|
||||
|
||||
WebSocket 那个开关是双跑最容易漏的一格:漏了的话页面一切正常,唯独学生盯着的
|
||||
「判题中…」永远不动 —— 而这恰恰是最不容易在自测里发现的一条,因为刷新一下结果就出来了。
|
||||
|
||||
### 4. 试跑期间要知道的
|
||||
|
||||
- **两边登录态不互通**。旧站是 Django session,新站是 Redis opaque token。
|
||||
学生到 oj2 要重新登录一次,这不是 bug。
|
||||
- **同一个库,双写**。结构完全兼容不会写坏,但提交、统计、成就都是**真实数据**,
|
||||
不是沙盒。别拿它做破坏性试验。
|
||||
- 后台判题机列表会出现**两台**(新旧各自心跳),正常。
|
||||
- 比赛排名等缓存两边各存各的 Redis,可能短暂不一致。
|
||||
**试跑期间不要在 oj2 上办正式比赛。**
|
||||
|
||||
### 5. 试跑要盯的是这些
|
||||
|
||||
都是只有真实数据 + 真实浏览器才暴露的:
|
||||
|
||||
- 机房那种 **Chrome < 94** 打开正常(`mermaid-legacy` 这条 fallback 只在老浏览器上生效)
|
||||
- 带图片的题面(`/public/upload/*` 走的是反代,不是 Caddy 直接读盘)
|
||||
- AI 分析的 **SSE 流式输出**经过 NPM 之后还是不是逐块下发(这一层最容易被缓冲住)
|
||||
- WebSocket 在 NPM 后面长时间挂着稳不稳(超时断连会不会自动重连)
|
||||
- 后台**上传测试用例压缩包**这条 200MB 的路
|
||||
- 老师日常用的后台:出题、建比赛、题单
|
||||
|
||||
## 五、正式切换
|
||||
|
||||
**两个站点必须同一天切。** 机房那套连的是服务器的库,只切一边的话,另一边的旧后端
|
||||
还在读写同一个库。
|
||||
|
||||
### 试跑过了 —— 那就只是改一行上游
|
||||
|
||||
新栈已经在 8090 上跑着、验过了,切换不需要重启任何容器:
|
||||
|
||||
1. NPM 里把 `xuyue.cc` 那台 proxy host 的上游从 `8080` 改成 `8090`(**Websockets Support 同样要开**)
|
||||
2. 看一眼首页和一次提交,正常
|
||||
3. 确认没问题之后再停旧栈:`docker compose -f docker-compose.yml stop oj-backend oj-judge`
|
||||
|
||||
**停机时间约等于零**,回滚就是把 NPM 那一行改回 `8080` —— 旧栈这时候都还没停。
|
||||
这正是设计文档最初写的那个回滚故事:「改一行上游」。
|
||||
|
||||
`oj2.xuyue.cc` 可以留着,它和 `xuyue.cc` 指向同一个新栈,没有坏处。
|
||||
|
||||
### 没试跑、直接切 —— 走下面这套
|
||||
|
||||
服务器(`/root/OJDeploy`):
|
||||
|
||||
```bash
|
||||
@@ -254,7 +335,9 @@ docker logs oj-api --tail 50 # 连不上库、token 不对都在这里
|
||||
docker logs oj-judge --tail 20 # 判题机注册不上看这条
|
||||
```
|
||||
|
||||
## 五、切换后验证
|
||||
## 六、切换后验证
|
||||
|
||||
这一节试跑刚起来时也照着跑一遍,只是端口换成试跑用的(`WEB_PORT`,例如 8090)。
|
||||
|
||||
先用命令快速过一遍(服务器 8080,机房 81):
|
||||
|
||||
@@ -284,7 +367,12 @@ curl -s -o /dev/null -w '未登录进后台 %{http_code}\n' $BASE/api/admin/das
|
||||
机房那边额外确认一条:**登录之后刷新页面还是登录态**。如果「登录成功又立刻变未登录」,
|
||||
就是 `COOKIE_SECURE` 没设成 false。
|
||||
|
||||
## 六、回滚
|
||||
## 七、回滚
|
||||
|
||||
**试跑之后切的**(推荐路径):NPM 里把 `xuyue.cc` 的上游从 `8090` 改回 `8080`。
|
||||
旧栈这时候还跑着,**秒级生效,什么都不用停不用起**。等确认稳定了再决定何时收掉新栈。
|
||||
|
||||
**直接切的**:
|
||||
|
||||
```bash
|
||||
cd /root/OJDeploy
|
||||
@@ -302,7 +390,7 @@ backend 和判题机再 start 起来。**不需要恢复数据库,不需要动
|
||||
|
||||
---
|
||||
|
||||
## 七、演练中踩到的坑(写下来是因为它们只在容器里出现)
|
||||
## 八、演练中踩到的坑(写下来是因为它们只在容器里出现)
|
||||
|
||||
### 1. pg_dumpall 备份会覆盖数据库口令 ⚠️
|
||||
|
||||
@@ -350,7 +438,7 @@ ERROR: database "onlinejudge" is being accessed by other users
|
||||
|
||||
---
|
||||
|
||||
## 八、镜像体积(没达到设计文档的预期,说明原因)
|
||||
## 九、镜像体积(没达到设计文档的预期,说明原因)
|
||||
|
||||
| 镜像 | 体积 |
|
||||
|---|---|
|
||||
@@ -376,7 +464,7 @@ clang-format,再加整个 Python 运行时和 Django 依赖,所以新镜像
|
||||
|
||||
---
|
||||
|
||||
## 九、演练产生的临时数据(已清理)
|
||||
## 十、演练产生的临时数据(已清理)
|
||||
|
||||
演练在 `OJ2/data/postgres` 下留下了**一份完整的生产数据副本**,其中包含 1710 名
|
||||
学生的 `raw_password` 明文列。演练结束后已删除该目录。
|
||||
|
||||
Reference in New Issue
Block a user