
本文详解如何在 go 中借助 redigo 客户端,将 struct 或 struct 切片序列化为 json 存入 redis,并安全反序列化还原,涵盖字段导出、json 编解码、redis 命令选择及常见陷阱。
在 Go 中操作 Redis 存储自定义结构体(如 Resource)时,Redis 本身只支持字符串、字节流等基础类型,因此必须对结构体进行序列化(marshaling)后存储,读取时再反序列化(unmarshaling)。你提供的代码中存在两个关键问题:一是结构体字段 title string 未导出(首字母小写),导致 json.Marshal 无法访问;二是 LPUSH 直接传入结构体对象会触发默认字符串化(非 JSON),无法正确还原。
✅ 正确做法:导出字段 + JSON 序列化 + 合理 Redis 数据结构
首先,修正结构体定义,确保字段可被 encoding/json 访问:
type Resource struct {
Title string `json:"title"` // 首字母大写(导出),并添加 JSON tag 保持键名一致
}⚠️ 注意:Go 中只有首字母大写的字段才是导出字段(exported),json 包仅序列化导出字段。原 title string 是私有字段,json.Marshal 将忽略它,结果为空对象 {}。
✅ 示例:保存与读取 struct 切片
以下是一个完整的 Redigo + JSON 实践示例(含错误处理和连接管理):
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/gomodule/redigo/redis"
)
type Resource struct {
Title string `json:"title"`
}
func main() {
// 建立 Redis 连接池(生产环境推荐)
pool := &redis.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", "localhost:6379")
},
}
defer pool.Close()
conn := pool.Get()
defer conn.Close()
// 准备数据
resources := []Resource{
{Title: "Getting Started with Go"},
{Title: "Advanced Redis Patterns"},
{Title: "Redigo Best Practices"},
}
// ✅ 步骤1:序列化为 JSON 字节数组
data, err := json.Marshal(resources)
if err != nil {
log.Fatal("JSON marshal failed:", err)
}
// ✅ 步骤2:存入 Redis —— 推荐使用 STRING 类型(语义清晰、读取高效)
_, err = conn.Do("SET", "resources:list", data)
if err != nil {
log.Fatal("Redis SET failed:", err)
}
// ✅ 步骤3:从 Redis 读取并反序列化
reply, err := redis.Bytes(conn.Do("GET", "resources:list"))
if err != nil {
log.Fatal("Redis GET failed:", err)
}
var loaded []Resource
err = json.Unmarshal(reply, &loaded)
if err != nil {
log.Fatal("JSON unmarshal failed:", err)
}
fmt.Printf("Loaded %d resources:\n", len(loaded))
for i, r := range loaded {
fmt.Printf("[%d] %s\n", i+1, r.Title)
}
// 输出:
// Loaded 3 resources:
// [1] Getting Started with Go
// [2] Advanced Redis Patterns
// [3] Redigo Best Practices
}? 关于 LPUSH / LRANGE 的说明(按需选用)
你原计划用 LPUSH 存入单个 struct,这在技术上可行,但需注意:
- 每次 LPUSH 只能推入一个 JSON 字符串(即每个 struct 单独序列化后入队);
- 读取时需用 LRANGE key 0 -1 获取全部元素,再逐个 json.Unmarshal;
- 更适合「消息队列」或「需保序追加」场景,而非「整体集合读写」。
若坚持使用列表方式,示例片段如下:
// 存入(每个 struct 独立序列化)
for _, r := range resources {
b, _ := json.Marshal(r)
conn.Do("LPUSH", "resources:queue", b)
}
// 读取全部
items, _ := redis.Values(conn.Do("LRANGE", "resources:queue", 0, -1))
var loaded []Resource
for _, item := range items {
var r Resource
json.Unmarshal(item.([]byte), &r)
loaded = append(loaded, r)
}? 关键注意事项总结
- ✅ 始终导出结构体字段(首字母大写),否则 json 包不可见;
- ✅ 优先使用 SET/GET 存储整个切片的 JSON,语义明确、I/O 更少、性能更优;
- ✅ 使用 redis.Bytes() 或 redis.String() 辅助函数安全转换返回值,避免类型断言 panic;
- ⚠️ 避免在 Do() 中直接传入未序列化的 struct——Go 会调用其 String() 方法(若实现)或打印类型名,绝非预期 JSON;
- ? 生产环境务必配置连接池、超时、重试及错误监控;
- ? 如需查询能力(如按 title 模糊匹配),考虑结合 Redis 模块(RedisJSON、Search)或改用关系型数据库。
通过以上方式,你就能在 Go 中稳定、可维护地将结构化数据持久化至 Redis,并精准还原——既发挥 Redis 的高性能优势,又不失 Go 类型系统的安全性与表达力。










