前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Go每日一库之196:go-redis(redis客户端库)

Go每日一库之196:go-redis(redis客户端库)

原创
作者头像
luckpunk
发布2025-01-07 15:37:03
发布2025-01-07 15:37:03
35200
代码可运行
举报
文章被收录于专栏:Go每日一库Go每日一库
运行总次数:0
代码可运行

在项目开发中redis的使用也比较频繁,本文介绍了Go语言中go-redis库的基本使用。

Redis介绍

Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外,通过复制、持久化和客户端分片等特性,我们可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。

Redis支持的数据结构

Redis支持诸如字符串(string)、哈希(hashe)、列表(list)、集合(set)、带范围查询的排序集合(sorted set)、bitmap、hyperloglog、带半径查询的地理空间索引(geospatial index)和流(stream)等数据结构。

String类型

常用命令: set,get,mget ,decr,incr等。 (写入值,取值,数值+1,数值-1) 使用场景:普通存值

List类型

常用命令:lpush,rpush,lpop,rpop,lrange等。 (从头插入,从尾插入,从头删除一个并返回元素,从尾删除第一个并返回元素,返回范围结果) 使用场景:比如粉丝列表,关注等等列表信息 实现方式: Redis list的实现为一个双向链表,可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

Hash类型

常用命令:hget,hset,hgetall 等。 (获取指定值,设置值,取出所有值) 使用场景:一般存储对象都是用Hash类型来存

set类型

常用命令:sadd,spop,smembers,sunion,sinter (添加元素,删除元素,获取所有元素,获取两个set的并集,获取两个set的交集) 使用场景:因为有求并集交集的方法,并且所存储的元素不可重复,所以可以很方便的实现共同好友,共同关注等应用场景

zset类型

常用命令:zadd,zrange,zrem,zcard等 (添加元素,返回指定区间内的元素,用于移除有序集中的一个或多个成员,用于计算元素个数) 使用场景:可以做带权重的队列b,然后按序获取任务

Redis应用场景

  • 缓存系统,减轻主数据库(MySQL)的压力。
  • 计数场景,比如微博、抖音中的关注数和粉丝数。
  • 热门排行榜,需要排序的场景特别适合使用ZSET。
  • 利用 LIST 可以实现队列的功能。
  • 利用 HyperLogLog 统计UV、PV等数据。
  • 使用 geospatial index 进行地理位置相关查询。

准备Redis环境

读者可以选择在本机安装 redis 或使用云数据库,这里直接使用Docker启动一个 redis 环境,方便学习使用。

使用下面的命令启动一个名为 redis507 的 5.0.7 版本的 redis server环境。

代码语言:javascript
代码运行次数:0
复制
docker run --name redis507 -p 6379:6379 -d redis:5.0.7

注意:此处的版本、容器名和端口号可以根据自己需要设置。

启动一个 redis-cli 连接上面的 redis server。

代码语言:javascript
代码运行次数:0
复制
docker run -it --network host --rm redis:5.0.7 redis-cli

go-redis库

安装

Go 社区中目前有很多成熟的 redis client 库,比如[https://github.com/gomodule/redigohttps://github.com/go-redis/redis,读者可以自行选择适合自己的库。本书使用 go-redis 这个库来操作 Redis 数据库。

使用以下命令下安装 go-redis 库。

代码语言:javascript
代码运行次数:0
复制
go get github.com/go-redis/redis/v8
代码语言:javascript
代码运行次数:0
复制
//redis 6 
go get github.com/go-redis/redis/v8
//redis 7
go get github.com/go-redis/redis/v9

连接

普通连接模式

go-redis 库中使用 redis.NewClient 函数连接 Redis 服务器。

代码语言:javascript
代码运行次数:0
复制
rdb := redis.NewClient(&redis.Options{
	Addr:     "localhost:6379",
	Password: "", // 密码
	DB:       0,  // 数据库
	PoolSize: 20, // 连接池大小
})

除此之外,还可以使用 redis.ParseURL 函数从表示数据源的字符串中解析得到 Redis 服务器的配置信息。

代码语言:javascript
代码运行次数:0
复制
opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
if err != nil {
	panic(err)
}

rdb := redis.NewClient(opt)
TLS连接模式

如果使用的是 TLS 连接方式,则需要使用 tls.Config 配置。

代码语言:javascript
代码运行次数:0
复制
rdb := redis.NewClient(&redis.Options{
	TLSConfig: &tls.Config{
		MinVersion: tls.VersionTLS12,
		// Certificates: []tls.Certificate{cert},
    // ServerName: "your.domain.com",
	},
})
Redis Sentinel模式

使用下面的命令连接到由 Redis Sentinel 管理的 Redis 服务器。

代码语言:javascript
代码运行次数:0
复制
func initClient() (err error) {

	rdb := redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:    "master",
		SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxx.xxx.xxx.xxx:26379"},
	})
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_, err = rdb.Ping(ctx).Result()
	return err
}
Redis Cluster模式

