案例速成GO+redis 个人笔记

更多个人笔记:(仅供参考,非盈利)

gitee: https://gitee.com/harryhack/it_note

github: https://github.com/ZHLOVEYY/IT_note

(更多GO+redis等见内部,会及时更新~)

  • 安装redis客户端:go get github.com/redis/go-redis/v9
  • 注意go mod tidy的时候不要import成了"github.com/go-redis/redis" 需要检查一下

数据持久化(RDB + AOF)

这个也是八股中很多的

Redis 的数据持久化通过 RDB (快照)和 AOF(追加日志)实现。Go 代码无需直接控制持久化(由 Redis 配置文件管理),但可以通过命令触发快照或检查持久化状态。

  • RDB(Redis Database Backup) (案例)
    • 原理:
      • RDB 通过定期生成内存数据的快照(二进制文件,.rdb),保存到磁盘。
      • 快照是某一时刻的完整数据副本,文件体积较小,恢复速度快。
    • 触发方式:
      • 自动触发:根据 save 配置(如 save 900 1 表示 900 秒内至少 1 次变更触发)。
      • 手动触发
        • SAVE:阻塞主线程,生成快照(生产慎用)。
        • BGSAVE:后台异步生成快照,常用。
    • 优点:恢复速度比AOF快文件紧凑,适合备份和快速恢复
    • 缺点:可能丢失数据(两次快照间隔内的变更会丢失!!!),BGSAVE 需要 fork 进程,内存占用较高
    • 适用场景
      • 定期备份(如每天全量备份)。
      • 对少量数据丢失可接受的场景(如缓存)。
        RDB的配置文件 redis.config (示范)
bahs 复制代码
save 900 1    # 900秒内至少1次变更触发快照
save 300 10   # 300秒内至少10次变更
save 60 10000 # 60秒内至少10000次变更  这几个同时生效保证不同场景
dir /var/redis # 快照文件存储路径 
dbfilename dump.rdb # 快照文件名

触发快照的意思就是会进行一次更改更新(类似虚拟机的快照,但是这个直接覆盖)

  • AOF(Append-Only File)
    • 原理
      • AOF 记录每次写操作(如 SET、DEL)到日志文件(.aof),每个写命令都会追加到 AOF 文件!!!类似数据库的 WAL(Write-Ahead Log)。
      • 重启时,Redis 重放 AOF 文件重建数据
    • 同步策略 (appendfsync)
      • always:每次写操作同步到磁盘,数据最安全但性能最低。
      • everysec:每秒同步,折中方案(最多丢 1 秒数据)。
      • no:依赖操作系统同步,性能最高但数据丢失风险大。
    • AOF重写
      • AOF 文件会随写操作不断增长,占用磁盘空间。
      • BGREWRITEAOF 合并冗余命令(如多次 INCR 合并为一个 SET),生成更小的 AOF 文件。
    • 优点:数据可靠性高,支持增量记录,适合高一致性场景
    • 缺点:文件体积较大恢复速度慢(需重放所有命令)。写频繁的时候性能开销高
    • 适用场景
      • 数据一致性要求高的场景(如订单、会话)。
      • 与 RDB 结合使用,兼顾恢复速度和可靠性。

AOF配置文件redis.config (示范)

bash 复制代码
appendonly yes        # 启用 AOF
appendfsync everysec  # 每秒同步
dir /var/redis        # AOF 文件存储路径
appendfilename appendonly.aof # AOF 文件名
auto-aof-rewrite-percentage 100 # AOF 文件增长100%时触发重写
auto-aof-rewrite-min-size 64mb  # AOF 文件至少64MB时触发重写

redis-cli中手动输入可以出发RDB和AOF:

bash 复制代码
BGSAVE # 手动触发快照
BGREWRITEAOF # 手动触发 AOF 重写

下面我们来实际操作一下

关于redis的启动(mac)

我的是mac,如果是homebrew启动的redis即通过指令 brew services start redis 那么即使执行 redis-cli shutdown 也无法关闭,homebrew启动的是类似全局的redis,需要使用 brew services start redis 进行关闭

bash 复制代码
# 查看当前运行的 Redis   可以用于检查问题
ps aux | grep redi
# 快速检查端口
lsof -i :6379  # 如果没有被占用就没有输出(显示为错误输出但实际没有输出的)

redis-cli config get dir 获取存储对应的目录,如果是homebrew启动的一眼就能看出来
redis-cli config get dbfilename 获取对应的dbfilename

redis.conf的demo:(结合了RDB和AOF)

bash 复制代码
save 900 1
save 300 10
dir ./redisstorage
dbfilename dump.rdb
appendonly yes
appendfsync everysec
appendfilename appendonly.aof
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

dir是目标存储文件夹,需要新建对应的文件夹

接着我们需要在当前文件夹的终端下执行 redis-server ./redis.conf 根据配置文件启动

