案例速成GO操作redis,个人笔记

时间:2025-04-27 07:19:59

更多个人笔记:(仅供参考,非盈利)
gitee: https://gitee.com/harryhack/it_note
github: https://github.com/ZHLOVEYY/IT_note

安装redis客户端:go get github.com/redis/go-redis/v9

注意go mod tidy的时候不要import成了"github.com/go-redis/redis" 需要检查一下

基础操作,连接redis和CRUD

package main

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

func main() {
	client := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default 
		//password和db如果都是默认值可以不设置,这里展示一下
	})
	ctx := context.Background() //创建上下文

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

	///设置键值对
	err = client.Set(ctx,"user1","Alice",0).Err()  //0表示永不过期
	if err!=nil {
		log.Fatal("设置键值对失败",err)
	}
	fmt.Println("设置了user1=Alice")

	//获取键值对
	name,err := client.Get(ctx,"user1").Result()
	if err!=nil {
		log.Fatal("获取键值对失败",err)
	}
	fmt.Println("user1的值是",name)

	//设置过期时间(10秒)
	err = client.SetEx(ctx,"session1","123456",10*time.Second).Err()
	if err!=nil {
		log.Fatal("设置过期时间失败",err)
	}
	fmt.Println("设置了session1=123456,过期时间为10秒")

	//等待2秒
	fmt.Println("等待2秒...")
	time.Sleep(2*time.Second)

    // 获取 session 的剩余时间
    ttl, err := client.TTL(ctx, "session1").Result()
    if err != nil {
        log.Fatal("获取过期时间失败", err)
    }
    fmt.Printf("session1 剩余时间: %v\n", ttl)

	//删除键
	_,err = client.Del(ctx,"user1").Result()
	if err!=nil {
		log.Fatal("删除键失败",err)
	}
	fmt.Println("删除了user1")

	//再次获取键值对
	name, err = client.Get(ctx, "user1").Result()
    if err == redis.Nil {  //如果不存在会返回redis.Nil
        fmt.Println("user1 不存在")  
    } else if err != nil {
        log.Fatal("获取键值对失败", err)
    } else {
        fmt.Printf("user1的值是: %s\n", name)
    }
}


  • 注意需要传入ctx上下文
  • 注意最后有.Result()或者.Err()的特点
  • 主题语法和Redis其实是比较像的

操作复杂数据结构(Hash 和 List)

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	client := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})
	ctx := context.Background() //创建上下文

	//操作Hash(存储用户对象)
	user := map[string]interface{}{ //interface{} 表示任意类型,就很方便
		"name": "Bob",
		"age":  30,
	}
	err := client.HSet(ctx, "user2", user).Err()
	if err != nil {
		log.Fatal("Hset失败", err)
	}
	fmt.Println("存储用户user2")

	//获取Hash
	name, err := client.HGet(ctx, "user2", "name").Result()
	if err != nil {
		log.Fatal("Hget失败", err)
	}
	fmt.Printf("获取用户user2的name:%s\n", name)

	//操作List(消息队列)
	queue := "message_queue"
	err = client.RPush(ctx, queue, "msg1", "msg2", "msg3").Err() //Rpush从列表右端(尾部)插入元素,和Lpush相反
	if err != nil {
		log.Fatal("RPush失败", err)
	}
	fmt.Println("消息入队到message_queue")

	//弹出
	msg, err := client.LPop(ctx, queue).Result()
	if err != nil {
		log.Fatal("LPop失败", err)
	}
	fmt.Printf("从message_queue弹出消息:%s\n", msg)
}

(一些设置要是输出怪可能是和已经存在的变量名有关,改一下就可以了)

  • Hash:HSet 和 HGet 适合存储结构化数据(如用户信息)。
  • List:RPush 和 LPop 实现 FIFO 队列,常用作任务队列。
  • 实际项目中,Hash 常用于缓存对象,List 用于异步任务处理

发布/订阅(Pub/Sub)

package main

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

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