使用下面的命令连接到 Redis Cluster,go-redis 支持按延迟或随机路由命令。

代码语言:javascript
代码运行次数:0
复制
func initClient() (err error) {

	rdb := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
	})

	// 若要根据延迟或随机路由命令,请启用以下命令之一
    // RouteByLatency: true,
    // RouteRandomly: true,
    
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	//Ping返回的是:*StatusCmd ; Result方法是StatusCmd结构体的方法(StatusCmd的val, baseCmd的err)
	_, err = rdb.Ping(ctx).Result()
	return err
}

使用 TLS

你需要手动设置 tls.Config,你可以在 这里 了解相关 tls.Config更多的配置信息:

代码语言:javascript
代码运行次数:0
复制
rdb := redis.NewClient(&redis.Options{
  TLSConfig: &tls.Config{
    MinVersion: tls.VersionTLS12,
    ServerName: "you domain",
    //Certificates: []tls.Certificate{cert}
  },
})

如果你使用的是域名连接,且遇到了类似 x509: cannot validate certificate for xxx.xxx.xxx.xxx because it doesn't contain any IP SANs的错误 ,应该在 ServerName 中指定你的域名:更多详情请参考本链接

代码语言:javascript
代码运行次数:0
复制
rdb := redis.NewClient(&redis.Options{
  TLSConfig: &tls.Config{
    MinVersion: tls.VersionTLS12,
    ServerName: "你的域名",
  },
})

SSH 方式

代码语言:javascript
代码运行次数:0
复制
sshConfig := &ssh.ClientConfig{
  User:      "root",
  Auth:      []ssh.AuthMethod{ssh.Password("password")},
  HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  Timeout:     15 * time.Second,
}
​
sshClient, err := ssh.Dial("tcp", "remoteIP:22", sshConfig)
if err != nil {
  panic(err)
}
​
rdb := redis.NewClient(&redis.Options{
  Addr: net.JoinHostPort("127.0.0.1", "6379"),
  Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
    return sshClient.Dial(network, addr)
  },
  // SSH不支持超时设置,在这里禁用
  ReadTimeout:  -1,
  WriteTimeout: -1,
})

基本使用

执行命令

下面的示例代码演示了 go-redis 库的基本使用。

代码语言:javascript
代码运行次数:0
复制
// doCommand go-redis基本使用示例
func doCommand() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	// 执行命令获取结果
	val, err := rdb.Get(ctx, "key").Result()
	fmt.Println(val, err)

	// 先获取到命令对象
	cmder := rdb.Get(ctx, "key")
	fmt.Println(cmder.Val()) // 获取值
	fmt.Println(cmder.Err()) // 获取错误

	// 直接执行命令获取错误
	err = rdb.Set(ctx, "key", 10, time.Hour).Err()

	// 直接执行命令获取值
	value := rdb.Get(ctx, "key").Val()
	fmt.Println(value)
}

执行任意命令

go-redis 还提供了一个执行任意命令或自定义命令的 Do 方法,特别是一些 go-redis 库暂时不支持的命令都可以使用该方法执行。具体使用方法如下。

代码语言:javascript
代码运行次数:0
复制
// doDemo rdb.Do 方法使用示例
func doDemo() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	// 直接执行命令获取错误
	err := rdb.Do(ctx, "set", "key", 10, "EX", 3600).Err()
	fmt.Println(err)

	// 执行命令获取结果
	val, err := rdb.Do(ctx, "get", "key").Result()
	fmt.Println(val, err)
}

redis.Nil

go-redis 库提供了一个 redis.Nil 错误来表示 Key 不存在的错误。因此在使用 go-redis 时需要注意对返回错误的判断。在某些场景下我们应该区别处理 redis.Nil 和其他不为 nil 的错误。

