本文详解 Redigo 使用过程中因未校验连接错误导致 nil pointer dereference panic 的根本原因,并提供安全初始化 redis 连接池、正确处理连接生命周期的完整实践方案。
本文详解 redigo 使用过程中因未校验连接错误导致 `nil pointer dereference` panic 的根本原因,并提供安全初始化 redis 连接池、正确处理连接生命周期的完整实践方案。
在使用 Redigo 构建 Redis 客户端时,开发者常因忽略错误检查顺序而触发运行时 panic:panic: runtime Error: invalid memory address or nil pointer dereference。该错误并非 Redigo 本身缺陷,而是由在未确认连接成功前就调用 con.Do() 等方法所引发——当 redis.Dial() 失败返回 nil, err 时,后续对 nil 连接对象的任何操作(如 con.Do(“select”, 1))都将直接崩溃。
? 根本问题:错误处理逻辑错位
观察原始代码片段中的关键隐患:
con, err := redis.Dial("tcp", *redisAddress) con.Do("SELECT", 1) // ⚠️ 危险!此处 con 可能为 nil if err != nil { return nil, err }
上述写法违反了 Go 的错误处理黄金法则:必须在使用返回值前检查 error。一旦网络不可达、Redis 未启动或地址配置错误,redis.Dial() 将返回 nil, err,此时执行 con.Do(…) 即等同于 nil.Do(…),触发空指针解引用。
✅ 正确做法:严格遵循“先检错,后使用”原则
修正后的连接工厂函数应如下编写:
redisPool := redis.NewPool(func() (redis.Conn, error) { con, err := redis.Dial("tcp", *redisAddress) if err != nil { // ✅ 先检查错误 return nil, err } // ✅ 此时 con 必然非 nil,可安全使用 if _, err := con.Do("SELECT", 1); err != nil { con.Close() // 显式关闭失败连接,避免资源泄漏 return nil, err } return con, nil }, *maxConnections)
? 注意:SELECT 命令也需检查其返回错误;若失败,应主动 Close() 连接再返回 nil, err,防止无效连接流入连接池。
? 完整可运行示例(含健壮性增强)
package main import ( "flag" "fmt" "log" "time" "github.com/garyburd/redigo/redis" ) var ( redisAddress = flag.String("addr", "127.0.0.1:6379", "Redis server address") maxConnections = flag.Int("max-connections", 10, "Maximum number of connections in pool") ) func main() { flag.Parse() // 创建带健康检查与超时的连接池 redisPool := &redis.Pool{ MaxIdle: 10, MaxActive: *maxConnections, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { con, err := redis.Dial("tcp", *redisAddress, redis.DialConnectTimeout(5*time.Second), redis.DialReadTimeout(5*time.Second), redis.DialWriteTimeout(5*time.Second), ) if err != nil { return nil, err } // 选择数据库并验证连接有效性 if _, err := con.Do("SELECT", 1); err != nil { con.Close() return nil, err } return con, nil }, TestOnBorrow: func(c redis.Conn, t time.Time) error { if time.Since(t) < time.Minute { return nil } _, err := c.Do("PING") return err }, } fmt.Println("Connecting to Redis...") conn := redisPool.Get() defer conn.Close() // 确保连接归还池中 // 执行操作 if _, err := conn.Do("SET", "Name", "BookMyShow"); err != nil { log.Fatal("SET failed:", err) } fmt.Println("Redis Connected and SET executed successfully.") val, err := redis.String(conn.Do("GET", "Name")) if err != nil { log.Fatal("GET failed:", err) } fmt.Printf("Retrieved value: %sn", val) }
⚠️ 关键注意事项总结
- 永远不要跳过 err 检查:所有 redis.Dial()、con.Do()、con.Send() 等调用后必须立即判断 err != nil。
- 连接池 Dial 函数中禁止 panic:应统一返回 (nil, err),由 Redigo 自动重试或拒绝分配。
- 启用 TestOnBorrow:避免将长时间空闲后失效的连接分发给业务逻辑。
- 设置合理的超时参数:DialConnectTimeout、DialReadTimeout 等可防止阻塞式挂起。
- 显式 defer conn.Close():即使使用连接池,Get() 返回的 Conn 仍需手动 Close()(本质是归还至池),否则将导致连接泄漏。
遵循以上规范,即可彻底规避 nil pointer dereference 类 panic,构建出高可用、易维护的 Redis 客户端集成方案。