用 map + sync.RWMutex 可实现高性能线程安全内存存储,读多写少时优于 Mutex;键推荐 string,值宜用具体类型;持久化可用 gob(注意版本兼容性与原子写),数据增长后应换用 boltdb。

用 map + sync.RWMutex 实现线程安全的内存存储
Go 标准库没有内置本地键值存储,但多数轻量场景根本不需要持久化——直接用 map 配合 sync.RWMutex 就够用,性能高、无依赖、零配置。
常见错误是直接暴露全局 map,导致并发写 panic(fatal error: concurrent map writes)。必须加锁,且读多写少时优先用读写锁而非互斥锁。
-
sync.RWMutex的RLock()允许多个 goroutine 同时读,比Mutex更高效 - 键类型建议用
string;若用结构体作 key,必须可比较(字段不能含slice、map、func) - 值类型推荐
interface{}或具体类型(如[]byte),避免运行时反射开销
把数据落地到文件:用 gob 序列化 map
需要重启不丢数据?gob 是 Go 原生二进制格式,比 JSON 更快、更紧凑,且天然支持 Go 类型(包括自定义 struct)。
注意:gob 不兼容跨语言,也**不保证版本兼容性**——结构体字段增删改后,旧文件可能解码失败。
- 保存前先写临时文件(如
data.gob.tmp),os.Rename()原子替换,避免写一半崩溃导致数据损坏 - 每次全量序列化整个
map,不适合超大存储(>100MB);高频更新时慎用,I/O 成瓶颈 - 打开文件务必检查
err,gob.NewDecoder(f).Decode(&m)失败不会自动清空m,需手动置空或重建
用 boltdb 替代手写持久化层
当数据量增长、需要事务或范围查询时,硬编码文件读写很快会失控。此时直接上 boltdb(纯 Go 实现的嵌入式 KV 数据库)更省心。
它不是“玩具库”:被 etcd、influxdb 等项目长期使用,ACID、内存映射、B+ tree 索引都已实现。
- 初始化只需
bolt.Open("my.db", 0600, nil),无需建表或连接池 - 所有操作在
tx中进行:tx.Bucket([]byte("users")).Put([]byte("id1"), []byte("...")) - 注意:
Bucket名必须是[]byte,不能用字符串字面量直接传("users"❌ →[]byte("users")✅) - 忘记调用
tx.Commit()会导致写入丢失,且tx不能复用
什么时候不该自己写存储引擎
如果你的需求包含以下任意一条,就该停手,改用成熟方案:
- 需要多机同步(选
badger或sqlite+ WAL) - 要求 SQL 查询或二级索引(直接上
sqlite,用mattn/go-sqlite3) - 单条 value 超过 100MB(
boltdb单 value 限制约 4GB,但实际应控制在几 MB 内) - 要支持热备份、压缩、加密等企业级特性
手写本地存储的核心价值在于可控和简单,不是功能完备。一旦开始补 log、加 snapshot、做 compaction,你就已经在重造 LevelDB 轮子了。


