代码语言:javascript
代码运行次数:0
复制
// getValueFromRedis redis.Nil判断
func getValueFromRedis(key, defaultValue string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	val, err := rdb.Get(ctx, key).Result()
	if err != nil {
		// 如果返回的错误是key不存在
		if errors.Is(err, redis.Nil) {
			return defaultValue, nil
		}
		// 出其他错了
		return "", err
	}
	return val, nil
}

Conn

redis.Conn 是从连接池中取出的单个连接,除非你有特殊的需要,否则尽量不要使用它。你可以使用它向 redis 发送任何数据并读取 redis 的响应,当你使用完毕时,应该把它返回给 go-redis,否则连接池会永远丢失一个连 接。

代码语言:javascript
代码运行次数:0
复制
cn := rdb.Conn(ctx)
defer cn.Close()

if err := cn.ClientSetName(ctx, "myclient").Err(); err != nil {
  panic(err)
}

name, err := cn.ClientGetName(ctx).Result()
if err != nil {
  panic(err)
}
fmt.Println("client name", name)

Pipeline

Redis Pipeline 允许通过使用单个 client-server-client 往返执行多个命令来提高性能。区别于一个接一个地执行100个命令,你可以将这些命令放入 pipeline 中,然后使用1次读写操作像执行单个命令一样执行它们。这样做的好处是节省了执行命令的网络往返时间(RTT)。

y在下面的示例代码中演示了使用 pipeline 通过一个 write + read 操作来执行多个命令。

代码语言:javascript
代码运行次数:0
复制
pipe := rdb.Pipeline()

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

cmds, err := pipe.Exec(ctx)
if err != nil {
	panic(err)
}

// 在执行pipe.Exec之后才能获取到结果
fmt.Println(incr.Val())

上面的代码相当于将以下两个命令一次发给 Redis Server 端执行,与不使用 Pipeline 相比能减少一次RTT。

代码语言:javascript
代码运行次数:0
复制
INCR pipeline_counter
EXPIRE pipeline_counts 3600

或者,你也可以使用Pipelined 方法,它会在函数退出时调用 Exec。

代码语言:javascript
代码运行次数:0
复制
var incr *redis.IntCmd

cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	incr = pipe.Incr(ctx, "pipelined_counter")
	pipe.Expire(ctx, "pipelined_counter", time.Hour)
	return nil
})
if err != nil {
	panic(err)
}

// 在pipeline执行后获取到结果
fmt.Println(incr.Val())

我们可以遍历 pipeline 命令的返回值依次获取每个命令的结果。下方的示例代码中使用pipiline一次执行了100个 Get 命令,在pipeline 执行后遍历取出100个命令的执行结果。

代码语言:javascript
代码运行次数:0
复制
cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
	for i := 0; i < 100; i++ {
		pipe.Get(ctx, fmt.Sprintf("key%d", i))
	}
	return nil
})
if err != nil {
	panic(err)
}

for _, cmd := range cmds {
    fmt.Println(cmd.(*redis.StringCmd).Val())
}

在那些我们需要一次性执行多个命令的场景下,就可以考虑使用 pipeline 来优化。

事务

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

在这种场景我们需要使用 TxPipeline 或 TxPipelined 方法将 pipeline 命令使用 MULTIEXEC包裹起来。

代码语言:javascript
代码运行次数:0
复制
// TxPipeline demo
pipe := rdb.TxPipeline()
incr := pipe.Incr(ctx, "tx_pipeline_counter")
pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
_, err := pipe.Exec(ctx)
fmt.Println(incr.Val(), err)

// TxPipelined demo
var incr2 *redis.IntCmd
_, err = rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
	incr2 = pipe.Incr(ctx, "tx_pipeline_counter")
	pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
	return nil
})
fmt.Println(incr2.Val(), err)

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

代码语言:javascript
代码运行次数:0
复制
MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

Watch

我们通常搭配 WATCH命令来执行事务操作。从使用WATCH命令监视某个 key 开始,直到执行EXEC命令的这段时间里,如果有其他用户抢先对被监视的 key 进行了替换、更新、删除等操作,那么当用户尝试执行EXEC的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试事务或者放弃事务。

Watch方法接收一个函数和一个或多个key作为参数。

代码语言:javascript
代码运行次数:0
复制
Watch(fn func(*Tx) error, keys ...string) error

