Go与Redis

准备

安装第三方开源Redis库

 go mod init redis-demo
 go get github.com/redis/go-redis/v9

然后我们先不管别的 ,我们来一段代码,来试试看看可不可以跑通,我们再来继续学习

 package main
 ​
 import (
  "context"
  "fmt"
 ​
  "github.com/redis/go-redis/v9" // 导入驱动
 )
 ​
 // Redis 的所有操作都需要一个“背景上下文”,用于管理超时或追踪
 var ctx = context.Background()
 ​
 func main() {
  // 1. 配置连接参数
  rdb := redis.NewClient(&redis.Options{
  Addr:     "localhost:6379", // Redis 运行地址
  Password: "",               // 如果没有设置密码则为空
  DB:       0,                // 默认数据库索引(0-15)
  })
 ​
  // 2. 检查连接状态 (Ping-Pong 机制)
  pong, err := rdb.Ping(ctx).Result()
  if err != nil {
  fmt.Println("连接 Redis 失败:", err)
  return
  }
  fmt.Println("连接成功:", pong) // 成功会输出 PONG
 }

解释:

  • redis.Newclient根据Options创建一个Redis客户端对象,初始化连接池等等组件。
  • redis.Optionsgo-redis库里面定义的一个结构体,用于配置Redis客户端连接参数。
image-20260131200430114

我们成功连接到了数据库,我们开始后续的学习。

如果发现,能够连接到数据库,但是有报错提示

image-20260131202414460

在设置中启用Go模块集成就好了。

String的操作

注意事项

1.context使用时,控制超时

 var ctx = context.Background()
  • go-redis 的所有操作(Set, Get, Ping)都强制要求第一个参数传 ctx。 它的作用是:如果你想让这个操作在 5 秒后如果没有结果就自动取消(超时控制),你就需要通过 ctx 来告诉 Redis。
  • context.Background() 是 Go 中上下文机制的“起点”,它本身不做任何事,但可以派生出有超时、可取消、带值的子上下文,主要用于主程序、服务启动、或作为其他上下文的父级。

2.错误处理,特别是怎么判断“key不存在”

代码案例

     /*链接数据库*/
  rdb := redis.NewClient(&redis.Options{
  Addr: "localhost:6379",
  DB:   0,
  })
  /*写入*/
  err := rdb.Set(ctx, "my_key", "Hello Go", 0).Err()
  if err != nil {
  panic(err)
  }
  fmt.Println("successful")
  /*读取*/
  _, err2 := rdb.Get(ctx, "not_exists").Result()
  if err2 == redis.Nil {
  fmt.Println("This key does not exist")
  }
 ​
  /*分布式锁*/
  isSuccess, err := rdb.SetNX(ctx, "lock_key", "locked", 10*time.Second).Result()
  if err != nil {
  panic(err)
  }
  if isSuccess {
  fmt.Println("Locked successfully")
  } else {
  fmt.Println("Locked Unsuccessfully")
  }
 ​

关键解析:

1.Set(ctx, string, interface{}, time.Duration)

  • ctx: 上下文(必填)。
  • key: 你的键名(字符串)。
  • value: 你的值。注意这里是 interface{},意味着你可以传 string, int, bool, bytes,Go 会尝试自动帮你转。
  • expiration: 过期时间
    • 0:表示永不过期(直到你手动删除或内存满了)。
    • 10 * time.Second:Go 语言的时间单位。表示 10 秒后 Redis 自动删除它。

2..Err().Result()

  • .Err():我不关心返回值,我只关心成没成功。
  • .Result():我要拿到具体的返回值。

3.redis.Nil

这是一个特定的错误厂里,如果不判断这个,程序可能吧没找到数据单程数据库挂了来处理。

4.rdb.SetNX(ctx, key, value, expiration)

这是一个分布锁,通过返回值判断抢没抢到锁

5.panic(err)

一旦发生错误,程序立刻停止,打印出错误,但是实际上班开发中,应该Println出日志,或者返回错误给前端,不能让服务器直接寄。

Hash操作

