渠道与容灾
渠道是什么、上传写到哪里、副本如何补齐、访问如何切换,以及健康探测、编辑、删除与用量告警的规则。
渠道是什么
一个渠道就是一个 rclone 后端实例:一种后端(s3、webdav、local……)加一组后端参数,再加上 ImgYoung 自己的几项属性:
| 属性 | 含义 |
|---|---|
| 渠道名 | 唯一,最长 64 字节,用于界面、日志、指标与告警 |
| 优先级 | 整数,越大越优先;后台表单接受 0–1000,新建默认 100 |
| 服务端加密 | 是否经 rclone crypt 加密存储,创建时固化,见渠道存储加密 |
| 允许 302 直链 | 访问时是否可以 302 跳转到渠道的公开地址,见对象存储与 WebDAV |
后端参数以映射直接交给 rclone,不经过 rclone.conf;参数整体用主密钥加密(AES-256-GCM)后存入元数据库。渠道在管理后台的「存储渠道」页创建与管理,只有管理员可见;对应接口见渠道管理接口。接口返回的渠道对象(列表、创建、编辑、重新探测的响应,以及元数据导出里的 channels)带 created_at:渠道的创建时刻(UTC),参与加密渠道的密钥派生,建后不变。
每个渠道最终都持有全部图片的副本:多个渠道之间是全量镜像,不是分片。规划容量时按每个渠道都要放下全站图片来算。
两个实例不能共用同一个渠道路径
每日对账只回收本服务写出的孤儿对象,必须同时满足:键名是本服务的对象键格式 <短 ID>_<SHA-256 前 8 位>;元数据库里没有该渠道上任何状态的副本记录;修改时间不早于渠道的绑定时刻;已超过 48 小时。深探测残留 .imgyoung-probe-* 过了 48 小时也会回收。别的程序写进同一路径的文件只计数(对账日志字段 foreign),不会删除。两个 ImgYoung 实例不能共用同一个渠道路径:对方写出的对象在本实例看来就是孤儿,会被删除。
绑定时刻是渠道记录开始对应当前存储位置的时刻:新建渠道时等于创建时刻,编辑改动配置时重置;从快照恢复(后台恢复,或把快照放成 <数据目录>/imgyoung.db.restore 再启动)时全部渠道一起重置。完整规则见元数据库备份的「对账 GC」。
内置 local 渠道
全新的元数据库(渠道表为空)首次启动时,会自动建一个名为 local 的渠道:后端 local,优先级 100,存储位置是 <数据目录>/blobs。这样不做任何配置也能上传。
- 只在渠道表为空时播种。删除或改名之后,重启不会让它重新出现。
- 它和元数据库、主密钥在同一台机器上,主机损坏即全部丢失。只有它一个渠道时,「存储渠道」页会提示「只有本机磁盘一个渠道」。接入一个异地渠道后,存量图片会自动补齐过去;元数据库快照每轮写到每个健康渠道,下一轮备份起新渠道上也有快照。
优先级:主渠道与备份渠道
ImgYoung 不设单独的「主渠道」开关,主备完全由优先级表达:
- 主渠道:健康、且不在清理中的渠道里优先级最高的一个。列表里标为「主写」。
- 备份渠道:其余渠道。正常情况下上传不直接写它们,由异步补齐把副本复制过去;主渠道写入失败顺延、或
min_success大于 1 时才会同步写入。 - 优先级相同时,先创建的渠道排在前面。
主渠道变为异常时,下一个健康渠道自动接替,无需人工切换;它恢复健康后重新成为主渠道。
上传写入
每次上传都按优先级从高到低遍历当前健康的渠道,同步写入,直到成功份数达到 min_success:
- 写入某个渠道失败(含单次 10 分钟的写入超时),顺延到下一个健康渠道。
- 凑够份数即返回成功。第一个写成功的渠道就是这张图的首个副本所在。
- 其余渠道(包括暂时异常的渠道)都记一条「待补齐」副本,交给异步补齐。
min_success 在「设置 → 运行配置 → 访问与链接 → 最少同步副本」,默认 1,最小 1。健康渠道不足或全部写失败时,上传返回 503 存储渠道暂不可用,请稍后重试(S3 协议上传返回 503 ServiceUnavailable),不落元数据;已写成功的那几份对象成为孤儿,由每日对账回收。
min_success 大于当前健康渠道数时,所有上传都会失败。设为 2 的意思是:至少两个渠道同时写成功才算上传成功,换来的是写入时延与对多渠道可用性的依赖。
异步补齐
副本状态存在元数据库的副本表里,这张业务表是唯一依据;任务队列(River,同一个 SQLite 库)只负责触发,同一张图在同一渠道上同时最多只有一个在途任务。
| 副本状态 | 含义 |
|---|---|
pending | 待补齐 |
uploading | 补齐进行中 |
done | 就绪,可用于访问 |
failed | 重试用尽仍失败 |
- 复制过程:从优先级最高、健康且已有就绪副本的其他渠道读出,流式写入目标渠道。任务队列 4 个并发(与删除、排空任务共用),单个补齐任务超时 15 分钟。
- 失败重试:失败后回到
pending,由 River 按指数退避重试:第 n 次失败后约等 n⁴ 秒(1 秒、16 秒、81 秒……),最多 25 次,前后约 20 天;最后一次仍失败记为failed。 - 周期对账:启动时与之后每 10 分钟各做一次:为每个在役渠道补种缺失的副本行;把健康渠道上的
failed副本复位为pending;把全部未完成副本重新入队(已在队列中的自动去重)。后台「设置 → 运行配置 → 引导配置」里显示的「副本对账间隔」就是它。 - 进程重启不丢:任务和副本状态都在 SQLite 里。正常停机时在途的补齐任务被取消,副本状态留在业务表里,下次启动由对账重新入队;进程被强杀时正在执行的任务,约 16 分钟后由 River 重新排程。
- 新渠道回填存量:创建渠道时,在同一个事务里为全部存量图片登记待补齐副本,随后立即开始补齐。与创建同时进行的上传如果漏登记了新渠道,周期对账会补上。
- 每日对账 GC:每 24 小时对健康渠道做一次双向核对。本服务写出、却没有任何副本记录的对象超过 48 小时后删除(孤儿,判定条件见本页开头);元数据库记为就绪、渠道上却不存在的副本改回
pending重新补齐(幻影)。
访问容灾
访问一张图时,候选渠道是:健康、不在清理中、且这张图的副本已就绪的渠道,按优先级排序。
- 代理模式(
access_mode = proxy,默认):按顺序读取,某个渠道读失败就切到下一个,对访问者透明。没有任何候选或全部读失败时返回 503渠道暂不可用。 - 302 模式(
access_mode = redirect):候选中第一个「允许 302 直链、未加密、配置了公开访问地址」的渠道直接 302 跳转;没有这样的候选就回到代理。带处理参数的请求(如?w=)始终走代理。
副本还没补齐到某个渠道时,该渠道不会成为这张图的候选。访问模式与缓存细节见访问与链接。
健康探测
健康状态决定一个渠道能否承接上传与访问。探测分两档:
| 轻探测 | 深探测 | |
|---|---|---|
| 做什么 | 查询一个约定不存在的对象 .imgyoung-health-probe(从不写入);查到或得到「不存在」都算可达 | 写入一个随机键的小对象 .imgyoung-probe-<随机十六进制>,读回核对内容,再删除 |
| 成本 | 只读,零写删 | 一次写、读、删 |
| 超时 | 10 秒 | 写与读共 20 秒,删除另有 10 秒 |
| 何时运行 | 周期循环,每 health_check_seconds(默认 60 秒) | 创建渠道后、编辑保存后、点「重新探测」、点「测试连接」;周期循环里异常渠道的轻探测通过时 |
周期循环的规则是轻探测只降不升:
- 健康渠道每轮只做轻探测,失败即标为异常。
- 异常渠道先做轻探测;通过后再做一次深探测,写、读、删全部通过才恢复健康。
- 只读成功不能推翻深探测的失败结论。例如只读凭证能通过轻探测,却写不进去,这类渠道不会被轻探测「救活」。
周期探测只用只读的轻探测:对按量计费的存储,每分钟写删一次探测对象是无谓的费用;写路径的问题由事件触发的深探测与恢复前的确认把关。
各渠道并行探测、各自计时,一个慢渠道不会拖累其他渠道。清理中的渠道不参与探测。停机时正在进行的一轮作废,不写入结果。
探测结果写回元数据库,在「存储渠道」页的「状态」列可见:「健康」「异常」或「清理中」;鼠标悬停在「异常」上显示失败原因(已翻译成排障提示,不含凭证);下方小字是最近一次探测的时间,从未探测过显示「未探测」。常见失败原因与排查见对象存储与 WebDAV的「常见报错与排查」。
渠道无法装载时
某个渠道的配置无法解密、或后端初始化失败时,该渠道会被跳过(日志 渠道装载失败,本渠道跳过,健康检查时重试),其余渠道照常服务。它在列表里显示为「异常」,失败原因以 装载失败: 开头。健康检查每一轮都会先重试装载,点「重新探测」也会先尝试装载;装上之后还要通过一次深探测才恢复健康、参与选路。启动时全部渠道都无法装载,则启动失败。
副本水位
「副本」列显示该渠道上的副本统计:「就绪」「待补」(含补齐进行中)「失败」。待补长期不降、或出现失败,说明这个渠道写入有问题:先看状态列的失败原因,点「重新探测」确认。渠道恢复健康后,失败副本会在下一次周期对账时重新补齐。
测试连接
创建与编辑表单底部的「测试连接」用表单当前内容做一次深探测,不保存、不建渠道:
- 通过显示「连接正常(写读删探测通过)」;失败显示「连接失败:」加原因,原因同样经过翻译,并抹掉表单里填的凭证。
- 不会创建存储桶,也不套加密层:验证的是后端连通性与写、读、删权限。
- 编辑时,保持掩码的凭证会先还原成已保存的原值再探测;沿用的已存令牌如果在测试中被刷新,新令牌直接写回该渠道。
- 本次表单里提交的令牌如果在测试中被服务商刷新(失败时也可能发生),新令牌交还给表单:结论下方提示「服务商在测试时刷新了授权令牌,表单已换用新令牌,保存时提交的就是它;原令牌可能已失效。」,授权栏显示「已授权」,之后保存提交的就是新令牌。详见网盘与 OAuth。
创建渠道
「存储渠道」页点「新建渠道」,先选存储类型,再填表单:
选类型:「对象存储」「网盘」下是内置模板,「高级」下是「自定义」(直接填 rclone 后端名与参数)。各模板的字段见对象存储与 WebDAV与网盘与 OAuth。
填写渠道名、模板字段、优先级,选择「服务端加密」;对象存储可开「允许 302 直链」与「自动创建存储桶」。
点「测试连接」,通过后点「创建」。
创建时先用填写的参数试构造一次后端(需授权的模板还会先补齐授权带出的参数,如 OneDrive 的驱动器),参数非法直接拒绝;入库后立即做一次深探测。深探测失败不会撤销创建:渠道保留,状态为「异常」,并提示「渠道已创建,但探测失败:」加原因,改好配置后点「重新探测」即可。
编辑渠道
行菜单「编辑」打开与创建相同的表单,可以改:渠道名、模板字段(或自定义参数)、优先级、「允许 302 直链」。
- 不能改:「服务端加密」(表单只显示「已加密」或「未加密」,接口拒绝变更并返回 409);存储类型(编辑沿用渠道创建时的模板;自定义渠道与内置 local 渠道以自定义表单打开);「自动创建存储桶」只在创建时出现。
- 凭证掩码回读:编辑表单回读配置时,凭证不下发,以固定的掩码值
__imgyoung_secret_keep__代替。掩码范围以 rclone 后端的选项元数据为准:rclone 标记为密码或敏感信息的参数都掩码,除密码、密钥与授权令牌外,还包括访问密钥 ID(Access Key、SecretId、Key ID)、WebDAV 用户名、应用 Client ID、OneDrive 的drive_id等;模板表单与自定义参数同一口径,这些栏以掩码输入框显示。保持掩码原样提交即沿用已保存的原值,输入新值即替换;掩码值填在不属于凭证的参数上会被拒绝(400)。 - 配置整体替换,不做增量合并。保存后只重建这一个渠道的驱动实例,其他渠道正在进行的读写不受影响;随后立即深探测,失败时提示「渠道已保存,但探测失败:」加原因。
- 绑定时刻重置:后端名或任何配置参数(包括公开访问地址)有改动,即把渠道的绑定时刻重置为保存时刻。重新输入同一个密码也算改动,因为 rclone 编码密码时带随机 IV。只改渠道名、优先级或「允许 302 直链」不重置,令牌刷新写回也不重置。重置之后,新位置上早于这一刻的本服务格式对象只计数、不回收,见元数据库备份的「对账 GC」。
不要用编辑来迁移存储位置
编辑不会触发回填。改了桶、前缀或服务端点后,这个渠道上原有的副本仍记为就绪,读取会失败并切到其他渠道,直到每日对账发现对象缺失、重新补齐。要换存储位置,应新建一个渠道,等「副本」列的「待补」归零后再删除旧渠道。
删除渠道
行菜单「删除」打开确认框,勾选项「同时删除本服务写在该渠道上的数据」决定走哪条路径:
| 删除并清理(默认勾选) | 仅移除(取消勾选) | |
|---|---|---|
| 立即效果 | 渠道转为「清理中」,立刻退出上传选路、补齐与探测 | 渠道与其副本记录立即删除 |
| 远端对象 | 本服务写的对象(图片副本、元数据库快照等)由后台排空任务逐个删除,删完自动移除渠道;别的程序写的文件不动 | 图片副本与元数据库快照原样保留,需要时自行到存储商处理 |
| 适用 | 正常下线一个渠道 | 凭证已失效、桶已不存在,或清理卡住 |
两条路径都有唯一副本保护:如果某张图在其他渠道上没有就绪副本(清理中的渠道不算),删除会被拒绝,返回 409 该渠道持有部分图片的唯一副本,删除会丢数据;请先迁移或删除相关图片。检查与删除在同一事务中完成,两个渠道先后删除也不会把同一张图的副本删光。在其他渠道上仍是「待补」的副本不算数,等它们补齐后再删。
清理的细节:
- 排空任务先按副本记录逐批删除图片对象并删掉对应记录,进度保存在元数据库里,中断后从剩余部分继续。
- 图片副本删完后,再删
_imgyoung/下的全部对象(含_imgyoung/backup/下的元数据库快照)与根目录下的深探测残留.imgyoung-probe-*,然后才删渠道记录。别的程序写的文件不动。完成时的日志渠道已清理完毕并删除带字段objects(图片副本数)与system_objects(系统对象数)。 - 单次排空最长 1 小时,超时后由下一次排空从剩余部分继续。
- 删除对象一失败,原因就写进状态列(以
清理渠道对象失败,会自动重试开头),同时开启「渠道排空失败」告警。排空按退避持续自动重试,不会停止,渠道恢复可用后会自行清完。排空自身超时或停机取消不算失败。 - 凭证已失效或存储桶已不存在时,用行菜单里唯一的「直接移除」:停止清理并立即移除渠道,尚未删除的远端对象原样保留。
导出 rclone 配置
行菜单「导出 rclone 配置」(清理中的渠道没有这一项)下载渠道的 rclone 配置文件 imgyoung-channel-<渠道 ID>.rclone.conf。标准 rclone 凭它就能脱离 ImgYoung 读写这个渠道上的数据:
- 远端
imgyoung-<渠道 ID>:渠道的后端,参数与 ImgYoung 交给后端的完全一致,含访问凭证与授权令牌。 - 远端
imgyoung-<渠道 ID>-crypt:只有加密渠道才有,是指向渠道存储路径的加密层,经它读写的就是明文。 - 文件开头的注释写明各远端的含义与用法示例。
文件里有渠道凭证(加密渠道还有解密密码),弹窗提示「和主密钥一样离线妥善保存:拿到它的人可以直接读写这个渠道上的全部数据。」。每次下载都记一条审计日志「导出渠道 rclone 配置」。文件的用法与注意事项见渠道存储加密的「自救」。
用量监控与告警
渠道用量按「该渠道上已就绪副本的图片大小之和」统计,是逻辑用量:不含元数据库快照与孤儿对象,也不等于存储商账单。
- 每
usage_report_seconds(默认 300 秒)刷新一次,写入指标imgyoung_channel_storage_bytes{channel="…"};「总览」页的「存储渠道」卡片展示各渠道的已存字节与占比。 - 告警阈值是「设置 → 运行配置 → 可靠性与渠道 → 渠道用量告警阈值」(
channel_quota_gb,单位 GB,按 2³⁰ 字节计);0 表示只上报、不告警。超过阈值开启「渠道用量超阈值」告警。
与渠道相关的告警共四类,和健康探测同一节奏评估,记录在「设置 → 告警历史」,配置了告警 Webhook 时同步外发:
| 告警 | 开启条件 | 恢复条件 |
|---|---|---|
| 渠道不健康 | 在役渠道健康标记为异常 | 探测恢复健康 |
| 副本补齐失败 | 渠道上存在 failed 副本 | 渠道上不再有 failed 副本(渠道健康时,周期对账会把它们复位重试) |
| 渠道用量超阈值 | 用量大于阈值 | 用量回落或阈值调高 |
| 渠道排空失败 | 清理中渠道删除对象失败 | 渠道被移除(清完自动移除或直接移除) |
其他相关指标:imgyoung_channels_healthy_total(健康渠道数)、imgyoung_channels_total(已装载且不在清理中的渠道数,含异常渠道)、imgyoung_channel_puts_total{channel, result}(向渠道写对象的次数,result 为 ok 或 error,含同步写入与异步补齐)。指标与告警的接入见监控与告警。