go操作redis

152 阅读3分钟

连接redis

package main

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

// 声明一个全局的rdb变量
var rdb *redis.Client

// 初始化连接
func initClient() (errerror) {
   rdb = redis.NewClient(&redis.Options{
      Addr:     "localhost:6379",
      Password: "",  // no password set
      DB:       0,   // use default DB
      PoolSize: 100, // 连接池大小
   })

   // ping判断是否连接成功
   _,err= rdb.Ping().Result()
   iferr!= nil {
      returnerr
}
   return nil
}

func main() {
   if err := initClient(); err != nil {
      fmt.Printf("init redis client failed, err: %v\n",err)
      return
   }
   fmt.Println("connect redis success...")
   // 程序退出时释放相关的资源
   defer rdb.Close()
}

操作Hash

func hgetDemo() {
   // 获取全部
   v, err := rdb.HGetAll("user").Result()
   if err != nil {
      // redis.Nil 和 其他错误
      fmt.Printf("hgetAll failed, err: %v\n", err)
      return
   }
   fmt.Println(v) // map[age:18 name:xiaoming]

   // 获取指定的多个
   v2 := rdb.HMGet("user", "name", "age").Val()
   fmt.Println(v2) // [xiaoming 18]

   // 获取一个
   v3 := rdb.HGet("user", "name").Val()
   fmt.Println(v3) // xiaoming
}

命令行操作

image.png

操作有序集合

func ZSetDemo() {
   zsetKey := "language_rank"
   languages := []redis.Z{
      redis.Z{Score: 98.0, Member: "Java"},
      redis.Z{Score: 95.0, Member: "Python"},
      redis.Z{Score: 90.0, Member: "Golang"},
      redis.Z{Score: 97.0, Member: "JavaScript"},
      redis.Z{Score: 99.0, Member: "C/C++"},
   }

   num, err := rdb.ZAdd(zsetKey, languages...).Result()
   if err != nil {
      fmt.Printf("zadd failed, err: %v", err)
      return
   }
   fmt.Printf("zadd %d succ.\n", num) // zadd 5 succ.

   // 把GO的分数加10
   newScore, err := rdb.ZIncrBy(zsetKey, 10.0, "Golang").Result()
   if err != nil {
      fmt.Printf("zincrby failed, err: %v\n", err)
      return
   }
   fmt.Printf("Go's score is %f now.\n", newScore) // Go's score is 100.000000 now.

   // 取分数最高的3个
   ret, err := rdb.ZRevRangeWithScores(zsetKey, 0, 2).Result()
   if err != nil {
      fmt.Printf("zrevrange failed, err: %v\n", err)
   }
   for _, z := range ret {
      fmt.Println(z.Member, z.Score)
   }
   /*
   Golang 100
   C/C++ 99
   Java 98
   */

   // 取95 - 100
   op := redis.ZRangeBy{
      Min: "95",
      Max: "100",
   }
   ret, err = rdb.ZRangeByScoreWithScores(zsetKey, op).Result()
   if err != nil {
      fmt.Printf("zrangebyscore failed, err: %v\n", err)
   }
   for _, z := range ret {
      fmt.Println(z.Member, z.Score)
   }
   /*
   Python 95
   JavaScript 97
   Java 98
   C/C++ 99
   Golang 100
   */
}

命令行操作

image.png

事务

Redis是单线程的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。但是,Multi/exec能够确保在multi/exec两个语句之间的命令之间没有其他客户端正在执行命令。

在这种场景我们需要使用TxPipelineTxPipeline总体上类似于上面的Pipeline,但是它内部会使用MULTI/EXEC包裹排队的命令。例如:

pipe := rdb.TxPipeline()

incr := pipe.Incr("tx_pipeline_counter")
pipe.Expire("tx_pipeline_counter", time.Hour)

_, err := pipe.Exec()
fmt.Println(incr.Val(), err)

上面代码相当于在一个RTT下执行了下面的redis命令:

MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

还有一个与上文类似的TxPipelined方法,使用方法如下:

var incr *redis.IntCmd
_, err := rdb.TxPipelined(func(pipe redis.Pipeliner) error {
	incr = pipe.Incr("tx_pipelined_counter")
	pipe.Expire("tx_pipelined_counter", time.Hour)
	return nil
})
fmt.Println(incr.Val(), err)

watch

func watchDemo() {
   // 监视watch_count的值,并在值不变的前提下将其+1
   key := "watch_count"
   err := rdb.Watch(func(tx *redis.Tx) error {
      n, err := tx.Get(key).Int()
      if err != nil && err != redis.Nil {
         return err
      }
      _, err = tx.Pipelined(func(pipeliner redis.Pipeliner) error {
         // 业务逻辑
         pipeliner.Set(key, n+1, 0)
         return nil
      })
      return err
   }, key)
   if err != nil {
      fmt.Printf("tx exec failed, err: %v\n",err)
      return
   }
   fmt.Println("tx exec success")
}

最后看一个V8版本官方文档中使用GET和SET命令以事务方式递增Key的值的示例,仅当Key的值不发生变化时提交一个事务。

func transactionDemo() {
	var (
		maxRetries   = 1000
		routineCount = 10
	)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Increment 使用GET和SET命令以事务方式递增Key的值
	increment := func(key string) error {
		// 事务函数
		txf := func(tx *redis.Tx) error {
			// 获得key的当前值或零值
			n, err := tx.Get(ctx, key).Int()
			if err != nil && err != redis.Nil {
				return err
			}

			// 实际的操作代码(乐观锁定中的本地操作)
			n++

			// 操作仅在 Watch 的 Key 没发生变化的情况下提交
			_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
				pipe.Set(ctx, key, n, 0)
				return nil
			})
			return err
		}

		// 最多重试 maxRetries 次
		for i := 0; i < maxRetries; i++ {
			err := rdb.Watch(ctx, txf, key)
			if err == nil {
				// 成功
				return nil
			}
			if err == redis.TxFailedErr {
				// 乐观锁丢失 重试
				continue
			}
			// 返回其他的错误
			return err
		}

		return errors.New("increment reached maximum number of retries")
	}

	// 模拟 routineCount 个并发同时去修改 counter3 的值
	var wg sync.WaitGroup
	wg.Add(routineCount)
	for i := 0; i < routineCount; i++ {
		go func() {
			defer wg.Done()
			if err := increment("counter3"); err != nil {
				fmt.Println("increment error:", err)
			}
		}()
	}
	wg.Wait()

	n, err := rdb.Get(context.TODO(), "counter3").Int()
	fmt.Println("ended with", n, err)
}