下面的代码片段演示了 Watch 方法搭配 TxPipelined 的使用示例。

代码语言:javascript
代码运行次数:0
复制
// watchDemo 在key值不变的情况下将其值+1
func watchDemo(ctx context.Context, key string) error {
	return rdb.Watch(ctx, func(tx *redis.Tx) error {
		n, err := tx.Get(ctx, key).Int()
		if err != nil && err != redis.Nil {
			return err
		}
		// 假设操作耗时5秒
		// 5秒内我们通过其他的客户端修改key,当前事务就会失败
		time.Sleep(5 * time.Second)
		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
			pipe.Set(ctx, key, n+1, time.Hour)
			return nil
		})
		return err
	}, key)
}

将上面的函数执行并打印其返回值,如果我们在程序运行后的5秒内修改了被 watch 的 key 的值,那么该事务操作失败,返回redis: transaction failed错误。

最后我们来看一个 go-redis 官方文档中使用 GETSETWATCH命令实现一个 INCR 命令的完整示例。

代码语言:javascript
代码运行次数:0
复制
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)

在这个示例中使用了 redis.TxFailedErr 来检查事务是否失败。

基本类型使用

String

  • Set
  • Get
  • GetSet 设置一个key的值返回这个key的旧值
  • SetNX 如果key不存在,则设置这个key的值
  • MSet批量设置key的值
  • MGet批量查询key的值
  • Incr /IncrBy针对一个key的数值进行递增操作
  • Del删除key操作,支持批量删除
  • Expire单独设置过期时间
代码语言:javascript
代码运行次数:0
复制
ctx := context.Background()

Set

代码语言:javascript
代码运行次数:0
复制
if err := client.Set(ctx, "cs", "测试", 0).Err(); err != nil {
		fmt.Printf("err: %v\n", err2)
	}

Get

代码语言:javascript
代码运行次数:0
复制
re,err := client.Get(ctx, "cs").Result();
if err != nil {
		fmt.Printf("err: %v\n", err2)
	}
fmt.Printf("re: %v\n", re)

GetSet

设置一个key的值返回这个key的旧值

代码语言:javascript
代码运行次数:0
复制
oldValu, err := client.GetSet(ctx, "cs", "new").Result()
	if err != nil {
		return err
	}
	fmt.Printf("oldValu: %v\n", oldValu)

SetNX

如果key不存在,则设置这个key的值

代码语言:javascript
代码运行次数:0
复制
	err := client.SetNX(ctx, "cs", "测试", 0).Err()
	if err != nil {
		return err
	}

MGet

批量查询key的值

代码语言:javascript
代码运行次数:0
复制
	vals, err := client.MGet(ctx, "key1", "key2", "key3").Result()
	if err !=nil{
		return err
	}

MSet

批量设置key的值

代码语言:javascript
代码运行次数:0
复制
err := MSet(ctx,"key1","value1","key2","value2").Err()
if err !=nil{
		return err
	}

Incr,IncrBy

针对一个key的数值进行递增操作

代码语言:javascript
代码运行次数:0
复制
//Incr 函数每次加一	
val, err := client.Incr(ctx, "key").Result()
	if err != nil {
		return err
	}
	fmt.Printf("val: %v\n", val)
//IncrBy 函数,可以指定每次递增多少
	val, err := client.IncrBy(ctx, "key", 2).Result()
	if err != nil {
		return err
	}
	fmt.Printf("val: %v\n", val)
//IncrByFloat 函数 可以每次指定递增,--区别累加的是浮点数
	val, err := client.IncrByFloat(ctx, "key", 2.2).Result()
	if err != nil {
		return err
	}
	fmt.Printf("val: %v\n", val)

Del

删除key操作,支持批量删除

代码语言:javascript
代码运行次数:0
复制
//删除 key	
client.Del(ctx, "key")
//批量删除
	if err := client.Del(ctx, "key1", "key2", "key3").Err(); err != nil {
		return err
	}

Expire

单独设置过期时间

代码语言:javascript
代码运行次数:0
复制
	client.Set(ctx, "key", "value", 0)
	client.Expire(ctx, "key", 10*time.Second)

