本文详解 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: %s\n", 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 客户端集成方案。










