
在 OA、合同管理、档案和云文档系统中,随着在线编辑请求增加,单个 ONLYOFFICE Document Server 往往需要扩展为多个实例。部署容器只是第一步,真正影响方案可用性的,是同一文档如何进入同一个编辑会话、保存结果如何可靠回写,以及节点故障后如何防止内容分叉。
本文面向 ONLYOFFICE Document Server 社区版,先介绍 9.3 及以前常见部署架构与 9.4 的主要变化,再给出以 9.3 为基线的多实例部署方案,最后说明 9.4 的配置与运维差异。这里的“集群”指业务系统调度多个独立实例,各实例自行维护编辑状态;商业版共享状态集群不在本文实施范围内。
适用范围:采用 Docs API 接入的自建业务系统。配置中的域名、镜像、地址和密钥需要替换为实际值;资源规格与时间参数是工程起点,未经目标环境压测,不作为容量或恢复承诺。更早版本可以复用设计思路,具体依赖、目录和 API 需要按版本核对。
9.3 及以前的常见社区版部署包含文档服务、转换服务,以及 PostgreSQL、RabbitMQ、Redis 等依赖。依赖可以随完整 Docker 镜像运行,也可以按对应版本支持的配置外置。本文以完整镜像为例,避免把内部服务拆分与业务调度混在一起。
9.4 社区版进行了明显调整。官方发布说明提到服务架构整合、移除 RabbitMQ 和数据库依赖,并取消此前的 20 个同时连接限制;这里的进程整合描述的是服务架构,不能据此认定整个容器只存在一个操作系统进程。
维度 | 9.3 及以前的常见社区版部署 | 9.4 官方轻量社区版 |
|---|---|---|
实例内部 | 文档服务、转换服务及传统中间件依赖 | 服务架构整合,减少独立依赖 |
PostgreSQL / RabbitMQ / Redis | 按具体镜像配置和维护 | 当前官方社区版 Docker 构建、启动流程不按传统方式启用这三项 |
连接限制 | 9.3 官方社区版存在原有连接限制;更早版本按发行说明核对 | 官方宣布取消 20 个同时连接限制 |
多实例路由 | 业务系统分配并固定会话归属 | 仍然需要 |
正式文件保存 | 业务系统处理下载与回调 | 仍然需要 |
跨实例恢复 | 独立实例不共享正在编辑的状态 | 轻量化没有自动提供会话迁移 |
当前官方 Docker 构建和启动脚本对商业版依赖与社区版进行了区分。实际交付时应固定镜像 digest,并以该镜像对应源码和配置为准,不能把持续更新的 master 分支当作历史版本安装手册。
对多实例方案而言,稳定的规则是:同一文件在同一轮协同编辑期间使用同一个实例和同一个 document.key;扩容时只给新会话选择节点。 ONLYOFFICE 官方路由说明也要求同一文档的协同请求进入同一个服务器节点。
建议在两台服务器上分别部署一个完整社区版实例,配置独立域名。两个域名可以解析到同一套高可用 Nginx,由 Nginx 根据域名固定转发。业务系统负责选择域名,浏览器与该实例直接建立编辑连接。
OnlyOffice 最新版本 9.x 镜像:https://onlyoffice.moqisoft.com/docs/install/docker
实例 | 对外地址 | 代理目标 | 独立数据 |
|---|---|---|---|
DS-01 | https://office01.example.com | 10.0.10.11:8080 | DS-01 的配置、中间件数据和缓存 |
DS-02 | https://office02.example.com | 10.0.10.12:8080 | DS-02 的配置、中间件数据和缓存 |

