缓存机制和缓存失效
本文档对 php-minecraft-server-info 项目中实现的多层缓存系统进行了全面的架构分析。该缓存策略旨在通过智能失效机制最大限度地减少昂贵的 I/O 操作(文件系统遍历、JAR 解析、哈希计算)和网络查询,同时保持数据一致性。
双层缓存架构
系统采用了一种复杂的双层缓存方法,分别在元数据级别和数据检索级别运行。该架构将昂贵操作(文件系统扫描、JAR 解析)与频繁访问的数据结构分离开来。
基础是 Mods 类中的 元数据哈希生成 系统,它对 mods 目录结构进行全面分析,并创建一个表示文件夹状态的确定性 SHA-256 哈希。此哈希值作为缓存失效决策的基石。系统遍历所有 JAR 文件,收集相对路径、修改时间和文件大小,然后生成一个排序的、确定性的哈希值,只要文件夹内容保持不变,该哈希值就保持一致。
来源:Mods.php
第二层在 持久缓存存储 级别运行,解析后的 mod 信息和元数据被序列化为 public/static/ 目录中的 JSON 文件。这些缓存文件以配置元数据哈希命名,确保不同的 mod 过滤配置(例如仅服务端与客户端 mods)维护单独的缓存文件。缓存负载包含用于验证的文件夹哈希、上次更新的时间戳以及序列化的 mod 对象。
缓存失效机制
通过文件夹哈希自动失效
主要的缓存失效机制依赖于嵌入在 getMods() 方法中的文件夹哈希比较算法。当收到缓存请求时,系统计算当前的文件夹哈希并将其与缓存文件中存储的哈希进行比较。如果哈希匹配,表明没有文件被添加、删除或修改,系统将反序列化并返回缓存的 mod 对象。这种方法为未更改的目录消除了昂贵的文件系统扫描和 JAR 解析操作。
来源:Mods.php
文件夹哈希算法结合了每个文件的 relative_path | mtime | size,在 SHA-256 哈希之前进行排序并用换行符连接。这确保了文件重新排序不会触发失效,而任何实际的文件更改(内容、名称、时间戳)都会触发。
基于配置的缓存隔离
getMetaHashed() 方法实现了辅助哈希机制,为不同的 mod 过滤配置创建唯一的缓存标识符。通过对基本路径、忽略前缀数组和仅前缀数组进行哈希处理,系统确保 common-mods、client-mods 和 server-mods 端点即使引用相同的底层物理目录,也能维护单独的缓存文件。这防止了缓存污染,即 mod 的过滤子集可能错误地返回来自不同配置的数据。
来源:Mods.php
通过查询参数手动失效
对于需要立即刷新缓存的场景(例如手动文件操作后),系统支持通过 force 查询参数进行手动缓存失效。当设置为 true、1 或 yes 时,此参数完全绕过缓存文件,强制进行完整的目录扫描、JAR 解析和缓存重新生成。此机制在 mod 列出端点和 ZIP 下载端点中均有公开,为管理员提供了对缓存行为的精细控制。
来源:public/mods.php
ZIP 存档缓存策略
ZIP 存档生成系统实现了一种独特的缓存机制,将文件夹哈希作为存档注释存储在 ZIP 文件本身内部。Zip 类提供了写入和读取此注释的方法,创建了一个自包含的验证系统。当收到 ZIP 下载请求时,系统将当前文件夹哈希与 ZIP 存档注释字段中存储的哈希进行比较。如果它们匹配,则直接提供现有的 ZIP 文件;如果不匹配,则生成带有更新哈希的新 ZIP 存档。
来源:Zip.php, public/mods.php
这种方法消除了对单独的元数据文件来跟踪 ZIP 存档有效性的需求,并确保 ZIP 存档及其有效性信息保持同步。ZIP 生成过程使用带有 CREATE | OVERWRITE 标志的 ZipArchive 类来原子地替换现有存档,防止并发请求期间的竞争条件。
模块级记忆化
单个 mod 对象对昂贵的操作(如 JAR 解析和文件哈希计算)实现惰性记忆化。Mod 类使用私有属性($name、$version、$authors、$md5、$sha1)来缓存计算值,在执行昂贵的操作之前检查是否为空。此模式在单个请求中特别有价值,其中多个方法可能访问相同的 mod 属性。
来源:Mod.php
记忆化扩展到加密操作,例如 SHA-1 和 MD5 哈希计算。这些操作每个 mod 对象实例仅执行一次,后续调用返回缓存的值。当 mod 列表多次显示或为同一 mod 集合请求不同的输出格式(JSON 与简单 MD5)时,此优化尤其有益。
服务器查询缓存
Server 类为 Minecraft 服务器查询结果实现了一个轻量级的内存中缓存机制。$pingData 属性存储服务器状态查询的结果,允许多个方法调用(getMaxPlayersCount()、getOnlinePlayersCount()、getPlayers())在单个请求内重用相同的网络响应。缓存由 outputPing() 方法控制,该方法仅在缓存为空时执行查询。
来源:Server.php
与 mod 缓存系统不同,由于服务器状态信息的易变性,服务器查询缓存不会跨请求持久化。系统优先考虑新鲜的服务器状态数据,而不是缓存效率,反映了服务器监控操作的实时要求。服务器查询缓存仅限于请求范围,在性能和数据新鲜度之间取得平衡。
缓存文件结构和存储
缓存文件存储在 public/static/ 目录中,命名约定包含配置元数据哈希:mods-{metadata_hash}.json。每个缓存文件包含一个 JSON 对象,具有三个主要组件:folder_hashed(mod 目录状态的 SHA-256 哈希)、update_at(缓存生成的 ISO 8601 时间戳)和 mods(序列化的 mod 对象数组)。此结构提供数据完整性验证和关于缓存新鲜度的元数据。
来源:Mods.php, Mods.php
缓存系统优雅地处理边缘情况,例如缓存文件损坏、文件丢失和格式更改。如果缓存读取因任何原因失败,系统将回退到新的目录扫描和 mod 解析操作。这种故障安全方法确保缓存错误不会阻止系统运行,尽管性能可能会降低。
性能影响和优化策略
多层缓存策略通过在多个级别消除冗余操作,提供了显著的性能优势。文件夹哈希计算(涉及文件系统迭代和 SHA-256 哈希)仅在 mod 目录结构更改时执行。同样,JAR 文件解析操作(通常是 mod 检索管道中最昂贵的操作)被持久缓存,将缓存请求的响应时间从几秒减少到几毫秒。
为了在生产环境中获得最佳性能,请确保 public/static/ 目录具有适当的写入权限并已从版本控制中排除。缓存系统自动处理文件创建和更新,但需要文件系统访问权限以在请求之间持久化缓存数据。
缓存失效策略优先考虑正确性,而不是严格的缓存一致性。文件夹哈希方法为 mod 目录更改提供了强一致性保证,但在多个进程执行并发更新时可能会表现出短暂的不一致窗口。然而,对于典型的读取繁重的 API 且 mod 更新不频繁的用例,这种权衡是可以接受的。
配置和调整
缓存行为通过应用程序配置中的几个参数进行配置。mods_path 参数确定扫描 mod 的基本目录,而 serverside_prefixs 数组定义哪些 mod 文件被视为仅服务端。这些配置参数影响缓存文件命名(通过元数据哈希)以及缓存中包含哪些文件。
来源:config.default.php
系统支持三种不同的 mod 配置(common、client、server),每种都有自己的缓存文件和过滤规则。这种模块化方法允许灵活的部署场景,其中不同的 mod 集可能通过不同的 API 端点公开,同时为每个配置维护高效缓存。ignore_serverside_prefix 和 only_serverside_prefix 参数在缓存生成级别提供对 mod 过滤的细粒度控制。
缓存监控和调试
系统提供了几种监控缓存行为和调试缓存相关问题的机制。getCacheUpdateTime() 方法返回上次缓存生成的时间戳,允许管理员评估缓存新鲜度。API 响应包括标准响应格式中的 modsHash 值和 updateAt 时间戳,提供每个请求的缓存状态可见性。
来源:Mods.php
启用 debug 配置参数后,可用于增加日志详细级别并公开内部缓存操作。结合 force 查询参数,这使管理员能够测试缓存失效行为并诊断性能问题,而不会干扰生产流量。系统的确定性哈希行为也促进了在开发环境中对缓存行为的自动化测试。
后续步骤
要深入了解如何从 JAR 文件解析缓存的 mod 数据,请参阅 Mod 解析系统 (NeoForge, Forge, Fabric) 文档。要了解如何生成和缓存 ZIP 存档,请参阅 ZIP 存档生成和管理 文档。有关全面的 API 使用示例,请参阅 Mod 信息 API 文档。