代码案例

 /*链接数据库*/
  rdb1 := redis.NewClient(&redis.Options{
  Addr: "localhost:6379",
  DB:   1,
  })
  key := "user:profile:1001"
  /*存入一整个对象,这里可以直接传一个map进去*/
  userMap := map[string]interface{}{
  "name":  "Ttdr",
  "age":   21,
  "base":  "NanJing",
  "money": 0,
  }
  err1 := rdb1.HSet(ctx, key, userMap).Err()
  if err1 != nil {
  panic(err1)
  }
  fmt.Println("Hash successful")
  /*获取一个字段*/
  _, err = rdb1.HGet(ctx, key, "name").Result()
  if err != nil {
  panic(err)
  }
  fmt.Println("name get successfully")
  /*字段增减*/
  rdb.HIncrBy(ctx, key, "age", 1)
  rdb.HIncrBy(ctx, key, "money", 100000000)
  fmt.Println("age and money have changed")
  /*获得全部*/
  allInfo, err := rdb.HGetAll(ctx, key).Result()
  if err != nil {
  panic(err)
  }
  for field, val := range allInfo {
  fmt.Printf("%s:%s\n", field, val)
  }

关键分析

1.rdb.HSet(ctx, key, values)

Go 语言里,value最优雅的写法是传入 map[string]interface{}

这里因为是空接口,可以接受任何类型的数据

2.HGetAll

返回值类型:map[string]string

Redis 的 Hash 底层本质上存的 value 都是字符串,即使你存进去的是int,返回的也是字符串,所以不能用这个返回值直接加减。

3.HIncrBy

它只锁定 Hash 里的某一个小格子进行修改,是原子性的,不影响其他格子,和Incr一样,具有并发安全。

补充对结构体的Hash

 type User struct {
  Name  string `redis:"name"`  // Redis 里字段名叫 name
  Age   int    `redis:"age"`   // Redis 里字段名叫 age
  Email string `redis:"email"` // Redis 里字段名叫 email
 }
 u1 := User{
  Name:  "极客兔兔",
  Age:   22,
  Email: "go@example.com",
  }
  err := rdb.HSet(ctx, key, &u1).Err()
  if err != nil {
  panic(err)
  }
  fmt.Println("Struct 保存成功!")
  var u2 User
  err = rdb.HGetAll(ctx, key).Scan(&u2)
  if err != nil {
  panic(err)
  }

不同的点是在于读取的时候用Scan填回一个新结构体,这里会把字符串的整数还原成int类型的整数

Redis连接池

每一次程序和Redi建立连接,都会进行TCP的三次握手,也会导致一个问题,在高并发情况下,所有CPU都在忙着握手回收,真正干活的很少,Redis就是来简化这个过程,避免了三次握手导致的资源浪费。

redis.NewClient返回的rdb对象本身就是一个线程安全(可以在多个 Goroutine 里随便用,不需要加锁。)的连接池,我们也就不需要其他操作。

我们就来从代码层面深入了解一下,怎么配置一个线程池。

     rdb := redis.NewClient(&redis.Options{
  Addr:         "localhost:6379",
  DB:           0,
  PoolSize:     10,
  MinIdleConns: 2,
  PoolTimeout:  30 * time.Second,
  MaxRetries:   3,
  })
  var wg sync.WaitGroup
  start := time.Now()
  for i := 0; i < 50; i++ {
  wg.Add(1)
  go func(id int) {
  defer wg.Done()
  err := rdb.Set(ctx, fmt.Sprintf("key:%d", id), id, 0).Err()
  if err != nil {
  fmt.Println(id, "协程", err)
  }
  }(i)
  }
  wg.Wait()
  fmt.Println("all ok time:", time.Since(start))
  fmt.Println("最终连接池状态:", rdb.PoolStats())

1.配置字段

  • PoolSize (最大连接数)
  • MinIdleConns (最小空闲连接,保证来了请求就能用)
  • PoolTimeout (等待超时)
  • MaxRetries (重试次数)

2.监控器func (c *Client) PoolStats() *PoolStats

返回结构体为:

 {
   "Hits": 50,
   "Misses": 6,
   "Timeouts": 0,
   "TotalConns": 42,
   "IdleConns": 0,
   "StaleConns": 0
 }
暂无评论

发送评论 编辑评论


				
上一篇
下一篇