Go 数据加载实践:singleflight、缓存库和 soroi 怎么选

发布时间:2026/10/28 19:31:56
Go 数据加载实践:singleflight、缓存库和 soroi 怎么选 一个用户详情接口每次请求都查数据库代码很简单。给它加缓存也不复杂。问题通常出现在后面多个请求同时缓存未命中数据库被重复查询用户资料修改后详情缓存删了列表缓存还没更新后台刷新失败调用方又需要继续使用上一次的数据。这些需求分别涉及请求合并、结果缓存、关联失效和查询状态。选工具之前先把它们分开看。本文用一个用户查询示例比较 Go 中常用的几种处理方式并介绍一个新的查询客户端 soroi。先看 singleflight 能解决到哪一步。假设 100 个请求同时读取user:42底层查询还没有完成。singleflight 可以让这些调用等待同一次执行共享返回结果。但第一次查询结束后再读取 user:42仍然会执行查询。singleflight 负责合并正在执行的调用不保存供后续请求复用的缓存结果。具体行为见 singleflight 官方 API 文档。场景需要的机制同一个 key 的请求同时到达合并执行中的请求同一个 key 在一段时间内反复读取缓存已经完成的结果实际项目经常组合缓存和 singleflight先查缓存未命中则进入 singleflight在回调中再次检查缓存最后才访问数据库并保存结果。第二次检查有实际作用从第一次缓存未命中到获得执行机会之间其他调用可能已经填好了缓存。如果项目已经有这套封装而且只需要返回一个值和一个错误继续使用就很合适。随着需求增加封装可能还要处理新鲜期、刷新状态、旧数据保留、关联查询失效以及订阅者的退出。这些也是 soroi 当前提供的能力。用一个程序验证缓存复用和失效。核心库要求 Go 1.22 或以上版本。在独立目录中初始化模块go mod init example.com/soroi-demo go get github.com/Waterkyuu/soroi将下面的代码保存为 main.go执行 go run .package main import ( context fmt sync/atomic time github.com/Waterkyuu/soroi ) // main demonstrates fresh-result reuse and reloading after invalidation. func main() { client : soroi.NewClient(soroi.Options{StaleTime: time.Minute}) defer client.Close() ctx, cancel : context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var calls atomic.Int32 load : func(ctx context.Context) (string, error) { if err : ctx.Err(); err ! nil { return , err } calls.Add(1) return Alice, nil } for i : range 3 { name, err : soroi.Fetch(ctx, client, tenant:7:user:42, load) if err ! nil { panic(err) } fmt.Printf(read %d: %s\n, i1, name) } fmt.Printf(source calls before invalidation: %d\n, calls.Load()) if err : client.Invalidate(tenant:7:user:42); err ! nil { panic(err) } name, err : soroi.Fetch(ctx, client, tenant:7:user:42, load) if err ! nil { panic(err) } fmt.Printf(after invalidation: %s; source calls: %d\n, name, calls.Load()) }输出如下read 1: Alice read 2: Alice read 3: Alice source calls before invalidation: 1 after invalidation: Alice; source calls: 2加载函数里的计数代表实际访问数据源的次数。这里返回固定字符串接入项目时可以替换成数据库查询或 HTTP 请求。前三次读取使用同一个 Client、同一个 key。第一次执行加载函数后两次复用一分钟新鲜期内的结果。调用 Invalidate 后下一次 Fetch 等待重新加载计数增加到 2。这个例子验证的是连续读取的结果复用。缓存为空时同 key 的并发加载也可以共享正在执行的请求。接入 HTTP 服务时Client 应在应用初始化时创建再注入各个处理器。每次请求都创建一个 Client会失去跨请求共享本地缓存和执行中请求的作用。传给 Fetch 的 context 则应来自当前请求例如 r.Context()。key 也需要包含影响结果的条件。示例使用 tenant:7:user:42是为了表达租户和用户范围。如果查询结果还受语言、权限或筛选条件影响这些差异也需要反映在 key 中。有缓存但正在刷新状态怎么处理HTTP 处理器通常只需要等待结果用 Fetch 就够了。TUI、管理面板或持续运行的任务可能还需要观察加载过程。例如一个任务详情已经显示在界面上。用户触发刷新后界面可以保留原来的内容同时显示加载提示刷新失败时也可以保留旧内容并展示错误。字段含义HasData当前是否有可用数据包括合法的零值Fetching是否正在加载包含后台刷新Stale数据是否已经过时或被标记失效Err最近一次加载错误soroi 通过 Query 创建订阅句柄通过 Snapshot() 读取当前状态通过 Updates() 接收更新。这里需要区分新鲜期和刷新机制StaleTime 控制结果什么时候被视为过时。时间到了并不会自动启动定时轮询。后续读取、显式刷新或失效操作才会按对应规则触发加载。Updates() 适合消费最新状态慢消费者可能跳过中间变化。如果需要追踪执行过程应使用诊断事件。写入之后缓存还需要处理关联关系。修改用户昵称至少可能影响用户详情 tenant:7:user:42 和用户列表 tenant:7:users:page:1。更新详情缓存不会自动更新列表里的昵称。只删除详情缓存也不能解决列表的数据过时问题。soroi 的 Mutation 允许应用在写入成功后执行缓存同步用接口返回的最新用户详情更新详情缓存。将相关列表查询标记失效。根据业务需要在后台刷新或者等待关联刷新完成。这些关联由业务代码声明。框架无法判断一次用户修改会影响哪些分页、搜索条件和统计结果。数据库写入成功以后列表刷新仍然可能失败。soroi 使用 SyncErr 单独表示这类同步错误调用方可以据此提示“修改成功列表刷新失败”避免因为一次刷新错误而重复提交业务写入。和缓存库、DataLoader 放在一起怎么选Ristretto 主要关注并发缓存、准入与淘汰策略提供基于成本的容量管理v2 也支持泛型。如果项目重点是缓存容量和访问性能应当在实际负载下评估这类专门的缓存库。graph-gophers/dataloader 主要解决批量加载问题。多个对象分别读取时可以收集 key再调用一次业务提供的批量查询。其默认缓存主要面向短生命周期 Loader例如单次 HTTP 请求也允许替换实现。soroi 同样提供 Batcher但使用前提仍然是数据源具有批量能力。例如执行一次 WHERE id IN (...)或者调用一个批量接口。在批量回调里循环执行单对象请求通常并不能减少下游调用次数。主要需求可以优先评估合并同 key 的并发调用singleflight保存结果重点管理容量与淘汰Ristretto 等缓存库聚合不同 key减少 N1 查询DataLoader同时管理共享结果、请求状态和关联刷新soroi使用缓存库搭配 singleflight同样可以完成结果复用和请求合并。是否引入 soroi主要看项目是否还需要查询状态、订阅和刷新生命周期以及团队是否愿意自行维护这些逻辑。熟悉前端开发的读者可能会想到 TanStack Query 或 SWR。它们也将数据获取、缓存与更新放在同一套使用模型中。soroi 采用了相近的查询管理思路但通过 Go 的函数、context 和 channel 使用由应用显式管理资源生命周期。把它们放在一起比较适合讨论设计思路实际选型仍然要回到各自的运行环境。soroi 默认在进程内共享结果和执行中的请求。当前仓库还提供可选 Redis 共享缓存与跨实例失效通知需要独立配置本地请求合并也不代表多个实例之间会进行全局去重。对于已经反复维护“查缓存、合并加载、保存结果、通知消费者、写入后刷新”这套流程的 Go 项目可以选一个共享查询试用再判断是否值得接入更多业务。项目地址https://github.com/Waterkyuu/soroi使用文档Typed Query Client and Shared Cache for Go | soroi

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询