使用gotenberg提供LibreOffice文档类型转换服务word转pdf 解决context deadline exceeded 503问题

本文一起做的事:

  • 解决convert to PDF: supervisor run task: context deadline exceeded 503 504 timeout 异常
  • 部署 gotenberg 服务提供 api 接口,实现 word 等 office 文件转换成 pdf 文件的功能。内部实现是 LibreOffice。
  • 使用负载均衡模式,来解决 LibreOffice 处理任务锁住导致并发上不去的问题。

解决convert to PDF: supervisor run task: context deadline exceeded 503/504/timeout异常

部署服务很简单可以直接往下看,这里我们先说解决这个异常,这个是最棘手的。

问题现象

现象出现在,需要处理很多文档的解析,有很多不通地区不同时间不同人不同电脑创建的office word文档,doc/docx 都有,需要转换成 pdf 文件再过 OCR 模型去解析,我使用 gotenberg 这个服务,能提供 api 接口并内部调用 LibreOffic 来实现 pdf 的转换,实际跑批时候发现这个服务经常出现 503/504/timeout 等问题,即使按照文档设置了接口超时到 3600s 也不行,同样会卡住 1h 然后提示超时。

表象是服务超时,但是 1h 还超时就肯定不对了,gotenberg 日志提示convert to PDF: supervisor run task: context deadline exceeded这里的 task 就是 LibreOffice 执行的,它异常,我认为 1h 已经是 LibreOffice 自己卡死了进入死循环了,那么为什么?什么会导致它卡死,为什么有的 word 正常,有的 word 不正常,这是我要找到的原因。

经过很长时间试错,直到我尝试对比多个 word 里包含的字体种类,我猜测是字体的原因。word 转 pdf 最容易出现的就是字体问题,比如 pdf 出现很多方框,还没想过缺少字体会把 LibreOffice 卡死。那么我尝试加上字体试试:

我们按照官网的方式重新打一个docker镜像 https://gotenberg.dev/docs/configuration#fonts

编写Dockerfile文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM gotenberg/gotenberg:8.23.1

USER root