Hash

  • Hset 根据key和field字段设置,field字段的值
  • HGet根据key和field字段,查询field字段的值
  • HGetAll根据key查询所有字段和值
  • HIncrBy根据key和field字段,累加字段的数值
  • Hkeys根据key返回所有的字段名
  • HLen根据key,查询hash的字段的数量
  • HMGet根据key和多个field名,批量查询多个hash字段值
  • HMSet根据多个字段名和字段值,批量设置hash字段值
  • HSetNX如果field字段不存在,则设置hash字段值
  • HDel根据key和field字段名删除hash的字段,支持批量删除
  • HExists检测hash字段名是否存在

Hset

根据key和field字段设置,field字段的值

代码语言:javascript
代码运行次数:0
复制
	err := client.HSet(ctx, "key", "fild", "value").Err()
	if err != nil {
		return err
	}

HGet

根据key和field字段,查询field字段的值

代码语言:javascript
代码运行次数:0
复制
	s, err := client.HGet(ctx, "key", "field").Result()
	if err != nil {
		return err
	}

HGetAll

根据key查询所有字段和值

代码语言:javascript
代码运行次数:0
复制
s, err := client.HGetAll(ctx, "key").Result()
	if err != nil {
		return err
	}
	for _, v := range s {
		fmt.Printf("v: %v\n", v)
	}

HIncrBy

根据key和field字段,累加字段的数值

代码语言:javascript
代码运行次数:0
复制
s, err := client.HIncrBy(ctx, "key", "field", 2).Result()
	if err != nil {
		return err
	}
	fmt.Printf("s: %v\n", s)

Hkeys

根据key返回所有的字段名

代码语言:javascript
代码运行次数:0
复制
	fields, err := client.Keys(ctx, "key").Result()
	if err != nil {
		return err
	}
	for _, v := range fields {
		fmt.Printf("v: %v\n", v)
	}

HLen

根据key,查询hash的字段的数量

代码语言:javascript
代码运行次数:0
复制
	size, err := client.HLen(ctx, "key").Result()
	if err != nil {
		return err
	}
	fmt.Printf("size: %v\n", size)

HMGet

根据key和多个field名,批量查询多个hash字段值

代码语言:javascript
代码运行次数:0
复制
	values, err := client.HMGet(ctx, "key", "field1", "field2").Result()
	if err != nil {
		return err
	}
//values是一个数组
	for _, v := range values {
		fmt.Printf("v: %v\n", v)
	}

HMSet

根据多个字段名和字段值,批量设置hash字段值

代码语言:javascript
代码运行次数:0
复制
	data := make(map[string]interface{})
	data["id"] = 1
	data["username"] = "zhangsan"
	//批量设置
	err := client.HMSet(ctx, "key", data).Err()
	if err != nil {
		return err
	}

HSetNX

如果field字段不存在,则设置hash字段值

代码语言:javascript
代码运行次数:0
复制
err := client.HSet(ctx, "key", "field", "value").Err()
	if err != nil {
		return err
	}

HDel

根据key和field字段名删除hash的字段,支持批量删除

代码语言:javascript
代码运行次数:0
复制
err := client.HDel(ctx, "key", "field1", "field2").Err()
	if err != nil {
		return err
	}

HExists

检测hash字段名是否存在

代码语言:javascript
代码运行次数:0
复制
	bool, err := client.HExists(ctx, "key", "field").Result()
	if err != nil {
		return err
	}

List

  • LPush 从列表左侧插入数据
  • LPushX根LPush的区别是,仅当列表存在的时候才插入数据,用法完全一样
  • RPop从列表的右边删除第一个数据,并返回删除的数据
  • RPush从列表的右侧插入
  • RPushX根RPush的区别是,仅当列表存在的时候才插入数据,用法完全一样
  • LPop从列表左边删除第一个数据,并返回删除的数据
  • LLen返回列表的大小
  • LRange返回一个范围内的数据,也可以返回全部数据
  • LRem删除列表中的数据

LPush

从列表左侧插入数据

代码语言:javascript
代码运行次数:0
复制
	err := client.LPush(ctx, "key", "value").Err()
	if err != nil {
		return err
	}
	err := client.LPush(ctx, "key", 1, 2, 3, 4, 5).Err()
	if err != nil {
		return nil, err
	}

LPushX

根LPush的区别是,仅当列表存在的时候才插入数据,用法完全一样

代码语言:javascript
代码运行次数:0
复制
	err := client.LPushX(ctx, "key", "value").Err()
	if err != nil {
		return err
	}
	err := client.LPushX(ctx, "key", 1, 2, 3, 4, 5).Err()
	if err != nil {
		return nil, err
	}

