一、什么是连接池,连接池有什么用
先看看别人是怎么介绍连接池的吧:
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
下面我来简单解释一下,因为每次 Redis
客户端连接 Redis
服务端都需要一段时间,而处理各种操作的时间很多时候都很短,如果每次进行各种操作时都需要重新连接 Redis
,那么就会浪费大量时间。因此 Redis
引入连接池,连接池可以实现建立多个客户端连接而不释放,避免浪费IO资源,不使用的时候就放在连接池,这样就减少了连接数据库所需要的时间,提高效率。
连接池就是建一个池子和一定量的管道。每次当管道被取尽时,就不能继续消耗IO资源了,这样就保证了IO资源不会耗尽。
二、代码展示
func GetRedis(config_name string) *redis.Pool { maxIdle := viper.GetInt(config_name + ".max_idle") maxActive := viper.GetInt(config_name + ".max_active") address := viper.GetString(config_name + ".dsn") password := viper.GetString(config_name + ".password") database := viper.GetInt(config_name + ".database") //该值表示如果连接池的的连接闲置超过该值就会关闭连接。如果该值为零,连接池中闲置的连接就不会关闭。应用程序应该设置这个限制超时时间不超过服务端的限制超时时间。 idleTimeoutInt := viper.GetInt(config_name + ".idle_timeout") idleTimeout := time.Duration(idleTimeoutInt) * time.Second timeoutInt := viper.GetInt(config_name + ".timeout") timeout := time.Duration(timeoutInt) * time.Second // 建立连接池 return &redis.Pool{ MaxIdle: maxIdle, //最大闲置连接数 MaxActive: maxActive, //最大活跃连接数,0代表无限 IdleTimeout: idleTimeout, //闲置连接的超时时间 Wait: false, Dial: func() (redis.Conn, error) { con, err := redis.Dial("tcp", address, redis.DialPassword(password), redis.DialDatabase(database), redis.DialConnectTimeout(timeout), redis.DialReadTimeout(timeout), redis.DialWriteTimeout(timeout)) if err != nil { panic("failed to init redis pool,err:" + err.Error()) } return con, nil }, TestOnBorrow: func(c redis.Conn, t time.Time) error { _, err := c.Do("PING") return err }, } } func demo(){ rds := common.RedisPool.Get() if rds.Err() == nil { //延时关闭连接 defer rds.Close() } rds.Do("SELECT", 0) key := "test:count" do, err := rds.Do("GET", key) count, err := redis.Int(do, err) count++ rds.Do("INCRBY", key, 5) stat := common.RedisPool.Stats() }
如果一个人占着管道不用,就会被闲置。如果在闲置处太久不动,达到闲置连接的超时时间,就会被请走。
三、底层详解
Redigo Pool 最重要的结构
type Pool struct { // 真正获取跟redis-server连接的函数, 必填参数 Dial func() (Conn, error) // 这是个可选参数, 用于在从 pool 获取连接时, 检查这个连接是否正常使用. 所以这个参数一般是必填的 TestOnBorrow func(c Conn, t time.Time) error // 最多有多少个空闲连接保留, 一般必填 MaxIdle int // 最多有多少活跃的连接数, 一般必填 MaxActive int // 空闲连接最长空闲时间, 一般必填 IdleTimeout time.Duration // Pool 的活跃的连接数达到 MaxActive, 如果 Wait 为 true, Get会阻塞 // 那么 Get() 将要等待一个连接放到 Pool中, 才会返回一个连接给使用方 Wait bool // 设置连接最大存活时间 MaxConnLifetime time.Duration chInitialized uint32 // set to 1 when field ch is initialized mu sync.Mutex // mu protects the following fields closed bool // 设置 Pool 是否关闭 active int // 当前 Pool 的活跃连接数 ch chan struct{} // 配合 Wait 为 true 使用 idle idleList // 空闲队列 }
特别注意事项
顺带说一句在 `pool.go` 里面总共有两个 Close() 函数: 1. func (p *Pool) Close() error {...} 这个函数是关闭 redigo 连接池的. 理论上可以不调用. 如果确实不放心, 需要在 main.go 里面 `defer pool.Close()` 来调用 2. func (ac *activeConn) Close() error { ...} 这个函数是用来将从 Pool 中获取到的 activeConn 放回到 Pool 里面. 这个函数是我们需要频繁调用的函数. 如果程序里Get() 之后没有 Close(), 那么就会造成 redis 连接泄漏. 更严重的情况, 如果Wait, MaxActive 都没有设置, 那么你的程序就会将 redis 搞瘫痪, 这是很危险的 3. Get连接以后,主动执行 rds.Do("SELECT", 0),切换到自己需要操作的库,因为默认取出来的链接,不一定默认select 0
Redigo 灵魂函数 -- put()
put 函数主要提供给 activeConn.Close() 调用
Close() 函数就不在详细说明, 主要根据 activeConn 的 stat, 判断在关闭连接之前是否发送过WATCH
, MULTI
, PSUBSCRIBE
, SUBSCRIBE
, MONITOR
这些命令. 如果发送过就会把这些命令结束
func (p *Pool) put(pc *poolConn, forceClose bool) error { p.mu.Lock() // 判断 pool 是否关闭, 并且该命令是否需要强制关闭 if !p.closed && !forceClose { pc.t = nowFunc() // 将该 activeConn 压入 idleList 中 p.idle.pushFront(pc) // 如果 idleList 的 count 已经大于 MaxIdle, 那么会将 idleList 的尾部的 activeConn pop 掉 if p.idle.count > p.MaxIdle { pc = p.idle.back p.idle.popBack() } else { pc = nil } } // 如果是需要强制关闭或者是从尾部 pop 掉的 conn, 那么就会真正的关闭这个连接 if pc != nil { p.mu.Unlock() pc.c.Close() p.mu.Lock() p.active-- } // 如果开启了 Wait = true, 那么往 channel 里面发送一个struct{}{}, 代表等待的客户端可以获取连接了 if p.ch != nil && !p.closed { p.ch <- struct{}{} } p.mu.Unlock() return nil }
当Pool.get获取的连接,并没有保存在连接池中,而是当activeConn.Close()时,才调用put,保存连接。所以我们刚开始初始化连接池的时候,连接池里面的活跃链接和空闲连接其实都是空的,并没有任何可用的链接,只有当Get到链接,并且Close以后才会放入到连接池中!!!
我们可以验证一下,刚开始初始化好连接池的时候,打印连接池的状态:pool.Stats() ,其中的
ActiveCount 0 IdleCount 0
这时候你Get一下再去打印 Stats()就会发现
ActiveCount 1 //本次get产生的,但是IdleCount 还是0 IdleCount 0
怎么让IdleCount > 0 呢,我们可以想一下,当我们多次Get,然后模拟一段处理时间比较长的业务,比如sleep 10秒,这时候3次请求同时开始,因为连接池没有空闲连接,所以每次Get都是新建立的链接,这时候等他们都执行完,都会Close,也就是上面说的都会put回连接池,这时候我们再看Stat ,就会发现连接池有空闲连接了。
ActiveCount 3 //3次get产生的 IdleCount 0
超级总结
Get 以后必须 conn.Close()
如果中间用到多个db,select db以后,放回连接池的默认还是切换后的db,所以Get之后最好是显示select db 切换到自己想要操作的db
IdleTimeout 必须设置的小于redis服务器的timeout,并且要小于nat的超时时间(一般5分钟)
- 本文固定链接: https://www.phpmianshi.com/?id=4778
- 转载请注明: admin 于 PHP面试网 发表
《本文》有 0 条评论