不可变文件发布
全库目录221
本文目录9
场景7的当前清单g7指向不可变版本A。发布者P准备版本B,发布者Q同时准备版本C。本文说明文件准备、清单代次比较、持久回执与暂存保护如何共同决定正式版本。字节读取与解码见文件IO;跨进程读者和删除器的共同仲裁见文件版本代理与回收。
不可变文件与持久清单
文件准备与正式入口
直接覆盖正式文件会让读者看到半份结果。单文件操作在同一文件系统创建唯一临时文件,写完、刷新、关闭并重新读取校验,再以平台替换操作切换名称。参考实现的publish使用mkstemp、flush、fsync和os.replace;替换失败清理暂存内容并保留正式文件。
flush只将语言缓冲交给系统,文件同步请求数据落到平台规定的持久边界;目录项和设备缓存仍有各自规则。原子可见表示读者不会看到半次名称替换,掉电持久表示重启后仍能找到指定字节,两者分开验收。需要强恢复保证的发布器使用目标文件系统要求的文件与父目录同步,实际能力写入部署契约。
多文件版本先写唯一不可变目录,校验每个文件的哈希、大小和依赖,再使目录元数据达到持久边界。控制数据库保存当前清单引用、单调代次和发布回执;在同一事务内比较期望旧代次,写新引用和回执。发布事务还比较目标版本已经完成校验且处于可读状态,并在切换引用的同一事务内建立当前版本保护根;Deleting版本不能被重新选中。发布CAS、读取接纳与删除器共用版本状态仲裁,才能阻止检查完成后目标目录又被删除。普通文件的“先读哈希再rename”没有这种条件比较能力。
文件持久化先于引用提交。暂存完成而事务未提交就崩溃,正式入口仍指旧版本;引用已提交后,恢复按回执返回同一次结果,不再重复推进代次。这个顺序与编辑器工具的事务与批处理的发布协议一致。
发布任务登记与续行
场景7的B文件准备好之后,发布任务还可能停在正式清单切换之前。发布器先在控制数据库耐久登记任务ID、发布ID、目标目录与哈希、期望代次g7及依赖清单,并建立暂存保护,再安排工作线程。工作线程把验证完成写为Prepared,然后调用控制事务发布。任务表保存交付意图,发布回执表保存权威结果,线程队列只负责执行调度。
登记后尚未派发、Prepared后尚未调用事务、事务成功后未收到响应,分别是三个可恢复状态。恢复器先按原发布ID查询回执:存在就把原结果并入任务;确定不存在且任务仍开放时,用同一ID、摘要和期望代次续交;查询不可用时保持Unknown与暂存保护,等待对账。两个恢复工作线程同时续交时,控制事务按发布ID及摘要仲裁,最多产生一份回执。线程投递失败不等于发布失败,也不能因此删除B。
任务采用回执并耐久保存OutcomeKnown后,才可通知上层或按失败规则解除暂存保护。成功发布已在控制事务内建立正式版本根,旧暂存根的解除不会使B失去保护。恢复再次中断时仍先查同一回执;任务阶段的推进只合并已有结果,不重复推进清单代次。发布事务还比较任务处于可发布状态及当前所有者纪元。取消通过同一控制事务把任务改为Closing,关闭新派发,与已经开始的发布竞争同一状态记录:发布先提交便保留成功回执,取消先提交便使尚未提交的发布失败。等待全部执行者退出后再次核对回执,已提交的B按正式版本处理,确定尚未提交的暂存B才可回收。
发布冲突与恢复
发布请求保存稳定发布ID、输入清单摘要、期望旧代次、目标版本和所有者纪元。稳定业务摘要绑定输入清单、期望旧代次与目标版本;所有者纪元属于本次调用的授权封套,单独核对,不进入稳定摘要。恢复者取得新纪元后,先以当前授权查询原发布ID的结果,再决定是否续交同一业务请求。控制事务先查询这个ID:已经成功便返回原回执,已经冲突便返回原冲突,摘要不同则拒绝身份复用;只有尚未处理的请求才执行代次比较并登记结果。提交响应丢失时查询原ID,不能直接把新读到的代次当作期望值重发,否则一次未知结果可能被执行成第二次发布。
例如P和Q都从g7/A开始,分别准备B和C。P先提交成为g8/B,Q比较g7失败,记录Conflict及观察到的g8,C仍在Q的暂存保护下。Q随后读取固定的g8/B,校验C的输入依赖是否与B兼容:依赖已经变化就重新构建;仅当发布策略允许覆盖且依赖仍满足时,才以新的发布ID、期望g8提交C。人工选择保留B则把Q任务终结,等待构建执行者退出后解除C的暂存保护。冲突后的继续、重建和放弃都有新的任务状态,失败CAS本身不会自动取得覆盖后来版本的权限。
依赖检查读取固定清单,输入依赖记录(资源ID,内容哈希),并包含构建规则及格式版本。Q对g8/B完成兼容性检查后,新请求仍比较期望g8;期间入口变化使CAS失败,检查结果随该请求终结。发布器重新取得清单、检查依赖后才创建下一请求,不能只把期望代次改成新值。正式清单之外还有独立可变依赖时,它们的版本条件也参与同一控制事务,或先归并成受g保护的不可变输入清单。只比较目标材质版本,会漏掉纹理或格式规则已变化的情况。
Q在冲突后、任务终结前退出时,恢复先查询Q的原回执,并恢复暂存登记;不能因为当前入口仍是B就推断Q从未执行。P若提交g8后丢响应,恢复返回同一个g8回执。版本文件、发布状态与暂存保护因此一起解释未知结果,过期目录时间只用于提出清理候选。
读取固定版本
读者在版本锁内读取当前清单并增加版本引用,再获得该清单;发布者也在同一锁内切换入口。随后读者只打开该版本列出的文件,不能每读取一个文件就重新查最新清单。这样材质和纹理来自同一逻辑版本,取得旧版本名与登记引用之间也没有被回收的空隙。
引用结束只意味着该读者不再访问文件。当前正式版本、回滚保留版本、构建检查点和撤销记录都是持久保护根;它们即使暂时没有读者也不能删除。文件哈希标识不可变内容,删除或修改被清单引用的文件会破坏所有消费者的前提。
验证方法
让P与Q同时从g7发布,核对只有一份结果进入g8;在提交后丢失响应,使用原发布ID取回原回执。分别改变依赖、目标代次与任务状态,确认旧请求无法取得覆盖权限。
实现与证据
文件参考样例包含真实临时文件写入、替换与替换前异常。控制数据库、暂存保护和多发布者协议按正文状态推演;尚未运行跨进程发布服务或断电持久性实验。
相关主题
参考资料
- Python3.11 os:os.fsync与os.replace;2026-10-22核读。
- Microsoft:Canceling Pending I/O Operations:取消请求与最终完成状态;2026-10-22核读。