RPop

从列表的右边删除第一个数据,并返回删除的数据

代码语言:javascript
代码运行次数:0
复制
	val, err := client.RPop(ctx, "key").Result()
	if err != nil {
		return err
	}
	fmt.Printf("val: %v\n", val)

RPush

从列表的右侧插入

代码语言:javascript
代码运行次数:0
复制
	err := client.RPush(ctx, "key", "value").Err()
	if err != nil {
		return err
	}
	err := client.RPush(ctx, "key", 1, 2, 3, 4, 5).Err()
	if err != nil {
		return nil, err
	}

RPushX

根RPush的区别是,仅当列表存在的时候才插入数据,用法完全一样

代码语言:javascript
代码运行次数:0
复制
	err := client.RPushX(ctx, "key", "value").Err()
	if err != nil {
		return err
	}
	err := client.RPushX(ctx, "key", 1, 2, 3, 4, 5).Err()
	if err != nil {
		return nil, err
	}

LPop

从列表左边删除第一个数据,并返回删除的数据

代码语言:javascript
代码运行次数:0
复制
	val, err := client.LPop(ctx, "key").Result()
	if err != nil {
		return err
	}
	fmt.Printf("val: %v\n", val)

LLen

返回列表的大小

代码语言:javascript
代码运行次数:0
复制
	size, err := client.LLen(ctx, "key").Result()
	if err != nil {
		return err
	}
	fmt.Printf("size: %v\n", size)

LRange

返回一个范围内的数据,也可以返回全部数据

代码语言:javascript
代码运行次数:0
复制
//0 -1 返回全部的数据
values ,err := client.LRange(ctx,"key",0,-1).Result()
if err!=nil{
    return err
}

LRem

删除列表中的数据

示例

初始化与基本使用

代码语言:javascript
代码运行次数:0
复制
package main
​
import (
  "context"
  "fmt"
  "github.com/redis/go-redis/v9"
  "time"
)
​
// 声明一个全局的 rdb 变量
var rdb *redis.Client
​
// 初始化连接
func initRedisClient() (err error) {
  // NewClient将客户端返回给Options指定的Redis Server。
  // Options保留设置以建立redis连接。
  rdb = redis.NewClient(&redis.Options{
    Addr:     "localhost:6379",
    Password: "", // 没有密码,默认值
    DB:       0,  // 默认DB 0 连接到服务器后要选择的数据库。
    PoolSize: 20, // 最大套接字连接数。 默认情况下,每个可用CPU有10个连接,由runtime.GOMAXPROCS报告。
  })
​
  // Background返回一个非空的Context。它永远不会被取消,没有值,也没有截止日期。
  // 它通常由main函数、初始化和测试使用,并作为传入请求的顶级上下文
  ctx := context.Background()
​
  _, err = rdb.Ping(ctx).Result()
  if err != nil {
    return err
  }
  return nil
}

func redisCommand() {
  ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
  defer cancel()
​
  // 获取Redis的“Get key”命令。它返回redis。当键不存在时出现Nil错误。
  val, err := rdb.Get(ctx, "key").Result()
  if err != nil {
    fmt.Printf("redis command failed: %v\n", err)
  }
  fmt.Printf("redis command get key %v\n", val)
​
  // 分别访问值和错误:
  get := rdb.Get(ctx, "key")
  fmt.Println("redis command get value: ", get.Val()) // 获取值
  fmt.Println("redis command get err: ", get.Err())   // 获取错误
​
  // 设置Redis ' Set key value [expiration] '命令。
  err = rdb.Set(ctx, "key", 10, time.Hour).Err()
  fmt.Printf("rdb set err: %v\n", err)
​
  // 获取Redis的“Get key”命令。它返回redis。当键不存在时出现Nil错误。
  value := rdb.Get(ctx, "key").Val()
  fmt.Printf("rdb get value: %v\n", value)
}
​

ZSET 使用

下面的示例代码演示了如何使用 go-redis 库操作 zset。

