golang redigo 在高并发下的问题。连接池占满CPU

最初我们使用了redigo【github.com/garyburd/redigo/redis】,使用上倒是没有什么不爽的,但是在压测的时候发现一个问题,即连接池的使用。

func factory(name string) *redis.Pool {
    conf := config.Get("redis." + name).(*toml.TomlTree)
    host := conf.Get("host").(string)
    port := conf.Get("port").(string)
    password := conf.GetDefault("passwd", "").(string)
    fmt.Printf("conf-redis: %s:%s - %s\r\n", host, port, password)

    pool := &redis.Pool{
        IdleTimeout: idleTimeout,
        MaxIdle:     maxIdle,
        MaxActive:   maxActive,
        Dial: func() (redis.Conn, error) {
            address := fmt.Sprintf("%s:%s", host, port)
            c, err := redis.Dial("tcp", address,
                redis.DialPassword(password),
            )
            if err != nil {
                exception.Catch(err)
                return nil, err
            }

            return c, nil
        },
    }
    return pool
}

/**
 * 获取连接
 */
func getRedis(name string) redis.Conn {
    return redisPool[name].Get()
}

/**
 * 获取master连接
 */
func Master(db int) RedisClient {
    client := RedisClient{"master", db}
    return client
}

/**
 * 获取slave连接
 */
func Slave(db int) RedisClient {
    client := RedisClient{"slave", db}
    return client
}

以上是定义了一个连接池,这里就产生了一个问题,在redigo中执行redis命令时是需要自行从连接池中获取连接,而在使用后还需要自己将连接放回连接池。最初我们就是没有将连接放回去,导致压测的时候一直压不上去。


那么有没有更好的包呢,答案当然是肯定的 —— gopkg.in/redis.v5

 
    
 
    
func factory(name string) *redis.Client {
    conf := config.Get("redis." + name).(*toml.TomlTree)
    host := conf.Get("host").(string)
    port := conf.Get("port").(string)
    password := conf.GetDefault("passwd", "").(string)
    fmt.Printf("conf-redis: %s:%s - %s\r\n", host, port, password)

    address := fmt.Sprintf("%s:%s", host, port)
    return redis.NewClient(&redis.Options{
        Addr:        address,
        Password:    password,
        DB:          0,
        PoolSize:    maxActive,
    })
}

/**
 * 获取连接
 */
func getRedis(name string) *redis.Client {
    return factory(name)
}

/**
 * 获取master连接
 */
func Master() *redis.Client {
    return getRedis("master")
}

/**
 * 获取slave连接
 */
func Slave() *redis.Client {
    return getRedis("slave")
}

可以看到,这个包就是直接返回需要的连接了。

那么我们去看一下他的源码,连接有没有放回去呢。

func (c *baseClient) conn() (*pool.Conn, bool, error) {
    cn, isNew, err := c.connPool.Get()
    if err != nil {
        return nil, false, err
    }
    if !cn.Inited {
        if err := c.initConn(cn); err != nil {
            _ = c.connPool.Remove(cn, err)
            return nil, false, err
        }
    }
    return cn, isNew, nil
}

func (c *baseClient) putConn(cn *pool.Conn, err error, allowTimeout bool) bool {
    if internal.IsBadConn(err, allowTimeout) {
        _ = c.connPool.Remove(cn, err)
        return false
    }

    _ = c.connPool.Put(cn)
    return true
}

func (c *baseClient) defaultProcess(cmd Cmder) error {
    for i := 0; i <= c.opt.MaxRetries; i++ {
        cn, _, err := c.conn()
        if err != nil {
            cmd.setErr(err)
            return err
        }

        cn.SetWriteTimeout(c.opt.WriteTimeout)
        if err := writeCmd(cn, cmd); err != nil {
            c.putConn(cn, err, false)
            cmd.setErr(err)
            if err != nil && internal.IsRetryableError(err) {
                continue
            }
            return err
        }

        cn.SetReadTimeout(c.cmdTimeout(cmd))
        err = cmd.readReply(cn)
        c.putConn(cn, err, false)
        if err != nil && internal.IsRetryableError(err) {
            continue
        }

        return err
    }

    return cmd.Err()
}
 
    

 可以看到,在这个包中的底层操作会先去connPool中Get一个连接,用完之后又执行了putConn方法将连接放回connPool。 
   


你可能感兴趣的:(golang redigo 在高并发下的问题。连接池占满CPU)