前段时间在看gorm,发现gorm是复用database/sql的连接池。
于是翻了下database/sql的数据库连接池的代码实现,看完代码,好像也不是很复杂,但是总觉得理解不够深刻,于是萌生了自己想写个连接池的想法。(最后也验证了,看源码的理解确实不够深刻,一看就会,一做就跪)
2、连接池的实现原理什么是连接池
- 顾名思义是一个池子
- 池子里面存放有限数量即时可用的连接,减少创建连接和关闭连接的时间
- 连接是有存活时间的
具体到数据库连接池,我根据自己的理解画了一张获取连接的流程图
从上图我们可以看出,除了连接池的容量大小,我们还有一个最大连接数的限制。池子里的连接让我们不用频繁的创建和关闭连接,同时应该也要有最大连接的限制,避免无限制的创建连接导致服务器资源耗尽,拖垮服务不可用。
池子中的连接也有存活时间,如果超过存活时间则会销毁连接。
3、实现连接池我们需要考虑哪些问题3.1 功能点
- 获取连接
- 释放连接
- Ping
- 关闭连接池
- 设置最大连接数和连接池容量(连接存活时间等等)
3.2 实现细节
- 连接应该有哪些属性,比如最大连接数、连接池容量、连接创建时间和存活时间
- 如何模拟使用连接池以及超过最大连接数后等待其他连接释放
- 如何保证在多协程操作下数据的一致性
- 如果实现连接的超时监听和通知
这里的连接池实现包括
- 设置最大连接数和连接池容量
- 获取连接
- 释放连接
4.1 结构定义
定义Conn结构体,这里包含了几乎所有的有关连接需要的信息属性
type Conn struct {maxConn int // 最大连接数maxIdle int // 最大可用连接数freeConn int // 线程池空闲连接数connPool []int // 连接池openCount int // 已经打开的连接数waitConn map[int]chan Permission // 排队等待的连接队列waitCount int // 等待个数lock sync.Mutex // 锁nextConnIndex NextConnIndex// 下一个连接的ID标识(用于区分每个ID)freeConns map[int]Permission // 连接池的连接}
这里并不会创建一个真正的数据库连接,而是使用一个非空的Permission表示拿到了连接。拿到一个非空的Permission才有资格执行后面类似增删改查的操作。
Permission对应的结构体如下
type Permission struct {NextConnIndex // 对应Conn中的NextConnIndexContent string // 通行证的具体内容,比如"PASSED"表示成功获取CreatedAt time.Time // 创建时间,即连接的创建时间MaxLifeTime time.Duration // 连接的存活时间,本次没有用到这个属性,保留}
NextConnIndex对应的结构体如下
type NextConnIndex struct {Index int}
还有一个用来设置最大连接数以及连接池最大连接数的Config
type Config struct {MaxConn intMaxIdle int}
4.2 初始化连接池参数
func Prepare(ctx context.Context, config *Config) (conn *Conn) {// go func() {//for {//conn.expiredCh = make(chan string, len(conn.freeConns))//for _, value := range conn.freeConns {//if value.CreatedAt.Add(value.MaxLifeTime).Before(nowFunc()) {//conn.expiredCh
这里主要是初始化上面的Conn结构体参数。
注释的部分,主要想通过启动一个监听协程,用于监听已经过期的连接,并通过channel发送。(这块还有一些细节没有想清楚,先搁置)
4.3 设置MaxConn和MaxIdle
在main.go中添加代码
ctx := context.Background()config := &custom_pool.Config{MaxConn: 2,MaxIdle: 1,}
这里意味连接池只能缓存一个连接,最大新建连接数为2,超过则要加入等待队列。
4.4 获取连接
// 创建连接func (conn *Conn) New(ctx context.Context) (permission Permission, err error) {conn.lock.Lock()select {default:case 0 {var (popPermission PermissionpopReqKey int) // 获取其中一个连接for popReqKey, popPermission = range conn.freeConns {break} // 从连接池删除delete(conn.freeConns, popReqKey)fmt.Println("log