代码语言:javascript
代码运行次数:0
复制
// zsetDemo 操作zset示例
func zsetDemo() {
	// key
	zsetKey := "language_rank"
	// value
	languages := []*redis.Z{
		{Score: 90.0, Member: "Golang"},
		{Score: 98.0, Member: "Java"},
		{Score: 95.0, Member: "Python"},
		{Score: 97.0, Member: "JavaScript"},
		{Score: 99.0, Member: "C/C++"},
	}
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

  // ZAdd Redis `ZADD key score member [score member ...]` command.
	err := rdb.ZAdd(ctx, zsetKey, languages...).Err()
	if err != nil {
		fmt.Printf("zadd failed, err:%v\n", err)
		return
	}
	fmt.Println("zadd success")

  // ZIncrBy 给某一个元素添加分数值 把Golang的分数加 10
	newScore, err := rdb.ZIncrBy(ctx, zsetKey, 10.0, "Golang").Result()
	if err != nil {
		fmt.Printf("zincrby failed, err:%v\n", err)
		return
	}
	fmt.Printf("Golang's score is %f now.\n", newScore)

  // 取分数最高的3个  适用于 排行榜、充值榜...
	ret := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Val()
	for _, z := range ret {
		fmt.Println(z.Member, z.Score)
	}

	// 取95~100分的
	op := &redis.ZRangeBy{
		Min: "95",
		Max: "100",
	}
	ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, op).Result()
	if err != nil {
		fmt.Printf("zrangebyscore failed, err:%v\n", err)
		return
	}
	for _, z := range ret {
		fmt.Println(z.Member, z.Score)
	}
}

执行上面的函数将得到如下输出结果。

代码语言:javascript
代码运行次数:0
复制
zadd success
Golang's score is 100.000000 now.
Golang 100
C/C++ 99
Java 98
Python 95
JavaScript 97
Java 98
C/C++ 99
Golang 100

按前缀获取 Key

你可以使用KEYS prefix:* 命令按前缀获取所有 key。

代码语言:javascript
代码运行次数:0
复制
vals, err := rdb.Keys(ctx, "prefix*").Result()

扫描或遍历所有key

如果需要扫描数百万的 key ,那速度就会比较慢(生产环境禁止此类操作)。

这种场景下你可以使用Scan 命令来遍历所有符合要求的 key。

代码语言:javascript
代码运行次数:0
复制
// scanKeysDemo1 按前缀查找所有key示例
func scanKeysDemo1() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	var cursor uint64
	for {
		var keys []string
		var err error
		// 按前缀扫描key
		keys, cursor, err = rdb.Scan(ctx, cursor, "prefix:*", 0).Result()
		if err != nil {
			panic(err)
		}

		for _, key := range keys {
			fmt.Println("key", key)
		}

		if cursor == 0 { // no more keys
			break
		}
	}
}

Go-redis 允许将上面的代码简化为如下示例。

代码语言:javascript
代码运行次数:0
复制
// scanKeysDemo2 按前缀扫描key示例
func scanKeysDemo2() {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	// 按前缀扫描key
	iter := rdb.Scan(ctx, 0, "prefix:*", 0).Iterator()
	for iter.Next(ctx) {
		fmt.Println("keys", iter.Val())
	}
	if err := iter.Err(); err != nil {
		panic(err)
	}
}

例如,我们可以写出一个将所有匹配指定模式的 key 删除的示例。

代码语言:javascript
代码运行次数:0
复制
// delKeysByMatch 按match格式扫描所有key并删除
func delKeysByMatch(match string, timeout time.Duration) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	iter := rdb.Scan(ctx, 0, match, 0).Iterator()
	for iter.Next(ctx) {
		err := rdb.Del(ctx, iter.Val()).Err()
		if err != nil {
			panic(err)
		}
	}
	if err := iter.Err(); err != nil {
		panic(err)
	}
}

此外,对于 Redis 中的 set、hash、zset 数据类型,go-redis 也支持类似的遍历方法。

代码语言:javascript
代码运行次数:0
复制
iter := rdb.SScan(ctx, "set-key", 0, "prefix:*", 0).Iterator()
iter := rdb.HScan(ctx, "hash-key", 0, "prefix:*", 0).Iterator()
iter := rdb.ZScan(ctx, "sorted-hash-key", 0, "prefix:*", 0).Iterator(

按通配符删除key

当通配符匹配的key的数量不多时,可以使用Keys()得到所有的key在使用Del命令删除。 如果key的数量非常多的时候,我们可以搭配使用Scan命令和Del命令完成删除。

代码语言:javascript
代码运行次数:0
复制
package main

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

var (
	rdb *redis.Client
)

// 初始化连接
func initClient() (err error) {
	rdb = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",  // no password set
		DB:       0,   // use default DB
		PoolSize: 100, // 连接池大小
	})
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_, err = rdb.Ping(ctx).Result()
	return err
}

