
本文详解 Redigo 中因未校验 redis.Dial 返回错误就直接使用连接对象,导致 panic: invalid memory address or nil pointer dereference 的典型问题,并提供安全、健壮的连接池初始化方案。
本文详解 redigo 中因未校验 `redis.dial` 返回错误就直接使用连接对象,导致 `panic: invalid memory address or nil pointer dereference` 的典型问题,并提供安全、健壮的连接池初始化方案。
在使用 Redigo 构建 Redis 连接池时,一个高频且隐蔽的崩溃原因是:在未检查 redis.Dial 是否成功的情况下,直接调用返回的 Conn 对象方法(如 Do 或 Send)。一旦网络不通、地址错误或 Redis 服务未启动,redis.Dial 将返回 nil, err,此时若继续执行 con.Do("SELECT", 1),Go 运行时便会触发 nil pointer dereference panic —— 正如报错堆栈中 pool.go:250 处的 con.Do(...) 所示。
根本问题出在连接工厂函数(NewPool 的第一个参数)内部逻辑顺序错误。原始代码片段中:
con, err := redis.Dial("tcp", *redisAddress)
con.Do("SELECT", 1) // ⚠️ 危险!con 可能为 nil
if err != nil {
return nil, err
}此处 con.Do(...) 被置于 err 检查之前,违反了 Go 的“错误优先、防御先行”原则。正确做法是:立即检查错误,仅在 err == nil 时才安全使用 con。
以下是修复后的完整、生产就绪示例:
package main
import (
"flag"
"fmt"
"log"
"time"
"github.com/garyburd/redigo/redis"
)
var (
redisAddress = flag.String("addr", "10.12.2.121:6379", "Redis server address (host:port)")
maxConnections = flag.Int("max-connections", 10, "Maximum number of connections in the pool")
)
func main() {
flag.Parse()
// ✅ 安全构建 Redis 连接池:工厂函数内严格校验错误
redisPool := &redis.Pool{
MaxIdle: 5,
MaxActive: *maxConnections,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
con, err := redis.Dial("tcp", *redisAddress)
if err != nil { // ✅ 必须先检查 err!
return nil, fmt.Errorf("failed to dial Redis at %s: %w", *redisAddress, err)
}
// ✅ 此时 con 确保非 nil,可安全执行初始化命令
if _, err := con.Do("SELECT", 1); err != nil {
con.Close() // 关闭异常连接,避免泄露
return nil, fmt.Errorf("failed to SELECT db 1: %w", 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 via connection pool...")
defer redisPool.Close() // 程序退出前释放资源
// 获取连接并执行操作
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 succeeded")
value, err := redis.String(conn.Do("GET", "Name"))
if err != nil {
log.Fatal("GET failed:", err)
}
fmt.Printf("Value Retrieved: %s\n", value)
}? 关键修复点与最佳实践总结:
- 错误检查必须前置:任何 redis.Dial、conn.Do、conn.Send 等可能失败的操作后,立即检查 err,禁止在 err != nil 时继续使用相关对象;
- 连接初始化需容错:SELECT 等初始化命令失败时,应主动 con.Close() 并返回错误,防止无效连接进入池中;
- 启用 TestOnBorrow:定期探测连接可用性,自动剔除失效连接,提升稳定性;
- 显式资源管理:使用 defer conn.Close() 确保连接及时归还,避免池耗尽;
- 避免裸用 NewPool:推荐使用结构体字面量初始化 &redis.Pool{},便于配置 MaxIdle、IdleTimeout、TestOnBorrow 等关键参数。
遵循以上模式,即可彻底规避 nil pointer dereference panic,构建高可用、易维护的 Redis 客户端连接层。










