LevelDB不能在Gin handler中每次请求都Open,因其OpenFile是重量级操作,频繁调用会导致fd泄漏和性能崩溃;必须用sync.Once全局单例复用DB实例,并确保绝对路径、权限正确及进程退出前显式Close。

LevelDB 本身不支持并发写,直接在 Gin 处理函数里 open + put/get 容易 panic 或数据错乱。必须封装一层带同步控制的实例,且不能每个请求都 Open —— 那会快速耗尽文件描述符。
为什么不能在 handler 里直接 leveldb.Open
LevelDB 的 Open 是重量级操作:它会加载 manifest、重建内存表、检查 sst 文件完整性。频繁调用不仅慢,还会因 fd 泄漏触发 too many open files 错误(尤其在高并发压测时)。Gin 的每个请求确实跑在独立 goroutine,但 LevelDB 实例不是 goroutine-safe 的写操作——多个 goroutine 同时 Put 同一个 DB 实例会 crash。
用 sync.Once + 全局单例初始化 DB
确保整个进程只打开一次 LevelDB,并复用同一实例。注意路径必须是绝对路径,相对路径在不同工作目录下会出错;同时要显式设置 CacheSize 和 DisableSeeksCompaction 避免默认配置拖慢小数据场景:
var db *leveldb.DB
var once sync.Once
<p>func getDB() <em>leveldb.DB {
once.Do(func() {
var err error
db, err = leveldb.OpenFile("/tmp/mydb", &opt.Options{
CacheSize: 16 </em> 1024 * 1024,
DisableSeeksCompaction: true,
})
if err != nil {
log.Fatal("failed to open leveldb:", err)
}
})
return db
}</p>
-
/tmp/mydb必须存在且进程有读写权限,否则OpenFile直接返回 error - 不要用
leveldb.Open(已废弃),必须用leveldb.OpenFile - 若需多 DB 实例(如按业务分库),用 map[string]*leveldb.DB +
sync.RWMutex控制访问,而非反复 open/close
Gin handler 中安全读写:用 db.Get/db.Put 就够了
LevelDB 原生支持并发读,写操作内部已加锁,只要保证 DB 实例全局唯一,handler 里直接调用是安全的。无需额外 channel 或 worker queue:
func handleGet(c *gin.Context) {
key := c.Param("key")
data, err := db.Get([]byte(key), nil)
if err != nil {
c.JSON(404, gin.H{"error": "not found"})
return
}
c.Data(200, "text/plain", data)
}
<p>func handlePut(c *gin.Context) {
key := c.Param("key")
val, _ := c.GetRawData()
err := db.Put([]byte(key), val, nil)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.Status(201)
}</p>
- 所有 key/value 必须是
[]byte,别传 string 直接转——Go 的 string 底层是只读字节数组,某些旧版 LevelDB 封装会 panic - 错误检查不能省:
leveldb.ErrNotFound是正常 error,不是 panic 条件 - 别在 handler 里 defer
db.Close()—— 这会关掉全局实例,后续请求全挂
进程退出前必须调用 db.Close()
Gin 没有内置 shutdown hook,得自己监听 OS 信号。LevelDB 若未 close,下次启动可能因 WAL 未 flush 导致数据丢失或 recovery 失败:
func main() {
r := gin.Default()
r.GET("/k/:key", handleGet)
r.PUT("/k/:key", handlePut)
<pre class="brush:php;toolbar:false;">go func() {
if err := r.Run(":8080"); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<p>}</p>LevelDB 的真正难点不在集成,而在生命周期管理——它不像内存缓存可随意 new,也不像 SQL 数据库有连接池抽象。开一次、用全程、关一次,这个模式必须严格守住,否则线上抖动就是它引发的。











