同事在初始化redis配置的时候,给Dial函数赋值时用了闭包,导致程序上线后,数据怎么都加载不到redis中去,排查了半个多小时,总算找到了罪魁祸首。虽然自己之前对闭包也算了解,但是看到他的那段代码的时候,乍一看竟也没发现出问题来,所以决定写篇文章加深印象,避免自己以后也犯类似的问题。
先上代码:
func InitRedis() error {
GRedis = make(map[string]*Cache)
for _, gConfig := range config.GConfigs.RedisList { //遍历配置文件,redisList存放了各个redis服务器的ip、端口号以及密码等信息。
c := new(Cache)
c.pool = &redis.Pool{
MaxIdle: gConfig.Conf.MaxConn,
MaxActive: gConfig.Conf.MaxConn,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) { //连接实例的Dial函数,这里用了闭包,其中gConfig为自由变量
c, err := redis.Dial("tcp", gConfig.Conf.IP+":"+gConfig.Conf.Port, redis.DialConnectTimeout(60*time.Second), redis.DialReadTimeout(60*time.Second), redis.DialWriteTimeout(60*time.Second))
if err != nil {
return nil, err
}
if gConfig.Conf.Password != "" {
if _, err := c.Do("AUTH", gConfig.Conf.Password); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
}
c.prefix = gConfig.Conf.Prefix
GRedis[gConfig.Name] = c
}
return nil
}
上述代码已对关键部分(涉及公司机密)进行了删除和处理。
乍一看是不是很难发现问题到底出在哪,我们当时碰到的问题是,数据没有从db加载到redis服务器1中,经过排查,发现db的数据全加载到了redis服务器3中了。恰巧redis服务器3是配置文件中的最后一个,也就是for循环中遍历的最后一个。进而我们发现Dial函数对应的闭包函数里引用了gConfig这个自由变量。
这会导致什么问题呢?我们先来温习一下闭包的特性:有自由变量的匿名函数是闭包,闭包的特性,就是内层函数引用了外层函数中的变量,注意是引用哦。
所以,这会导致redis连接池中每个连接去做Dial的时候,拿到的都是for循环中最后一个gConfig,即连接的都是redis服务器3,去redis服务器1里面拿数据当然就什么都拿不到了。