90%的Go开发者都在重复造轮子:一个泛型资源管理库帮你终结连接池噩梦
你有没有遇到过这种情况?
项目里同时用了MySQL、Redis、MongoDB,每个都要写一套初始化逻辑。配置散落在各个角落,关闭资源的时候漏掉一个,内存泄漏查半天。更崩溃的是,换个项目又得把这些代码复制粘贴一遍。
今天介绍一个开源库,用不到300行代码,彻底解决资源管理的混乱问题。

一、核心设计:三个概念搞定一切
这个库的设计极其精简,只有三个核心概念:
Opener —— 告诉库怎么创建资源
Closer —— 告诉库怎么销毁资源
Manager —— 帮你统一管理所有资源
看一眼类型定义你就懂了:
type Opener[C any, T any] func(ctx context.Context, cfg C) (T, error)
type Closer[T any] func(ctx context.Context, val T) error
C是配置类型,T是资源类型。Go 1.18泛型的威力在这里体现得淋漓尽致——一套代码管理所有类型的资源。
二、惰性初始化:不用不创建
很多人写资源管理,喜欢在程序启动时把所有连接都建好。数据库连接池、Redis客户端、各种SDK,一股脑全初始化。
问题来了:如果某个服务这次请求根本用不到MongoDB,为什么要提前建连接?
这个库采用惰性初始化策略。你先注册配置,资源在第一次Get的时候才真正创建:
// 注册只保存配置,不创建连接
group.Register(ctx, "main-db", dbConfig)
group.Register(ctx, "cache", redisConfig)
// 首次调用才真正建立连接
db, err := group.Get(ctx, "main-db")
这个设计带来两个好处:启动速度快,资源按需分配。
三、并发安全:双重检查锁的教科书实现
多个goroutine同时请求同一个资源怎么办?
库里用了经典的双重检查锁定模式。先用读锁快速判断资源是否就绪,没就绪再升级写锁创建。这样既保证了并发安全,又避免了每次访问都加重锁的性能损耗。
// 读锁:快速路径
g.m.mu.RLock()
if conn.ready {
val := conn.val
g.m.mu.RUnlock()
return val, nil
}
g.m.mu.RUnlock()
// 写锁:慢速路径,惰性创建
g.m.mu.Lock()
defer g.m.mu.Unlock()
// 二次检查,防止重复创建
if conn.ready {
return conn.val, nil
}
val, err := g.m.opener(ctx, conn.cfg)
这段代码可以当作Go并发编程的范本来学习。
四、分组管理:多租户场景的救星
实际项目中,你可能需要管理多套同类型的资源。比如SaaS系统里,每个租户一套数据库配置。
这个库原生支持分组:
manager := registry.New(dbOpener, dbCloser)
// 按租户分组
manager.AddGroup("tenant-A")
manager.AddGroup("tenant-B")
// 获取租户A的数据库
groupA, _ := manager.Group("tenant-A")
groupA.Register(ctx, "primary", tenantAConfig)
db, _ := groupA.Get(ctx, "primary")
每个Group独立管理自己的资源,互不干扰。关闭时可以单独关闭某个组,也可以一键关闭整个Manager。
五、优雅关闭:告别资源泄漏
程序退出时忘记关闭连接,这种bug隐蔽又致命。
库提供了统一的Close方法,自动遍历所有已创建的资源并关闭:
// 关闭单个组
errs := group.Close(ctx)
// 关闭整个管理器
errs := manager.Close(ctx)
返回值是错误切片,方便你知道哪些资源关闭失败了。
六、实战:GORM接入完整示例
废话不多说,直接上可运行的代码:
package main
import (
"context"
"fmt"
"github.com/qq1060656096/bizutil/registry"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 数据库配置
type DBConfig struct {
DSN string
}
// 创建数据库连接(opener)
func openDB(ctx context.Context, cfg DBConfig) (*gorm.DB, error) {
return gorm.Open(mysql.Open(cfg.DSN), &gorm.Config{})
}
// 关闭数据库连接(closer)
func closeDB(ctx context.Context, db *gorm.DB) error {
sqlDB, _ := db.DB()
return sqlDB.Close()
}
func main() {
ctx := context.Background()
// 创建 registry(单组模式)
DB := registry.NewGroup[DBConfig, *gorm.DB](
openDB,
closeDB,
)
// 注册数据库(此时不会真正连接)
DB.Register(ctx, "main", DBConfig{
DSN: "user:pass@tcp(127.0.0.1:3306)/test?parseTime=true",
})
// 第一次获取时才创建连接
db := DB.MustGet(ctx, "main")
// 使用 GORM
data := make(map[string]interface{})
db.Raw("SELECT 'hello' AS demo").Scan(&data)
fmt.Println(data) // map[demo:hello]
// 程序退出时统一关闭
DB.Close(ctx)
}
整个流程清晰明了:定义opener和closer → 创建registry → 注册配置 → 按需获取 → 统一关闭。
如果你的项目需要管理多套数据库(比如读写分离、多租户),用Manager模式:
manager := registry.New(openDB, closeDB)
// 按用途分组
manager.AddGroup("write")
manager.AddGroup("read")
// 注册主库
writeGroup, _ := manager.Group("write")
writeGroup.Register(ctx, "master", masterConfig)
// 注册从库
readGroup, _ := manager.Group("read")
readGroup.Register(ctx, "slave-1", slave1Config)
readGroup.Register(ctx, "slave-2", slave2Config)
// 写操作用主库
masterDB, _ := writeGroup.Get(ctx, "master")
// 读操作用从库
slaveDB, _ := readGroup.Get(ctx, "slave-1")
// 程序退出时一键关闭所有连接
defer manager.Close(ctx)
七、源码亮点
翻了一遍源码,有几个细节值得学习:
- 泛型约束用any:最大化灵活性,任何类型都能管理
- Closer可以为nil:有些资源不需要显式关闭,设计上考虑到了
- 错误类型丰富:ErrGroupNotFound、ErrResourceNotFound、ErrCloseResourceFailed,定位问题很方便
- Must系列方法:确定资源存在时可以用MustGet,代码更简洁
写在最后
这个库的代码量不大,但设计思路值得借鉴。泛型让Go的资源管理终于可以做到真正的通用化,不用再为每种资源写重复的管理代码。
仓库地址:github.com/qq1060656096/bizutil/registry
如果你的项目里也有资源管理的痛点,不妨试试。有问题欢迎在评论区交流。
觉得有用的话,点个赞收藏一下,我会持续分享Go语言的实用技巧。