跳转到内容

输入功能名称或 API · Enter 打开首项 · Esc 关闭

统一查询配置与可复用查询定义

QueryOptions 是 Query、Fetch 和 Prefetch 共用的配置,用于覆盖 Client 默认的新鲜期、重试、超时和执行预算。Disable 只控制 Query 的自动加载;显式 Fetch、Prefetch 和查询对象的 Refetch 仍会执行。

QueryDefinition[V] 将查询键、类型化加载函数和 QueryOptions 组合为可复用的查询定义。创建定义不会加载数据,也不会创建缓存或订阅。

以下完整示例为三个入口使用同一份一小时新鲜期配置。禁用自动加载的订阅接收 Prefetch 显式加载的数据,Fetch 复用这份新鲜缓存。Client 默认值保持不变。将程序保存为已导入 cacheq 的 Go 模块中的 main.go,运行 go run .。

package main
import (
"context"
"fmt"
"time"
"github.com/Waterkyuu/cacheq"
)
// main reuses one loading policy across subscription, prefetch, and explicit reads.
func main() {
client := cacheq.NewClient(cacheq.Options{StaleTime: time.Minute})
defer client.Close()
ctx := context.Background()
load := func(context.Context) (string, error) { return "hello", nil }
hour := time.Hour
options := cacheq.QueryOptions{Disable: true, StaleTime: &hour}
query := cacheq.Query(client, "greeting", load, options)
defer query.Close()
if err := cacheq.Prefetch(ctx, client, "greeting", load, options); err != nil {
panic(err)
}
value, err := cacheq.Fetch(ctx, client, "greeting", load, options)
if err != nil {
panic(err)
}
fmt.Println(value)
fmt.Println("observed:", query.Snapshot().Data)
}

预期输出:

hello
observed: hello

Query(client, key, load, options) 在创建时复制配置,后续自动加载、SetEnabled 和手动 Refetch 继续使用该配置。

入口 Disable: true 的作用
Query 订阅共享状态更新,不启动自动加载
Fetch 执行显式读取,可以直接使用新鲜缓存
Prefetch 使用同一套加载策略显式预热缓存
查询对象的 Refetch 禁用自动加载时仍允许显式刷新

不传配置或 Disable 为 false 时,允许自动加载。禁用对象不会取消共享请求,也不会禁用其他对象。修改原始配置不会改变已有对象;后续启停使用 SetEnabled(bool)。

Fetch(ctx, client, key, load, options) 和 Prefetch(ctx, client, key, load, options) 的配置只对本次调用有效,二者都忽略 Disable。它们不修改 Client 默认值,也不注册该键后续调用的策略。Prefetch 返回加载错误,不返回数据,也不创建持续订阅。

QueryDefinition:集中定义类型化查询

Section titled “QueryDefinition:集中定义类型化查询”

查询定义包含四个字段:

字段 职责
Key 用非 nil、可比较的键标识查询,包含全部查询参数和身份范围
Fetcher 通过业务的 HTTP、数据库或其他依赖加载 V
Tags 声明用于关联查询分组的标签;登记与生命周期见查询标签
Options 将同一份 QueryOptions 用于读取、订阅和预加载

每次操作显式传入 Client,由 Client 管理缓存、共享请求和关闭生命周期。同一份定义可以用于多个 Client,各 Client 的缓存仍相互独立。返回的查询对象由调用者负责关闭。

以下完整示例展示定义构造、预加载、缓存复用和显式失效:

package main
import (
"context"
"fmt"
"time"
"github.com/Waterkyuu/cacheq"
)
// main reuses a typed definition without creating a second cache or automatic loading.
func main() {
client := cacheq.NewClient(cacheq.Options{})
defer client.Close()
ctx := context.Background()
hour := time.Hour
calls := 0
definition := cacheq.QueryDefinition[string]{
Key: "greeting",
Fetcher: func(context.Context) (string, error) {
calls++
return "hello", nil
},
Options: cacheq.QueryOptions{Disable: true, StaleTime: &hour},
}
query := definition.Query(client)
defer query.Close()
fmt.Println("initial calls:", calls)
if err := definition.Prefetch(ctx, client); err != nil {
panic(err)
}
value, err := definition.Fetch(ctx, client)
if err != nil {
panic(err)
}
fmt.Println(value, "calls:", calls)
if err := client.Invalidate(definition.Key); err != nil {
panic(err)
}
if _, err := definition.Fetch(ctx, client); err != nil {
panic(err)
}
fmt.Println("refreshed calls:", calls)
}

预期输出:

initial calls: 0
hello calls: 1
refreshed calls: 2
方法 返回结果
definition.Fetch(ctx, client) (V, error),契约与 Fetch 相同
definition.Query(client) *QueryHandle[V],契约与 Query 相同
definition.Prefetch(ctx, client) 加载错误,契约与 Prefetch 相同