func V8Example() {
	initClient()
	ctx := context.Background()
	rdb.Do(ctx, "set", "key1", "value1").Result()
	rdb.Do(ctx, "set", "key2", "value2").Result()
	vals1, _ := rdb.Keys(ctx, "k*").Result()
	fmt.Println(vals1) //输出:[key2 key1]
	iter := rdb.Scan(ctx, 0, "k*", 0).Iterator()
	for iter.Next(ctx) {
		err := rdb.Del(ctx, iter.Val()).Err()
		if err != nil {
			panic(err)
		}
	}
	vals2, _ := rdb.Keys(ctx, "k*").Result()
	fmt.Println(vals2)//输出:[]
}
func main() {
	V8Example()
}

事务 示例 1

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

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

代码语言:javascript
代码运行次数:0
复制
package main

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

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

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

	_, err = rdb.Ping().Result()
	if err != nil {
		return err
	}
	return nil
}
func V8Example() {
	initClient()
    //Do是baseClient结构体的方法
	rdb.Do("set", "key8", "value8")
    //Client结构体里面有cmdable结构体
    //Keys是cmable结构体的方法,返回*StringSliceCmd
    // Result是StringSliceCmd结构体的方法
	vals, _ := rdb.Keys("k*").Result()
	fmt.Println(vals) //输出:[key8]
     //返回的是Pipeliner接口
	pipe := rdb.TxPipeline() //相当于MULTI开启事务
    //Pipeliner接口里定义有Do这个方法
	pipe.Do("set", "key8", "value8")
	pipe.Do("set", "key9", "value9")
    
	vals1, _ := rdb.Keys("k*").Result()
	fmt.Println(vals1) //输出:[key8]
	vals2, _ := pipe.Keys("k*").Result()
	fmt.Println(vals2) //输出:[]
    //Pipeliner接口里定义有Exec这个方法
	pipe.Exec() ///相当于EXEC事务

	vals3, _ := rdb.Keys("k*").Result()
	fmt.Println(vals3) //输出[key8 key9]

}
func main() {
	V8Example()
}

事务示例 2

代码语言:javascript
代码运行次数:0
复制
package main

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

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

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

	_, err = rdb.Ping().Result()
	if err != nil {
		return err
	}
	return nil
}
func V8Example() {
	initClient()
	rdb.Do("set", "tx_pipeline_counter", "1")
	val, _ := rdb.Get("tx_pipeline_counter").Result()
	fmt.Println(val) //输出:1

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

	pipe.Exec()
	fmt.Println(incr.Val())//输出:2
	val1, _ := rdb.Get("tx_pipeline_counter").Result()
	fmt.Println(val1) //输出:2

}
func main() {
	V8Example()
}

参考

  1. redis 命令使用 http://doc.redisfans.com

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Redis介绍
    • Redis支持的数据结构
  • String类型
  • List类型
  • Hash类型
  • set类型
  • zset类型
    • Redis应用场景
    • 准备Redis环境
  • go-redis库
    • 安装
    • 连接
      • 普通连接模式
      • TLS连接模式
      • Redis Sentinel模式
      • Redis Cluster模式
    • 使用 TLS
    • SSH 方式
  • 基本使用
    • 执行命令
    • 执行任意命令
    • redis.Nil
    • Conn
  • Pipeline
  • 事务
    • Watch
  • 基本类型使用
    • String
  • Set
  • Get
  • GetSet
  • SetNX
  • MGet
  • MSet
  • Incr,IncrBy
  • Del
  • Expire
    • Hash
    • Hset
  • HGet
  • HGetAll
  • HIncrBy
  • Hkeys
  • HLen
  • HMGet
  • HMSet
  • HSetNX
  • HDel
  • HExists
    • List
  • LPush
  • LPushX
  • RPop
  • RPush
  • RPushX
  • LPop
  • LLen
  • LRange
  • LRem
  • 示例
    • 初始化与基本使用
    • ZSET 使用
    • 按前缀获取 Key
    • 扫描或遍历所有key
    • 按通配符删除key
    • 事务 示例 1
    • 事务示例 2
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档