3. 核心接口设计:缓存接口抽象与 Get/Set/Delete/DelPattern 定义
作为本地数据库缓存层的基石,接口抽象决定了上层应用与底层存储之间的耦合度。本章从“契约优先”原则出发,定义一套通用缓存接口,将Get、Set、Delete、DelPattern四个核心操作标准化,并给出实现的参考骨架。
3.1 接口设计原则
- 最小完备性:四个方法即可覆盖绝大部分缓存场景(读、写、单删、批量删除)。
- 实现无关:接口不暴露底层存储类型(内存、SQLite、RocksDB 等)。
- 错误可传递:所有操作返回 error 或等效错误对象,支持调用方精细处理。
3.2 接口定义(以 Go 语言为例)
cache.go — 缓存接口抽象
package cache
import "context"
// Cache 定义本地缓存层的基础操作。
// Key 为字符串,Value 为任意可序列化类型(使用字节表示)。
type Cache interface {
// Get 读取键对应的值,如果不存在返回 ErrMiss。
Get(ctx context.Context, key string) ([]byte, error)
// Set 写入键值对,ttl 为过期秒数(0 表示永不过期)。
Set(ctx context.Context, key string, value []byte, ttl int64) error
// Delete 删除单个键,无论是否存在都不算错误。
Delete(ctx context.Context, key string) error
// DelPattern 批量删除匹配某个前缀/模式的键。
// pattern 语法由具体实现定义,常见为 glob 或前缀匹配。
DelPattern(ctx context.Context, pattern string) error
// Close 释放后端资源(连接、文件句柄等)。
Close() error
}
// ErrMiss 是缓存未命中的标准错误。
var ErrMiss = errors.New("cache miss")
关键设计点:
①
②
③
①
Get 返回 ErrMiss 而非空值,让调用方明确区分“不存在”与“空内容”。②
Delete 对于不存在的键不返回错误,方便幂等调用。③
DelPattern 采用“模式字符串”而非闭包回调,确保接口可跨语言序列化。3.3 接口方法对比表
| 方法 | 输入 | 输出 | 语义 |
|---|---|---|---|
| Get | key string | (value []byte, error) | 读取单个键;未命中返回 ErrMiss |
| Set | key string, value []byte, ttl int64 | error | 写入键值对,设置 TTL(秒) |
| Delete | key string | error | 删除单个键,不存在不报错 |
| DelPattern | pattern string | error | 删除所有匹配 pattern 的键 |
| Close | 无 | error | 关闭资源,清理连接 |
3.4 接口抽象结构图
下图展示缓存接口层与几种典型实现的关系:
3.5 实现流程与步骤
- 定义接口类型:按 3.2 节骨架编写
Cache接口,包含四个核心方法与Close。 - 选择底层存储:本章后续将用 SQLite 作为首个实现(第 4 章),也可先写一个内存桩实现用于测试。
- 实现 Get/Set:Get 从底层查询键,若不存在返回
ErrMiss;Set 写入并记录过期时间。 - 实现 Delete:执行删除操作,忽略不存在的键。
- 实现 DelPattern:遍历所有键,使用前缀/通配符匹配后逐条删除;注意事务一致性。
- 测试接口契约:编写单元测试覆盖正常、空、过期、模式匹配等场景。
3.6 调试与常见错误
⚠️ 常见陷阱
•
•
• TTL 字段类型不一致:接口中统一为
•
Get 返回空 slice ([]byte{}) 与 nil 的语义混淆:建议统一用 ErrMiss 而非空值判断。•
DelPattern 如果使用 LIKE 操作,需要对模式中的 %、_ 做转义;最好限制为前缀匹配以避免正则注入。• TTL 字段类型不一致:接口中统一为
int64 秒,实现内转换为毫秒或时间。3.7 性能优化提示
- 批量操作管化:对于
DelPattern匹配大量键时,使用事务一次性提交删除,避免每条删除独立 I/O。 - Get 的缓存行:底层实现可对热键增加内存 LRU 缓存(二级缓存),减少数据库访问。
- 接口方法签名:若语言支持泛型(如 Go 1.18+),可将
Value类型参数化,避免调用方反复序列化/反序列化。