搞 SchoolTools 的时候我低估了缓存和更新这两个模块的复杂度。原本以为就是套个 hash map、定时检查下服务器版本结果实际开发中踩了一堆坑最后多写了整整两周代码才稳定下来。今天记录下 SQCache 和 SQUIUpdate 这两个模块的设计思路如果你也在做类似的东西希望能少走点弯路。缓存不止是个 Hash Map我一开始真的以为缓存就是std::unordered_mapstd::string, std::string存个键值对就完事了。但 SchoolTools 的缓存面临几个具体问题多线程读写SchoolTools 的 API 层是异步回调驱动的多个线程同时读写缓存的情况很常见。简单加锁可以但性能会炸。而且我们用了多线程读写锁写锁和读锁的粒度必须精确控制否则死锁问题排查起来头大。持久化需求缓存不能只在内存里。用户关掉软件再打开缓存不应该全部失效。我们选择了 SQLite 做持久化存储但 SQLite 和内存缓存的同步策略又是个问题——全量同步太慢增量同步又复杂。过期策略哪些缓存需要 TTL哪些需要主动失效比如用户配置可以永久缓存但某些 API 返回的数据必须 5 分钟内过期。我们给 SQCache 加了ttl参数支持每个 key 独立设置过期时间。更新自动更新比想象中难搞SQUIUpdate 是自动更新模块负责检测新版本、下载、校验、安装。听起来简单实际实现时各种 edge caseWindows 上文件锁定Windows 可执行文件在运行时被系统锁定无法直接替换。我们需要先启动一个独立的 updater 进程主进程退出后updater 替换文件再重启主进程。这个流程在 macOS 和 Linux 上完全不需要但 Windows 上必须这么搞。断点续传用户下载到一半断网了下次不应该从头下载。我们支持了 HTTP Range 请求把下载进度和临时文件存在缓存目录里。但断点续传和文件校验的组合又引入了新问题如果服务器端文件被替换了比如热修复断点续传的文件可能损坏需要校验失败后重新下载。版本回退如果更新后用户发现软件无法启动怎么办我们在更新前备份了旧版本文件启动时如果检测到新版本连续崩溃 3 次自动回退到备份版本。这个逻辑很保守但真的救过命。跨平台一致性SchoolTools 支持 Windows、macOS 和 Linux三个平台的缓存路径和更新机制完全不同Windows%APPDATA%\SchoolTools更新需要 updater 进程macOS~/Library/Application Support/SchoolTools更新直接替换.app包Linux~/.local/share/SchoolTools更新替换可执行文件SQCache 的存储路径统一用QStandardPaths管理SQUIUpdate 的平台差异用#ifdef封装。但测试覆盖是个大问题每次修改更新逻辑三个平台都得手动测一遍很痛苦。写在最后说实话缓存和更新这两个模块的代码量加起来差不多占了整个项目业务逻辑代码的 30%。但它们不是核心功能很难在产品介绍里展示。用户只会觉得软件启动很快“更新很顺滑”背后就是这些基础设施的功劳。如果你也在做类似的东西我的建议是早点设计多写测试。缓存和更新的问题往往是延迟暴露的等用户抱怨怎么又需要重新登录“更新后打不开了”再修复就晚了。有问题欢迎评论区交流。
SSL 证书续期脚本跑失败了:我用 ACME + 自动化巡检把证书过期事故归零
搞 SchoolTools 的时候我低估了缓存和更新这两个模块的复杂度。原本以为就是套个 hash map、定时检查下服务器版本结果实际开发中踩了一堆坑最后多写了整整两周代码才稳定下来。今天记录下 SQCache 和 SQUIUpdate 这两个模块的设计思路如果你也在做类似的东西希望能少走点弯路。缓存不止是个 Hash Map我一开始真的以为缓存就是std::unordered_mapstd::string, std::string存个键值对就完事了。但 SchoolTools 的缓存面临几个具体问题多线程读写SchoolTools 的 API 层是异步回调驱动的多个线程同时读写缓存的情况很常见。简单加锁可以但性能会炸。而且我们用了多线程读写锁写锁和读锁的粒度必须精确控制否则死锁问题排查起来头大。持久化需求缓存不能只在内存里。用户关掉软件再打开缓存不应该全部失效。我们选择了 SQLite 做持久化存储但 SQLite 和内存缓存的同步策略又是个问题——全量同步太慢增量同步又复杂。过期策略哪些缓存需要 TTL哪些需要主动失效比如用户配置可以永久缓存但某些 API 返回的数据必须 5 分钟内过期。我们给 SQCache 加了ttl参数支持每个 key 独立设置过期时间。更新自动更新比想象中难搞SQUIUpdate 是自动更新模块负责检测新版本、下载、校验、安装。听起来简单实际实现时各种 edge caseWindows 上文件锁定Windows 可执行文件在运行时被系统锁定无法直接替换。我们需要先启动一个独立的 updater 进程主进程退出后updater 替换文件再重启主进程。这个流程在 macOS 和 Linux 上完全不需要但 Windows 上必须这么搞。断点续传用户下载到一半断网了下次不应该从头下载。我们支持了 HTTP Range 请求把下载进度和临时文件存在缓存目录里。但断点续传和文件校验的组合又引入了新问题如果服务器端文件被替换了比如热修复断点续传的文件可能损坏需要校验失败后重新下载。版本回退如果更新后用户发现软件无法启动怎么办我们在更新前备份了旧版本文件启动时如果检测到新版本连续崩溃 3 次自动回退到备份版本。这个逻辑很保守但真的救过命。跨平台一致性SchoolTools 支持 Windows、macOS 和 Linux三个平台的缓存路径和更新机制完全不同Windows%APPDATA%\SchoolTools更新需要 updater 进程macOS~/Library/Application Support/SchoolTools更新直接替换.app包Linux~/.local/share/SchoolTools更新替换可执行文件SQCache 的存储路径统一用QStandardPaths管理SQUIUpdate 的平台差异用#ifdef封装。但测试覆盖是个大问题每次修改更新逻辑三个平台都得手动测一遍很痛苦。写在最后说实话缓存和更新这两个模块的代码量加起来差不多占了整个项目业务逻辑代码的 30%。但它们不是核心功能很难在产品介绍里展示。用户只会觉得软件启动很快“更新很顺滑”背后就是这些基础设施的功劳。如果你也在做类似的东西我的建议是早点设计多写测试。缓存和更新的问题往往是延迟暴露的等用户抱怨怎么又需要重新登录“更新后打不开了”再修复就晚了。有问题欢迎评论区交流。