连接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
}
命令行操作
操作有序集合
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
*/
}
命令行操作
事务
Redis是单线程的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如在它们之间交替执行。但是,Multi/exec能够确保在multi/exec两个语句之间的命令之间没有其他客户端正在执行命令。
在这种场景我们需要使用TxPipeline。TxPipeline总体上类似于上面的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)
}