例如合同 A 分配给 DS-01,方案 B 分配给 DS-02。之后无论谁打开合同 A,都加入 DS-01 上的同一个编辑会话。两份不同文档可以分担到不同实例,一份正在编辑的文档不能因节点变忙而把后加入的用户分流到另一实例。
数据类型 | 管理方式 |
|---|---|
正式文件、历史版本、编辑快照 | 业务文件存储统一管理 |
文件权限、租户、用户 | 业务系统统一管理 |
实例信息、编辑归属、保存记录 | 业务数据库持久化 |
DS 内部数据库、队列、缓存 | 每实例独立维护 |
JWT 与下载链接签名密钥 | 每实例固定,建议分别配置 |
字体、插件和定制功能 | 同版本实例保持一致 |
首期优先使用每实例独立依赖。若后续需要共用基础设施,也应使用独立数据库、RabbitMQ vhost 和独立缓存命名空间;不要把“连接相同数据库和 Redis”当作独立实例升级为共享状态集群的方法。Redis 的逻辑库也不能作为所有通信机制的隔离保证,Pub/Sub 等场景需要额外核对。
正式文件存储由业务系统提供,DS 缓存承担运行中的中间结果,两者不能相互替代。
以下配置面向已验证的 9.3 完整社区版镜像。在两台服务器上分别放置一份,不共用宿主机数据目录。通过 .env 或部署系统注入 OO_IMAGE、OO_JWT_SECRET、OO_LINK_SECRET;镜像使用固定发行标签并记录 digest,生产环境优先直接固定 digest。
services:
documentserver:
image: ${OO_IMAGE:?请设置已验证的9.3社区版镜像}
restart: unless-stopped
ports:
- "8080:80"
environment:
JWT_ENABLED: "true"
JWT_SECRET: ${OO_JWT_SECRET:?请设置JWT密钥}
JWT_HEADER: "Authorization"
JWT_IN_BODY: "false"
SECURE_LINK_SECRET: ${OO_LINK_SECRET:?请设置下载链接签名密钥}
volumes:
- ./data:/var/www/onlyoffice/Data
- ./logs:/var/log/onlyoffice
- ./lib:/var/lib/onlyoffice
- ./postgresql:/var/lib/postgresql
- ./rabbitmq:/var/lib/rabbitmq
- ./redis:/var/lib/redis
shm_size: "1gb"
stop_grace_period: 5m显式配置 JWT 可以避免重建时密钥变化导致接入失败;固定下载链接签名密钥有助于维持重启前后仍有效链接的校验一致性,但不能保证其引用文件一定存在。持久化目录、密钥及必要定制配置都要纳入备份。更早版本是否支持上述变量及目录,以对应发行包为准。
docker compose config --quiet
docker compose up -d
docker compose ps
curl --fail http://127.0.0.1:8080/healthcheck镜像应预先检查 CPU 架构、字体和插件。首次安装完成后,至少验证一次 DOCX、XLSX、PPTX 的打开、编辑、关闭和回写。健康检查成功只说明基础服务可达,不能证明保存链路正常。
资源可从每实例 4 核、8GB 内存开始测试;复杂表格、大图片演示或批量转换较多时增加内存并限制并发。需要在共享宿主机上运行多个实例时,设置各容器资源边界;多个容器部署在同一台机器上只能分摊进程负载,不能隔离主机故障。
以下为 DS-01 的 Nginx 示例。DS-02 使用另一个 server 配置,替换域名、证书和目标地址。两个实例域名均指向代理入口。
# http 上下文,只定义一次
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name office01.example.com;
ssl_certificate /etc/nginx/certs/office01.crt;
ssl_certificate_key /etc/nginx/certs/office01.key;
client_max_body_size 100m;
location / {
proxy_pass http://10.0.10.11:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_next_upstream off;
}
}WebSocket 代理需要传递协议升级头;入口前如果还有网关或负载均衡,其长连接超时也要协调设置。
入口只做固定转发,不配置跨 DS 实例的轮询和自动备用转发。DS-01 故障时将它的域名直接指向 DS-02,会让用户进入缺少原会话状态的实例。前端编辑、后端命令和结果下载都应使用会话所属实例地址;业务后端可通过内部 DNS 解析到同一入口,保持域名和协议正确。
还要打通四条链路:浏览器访问 DS、DS 获取源文件、DS 请求业务回调、业务后端下载 DS 结果文件。私网源文件需要按该版本的请求过滤规则配置可达性,签名地址有效期要覆盖实际排队与转换时间。回调下载地址应限制到受信任实例,防止通过伪造回调请求任意地址。
建议在业务库中维护以下记录。表名仅为示例,可合并到现有文件、版本和任务模型中。
记录 | 关键字段 | 作用 |
|---|---|---|
office_node | node_id、public_url、version、secret_ref、status、capacity | 注册节点、记录密钥引用和准入容量 |
file_edit_owner | tenant_id、file_id、active_session_id、epoch | 保存文件当前有效会话,文件维度唯一 |
office_session | session_id、document_key、node_id、source_version、epoch、state | 保存会话归属与生命周期 |
office_save | save_id、session_id、object_key、checksum、state | 跟踪快照和最终保存结果 |
document_key 使用唯一约束;file_edit_owner 以租户与文件作为唯一键。epoch 是业务侧的递增代次,故障后重新建立会话时递增,用于防止旧实例回调覆盖新结果。密钥通过引用在后端取得,不能返回给浏览器。
会话归属必须持久化。Redis 可以用于缓存、短期容量预留或协调,但缓存过期不代表编辑已经结束,更不能因此把同一文件重新分配到别的实例。
下面为调度伪代码,重点是事务边界和复用规则,不代表可以直接编译的 Java 实现。
openEditor(tenantId, fileId, userId):
校验文件权限
开启数据库事务
锁定文件编辑归属记录(不存在则安全创建,再加锁)
如果已有有效编辑会话:
复用其 nodeId、documentKey、epoch
检查原实例可达性,并原子预留本次加入的容量
实例不可用或已满时返回明确状态,不改派其他实例
否则如果正在最终保存或故障恢复:
返回等待状态
否则:
从 ONLINE 实例中选择有余量的节点
原子预留容量,失败则重新选择或返回繁忙
创建 sessionId、documentKey 和新的 epoch
固定源文件版本并持久化归属
提交事务
按所属实例的密钥签名编辑配置
返回 apiJsUrl 和编辑配置在 Spring Boot 中,可以通过业务文件行锁或独立归属表的 SELECT ... FOR UPDATE 实现文件级互斥。所有实例容量更新也要使用条件更新或独立原子机制,避免不同文件的并发请求一起选中同一节点。远程探测和文件下载放在事务外,减少锁占用时间。
容量预留应有短期租约,连接建立后转为活动统计,未真正打开的请求及时释放。统计需要结合 DS 连接信息与业务接入记录校准:文档数不等于编辑连接数,用户数也不一定等于打开的标签页数。给已有文档的新增协作者预留余量;如果原节点没有容量,提示稍后加入,不能将其分流到另一独立实例。
节点状态建议分为 ONLINE、DRAINING、OFFLINE。其中 DRAINING 停止接收新文档会话,已有会话及其协作者继续使用原节点,仍需遵守容量约束。
推荐为每轮编辑会话生成新的不透明 key,例如 t01_f123_s1001_e7。相同会话的所有用户使用相同 key;不同文件、租户、独立会话必须避免碰撞。官方允许的字符与长度也要遵守,最长 128 字符。
场景 | key 和实例处理 |
|---|---|
第二个用户加入正在编辑的文件 | 复用当前 key 和实例 |
编辑中强制保存快照 | 保持当前 key 和实例 |
本轮最终保存完成,开始下一轮编辑 | 创建新 key,可重新选择实例 |
故障后从保存副本恢复 | 创建新 key 和 epoch |
文件被外部系统更新 | 与编辑会话互斥或创建冲突版本,不能静默替换当前源文件 |
不要每次打开都生成随机 key,也不要每次请求都重新计算 hash(fileId) % 节点数。前者拆散协作者,后者会在扩容时改变旧会话归属。一致性哈希可以辅助选择新会话的候选节点,持久化归属才是后续请求的依据。
后端响应可采用以下结构。示例中的 token 与下载地址均为占位值。
{
"sessionId": "S-1001",
"nodeId": "DS-01",
"apiJsUrl": "https://office01.example.com/web-apps/apps/api/documents/api.js",
"config": {
"documentType": "word",
"document": {
"fileType": "docx",
"title": "合同A.docx",
"key": "t01_f123_s1001_e7",
"url": "https://files.example.com/download/signed-source-version"
},
"editorConfig": {
"mode": "edit",
"callbackUrl": "https://app.example.com/office/callback/S-1001",
"user": { "id": "t01_u100", "name": "张三" }
},
"token": "由DS-01对应密钥签名的配置token"
}
}编辑宿主页加载 apiJsUrl 后,通过 new DocsAPI.DocEditor("editor", response.config) 创建编辑器。一个业务页面同时展示多个实例的文档时,建议为每个编辑宿主页使用独立 iframe,避免不同来源的 DocsAPI 全局对象互相覆盖。节点选择结果必须同时决定 api.js 来源、JWT 签名密钥和命令发送地址。
ONLYOFFICE 通过 callbackUrl 通知业务系统,业务系统再下载并保存结果文件。主要状态如下。
status | 含义 | 业务动作 |
|---|---|---|
1 | 文档正在编辑 | 更新活动状态,不按回调次数累加连接数 |
2 | 最终文件已准备好保存 | 下载并提交结果,完成本轮会话 |
3 | 保存错误 | 记录错误,进入补偿或恢复处理 |
4 | 无修改结束 | 核对当前会话状态后释放归属 |
6 | 编辑中的强制保存结果 | 保存快照,不结束会话 |
7 | 强制保存错误 | 告警并记录失败任务 |
可靠的保存顺序为:验证回调 JWT、会话与 key → 下载到新的不可变对象 → 校验文件和大小 → 在事务中核对当前 epoch 与版本 → 写入保存记录及版本指针 → 返回 {"error":0}。下载和对象写入在短数据库事务之前进行,避免大文件拖长事务;对象存储与数据库之间用保存记录和补偿任务处理部分成功。
回调可能重复、延迟或乱序。对于已处理的重复通知返回幂等结果;旧 epoch 的文件进入隔离恢复记录,不更新当前版本。同一会话可有多次 status=6,不能只按 key 去重,更不能按回调到达先后认定内容新旧。业务发起的强制保存建议每会话串行,附带自有保存任务标识,待对应任务完成后再发起下一次;最终保存完成后,不允许较早的快照回调回退正式版本。
最后一个标签页关闭与最终保存完成之间存在时间间隔,不能在浏览器 beforeunload 或前端连接计数归零时立即删除归属。
可以建立 OPEN → SAVING → CLOSED,以及 RECOVERING 等业务状态,但这些是业务状态,不能机械地按某一条回调修改。原会话仍可编辑时复用原会话;已进入最终保存处理时,新的打开请求等待保存完成再取得配置。对较早签发、延迟使用的编辑配置,应设置合理有效期,并纳入“重开、断网重连、旧回调到达”的测试;发生状态冲突时先保留结果,不直接解除归属或覆盖版本。
强制保存、查询文档状态等命令必须发送到当前会话实例。以 9.3 为例:
POST https://office01.example.com/command?shardkey=t01_f123_s1001_e7
Content-Type: application/json请求按该实例的 JWT 配置签名。forcesave 返回已接受不等于正式文件已保存成功,仍需确认保存回调与持久化结果。旧版本命令路径存在差异:8.2 以前使用 /coauthoring/CommandService.ashx,8.1 起官方建议携带 shardkey。按实例域名路由时,这个参数不会自动让请求在独立实例之间迁移。
独立的异步格式转换任务同样需要记录 taskId → nodeId → conversionKey,提交、轮询和结果下载访问同一实例。任务超时要先核实原任务状态;若另起重试尝试,分配新的尝试标识,并防止旧结果覆盖当前任务结果。批量转换量较大时,可单独划分转换实例池,降低对交互编辑的影响。
增加 DS-03 时,先部署、配置独立域名和密钥,完成打开与回写测试,再注册为 ONLINE。后续新文档会话可以进入 DS-03,旧会话继续留在原实例。首期不建议频繁自动缩容,以免反复中断长时间编辑的文档。
下线或升级节点时,将其标记为 DRAINING,停止分配新会话,等待原会话结束、最终保存及回调补偿完成。已有会话持续很久时,需要安排维护窗口;不能只设置退出等待时间就认为运行状态已经安全保存。节点、代理、业务服务的部署都要预留退出和连接恢复时间。
节点检查应结合 /healthcheck、端口、进程、资源及保存链路,并设置连续失败阈值,避免短暂抖动反复上下线。节点标记为 OFFLINE 后,停止分配新会话,已有会话进入等待或恢复流程。
故障情况 | 处理方式 | 边界 |
|---|---|---|
进程短暂异常,实例数据完整 | 保留归属,优先恢复原实例并验证重连 | 不能仅凭数据目录存在承诺完整恢复 |
主机不可用,短期无法恢复 | 冻结旧会话,从最近成功保存版本或快照重建 | 用户需重新打开,可能缺少未保存修改 |
结果下载或回调处理失败 | 记录保存任务并补偿,监控结果链接有效期 | 不预先返回保存成功 |
旧实例恢复后发送延迟回调 | 根据 epoch 隔离旧结果 | 不覆盖新会话产出的正式版本 |
重建时应先冻结文件的旧归属并递增 epoch,再用新 key 创建会话;同时尽可能隔离旧节点入口、断开旧会话,阻止继续编辑。epoch 只能约束业务侧版本提交,无法单独关闭浏览器已有连接,也不能合并两份分叉内容。旧节点迟到的有价值文件保留为恢复副本,供后续核对。
对重要文档可以设置周期性强制保存,例如以 1~5 分钟作为初始评估范围,按文档复杂度和转换成本调整。恢复范围应以“最近成功持久化快照”衡量,而非定时器配置;严格协同模式中尚未提交的修改,也不能假定已包含在快照中。
备份应覆盖业务数据库、正式对象文件、密钥配置和各实例所需运行数据;对 9.3 的内置数据库与队列,在线直接复制目录不等于一致性备份。先完成会话保存,再采用停机备份或各依赖支持的备份方式,并通过恢复演练验证。
采用官方 9.4 轻量社区版时,业务归属表、节点域名、会话调度、JWT、保存与故障隔离逻辑继续沿用。实例部署配置需要按新架构整理,移除传统 PostgreSQL、RabbitMQ、Redis 挂载和依赖连接配置;保留配置、日志和文件缓存目录。官方当前社区版 Docker 示例采用以下基础挂载。
# 在独立的9.4部署文件中替换镜像和volumes段;其余通用参数按需沿用
image: ${OO_IMAGE:?请设置已验证的9.4社区版镜像}
volumes:
- ./data:/var/www/onlyoffice/Data
- ./logs:/var/log/onlyoffice
- ./lib:/var/lib/onlyoffice上面是需要调整的配置片段,不是完整 Compose 文件。若通过多个 Compose 文件叠加配置,要检查最终合并结果,避免旧挂载和环境变量仍被保留。旧数据库目录先备份留存,确认旧会话和升级结果后再安排清理。
移除的是 DS 内部的传统依赖,业务后端用于权限、文件版本、会话归属、锁和补偿任务的数据库或 Redis 仍然保留。监控重点相应转向实例进程、实际连接、转换耗时、CPU、内存、磁盘与保存回调,避免把不存在的 RabbitMQ 进程作为 9.4 健康标准。
9.4 取消连接限制后,节点容量不能继续简单按原有固定连接数分配。建议使用实际编辑器连接数、复杂文档比例、打开和保存的 P95 耗时,以及内存峰值共同确定准入上限。高并发打开与集中保存要分别压测,闲置连接很多并不代表转换压力低。
多实例仍用于分担不同文档、隔离大文件压力和缩小故障范围。一份文档的协作者始终归属同一实例,因此“单份热门文档的协同能力”和“多份文档的总体承载能力”必须分别测量。不要把轻量化换算成未经测试的固定内存降幅或容量倍数。
架构变化后,9.3 依赖内部数据库、队列和缓存形成的恢复经验不能直接作为 9.4 的保证。对固定的 9.4 发行镜像,应重新验证编辑进程退出、容器重建、主机故障及保存过程中断。保留挂载目录与等待退出时间都是必要准备,能够恢复哪些状态必须以测试结果为依据。
从 9.3 升级时,建议新增 9.4 实例和独立目录,先验证字体、插件和典型文档,再逐步承接新会话。9.3 旧会话保持原归属,全部保存后下线。不同版本不要同时读写同一个 DS 运行目录,正在编辑的文档也不要仅修改节点映射就直接跨版本接管。
首期以两台服务器、两个实例域名和一套业务调度逻辑落地。入口和业务数据库也要具备所需可用性;只部署多个 DS,不能消除代理或业务后端的单点。实现顺序建议为:
测试场景 | 必须确认的结果 |
|---|---|
两个用户同时首次打开同一文件 | 只创建一个有效会话,得到相同 key 和实例 |
多份文档并发打开 | 分配到不同实例,容量预留不会超卖 |
同一文档新增协作者,原节点接近满载 | 加入原节点或明确等待,不改派其他实例 |
增加第三个实例 | 旧会话归属不变,新会话可进入新节点 |
最终保存期间重新打开 | 不创建与旧保存竞争的新会话,不覆盖新版本 |
重复、延迟、乱序回调 | 幂等处理,快照不回退最终结果,旧 epoch 不覆盖当前文件 |
编辑时停止进程、容器或主机 | 记录实际重连或重开时间,核对恢复内容 |
保存结果链接暂时无法下载 | 不误报成功,补偿可追踪 |
旧节点在故障重建后恢复 | 旧回调被隔离,恢复副本可核对 |
大文档、复杂 XLSX、批量转换 | 无持续 OOM,打开和保存延迟满足目标 |
9.3 向 9.4 逐步切换 | 新旧会话隔离,字体、插件与保存结果一致 |
验收应记录镜像 digest、字体与插件版本、测试文件集、并发模式、打开/保存 P95、故障恢复耗时以及最后成功快照位置。本文配置与伪代码提供实施基线;只有这些业务测试与恢复演练完成,才具备对目标环境承诺容量和恢复指标的依据。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。