go-redis库使用笔记

安装

go get -u github.com/go-redis/redis

连接

  1. 普通连接
func NewClient() (c *redis.Client, err error) {
	c = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})
	_, err = c.Ping().Result()
	if err != nil {
		return nil, err
	}
	return
}
  1. 连接redis哨兵模式
func NewClient() (c *redis.Client, err error) {
	c = redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:"master",
		SentinelAddrs:[]string{"xxxx:26379","xxxx:26379"},
	})
	_, err = c.Ping().Result()
	if err != nil {
		return nil, err
	}
	return
}
  1. 连接redis集群
func NewClient() (c *redis.ClusterClient, err error) {
	c = redis.NewClusterClient(&redis.ClusterOptions{
		Addrs: []string{":7001", ":7002", ":7003"},
	})
	_, err = c.Ping().Result()
	if err != nil {
		return nil, err
	}
	return
}

基本使用

set/get

//go get -u github.com/sirupsen/logrus
func printErr(err error) {
	if err != nil {
		logrus.Errorf("err:%+v\n", err)
	}
}
func SetGetEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}
	err = c.Set("score", 100, 0).Err()
	printErr(err)
	val, err := c.Get("score").Result()
	printErr(err)
	logrus.Println("score", val)
	c.Del("name")
	val2, err := c.Get("name").Result()
	if err == redis.Nil {
		logrus.Println("name does not exist")
	} else if err != nil {
		logrus.Errorf("get name failed err:%+v\n")
	} else {
		logrus.Println("name:", val2)
	}
}

zset

func ZsetEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}
	zsetKey := "language_rank"
	languages := []redis.Z{
		redis.Z{Score: 90.0, Member: "Golang"},
		redis.Z{Score: 98.0, Member: "Java"},
		redis.Z{Score: 95.0, Member: "Python"},
		redis.Z{Score: 97.0, Member: "JavaScript"},
		redis.Z{Score: 99.0, Member: "C/C++"},
	}
	num, err := c.ZAdd(zsetKey, languages...).Result()
	printErr(err)
	logrus.Printf("zadd %d succ.\n", num)
	//golang + 10分
	newScore, err := c.ZIncrBy(zsetKey, 10.0, "Golang").Result()
	printErr(err)
	logrus.Println("Golang score ", newScore)
	//取前三 从小到大排序
	rets, err := c.ZRangeWithScores(zsetKey, 0, 2).Result()
	printErr(err)
	for _, v := range rets {
		logrus.Println(v.Member, v.Score)
	}
	//取95~100分间的
	op := redis.ZRangeBy{
		Min: "95",
		Max: "100",
	}
	rets, err = c.ZRangeByScoreWithScores(zsetKey, op).Result()
	printErr(err)
	for _, v := range rets {
		logrus.Println(v.Member, v.Score)
	}
}

pipeline

Pipeline 主要是一种网络优化。它本质上意味着客户端缓冲一堆命令并一次性将它们发送到服务器。这些命令不能保证在事务中执行。这样做的好处是节省了每个命令的网络往返时间(RTT)

func PipelineEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}
	pipe := c.Pipeline()
	incr := pipe.Incr("pipeline_cnt")
	pipe.Expire("pipeline_cnt", time.Hour)
	_, err = pipe.Exec()
	printErr(err)
	logrus.Println("incr.Val() ", incr.Val())
}

func PipelinedEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}
	_, err = c.Pipelined(func(pipe redis.Pipeliner) error {
		incr := pipe.Incr("pipeline_cnt")
		pipe.Expire("pipeline_cnt", time.Hour)
		logrus.Println("incr.Val() ", incr.Val())
		return nil
	})
	printErr(err)

}

multi/exec

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

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

func TxPipelineEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}
	pipe := c.TxPipeline()
	incr := pipe.Incr("pipeline_cnt")
	pipe.Expire("pipeline_cnt", time.Hour)
	_, err = pipe.Exec()
	printErr(err)
	logrus.Println("incr.Val() ", incr.Val())
}

func TxPipelinedEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}
	_, err = c.TxPipelined(func(pipe redis.Pipeliner) error {
		incr := pipe.Incr("pipeline_cnt")
		pipe.Expire("pipeline_cnt", time.Hour)
		logrus.Println("incr.Val() ", incr.Val())
		return nil
	})
	printErr(err)

}

watch

在某些场景下,我们除了要使用MULTI/EXEC命令外,还需要配合使用WATCH命令。在用户使用WATCH命令监视某个键之后,知道该用户执行EXEC命令的这段时间里,如果有其他用户抢先对被监视的键进行了替换、更新、删除等操作,那么当用户尝试执行EXEC的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务

Watch(fn func(*Tx) error, keys ...string) error
func WatchEx() {
	c, err := NewClient()
	if err != nil {
		logrus.Errorf("NewClient err:%+v", err)
		return
	}

	key := "watch_count"
	err = c.Watch(func(tx *redis.Tx) error {
		n, err := tx.Get(key).Int()
		if err != nil && err != redis.Nil {
			return err
		}
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			pipe.Set(key, n+1, 0)
			return nil
		})
		return err
	}, key)
	printErr(err)

}

PS:官方文档中使用GET和SET命令以事务方式递增Key的值的示例

const routineCount = 100

increment := func(key string) error {
	txf := func(tx *redis.Tx) error {
		// 获得当前值或零值
		n, err := tx.Get(key).Int()
		if err != nil && err != redis.Nil {
			return err
		}

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

		// 仅在监视的Key保持不变的情况下运行
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			// pipe 处理错误情况
			pipe.Set(key, n, 0)
			return nil
		})
		return err
	}

	for retries := routineCount; retries > 0; retries-- {
		err := rdb.Watch(txf, key)
		if err != redis.TxFailedErr {
			return err
		}
		// 乐观锁丢失
	}
	return errors.New("increment reached maximum number of retries")
}

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("counter3").Int()
fmt.Println("ended with", n, err)

版权声明:本文为u013246898原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。