Documentation
¶
Overview ¶
Package idempotency 提供幂等执行原语:同一 key 的重复请求只执行一次, 后续请求(含并发)复用首次结果,直到该 key 过期。
解决的问题:网络重试 / 客户端重发 / 消息重投会让同一逻辑操作执行多次—— 抽卡重复扣钱、充值重复到账、发奖重复发放。给每次操作分配一个幂等 key (订单号 / 请求 ID / txID),用本原语包住执行体,重复 key 不再重复执行。
提供两套 API:
Do(key, fn) — 计算包裹式(fn→SETNX,at-least-once) ¶
- 去重(dedup):key 已有完成结果 → 直接返回缓存的结果,不再执行 fn;
- 并发合并(singleflight):同一 key 多个请求同时到达 → 只有一个执行 fn, 其余阻塞等待同一结果,避免"缓存击穿式"重复执行。
- 适用于纯计算/确定性操作。
Acquire/Commit/Release — SETNX-first 守卫式(SETNX→fn,at-most-once) ¶
- Acquire 先用 SETNX 原子占位,确保并发只有一个调用方获得执行权;
- 获得执行权后执行业务逻辑,成功调 Commit 写入结果,失败调 Release 释放占位;
- 适用于有外部副作用的操作(DB 事务、RPC、消息发送),避免重复执行。
结果按 TTL 过期(默认 10 分钟),到点后同 key 可重新执行。fn 返回 error 时 默认不缓存(允许重试);可用 WithCacheErrors 改为连错误一起缓存(适合 "确定性失败无需重试"的场景)。
泛型 T 为结果类型。并发安全。零值不可用,用 New 构造;Stop 后清扫 goroutine 退出。
Index ¶
- Variables
- type Option
- type Store
- func (s *Store[T]) Acquire(key string) (*T, error)
- func (s *Store[T]) Commit(key string, value T)
- func (s *Store[T]) Do(key string, fn func() (T, error)) (result T, err error, shared bool)
- func (s *Store[T]) Forget(key string)
- func (s *Store[T]) Get(key string) (T, bool)
- func (s *Store[T]) Len() int
- func (s *Store[T]) Release(key string)
- func (s *Store[T]) Stop()
Constants ¶
This section is empty.
Variables ¶
var ErrConflict = errors.New("idempotency: key is being processed by another caller")
ErrConflict 表示另一个调用方正在处理同一 key(Acquire 占位中,尚未 Commit/Release)。 调用方应稍后重试或直接返回繁忙提示。
Functions ¶
This section is empty.
Types ¶
type Option ¶
type Option func(*config)
Option 配置 Store。
func WithCacheErrors ¶
WithCacheErrors 设置是否缓存失败结果(默认 false)。 false:fn 返回 error 不缓存,同 key 下次请求会重新执行(适合瞬时错误可重试); true:错误也按 TTL 缓存,同 key 直接返回该错误(适合确定性失败,避免无谓重试)。 注意:store 模式只缓存**成功**结果(错误不跨进程序列化),此选项仅内存模式生效。
func WithGCInterval ¶
WithGCInterval 设置过期清扫间隔(默认 1 分钟,仅内存模式)。
func WithOnStoreError ¶
WithOnStoreError 设置 store 出错回调。默认静默 + 降级为直接执行 fn。
func WithStore ¶
WithStore 让幂等结果走外部共享存储(如 Redis),使去重跨实例生效。 结果用 JSON 序列化后存入 store(故 T 须可被 encoding/json 编解码)。
语义差异(务必知悉):内存模式提供"去重 + 并发单飞(阻塞等待首次结果)"; store 模式只提供**去重 + 复用已完成结果**——用 SetNX 抢占执行权,抢到的执行 fn 并写回结果,没抢到的**不阻塞等待**,而是读已有结果(若尚未写完则自己执行一次)。 即:跨实例的并发同 key 可能各自执行一次,幂等性由"结果最终以 key 唯一存储 + 复用" 保证,而非"全局只执行一次"。要严格单飞请在 fn 内配合分布式锁,或接受此 at-least-once 语义(这正是幂等键要求业务操作本身可安全重试的原因)。 只缓存成功结果;fn 返回 error 时不写 store,允许重试。
type Store ¶
type Store[T any] struct { // contains filtered or unexported fields }
Store 幂等结果存储。按 key 维护"执行中/已完成"记录。 零值不可用,用 New 构造。并发安全。
func (*Store[T]) Acquire ¶
Acquire 原子占位:
- 返回 (nil, nil):成功获得执行权,调用方应执行业务逻辑,完成后调 Commit 或 Release;
- 返回 (*T, nil):已有已提交结果(幂等命中),直接使用;
- 返回 (nil, ErrConflict):另一个调用方正在处理中(占位存在但尚未提交)。
func (*Store[T]) Do ¶
Do 以 key 为幂等键执行 fn:
- key 首次出现:执行 fn,缓存结果(按配置决定是否缓存 error),返回 (result, err, false);
- key 执行中(并发):阻塞等待首次执行完成,返回其结果 + shared=true;
- key 已完成且未过期:直接返回缓存结果 + shared=true,不执行 fn。
shared 表示本次结果是否复用自其他请求(true=未真正执行 fn)。 fn 内 panic 不被捕获——调用方若需防护请在 fn 内自行 recover;panic 会 导致等待同 key 的其他请求也观察到该记录被清理(可重试)。