COPY ./fonts/* /usr/local/share/fonts/

RUN sed -i 's@deb.debian.org@repo.huaweicloud.com@g' /etc/apt/sources.list.d/debian.sources

RUN apt-get update && apt-get install -y \
fonts-noto-core \
fonts-noto-cjk \
fonts-noto-color-emoji \
fonts-liberation \
fonts-wqy-zenhei \
fonts-arphic-gbsn00lp \
fonts-wqy-microhei \
fonts-arphic-gkai00mp \
ttf-wqy-zenhei \
fonts-symbola \
fontconfig \
libgl1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN fc-cache -f -v

# USER gotenberg
  • ./fonts/* 目录里是我当前 windows10 电脑里的所有字体,都是 ttf 文件。目录在C:\Windows\Fonts直接全选复制粘贴到我们的这个文件夹里。
  • fonts-noto-color-emoji 是 emoji,没啥用但是加上吧还是。
  • fontconfig 是一个字体管理工具,必须的。
  • fonts-wqy-zenhei 文泉驿正黑,一个高质量的开源中文字体,覆盖了大部分汉字。
  • fonts-noto-cjk 谷歌的思源字体,同时支持中文、日文和韩文,是处理多语言文档的理想选择。能够保障覆盖绝大多数字体。
  • fonts-arphic-gkai00mp 文鼎 PL 简报楷,高质量的楷体字体。
  • fonts-arphic-gbsn00lp 文鼎 PL 简报宋,高质量的宋体字体。
  • fonts-symbola 包含大量 Unicode 符号和特殊字符,对于处理数学公式或特殊符号的文档很有帮助。
  • ttf-mscorefonts-installer 包含微软常用的核心字体,如 宋体 (SimSun)、黑体 (SimHei)、Times New Roman 等。这个包能很好地解决 Word 文档兼容性问题。gotenberg原始镜像里包含这个工具,所以我没有重新装。

你要问上边这些怎么选出来的:Gemini给的。

fc-cache -f -v是使用 fontconfig 刷新系统字体缓存。

注意官方最后还是用了USER gotenberg,但是这里我不用,仍然用 root 账户。

然后我们编写docker-compose.yml文件

1
2
3
4
5
6
7
8
9
10
services:
gotenberg-api:
build:
context: .
dockerfile: Dockerfile
environment:
- TZ=Asia/Shanghai
env_file:
- .env
restart: unless-stopped

编写 gotenberg 的环境变量文件.env

1
2
3
4
5
6
API_TIMEOUT=240s  # gotenberg api接口内部超时时间
#
LIBREOFFICE_START_TIMEOUT=60s # LibreOffice启动最长等待时间
LIBREOFFICE_MAX_QUEUE_SIZE=1 # 最大队列1
LIBREOFFICE_RESTART_AFTER=1 # 执行一个重启一次
LIBREOFFICE_AUTO_START=true # 在启动容器时LibreOffice自动启动

我们编译新的镜像,并启动服务

1
2
docker compose build
docker compose up -d

这时候再跑之前老转换超时的 word 文件,应该就能解决问题。

最重要的一点,这个方法解决了我大部分问题,但是还是有 word 文档超时,504/503 问题还是会出现,所以还要持续观察一段时间,看是别的原因,还是又缺少别的字体。

配置负载均衡模式提高服务并发

LibreOffice 自己带锁,一次只跑一个任务,所以我们要上 nginx,启动多个 gotenberg 实例来负载均衡。

编写docker-compose.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
services:
gotenberg1:
build:
context: .
dockerfile: Dockerfile
container_name: gotenberg-instance-1
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
networks:
- gotenbergNetwork
gotenberg2:
build:
context: .
dockerfile: Dockerfile
container_name: gotenberg-instance-2
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
networks:
- gotenbergNetwork
nginx:
image: nginx:alpine
container_name: gotenberg-load-balancer
ports:
- "33332:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- gotenberg1
- gotenberg2
restart: unless-stopped
networks:
- gotenbergNetwork
networks:
gotenbergNetwork:
driver: bridge

也可以用云原生docker命令直接启动多个实例。我懒,所以不用。

编写nginx.conf文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
upstream gotenberg_backend {
# 指向你的 Gotenberg 服务,使用容器名称作为主机名
server gotenberg-instance-1:3000;
server gotenberg-instance-2:3000;
# 负载均衡算法
random;
}

server {
listen 80; # Nginx 监听容器内部的 80 端口
server_name localhost; # 可以是你的域名或IP

# 设置客户端最大上传文件大小(根据需要调整)
client_max_body_size 500M;

location / {
proxy_pass http://gotenberg_backend; # 将请求代理到 Gotenberg 后端集群
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;

# 增加 Nginx 与后端服务的超时时间,以匹配 Gotenberg 的 --api-timeout
proxy_connect_timeout 480s;
proxy_send_timeout 480s;
proxy_read_timeout 480s;
}
}

编写 gotenberg 的环境变量文件.env

1
2
3
4
5
6
API_TIMEOUT=480s  # gotenberg api接口内部超时时间
#
LIBREOFFICE_START_TIMEOUT=60s # LibreOffice启动最长等待时间
LIBREOFFICE_MAX_QUEUE_SIZE=1 # 最大队列1
LIBREOFFICE_RESTART_AFTER=1 # 执行一个重启一次
LIBREOFFICE_AUTO_START=true # 在启动容器时LibreOffice自动启动

注意这里的 API_TIMEOUT 数值,要和 nginx.conf 里的 timeout 值都写相同值。

最后我们编译镜像并启动容器即可使用。

其他

官方文档 https://gotenberg.dev/docs/configuration
我给官方提的 QA,官方也搞不清楚怎么解决 https://github.com/gotenberg/gotenberg/discussions/1335