QueryOptions 是 Query、Fetch 和 Prefetch 共用的配置,用于覆盖 Client 默认的新鲜期、重试、超时和执行预算。Disable 只控制 Query 的自动加载;显式 Fetch、Prefetch 和查询对象的 Refetch 仍会执行。
QueryDefinition[V] 将查询键、类型化加载函数和 QueryOptions 组合为可复用的查询定义。创建定义不会加载数据,也不会创建缓存或订阅。
快速开始:复用同一份配置
Section titled “快速开始:复用同一份配置”以下完整示例为三个入口使用同一份一小时新鲜期配置。禁用自动加载的订阅接收 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)}预期输出:
helloobserved: hello自动加载与显式读取
Section titled “自动加载与显式读取”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: 0hello calls: 1refreshed 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。应用需自行同步回调捕获的共享可变数据。
执行预算覆盖
Section titled “执行预算覆盖”使用 Executor 为单个查询选择其他后端预算;如果 Batcher 已经限制实际批量回调,则使用 DisableExecutor: true。Executor 为 nil 时继承 Client 预算,除非显式禁用。禁用外层准入不会禁用 Batcher 自身的预算。
options := cacheq.QueryOptions{ DisableExecutor: true,}使用 Batcher.Fetcher 时,可以将同一份配置传给 Query、Fetch 或 Prefetch。在两层使用相同预算且传递回调 context 时,会返回 ErrExecutionReentry。完整配置示例与检测限制见执行并发预算。
默认值与覆盖规则
Section titled “默认值与覆盖规则”- 不传配置或传空配置时,使用 Client 默认值;
QueryOptions.Disable默认为 false。 - 传入多份配置时,仅最后一份生效,不合并各份字段。最后一份中为 nil 的字段继承 Client 默认值。
- 创建
Query或调用Fetch、Prefetch时会复制时间与重试次数指针指向的值。查询定义保留配置指针,直到这些操作实际应用配置。之后修改原变量不会改变已有配置。Executor 引用保持共享,不复制预算容量。 - 同一个键仍共享数据和请求。不同配置不创建独立缓存,也不替代键中的查询参数或身份范围。
共享请求的配置
Section titled “共享请求的配置”新加载使用发起者的加载函数、重试配置、超时和执行预算。后加入的同键调用不会覆盖这些设置;加入者的 context 可以结束自己的等待,但其 Timeout 配置只用于自己发起的新加载。
失效触发自动刷新时,使用最早创建且仍启用的查询对象配置。该对象关闭或禁用后,由下一个启用对象负责后续自动刷新。手动 Refetch 发起新加载时使用当前查询对象配置。
缓存策略限制
Section titled “缓存策略限制”StaleTime 只调整普通缓存的新鲜度判断,不触发轮询。时间经过不会自动发送通知或启动请求;Snapshot() 在读取时重新判断状态。Get 和未传配置的 Fetch、Prefetch 使用 Client 默认新鲜期。
显式失效和未写入新数据的刷新失败,会使普通缓存对所有查询变旧。FetchWithExpiry 返回的绝对截止时间不受 StaleTime 覆盖影响,包括显式缓存的兜底数据和错误。
缓存容量、MaxAge、GC 和时钟由 Client 管理。较长的 StaleTime 不能延长 MaxAge 限制的数据可用时间。