运行GO

同样当前文件夹下,运行下面的go文件: (go run xxx.go)

GO 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/redis/go-redis/v9"
)

func main() {
	// 连接 Redis
	client := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})
	ctx := context.Background()

	// 测试连接
	_, err := client.Ping(ctx).Result()
	if err != nil {
		log.Fatal("连接失败:", err)
	}
	fmt.Println("连接成功")

	// 写入数据(触发 AOF 记录)
	err = client.Set(ctx, "persistent_key", "critical_data", 0).Err()
	if err != nil {
		log.Fatal("设置失败:", err)
	}
	fmt.Println("设置 persistent_key = critical_data")

	// 触发 RDB 快照
	err = client.BgSave(ctx).Err()
	if err != nil {
		log.Fatal("触发 RDB 快照失败:", err)
	}
	fmt.Println("触发 RDB 快照")

	// 触发 AOF 重写(优化 AOF 文件)
	err = client.BgRewriteAOF(ctx).Err()
	if err != nil {
		log.Fatal("触发 AOF 重写失败:", err)
	}
	fmt.Println("触发 AOF 重写")

	// 等待持久化完成
	time.Sleep(2 * time.Second)

	// 检查持久化状态
	info, err := client.Info(ctx, "persistence").Result()
	if err != nil {
		log.Fatal("获取持久化信息失败:", err)
	}
	fmt.Println("持久化状态:\n", info)

	// 验证数据
	value, err := client.Get(ctx, "persistent_key").Result()
	if err != nil {
		log.Fatal("读取失败:", err)
	}
	fmt.Printf("读取 persistent_key = %s\n", value)
}
  • 运行后可以发现redisstorage中有添加存储数据
  • redis-cli 进入redis服务 get persistent_key 发现可以看到键对应的值结果critical_data
  • 然后可以通过Ctrl-c退出运行redis的终端,或者redis-cli shutdown 关闭服务
  • 接着再次redis-server ./redis.conf启动服务,可以发现get persistent_key还是可以得到对应的结果,说明成功(如果不配置持久化的话,就是不写配置文件,redis也会有默认配置文件存储在运行redis的文件夹下 )

进阶

GO 复制代码
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/redis/go-redis/v9"
)

// User 结构体,表示用户信息
type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	// 连接 Redis
	client := redis.NewClient(&redis.Options{
		Addr:         "localhost:6379",
		PoolSize:     10, // 连接池
		MinIdleConns: 2,
	})
	ctx := context.Background()

	// 测试连接
	_, err := client.Ping(ctx).Result()
	if err != nil {
		log.Fatal("连接失败:", err)
	}
	fmt.Println("连接成功")

	// 模拟用户数据
	users := []User{
		{ID: 1, Name: "Alice", Age: 25},
		{ID: 2, Name: "Bob", Age: 30},
	}

	// 使用 Pipeline 批量写入缓存
	pipe := client.Pipeline()
	for _, user := range users {
		key := fmt.Sprintf("user:%d", user.ID)
		data, err := json.Marshal(user)
		if err != nil {
			log.Printf("序列化用户 %d 失败: %v", user.ID, err)
			continue
		}
		// 设置缓存,TTL 1 小时
		pipe.Set(ctx, key, data, time.Hour)
	}
	_, err = pipe.Exec(ctx)
	if err != nil {
		log.Fatal("批量写入失败:", err)
	}
	fmt.Println("批量写入用户缓存")

	// 触发 RDB 快照
	err = client.BgSave(ctx).Err()
	if err != nil {
		log.Fatal("触发 RDB 快照失败:", err)
	}
	fmt.Println("触发 RDB 快照")

	// 检查 AOF 文件大小,决定是否重写
	info, err := client.Info(ctx, "persistence").Result()
	if err != nil {
		log.Fatal("获取持久化信息失败:", err)
	}
	var aofSize int64
	fmt.Sscanf(info, "aof_current_size:%d", &aofSize)
	if aofSize > 64*1024*1024 { // 超过 64MB
		err = client.BgRewriteAOF(ctx).Err()
		if err != nil {
			log.Fatal("触发 AOF 重写失败:", err)
		}
		fmt.Println("触发 AOF 重写")
	} else {
		fmt.Printf("AOF 文件大小: %d 字节,无需重写\n", aofSize)
	}

	// 等待持久化完成
	time.Sleep(2 * time.Second)

	// 读取并验证缓存
	for _, user := range users {
		key := fmt.Sprintf("user:%d", user.ID)
		data, err := client.Get(ctx, key).Result()
		if err != nil {
			log.Printf("读取用户 %d 失败: %v", user.ID, err)
			continue
		}
		var cachedUser User
		err = json.Unmarshal([]byte(data), &cachedUser)
		if err != nil {
			log.Printf("反序列化用户 %d 失败: %v", user.ID, err)
			continue
		}
		fmt.Printf("读取用户: %+v\n", cachedUser)
	}

	// 模拟缓存失效后从数据库加载
	key := "user:1"
	 //手动删除缓存
	 client.Del(ctx, key)
	 // 现在尝试获取,会触发缓存缺失
	 _, err = client.Get(ctx, key).Result()
	 if err == redis.Nil {
		 fmt.Println("缓存缺失,模拟从数据库加载")
		 user := User{ID: 1, Name: "Alice", Age: 26}
		 data, _ := json.Marshal(user)
		 err = client.Set(ctx, key, data, time.Hour).Err()
		 if err != nil {
			 log.Fatal("重新缓存失败:", err)
		 }
		 fmt.Println("重新缓存用户 1")
	 }
}

