解决Halo2.x版本升级后启动卡死,MySQL 5.7版本兼容性及索引报错解决方案
最近用 1Panel 把 Halo 升级到了 2.25.0,结果容器死活起不来。看日志发现卡在数据库连接那里不动了,健康检查也一直超时。中间重启了几次,偶尔还会爆出 Duplicate key name 'idx_xxx' 的索引重复错误。
折腾了一晚上,总算把问题理顺了,记录一下给遇到同样问题的朋友避个坑。
问题到底出在哪?
一开始看到 Duplicate key name,我以为是数据库索引冲突导致启动失败,跑去 MySQL 里把报错的索引删了,结果重启还是卡死。
后来仔细查了下资料,发现其实是两个问题叠加导致的,而且“卡死”的真凶根本不是索引报错。
1. 日志卡死不动的真凶:TLS 握手失败
官方给出的排查结论是:新版 OpenJDK 默认禁用了 TLS_RSA_* TLS 算法,导致无法和低版本 MySQL(5.7)建立 SSL 连接。
通俗点说就是,新版 Halo 镜像里的 JDK 比较新,觉得老的加密算法不安全就给禁了;但 MySQL 5.7 默认就支持这些老算法。结果就是 Halo 尝试用 SSL 连 MySQL 时,握手直接失败或者无限挂起,导致 Spring Boot 启动流程直接卡死。
2. 索引报错的原因:初始化脚本不严谨
Halo 自带的 schema-mysql.sql 脚本在创建某些索引时,没加 IF NOT EXISTS 判断。如果数据库里已经有这些索引了,它再次执行就会报 Duplicate key name 错误,阻断启动。
解决方法:
针对 TLS 握手卡死的问题,官方其实给出了三种解决方案:
在启动参数中添加
--spring.r2dbc.properties.sslMode=disabled(仅对 MySQL 驱动有效)。在
spring.r2dbc.url尾部添加?sslMode=disabled(仅对 MySQL 驱动有效)。升级至 MySQL 8。
至于那个 Duplicate key name 的索引报错,我们需要再加一个参数 --spring.sql.init.continue-on-error=true 让脚本忽略错误继续执行。
如果你是用 Docker Compose 或者 1Panel 部署的,找到 Halo 的配置,在启动参数(command)里改一下。官方的方案 1 和方案 2 任选其一即可,我这里用的是
方案 1:
networks:
1panel-network:
external: true
services:
halo:
command:
- --spring.r2dbc.url=r2dbc:pool:${PANEL_DB_TYPE}://${PANEL_DB_HOST}:${PANEL_DB_PORT}/${PANEL_DB_NAME}
# 官方方案1:直接在参数里禁用 SSL(如果是方案2,就把 ?sslMode=disabled 拼到下面 url 的末尾)
- --spring.r2dbc.properties.sslMode=disabled
# 允许初始化脚本报错并继续,解决索引重复报错
- --spring.sql.init.continue-on-error=true
- --spring.r2dbc.username=${PANEL_DB_USER}
- --spring.r2dbc.password=${PANEL_DB_USER_PASSWORD}
- --spring.sql.init.platform=${PANEL_DB_TYPE}
- --halo.external-url=${HALO_EXTERNAL_URL}
#添加redis
- --spring.data.redis.host=redis
- --spring.data.redis.database=0
- --spring.data.redis.port=6379
- --spring.data.redis.password=redis密码
- --halo.session.store-type=redis
- --halo.redis.enabled=true
# ... 其他参数保持原样 ...
container_name: ${CONTAINER_NAME}
deploy:
resources:
limits:
cpus: ${CPUS}
memory: ${MEMORY_LIMIT}
environment:
- JVM_OPTS=
healthcheck:
interval: 30s
retries: 5
start_period: 30s
test:
- CMD
- curl
- -f
- http://localhost:8090/actuator/health/readiness
timeout: 5s
image: halohub/halo-pro:2.25.0
labels:
createdBy: Apps
networks:
- 1panel-network
ports:
- ${HOST_IP}:${PANEL_APP_PORT_HTTP}:8090
restart: always
volumes:
- ./data:/root/.halo2
改完之后重启容器,再看日志,应该就能顺利通过数据库连接阶段,最后输出 Netty started on port 8090 了。
为什么不用 init.mode=never?
在解决索引报错时,很多人(包括我一开始)会想到直接把数据库初始化关掉,也就是加上 --spring.sql.init.mode=never。
强烈建议别这么干。
如果直接设为 never,虽然眼前的索引报错没了,但如果以后 Halo 升级,schema-mysql.sql 里加了新的基础表,系统就不会去自动建表了,到时候会引发更严重的故障。
用 continue-on-error=true 就稳妥得多:
遇到已存在的索引报错,它会忽略并继续往下走,不会卡死。
如果遇到真正需要新建的表(
CREATE TABLE),它依然会正常执行。至于 Halo 核心的版本升级(Flyway)和插件建表(Extension 机制),都是独立运行的,不受这个参数影响。
关于为什么不直接听官方的“升级 MySQL 8”?
看到官方给的第三个方案“升级至 MySQL 8”,肯定有人会问:既然 5.7 八字不合,为啥不直接升 8.x 一劳永逸?
实不相瞒,我这台服务器就是个 4核4G 的配置,上面还跑着一大堆其他的应用和服务。MySQL 8 那个吃内存的德行,真跑起来估计分分钟 OOM 把整台机器干宕机,4G 内存跑 MySQL 8 纯属给自己找不痛快。
再加上升级数据库还得停机、备份、倒腾数据,服务器上业务多,实在懒得折腾。所以对于我这种配置受限又不想折腾的人来说,直接用官方的方案 1 或方案 2,加个 sslMode=disabled 凑合用,才是最省事的解法。反正 Halo 和 MySQL 都在本机内网通信,关掉 SSL 也没什么安全风险,还能稍微省点 CPU 开销。
最后
启动成功后,日志里可能还会偶尔飘过几条 Duplicate key name 的 WARN 警告,不用管它,只要服务正常跑起来了就行。希望能帮到同样在半夜折腾 Halo 升级的兄弟。