Phorge 现代化改造实战(九):迁移文件存储,写得进去也要读得回来
内容提要
本文介绍Phorge文件存储服务现代化改造,核心是将文件存储迁移至单仓,新增独立二进制、三个存储后端及路由。改造重点包括直接传输二进制而非base64、保留8MB分块限制、按优先级选择后端、处理存量handle格式、解决首次启动依赖死锁。强调配置合法不等于文件真正落入预期后端,需显式切换并保护存量数据可达性。
延伸解读
配置合法不等于文件落入预期后端
文章强调,即使服务启动、引擎出现在列表、S3配置完整,新文件也不一定写入预期后端。例如,只要本地磁盘仍启用且写入成功,优先级5的本地盘会先接下所有blob不收的文件,优先级100的S3没有机会。因此,配置后需显式关闭原生后端,并检查Config页面上的SetupCheck提示,才能确保文件真正进入Gorge。
保留8MB限制的深层原因
Gorge引擎没有覆写hasFilesizeLimit()为false,看似限制能力,实则保护Phorge的分块机制。若宣称无上限,Phorge将不再分块,2GB文件会变成一次2GB HTTP请求,破坏断点续传和有界内存。保留8MB限制使文件<=8MB直接写入,>8MB由Phorge切成4MB块再写入,既保证大文件存储,又维持原有可靠性。
存量数据可达性是迁移核心
迁移中,复合handle的格式(如blob/12345)必须按第一个斜杠拆分,不能随意改名或改变前缀,否则新文件自洽但存量文件全部不可达。此外,删除不存在对象返回成功,是为了避免Phorge元数据记录残留。回滚只影响新写入,已存文件仍由Gorge读取,但移除服务或卷会导致文件不可达,需谨慎操作。
首次启动依赖死锁与就绪探针设计
全新部署时,Phorge等待file-storage健康,而file-storage的/readyz若检查表存在,则需等待Phorge执行storage upgrade,形成死锁。因此/readyz只检查引擎注册和数据库连接,不查询表。编排中file-storage探活/readyz,而mailer和search只探/healthz,因为file-storage有本地卷可写,且文件是同步依赖,不可写时让站点等待更合适。
Q&A
Phorge 文件存储迁移中,为什么 Gorge 引擎不能声明为“无文件大小限制”?
因为该限制参与 Phorge 分块引擎的选择。若声明无限制,Phorge 会认为 Gorge 能直接接受任意大小文件,从而不再分块,导致大文件变成一次巨大的 HTTP 请求,破坏断点续传和有界内存。保留 8MB 限制可让大文件先由 Phorge 切成 4MB 块,再逐块写入 Gorge。
Phorge 文件存储迁移后,为什么读取和删除文件时必须同时提供 engine 和 handle?
因为不同后端的 handle 可能重叠,如果只根据 handle 猜测后端,可能读到另一个对象。因此每次读取和删除都必须明确指定 engine 和 handle,以确保访问正确的数据。
Phorge 文件存储迁移中,为什么新接口直接传输二进制而不是 base64?
因为 base64 会使数据体积增加约 33%,且 PHP 和 Go 两端都需要额外的编码解码,放大网络体积、内存峰值和复制次数。直接传输 application/octet-stream 更高效。
Phorge 文件存储迁移后,如何确保新文件真正写入 S3?
需要关闭本地磁盘和 blob 后端,因为默认优先级中本地磁盘(优先级5)会先于 S3(优先级100)接收文件。要让大文件进入 S3,需关闭本地磁盘;要让所有文件进入 S3,还需同时关闭 blob。
Phorge 文件存储迁移中,为什么 /readyz 不检查 file_storageblob 表是否存在?
因为该表由 Phorge 的 bin/storage upgrade 创建,而 Phorge 容器又等待文件服务就绪,若 /readyz 检查表存在会导致死锁。因此 /readyz 只检查引擎注册和数据库连接,不查询表。
Phorge 文件存储迁移后,为什么删除不存在的对象返回成功?
因为 Phorge 的清理流程是先删除字节再删除元数据记录。如果字节已不存在却返回错误,元数据记录会残留,导致后续清理持续失败。因此删除不存在的对象返回成功,符合清理流程。
Phorge 文件存储迁移后,如何切换新文件写入 Gorge 引擎?
需要关闭 Phorge 原生的三个后端:设置 storage.mysql-engine.max-size 为 0、storage.local-disk.path 为 null、storage.s3.bucket 为 null。这样 Gorge 成为优先级最高的可写引擎。启动脚本不会自动执行此步骤,需操作者显式决定。
Phorge 文件存储迁移后,为什么复合 handle 必须按第一个斜杠拆分?
因为 handle 格式如 local-disk/ab/cd/...,若按最后一个斜杠切会错误地将 local-disk/ab/cd 当作引擎名;直接 explode('/') 取两段又会截断真正的 handle。按第一个斜杠拆分才能正确分离引擎名和 handle。