大家可以新建一个自己拿这个尝试一下,多设置了AOF的自动更新,可以增加插入数据尝试

哨兵模式

哨兵模式(Sentinel)用于 Redis 高可用,监控主从节点,自动故障转移。Go 客户端通过 go-redis 的 FailoverClient 连接哨兵

新建sentinel.conf 文件:

bash 复制代码
port 26379
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 15000
  • 设置端口为26379
  • 设置监控的主节点,以及只用一个哨兵就可以完成选举(多配置哨兵可以实现高可用)
  • 主节点响应超过 5000 毫秒(5秒)就认为主观下线
  • 障转移超时时间为 15000 毫秒(15秒),时间内没完成转移认为转移失败

准备好GO文件:

GO 复制代码
package main

import (
	"context"
	"fmt"
	"log"
	"github.com/redis/go-redis/v9"
)

func main() {
	// 连接哨兵
	client := redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:    "mymaster",           // 哨兵监控的主节点名称
		SentinelAddrs: []string{"localhost:26379"}, // 哨兵地址
	})
	ctx := context.Background()

	// 测试连接
	_, err := client.Ping(ctx).Result()
	if err != nil {
		log.Fatal("连接失败:", err)
	}
	fmt.Println("哨兵模式连接成功")

	// 写入数据
	err = client.Set(ctx, "sentinel_key", "high_availability", 0).Err()
	if err != nil {
		log.Fatal("设置失败:", err)
	}
	fmt.Println("设置 sentinel_key = high_availability")

	// 读取数据
	value, err := client.Get(ctx, "sentinel_key").Result()
	if err != nil {
		log.Fatal("读取失败:", err)
	}
	fmt.Printf("读取 sentinel_key = %s\n", value)

	// 模拟主节点故障(手动停止主节点)
	fmt.Println("请手动停止主节点(6379),然后再次读取")
	// 等待用户操作
	// 假设主节点故障,哨兵自动切换到从节点
	time.Sleep(10 * time.Second)

	// 再次读取,验证故障转移
	value, err = client.Get(ctx, "sentinel_key").Result()
	if err != nil {
		log.Fatal("故障转移后读取失败:", err)
	}
	fmt.Printf("故障转移后读取 sentinel_key = %s\n", value)
}

需要开启多个终端:

配置主节点redis-server --port 6379

配置从节点:redis-server --port 6380 --replicaof 127.0.0.1 6379

启动哨兵:redis-sentinel ./sentinel.conf

运行GO文件:go run xxx.go

接着自己手动关闭主节点的redis服务,等待后可以发现故障转移后可以读取!

  • 通过redis-cli验证

    redis-cli -p 26379
    SENTINEL get-master-addr-by-name mymaster # 查看当前主节点

    输出:127.0.0.1 6379

    停止主节点(另一个终端)

    redis-cli -p 6379 SHUTDOWN

    再次检查主节点(哨兵应切换到 6380)

    SENTINEL get-master-addr-by-name mymaster

    输出:127.0.0.1 6380

还有集群+多哨兵等,就需要启动多个终端,在此先不拓展

相关推荐
豆沙沙包?2 小时前
5.学习笔记-SpringMVC(P61-P70)
数据库·笔记·学习
每次的天空4 小时前
Android学习总结之Room篇
android·学习·oracle
白泽来了4 小时前
2个小时1.5w字| React & Golang 全栈微服务实战
笔记·go·react
柏油5 小时前
MySQL InnoDB 行锁
数据库·后端·mysql
咖啡调调。5 小时前
使用Django框架表单
后端·python·django
丶Darling.5 小时前
26考研 | 王道 | 数据结构笔记博客总结
数据结构·笔记·考研
白泽talk5 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务
摆烂工程师5 小时前
全网最详细的5分钟快速申请一个国际 “edu教育邮箱” 的保姆级教程!
前端·后端·程序员
一只叫煤球的猫5 小时前
你真的会用 return 吗?—— 11个值得借鉴的 return 写法
java·后端·代码规范
道长没有道观5 小时前
计算机操作系统笔记
笔记·考研·操作系统