方法直接使用定义中的键、加载函数和配置。如果需要定制一个消费者,可以先复制定义,再修改副本的配置并调用方法。修改 Key、Fetcher 或 Options 影响后续操作,不重新配置已有查询对象。后续操作声明的标签会合并到共享查询状态中。配置指针指向的值在每次操作开始时读取。业务需同步共享定义、指针值以及加载函数捕获的可变状态。

定义不会从加载函数推断查询键,不增加授权检查,也不串行业务操作。可以用普通业务函数构造带参数的定义,使键和加载函数捕获一致的参数。结果类型绑定、取消、错误及正在执行的请求共享遵循已有查询契约。显式失效使用 client.Invalidate(definition.Key) 或 client.InvalidateAndWait(ctx, definition.Key)。

以下加载策略字段同时适用于 Query、Fetch 和 Prefetch。

字段 类型 默认行为 设置后的行为
StaleTime *time.Duration 继承 Client 新鲜期 从数据的 UpdatedAt 计算新鲜期;零值表示立即过期
Retry *int 继承 Client 重试次数 首次失败后的追加尝试次数;零值或负数不重试
RetryIf func(error) bool 继承 Client 错误筛选函数 返回 true 才允许追加尝试,仍受次数和终止错误约束
RetryDelay func(int) time.Duration 继承 Client 重试等待策略 返回追加尝试前的等待时间;参数从 1 开始
Timeout *time.Duration 继承 Client 加载超时 限制整次加载,包括排队、执行和重试等待;零值或负数不额外设置超时
Executor *Executor nil,继承 Client 预算 新的加载尝试使用这个共享预算
DisableExecutor bool false 跳过外层 Client 预算,优先于 Executor

指针字段用于区分“继承默认值”和“显式设置零值”。例如 Retry: nil 继承默认重试次数,指向 0 则关闭重试。即使 Timeout 为零,调用者 context 的截止时间和取消仍然有效。

取消、截止时间错误、ErrQueueFull、ErrBatcherClosed 和 ErrExecutionReentry 不重试,包括包装后的错误,也不会调用 RetryIf。RetryIf 仅在还有重试次数且错误可重试时执行。传入始终返回 true 的函数可以覆盖 Client 的筛选条件,但不能绕过终止错误。

RetryIf 和 RetryDelay 在 Client 锁外执行,可以调用 Client API。应用需自行同步回调捕获的共享可变数据。

使用 Executor 为单个查询选择其他后端预算;如果 Batcher 已经限制实际批量回调,则使用 DisableExecutor: true。Executor 为 nil 时继承 Client 预算,除非显式禁用。禁用外层准入不会禁用 Batcher 自身的预算。

options := cacheq.QueryOptions{
DisableExecutor: true,
}

使用 Batcher.Fetcher 时,可以将同一份配置传给 Query、Fetch 或 Prefetch。在两层使用相同预算且传递回调 context 时,会返回 ErrExecutionReentry。完整配置示例与检测限制见执行并发预算。

  • 不传配置或传空配置时,使用 Client 默认值;QueryOptions.Disable 默认为 false。
  • 传入多份配置时,仅最后一份生效,不合并各份字段。最后一份中为 nil 的字段继承 Client 默认值。
  • 创建 Query 或调用 Fetch、Prefetch 时会复制时间与重试次数指针指向的值。查询定义保留配置指针,直到这些操作实际应用配置。之后修改原变量不会改变已有配置。Executor 引用保持共享,不复制预算容量。
  • 同一个键仍共享数据和请求。不同配置不创建独立缓存,也不替代键中的查询参数或身份范围。

新加载使用发起者的加载函数、重试配置、超时和执行预算。后加入的同键调用不会覆盖这些设置;加入者的 context 可以结束自己的等待,但其 Timeout 配置只用于自己发起的新加载。

失效触发自动刷新时,使用最早创建且仍启用的查询对象配置。该对象关闭或禁用后,由下一个启用对象负责后续自动刷新。手动 Refetch 发起新加载时使用当前查询对象配置。

StaleTime 只调整普通缓存的新鲜度判断,不触发轮询。时间经过不会自动发送通知或启动请求;Snapshot() 在读取时重新判断状态。Get 和未传配置的 Fetch、Prefetch 使用 Client 默认新鲜期。

显式失效和未写入新数据的刷新失败,会使普通缓存对所有查询变旧。FetchWithExpiry 返回的绝对截止时间不受 StaleTime 覆盖影响,包括显式缓存的兜底数据和错误。

缓存容量、MaxAge、GC 和时钟由 Client 管理。较长的 StaleTime 不能延长 MaxAge 限制的数据可用时间。