func main() {
	client := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})
	ctx := context.Background() //创建上下文

	//订阅者
	go func(){  //通过协程持续监听
		pubsub := client.Subscribe(ctx,"channel1")
		defer pubsub.Close()

		for msg := range pubsub.Channel(){ //调用channel方法,获取消息
			fmt.Printf("收到消息:%s(频道:%s)\n",msg.Payload,msg.Channel)
		}
	}()

	//发布者
	time.Sleep(1*time.Second) //确保订阅者启动
	err := client.Publish(ctx,"channel1","hello,Redis").Err()
	if err != nil{
		log.Fatal("发布失败",err)
	}
	fmt.Println("发布消息成功")

	//保持运行
	time.Sleep(2 * time.Second)
}

分布式锁

通过lua脚本等简单了解分布式锁的概念

package main

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

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

func acquireLock(client *redis.Client, ctx context.Context, lockKey string, value string, ttl time.Duration) bool {
	// 使用 SETNX 命令尝试获取锁
	result, err := client.SetNX(ctx, lockKey, value, ttl).Result()
	if err != nil {
		log.Println("Failed to acquire lock:", err)
		return false
	}
	return result
	//和SetNX返回值有关,redis中当键不存在时,设置成功,返回 1
	//SetNX:set if not exist
}

func releaseLock(client *redis.Client, ctx context.Context, lockKey, value string) bool {
	// Lua 脚本确保原子性释放
	script := `
		if redis.call("GET", KEYS[1]) == ARGV[1] then  
			return redis.call("DEL", KEYS[1])
		else
			return 0
		end  
	` // KEYS[1] 是锁的键,ARGV[1] 是锁的值,检查锁的值是否匹配,如果匹配则删除锁
	result, err := client.Eval(ctx, script, []string{lockKey}, value).Int() //[]string{lockKey} 是 Lua 脚本的参数,value 是锁的值
	if err != nil {
		return false
	}
	return result == 1
}

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

	lockKey := "mylock"
	value := "unique_123"
	ttl := 20 * time.Second
	if acquireLock(client, ctx, lockKey, value, ttl) {
		fmt.Println("获取锁成功")
		// 模拟业务操作
		time.Sleep(2 * time.Second)
		//释放锁
		if releaseLock(client, ctx, lockKey, value) {
			fmt.Println("释放锁成功")
		} else {
			fmt.Println("释放锁失败")
		}
	}else {
		fmt.Println("获取锁失败")
	}
}

  • 关键在于做到只能释放自己的锁,防止竞争
  • 可以用于库存扣减、分布式任务调度

连接池和性能优化

简单了解
包括通过pipeline进行批量输入

package main

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

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

func main() {
	client := redis.NewClient(&redis.Options{
		Addr:         "localhost:6379",
		PoolSize:     10,
		MinIdleConns: 2,
		MaxRetries:   3,
		DialTimeout:  time.Second * 5,    
		ReadTimeout:  time.Second * 3,    
		WriteTimeout: time.Second * 3,    
	})
	ctx := context.Background()

	// 批量写入(使用 Pipeline 提升性能)
	pipe := client.Pipeline()
	for i := 0; i < 100; i++ { //将命令加入到管道中,但不立即执行
		pipe.Set(ctx, fmt.Sprintf("key:%d", i), i, time.Second*60)
		//sprinf形成新的变量比如key:0,key:1,key:2
	}
	_, err := pipe.Exec(ctx) //一次性执行所有命令
	if err != nil {
		log.Fatal("Pipeline 执行失败:", err)
	}
	fmt.Println("批量写入 100 个键完成")

	// 并发读取
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1) // 增加等待组中的计数
		go func(id int) {
			defer wg.Add(-1)
			val, err := client.Get(ctx, fmt.Sprintf("key:%d", i)).Result()
			if err != nil {
				log.Printf("读取 key:%d 失败: %v", id, err)
				return
			}
			fmt.Printf("读取 key:%d = %s\n", id, val)
		}(i)
	}
	wg.Wait() // 等待所有协程完成
}

  • 接池:PoolSize 和 MinIdleConns 控制连接复用,适合高并发。
  • Pipeline:批量执行命令,减少网络开销。
  • 并发:使用 goroutine 并发操作,结合 sync.WaitGroup 同步。
  • 超时配置:防止网络问题导致阻塞。