用 Linux 虚拟机部署 Hydro 这套流程,我前后完整走过七八遍,从最早拿一台四年前的旧笔记本跑 VMware,到后来在服务器上开 KVM 做正式的校内选拔赛,中间踩的坑几乎能凑成一本小册子。Hydro 是一套开源的在线评测系统,用 Node.js 加 MongoDB 搭起来,能自动判题、组织比赛、管理题库、排训练计划,做校内算法训练或者团队内部刷题都很合适。而"先开一台 Linux 虚拟机再装"这个做法,本质上是为了给自己留一条退路:装崩了直接回滚快照,不用重装系统,也不用担心把日常工作机搞乱。这篇内容写给两类人——一类是完全没碰过虚拟机的同学,照着做也能跑通;另一类是已经在物理机上装过一次、结果被依赖问题折腾到怀疑人生的朋友,这篇里的排查链路应该能帮你省掉不少时间。下面所有的命令和参数,都是我在 Ubuntu 22.04 和 Debian 12 上实测过的,路径和版本号会随发行版变化,遇到对不上的地方,以你本机--help的输出为准。
1. 开工前先想清楚:Hydro 到底要吃多少资源
1.1 Hydro 的组成决定了它的资源胃口
很多人对在线评测系统的第一印象是"不就是个网站吗,1 核 1G 随便跑"。真按这个规格开虚拟机,大概率会在编译前端资源那一步卡死,或者跑起来之后每隔十分钟被 OOM Killer 干掉一次。原因在于 Hydro 不是单体应用,它至少由三块东西拼成:Web 服务进程本身(Node.js,负责页面渲染、接口、任务调度)、MongoDB(存题目、提交记录、用户数据、比赛信息)、以及评测沙箱(真正执行用户代码、限制时间和内存的那部分)。
这三块对资源的需求完全不一样。MongoDB 的 WiredTiger 存储引擎默认会申请大约"物理内存减去 1GB 再除以 2"作为缓存,也就是说你给虚拟机 4GB 内存,它一上来就想占掉 1.5GB 左右;Node.js 主进程在空闲时大概 200 到 400MB,但如果同时在编译前端资源或者处理多个提交,冲到 1GB 也不奇怪;评测沙箱每跑一个测试点评测,都会 fork 出独立进程并施加资源限制,虽然单个进程被限制得很小,但并发评测时是叠加的。
把这三块加在一起,再留出操作系统自身和文件缓存的空间,就能倒推出配置单了。我一般按下表来开机器,实测下来余量比较舒服:
| 使用场景 | vCPU | 内存 | 磁盘 | 备注 |
|---|---|---|---|---|
| 个人学习 / 跑通流程 | 2 | 4GB | 40GB | 编译慢,但能跑起来 |
| 小团队内部训练(几十人) | 4 | 8GB | 60GB | 我推荐的起步配置 |
| 正式比赛(百人以上) | 8 | 16GB 起 | 100GB SSD | 建议把评测拆到独立机器 |
磁盘这一项特别提醒一句:虚拟机磁盘别用动态分配的"精简置备"跑正式用途。表面上看 40GB 的虚拟磁盘只占了宿主机 15GB,很省地方,但比赛期间提交记录和测试数据增长很快,磁盘写满之后 MongoDB 会直接拒绝写入,那时候想扩容还得关机、改配置、再开机,比赛正在进行的话就非常被动。
1.2 系统版本选哪个,别选太新的也别选太老的
Hydro 的部署脚本和官方文档主要以 Debian 系为基础,所以选系统的时候没必要给自己加难度。下面是我试过或者身边人试过的几种情况:
| 系统 | 部署顺畅度 | 说明 |
|---|---|---|
| Ubuntu 22.04 LTS | 很顺 | 官方脚本基本是为它写的,资料最多 |
| Ubuntu 24.04 LTS | 较顺 | Node 和 Mongo 源需要确认是否已支持 |
| Debian 12 | 很顺 | 更干净,占资源更少 |
| Rocky / AlmaLinux 9 | 一般 | SELinux 会拦住端口和文件访问,要多配一步 |
| Kali | 不推荐 | 预装了大量安全工具,依赖冲突概率高 |
Kali 单独说一句,热搜里经常有人问能不能用 Kali 学这套东西。Kali 本质上也是 Debian,理论上是能装的,但它的软件源里塞了一堆渗透测试工具,某些库的版本跟 MongoDB 或者 Node 官方源会打架,我之前在 Kali 上装到一半碰到libssl版本冲突,换 Debian 之后同样的命令一次过。学习环境就别给自己找麻烦了。
1.3 创建虚拟机时那几个容易被忽略的开关
这部分是纯经验,书上通常不写,但出问题的时候特别让人抓狂。
第一,宿主机 BIOS 里的虚拟化开关必须打开。Intel 平台叫 VT-x,AMD 平台叫 SVM,名字不一样,位置通常在 Advanced 或者 CPU Configuration 里面。这个开关没开,虚拟机要么起不来,要么装系统过程中直接蓝屏。热搜里"虚拟机安装linux蓝屏"这个搜索词,我估计八成的人栽在这里。判断方法很简单:在任务管理器或者 CPU-Z 里看有没有"虚拟化:已启用",没有就去 BIOS 里开。
第二,虚拟机软件里那个"虚拟化引擎"的选项要勾上。VMware 的路径是虚拟机设置 → 处理器 → 虚拟化引擎,里面有两个复选框。如果你打算在虚拟机里面再跑 Docker 或者容器化的评测环境,这两个必须勾。不勾的话虚拟机内部会报"这台主机不支持硬件虚拟化"。
第三,别用"简易安装"。用 VMware 新建虚拟机的时候,它会检测到你选了 Ubuntu 的 ISO 就自动走简易安装流程,帮你把用户名密码都设好了,看起来省事,实际上会跳过分区和软件包选择,装出来的系统经常缺一些基础组件,后面apt各种报错。正确做法是在向导里明确选"稍后安装操作系统",把虚拟机建好之后再把 ISO 挂上去手动装。
第四,虚拟机目录如果建在机械硬盘或者外接移动硬盘上,编译前端资源那一步会慢到你想砸键盘。Hydro 的依赖安装加上前端构建,在 SSD 上大概五到十分钟,在机械盘上可能要半小时以上,还容易超时中断。
2. 网络配置:让宿主机和局域网都能打开 Hydro 页面
2.1 NAT 和桥接的本质区别,以及为什么我倾向桥接
虚拟机网络这块,最让人迷糊的就是 NAT 和桥接到底选哪个。用一个类比解释:NAT 模式相当于虚拟机住在宿主机家里,对外的一切通信都要宿主机帮忙转达,外部的人根本不知道虚拟机这个"家庭成员"的存在;桥接模式相当于虚拟机直接在小区里分了一套房,有自己的门牌号,邻居(局域网里的其他设备)可以直接敲门。
具体到装 Hydro 这件事上,区别是这样的:
- 只用宿主机访问:NAT 就够了,宿主机的浏览器直接敲虚拟机 IP 就能打开页面。
- 要让同学用手机或者自己的电脑访问:必须桥接,或者手动在 NAT 里做端口映射。
- 要做比赛,让外网也能访问:桥接之后配合路由器端口映射,或者干脆部署到有公网 IP 的机器上。
我踩过的一个坑是:VMware 在 NAT 模式下,虚拟机的 IP 段通常是 192.168.X.0/24,而宿主机上的其他虚拟网卡(比如 Docker 的默认网桥、某些远程办公软件创建的虚拟网卡)很可能占用了同样的网段,结果就是宿主机能 ping 通虚拟机,但浏览器死活打不开页面。这种问题排查起来特别费劲,因为看着都正常。后来我的做法就是把虚拟机网络统一改成桥接,IP 段由家里的路由器分配,跟宿主机平级,冲突概率低很多。
如果实在不方便用桥接(比如公司网络做了端口隔离),那就老老实实用 NAT 加端口映射。VMware 的路径是:编辑 → 虚拟网络编辑器 → 选中 VMnet8 → 右下角"NAT 设置" → 添加一条映射,宿主机端口填 8080,虚拟机 IP 填你的虚拟机地址,虚拟机端口填 80。这样在宿主机的浏览器里访问http://127.0.0.1:8080就等于访问虚拟机的 80 端口。
2.2 给虚拟机配一个固定 IP,别让它每次都换
动态分配的 IP 在重启之后可能变,每次都要去虚拟机里敲ip a看一眼,麻烦且容易出错。建议在装完系统之后第一件事就把 IP 固定下来。
Ubuntu 从 17.10 之后用 netplan 管网络,配置文件在/etc/netplan/目录下,通常是00-installer-config.yaml或者50-cloud-init.yaml。内容大概长这样:
network: version: 2 ethernets: ens33: dhcp4: no addresses: - 192.168.1.150/24 routes: - to: default via: 192.168.1.1 nameservers: addresses: [223.5.5.5, 119.29.29.29]改完执行sudo netplan apply生效。这里有几个细节要注意:网卡名字ens33必须换成你自己ip a看到的名字,Ubuntu 桌面版可能是ens160或者enp0s3;网关地址要跟你家路由器一致;DNS 我用的是公共 DNS,比走路由器转发在某些网络环境下响应快一点。如果netplan apply之后网络断了,别慌,直接在虚拟机控制台里改回来就行,桌面版还可以用sudo dhclient ens33临时恢复。
Debian 12 用的是/etc/network/interfaces,写法更传统:
auto ens33 iface ens33 inet static address 192.168.1.150 netmask 255.255.255.0 gateway 192.168.1.1 dns-nameservers 223.5.5.5 119.29.29.29Rocky 或者 AlmaLinux 走的是 NetworkManager,用nmcli改最省事:
sudo nmcli con mod ens33 ipv4.addresses 192.168.1.150/24 sudo nmcli con mod ens33 ipv4.gateway 192.168.1.1 sudo nmcli con mod ens33 ipv4.dns "223.5.5.5 119.29.29.29" sudo nmcli con mod ens33 ipv4.method manual sudo nmcli con up ens33配完之后从宿主机 ping 一下虚拟机地址,通了再往下走。这一步不通,后面所有关于页面的问题都免谈。
2.3 宿主机打不开虚拟机网页的排查顺序
这个场景太常见了,我把排查顺序固化下来,按顺序走一遍基本就能定位:
- 宿主机的命令行 ping 虚拟机 IP。不通说明是网络层问题,回到 2.2 检查 IP、网关、防火墙。
- 在虚拟机里
curl -I http://127.0.0.1:8888。不通说明服务根本没起来,去看服务日志。 - 在虚拟机里
ss -lntp | grep 8888。如果显示的监听地址是127.0.0.1:8888而不是0.0.0.0:8888,那外部无论如何都访问不到。Hydro 默认只监听本地回环,要通过--host 0.0.0.0或者配置文件放开。 - 检查虚拟机自身的防火墙。Ubuntu 上是
sudo ufw status,如果状态是 active,需要sudo ufw allow 8888/tcp。Rocky 上是sudo firewall-cmd --list-all,用--add-port=8888/tcp --permanent加规则再--reload。 - 再在宿主机上
curl -I http://虚拟机IP:8888。到这一步还不通,基本就是虚拟网络模式的问题了,回到 2.1 检查 NAT 或桥接设置。
提示:排查过程中不要同时在好几层动手改配置,一次只动一个地方,改完立刻验证。我见过有人在防火墙、监听地址、Nginx 配置三处同时改了,最后虽然通了,但完全不知道是哪一步起了作用,下次遇到同样问题还是不会。
3. Hydro 依赖三件套:MongoDB、Node.js 和评测沙箱
3.1 MongoDB 的版本选择和 AVX 指令集的坑
Hydro 的数据全部存在 MongoDB 里,题库、提交、比赛、用户、评分记录,一个都跑不掉。所以 MongoDB 装不上,后面全白搭。
版本方面,现在主流是 5.0、6.0、7.0 这几条线。这里有个特别隐蔽的坑:MongoDB 从 5.0 开始,编译时启用了 AVX 指令集优化,如果你的 CPU 不支持 AVX,mongod启动会直接崩掉,日志里会出现Illegal instruction这一行。这个问题在物理机上很少见(近十年的 CPU 基本都有 AVX),但在虚拟机里出现的概率明显高一些——因为某些虚拟机软件的默认 CPU 兼容性设置会把 AVX 指令屏蔽掉,或者你用的是很老的宿主机。
判断方法是在虚拟机里执行:
grep -o 'avx' /proc/cpuinfo | head -1有输出说明支持 AVX,能装 5.0 以上;没输出就只能退到 4.4 版本,或者在虚拟机设置里把 CPU 的指令集透传打开(VMware 在虚拟机设置的处理器选项里可以调整 CPU 掩码,把 AVX 暴露给客户机)。
安装 MongoDB 官方源的步骤大致是:导入 GPG 公钥、添加 apt 源、更新索引、安装mongodb-org。具体命令每个版本略有差别,建议直接去 MongoDB 官方文档复制当前版本的安装命令,别照抄博客里的老命令,因为 GPG key 会轮换,老 key 过期之后apt update会报签名验证失败。
装完之后做两件事。第一件是确认服务状态:
sudo systemctl status mongod sudo systemctl enable mongod第二件是调一下缓存大小。前面说过 WiredTiger 默认要吃一半内存,在只给 4GB 的虚拟机上太激进了。编辑/etc/mongod.conf,加上:
storage: wiredTiger: engineConfig: cacheSizeGB: 1这个值给多少合适?我的经验是虚拟机内存的 25% 到 40% 之间。4GB 的机器给 1GB,8GB 的机器给 2 到 3GB,16GB 的可以给 4 到 6GB。给太少会频繁读盘导致评测时卡顿,给太多会挤占 Node 和评测沙箱的空间。
安全方面再强调一次:/etc/mongod.conf里的bindIp保持127.0.0.1,不要改成0.0.0.0。数据库端口暴露出去,等于把全部题目和用户数据摆在大街上。Hydro 的 Web 服务在同一台机器上,走本地回环访问完全够用。
3.2 Node.js 版本和依赖源的加速
Hydro 的运行需要 Node.js,版本要求跟着 Hydro 自身的版本走,新版本一般要求 18 或 20 以上。我建议直接用 NodeSource 的源装,别用系统自带的 apt 版本,Ubuntu 仓库里的 Node 通常偏老:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs node -v npm -v国内网络环境下载 npm 包会比较慢,改一下源能明显提速:
npm config set registry https://registry.npmmirror.com yarn config set registry https://registry.npmmirror.com这里有个容易踩的坑:Hydro 用的是 Yarn 1.x 那套工作区机制,如果你手贱装了 Yarn 3 或者 4(也就是所谓的 Berry),构建时会出现一堆 PnP 相关的报错。所以全局安装的时候一定要指定 Yarn 1 的最后一个版本,装完用yarn -v确认输出是1.22.x这种格式。
还有一个小细节:yarn global add装的包默认在~/.yarn/bin目录下,这个目录不一定在PATH里。如果是用 root 装的,路径通常是/usr/local/bin,反而没这个问题。所以我的习惯是全程用普通用户加sudo操作,遇到"命令找不到"的时候先which hydrooj看一眼,找不到就检查 PATH。
3.3 评测沙箱:最容易被忽略但决定成败的一环
Hydro 本身不直接运行用户提交的代码,真正干这件事的是一个独立的评测沙箱程序。如果没有它,你点"提交"之后页面会一直显示"等待评测",然后就没有然后了。我见过不少人卡在这一步,以为是自己代码有问题,其实服务端压根没在跑评测。
沙箱程序的部署方式有两种:一键脚本会帮你自动下载对应架构的二进制文件并放到合适的目录;手动部署的话需要自己去项目发布页下载,注意选对架构(绝大多数虚拟机是amd64,树莓派或者某些 ARM 服务器是arm64),下载之后给可执行权限,放到PATH覆盖得到的目录里。
沙箱对权限比较敏感。它需要限制用户进程能用的系统调用、能用的内存、能用的 CPU 时间,这些能力在不同内核版本上实现方式不一样。如果在容器里跑,宿主机的 seccomp 策略可能会拦住沙箱的部分操作,表现为评测进程启动后立刻退出。这种情况我一般先在裸机上验证沙箱能跑通,再考虑容器化。
验证沙箱是否正常,最直接的办法是在 Hydro 里给自己开一道 A+B 的简单题,用 C++ 或者 Python 提交一次,看能不能拿到结果。能拿到Accepted,说明整条链路从 Web 到数据库到沙箱全部通畅。
4. 两条部署路径:一键脚本和手动安装
4.1 一键脚本替你做了哪些事
Hydro 官方提供了一键安装脚本,在 Debian 系系统上执行一条命令就能把整套环境拉起来,包括装 MongoDB、装 Node、装 Yarn、全局安装 Hydro、安装默认 UI 插件、生成 systemd 服务单元、最后启动服务。对于只是想快速跑通的人来说,这是最省事的路径。
需要提醒的是:脚本的地址会随版本更新,别抄博客里三五年前的那条 curl 命令,去官方文档页面上复制当前的那条。另外,一键脚本对系统版本有假设,跑在非 Debian 系系统上可能中途失败。执行之前我一般会做两件事:一是确保curl和git已经装上,二是先给虚拟机打个快照。因为脚本一旦跑到一半失败了,环境会处于半残状态,回滚比重来快得多。
脚本执行完之后,用这几条命令确认状态:
systemctl status mongod systemctl status hydro ss -lntp | grep 8888三条都正常,就可以在宿主机浏览器里打开http://虚拟机IP:8888了。
4.2 手动部署的完整过程
想搞清楚每一步在干什么,或者脚本在你机器上失败了,就走手动这条路。我在下面把关键环节列出来,具体的包版本按当前官方文档来。
第一步,准备基础工具和用户:
sudo apt update sudo apt install -y curl gnupg git nginx sudo useradd -r -m -d /data/hydro -s /bin/bash hydro第二步,装 MongoDB 并确认能连上,这一步前面 3.1 讲过了。
第三步,装 Node.js 和 Yarn 1,前面 3.2 讲过了。
第四步,全局安装 Hydro 和默认界面插件:
sudo yarn global add hydrooj sudo hydrooj addon add @hydrooj/ui-defaultaddon add这条命令的作用是把界面插件注册到 Hydro 的插件列表里,不加的话能打开接口但看不到正常的页面。这也是新手最容易漏掉的一步。
第五步,先手动跑一次确认没问题,再交给 systemd:
sudo -u hydro hydrooj --port=8888 --host 0.0.0.0用一个普通用户跑,是为了避免整站以 root 身份运行——评测沙箱要以低权限身份执行陌生代码,这一点特别重要。看到控制台打出监听日志之后,宿主机浏览器打开页面能显示,说明配置正确,按 Ctrl+C 停掉,进入下一步。
4.3 用 systemd 托管,让它开机自启、挂了自动拉起
Hydro 支持被 systemd 托管,也能被 PM2 之类的进程管理器托管。我倾向 systemd,因为它是系统自带的,少装一个组件,而且日志直接进 journald,查起来方便。下面是服务单元的样子:
[Unit] Description=Hydro Online Judge After=network.target mongod.service Wants=mongod.service [Service] Type=simple User=hydro Group=hydro WorkingDirectory=/data/hydro ExecStart=/usr/local/bin/hydrooj --port=8888 --host 0.0.0.0 Restart=always RestartSec=5 Environment=NODE_ENV=production StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target几个关键点单独说一下。ExecStart里必须写hydrooj的绝对路径,因为 systemd 的PATH环境比你在终端里窄得多,写相对命令名会报"找不到命令"。User=hydro保证进程以普通用户身份运行。Restart=always配合RestartSec=5让它在异常退出后自动拉起来,评测系统半夜崩了没人管,这个配置能救你一命。
保存到/etc/systemd/system/hydro.service,然后:
sudo systemctl daemon-reload sudo systemctl enable --now hydro sudo systemctl status hydro sudo journalctl -u hydro -f最后那条journalctl -f是盯日志用的,出问题的时候第一时间看这里,比翻各种日志文件快得多。
5. 用 Nginx 做入口转发,顺便解决 WebSocket 和大文件上传
5.1 为什么要在 Hydro 前面再放一层 Nginx
Hydro 自己监听的 8888 端口对外直接暴露当然也能用,但有两个明显短板。一是端口号难看,每次访问都要记住:8888;二是 HTTPS、大文件上传、超时时间这些都在这一层更好控制。所以正式一点的做法是在前面放一层 Nginx,把 80 端口的请求转给本机的 8888。
配置大概长这样:
server { listen 80; server_name oj.example.com; client_max_body_size 512m; location / { proxy_pass http://127.0.0.1:8888; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; proxy_send_timeout 300s; } }5.2 逐行解释那几个容易被抄漏的参数
proxy_http_version 1.1加Upgrade和Connection两个头,是为了让 WebSocket 能穿透这一层。Hydro 的比赛页面排行榜、评测状态推送都走 WebSocket,这三个少了任何一个,页面能打开但实时数据永远不刷新。
client_max_body_size 512m是允许上传的最大请求体积。你导入一套带完整测试数据的题目压缩包,几百 MB 很正常,用默认的 1MB 限制会直接返回 413 错误。这个值我一般给到 512MB,够用了,别开到几个 G,容易被人拿来做别的事。
X-Real-IP和X-Forwarded-For这两行是为了让 Hydro 能拿到访问者的真实 IP。不加的话,所有记录里的 IP 都是127.0.0.1,做比赛防作弊或者排查异常提交时会很难受。
proxy_read_timeout 300s是为长时间请求留余量。比如导出大量提交记录、生成比赛成绩单这种操作,几秒钟可能跑不完,默认 60 秒会断开。
配置改完执行sudo nginx -t检查语法,再sudo systemctl reload nginx生效。这里我踩过一次坑:配置文件里server_name写了个域名,但测试的时候用的是 IP 访问,结果 Nginx 把请求路由到了默认站点,返回一个欢迎页。要么把server_name改成_,要么把域名解析配上,别让请求落到默认站点上。
5.3 HTTPS 要不要上
如果只是内网环境自己用,HTTP 就够了,别折腾证书。如果要把系统放到公网、让外校同学参加比赛,那 HTTPS 基本是必须的——现代浏览器对 HTTP 页面会显示"不安全"警告,而且某些功能(比如剪贴板接口)只在安全上下文里可用。
申请证书这块现在流程已经很成熟了,用通用工具自动申请加自动续期,几分钟的事。唯一需要注意的一点是:启用 HTTPS 之后,Hydro 里配置的站点地址也要同步改成https://开头,否则系统生成的一些回调链接和邮件链接仍然是 http 的,会出现混合内容被浏览器拦截的情况。
6. 上线之后的运维:备份、升级和评测扩容
6.1 数据备份:出事之前做,不是出事之后想
MongoDB 自带mongodump和mongorestore两个工具,这是最基本的备份手段:
mongodump --db hydro --out /data/backup/$(date +%F)这条命令把hydro数据库完整导出成 BSON 文件,恢复的时候用mongorestore指回去就行。注意导出期间对数据库有读压力,比赛高峰别执行,我一般放在凌晨的低谷时段。
除了数据库,还有两样东西要一起备份:一是 Hydro 的工作目录,里面存着上传的题目附件、用户头像这类文件;二是你的配置文件,包括/etc/mongod.conf、/etc/systemd/system/hydro.service、Nginx 的站点配置。这三样凑齐,换台机器十分钟就能恢复出一套一模一样的系统。
用 crontab 做定时任务,注意加日志重定向,不然出错的时候你完全不知道:
0 3 * * * /usr/bin/mongodump --db hydro --out /data/backup/$(date +\%F) >> /var/log/hydro-backup.log 2>&1保留策略上,我一般保留最近 14 天的完整备份,每周再额外保留一份到独立磁盘上。虚拟机磁盘一旦损坏,跟它放在同一块盘上的备份也一起没了,这一点很多人想不到。
6.2 升级 Hydro 的正确姿势
Hydro 迭代速度不慢,新版本会修 bug 也会带来新功能。升级流程我固化成了四步,一步都不能省:
- 先备份。升级失败要回滚,没备份就只能重装。
- 打快照。虚拟机的优势就在这里,升级前打一个,出问题直接回滚到升级前状态,比任何回滚脚本都快。
- 更新包。执行全局安装命令把 Hydro 更新到最新版,然后重新执行一次
hydrooj addon add更新插件。 - 看日志确认。
journalctl -u hydro -f盯一到两分钟,确认没有数据库迁移报错。
注意:Hydro 的某些版本升级会涉及数据库结构变更,第一次启动时自动执行迁移。迁移过程中不要中途重启服务,否则数据库可能处于中间状态。我一般的做法是升级前把数据库也完整 dump 一份,迁移出问题时可以恢复到升级前的结构。
6.3 评测能力的扩展思路
单机跑到几十人的规模还没什么问题,上百人同时提交,评测队列就会开始堆积,页面上的状态会长时间停在"等待评测"。这时候有几个方向可以走。
一是给虚拟机加核加内存。评测沙箱是并发跑的,CPU 核数直接决定能同时判多少份代码。从 4 核加到 8 核,吞吐量大概能翻一倍。
二是把评测进程拆出来单独跑。较新版本的 Hydro 支持把 Web 服务和评测角色分开启动,具体的启动参数写法和配置项建议直接看hydrooj --help的输出,版本之间变化比较大,硬抄老教程很容易起不来。拆开之后,Web 机器专注处理页面和接口,评测机器专注判题,两边互不干扰。
三是限制单个评测的资源上限。在后台设置里可以给每道题指定时间限制和内存限制,这个值别设得太宽松。我见过有人把所有题目统一设成 5 秒 512MB,结果一个死循环提交就能吃掉一个核心好几分钟,队列直接堵死。合理的做法是按题目类型区分:简单题 1 秒 256MB,复杂算法题 2 到 3 秒 512MB,别给到几十秒。
7. 踩坑记录:那几次让我熬到凌晨的报错
7.1 mongod 启动就退出,日志里只有一行 Illegal instruction
这个坑我在一台 2012 年的老笔记本上遇到过,当时第一反应是磁盘满了或者权限不对,查了半天磁盘、查了文件属主,全部正常,最后才在/var/log/mongodb/mongod.log里看到那行Illegal instruction。原因就是这台机器的 CPU 不支持 AVX,装的是 MongoDB 6.0。
解决办法有两条:要么降级到 MongoDB 4.4,要么在虚拟机设置里把 AVX 指令透传给客户机。降级更省事,但要注意 4.4 版本对 Ubuntu 22.04 的支持情况,可能得手动加源。这条经验告诉我,装数据库之前先跑一遍grep -o 'avx' /proc/cpuinfo,一秒钟的事,能省两小时。
7.2 页面能打开,提交之后一直显示等待评测
这个现象我第一次遇到的时候查错了方向,以为是前端的问题,把界面插件卸了重装,没用。真正的原因在沙箱那一层。
排查链路是这样的:先在虚拟机里看 Hydro 的日志,没有明显报错;然后手动在命令行里跑一次沙箱程序,发现它启动后立刻退出,退出码不是 0;继续查,发现是沙箱文件没有可执行权限,之前用普通用户下载的,权限被设成了 644。加个chmod +x之后正常了。
后来我又遇到过几次类似现象,原因各不相同:有的是沙箱路径没写进 systemd 的PATH,有的是虚拟机的内核版本太老不支持某些系统调用,还有一次是内存不够,沙箱 fork 子进程的时候被 OOM Killer 杀了。所以遇到"一直等待评测",第一件事就是去看服务日志,别在页面上瞎点。
7.3 虚拟机装系统蓝屏,或者提示需要管理员权限
热搜里"虚拟机安装linux蓝屏"和"vm虚拟机修复时显示需要管理员权限"这两个词我很有共鸣。前者刚才说过了,八成是 BIOS 虚拟化没开,或者虚拟化软件跟系统自带的 Hyper-V 冲突。Windows 上的 Hyper-V 会独占虚拟化能力,导致 VMware 只能以兼容模式运行,性能差还会出各种奇怪问题。解决办法是在"启用或关闭 Windows 功能"里把 Hyper-V 相关项取消勾选,重启,再试一次。
后者通常是虚拟机软件本身的安装或修复需要管理员权限,右键用管理员身份运行安装程序就行。还有一种情况是你把虚拟机文件放在了需要权限的目录里(比如C:\Program Files下面),换到用户目录下重新创建就能解决。我现在的习惯是统一放在D:\VMs\这种没什么权限限制的路径下。
7.4 上传的测试数据包解压出来全是乱码
从别人那里拿到题目数据压缩包,传到 Linux 里解压,中文文件名变成一堆问号或者奇怪的方块,这是编码问题。Windows 下用压缩软件打出来的 zip,文件名通常按 GBK 编码存,而 Linux 默认按 UTF-8 解析,两边对不上就乱码了。
处理办法:如果系统装了 unzip,可以用unzip -O GBK data.zip指定编码;更好的做法是用7z或者unar,它们对编码的自动探测更聪明一些:
sudo apt install p7zip-full 7z x data.zip -o./data如果只是内容乱码而不是文件名乱码,那多半是文件本身就是 GBK 编码的文本,用iconv -f GBK -t UTF-8 input.txt -o output.txt转一下就行。为了避免这类反复出现的问题,我现在拿到压缩包第一件事是file -i data.zip看一眼编码信息。
最后分享两个我用了很久、确实省事的小习惯。第一个是每次部署完拍完快照之后,把这次用到的所有命令按顺序记在一个文本文件里,放在虚拟机桌面上或者/root/deploy-notes.md。别觉得麻烦,几个月后你要么是给第二台机器部署,要么是把整套东西搬到服务器上,有这个文件在,十分钟就能复现,比到处翻历史记录强太多。第二个是给虚拟机的磁盘单独挂一块虚拟硬盘专门放 MongoDB 数据和题目附件,系统盘只放系统。这样一来,备份只需要对着数据盘操作,扩容的时候也能单独扩数据盘,不用动系统。我吃过一次系统盘被日志撑满、连 SSH 都连不进去的亏,之后就一直这么干了。