跟我一起来学golang之《操作redis》(二)

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

承接上一篇文章,我们继续了解golang操作redis

7.list的相关用法

Redis列表是简单的字符串列表,列表是有序的,列表中的元素可以重复。

可以添加一个元素到列表的头部(左边)或者尾部(右边)

golang redis list数据操作常用函数:

  • LPush - 从列表左边插入数据
  • LPushX - 跟LPush的区别是,仅当列表存在的时候才插入数据
  • RPop - 从列表的右边删除第一个数据,并返回删除的数据
  • RPush - 从列表右边插入数据
  • RPushX - 跟RPush的区别是,仅当列表存在的时候才插入数据
  • LPop - 从列表左边删除第一个数据,并返回删除的数据
  • LLen - 返回列表的大小
  • LRange - 返回列表的一个范围内的数据,也可以返回全部数据
  • LRem - 删除列表中的数据
  • LIndex - 根据索引坐标,查询列表中的数据
  • LInsert - 在指定位置插入数据

1.LPush

从列表左边插入数据

// 插入一个数据
client.LPush("key", "data1")

// LPush支持一次插入任意个数据
err := client.LPush("key", 1,2,3,4,5).Err()
if err != nil {
	panic(err)
}
复制代码

2.LPushX

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

3.RPop

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

val, err := client.RPop("key").Result()
if err != nil {
	panic(err)
}

fmt.Println(val)
复制代码

4.RPush

从列表右边插入数据

// 插入一个数据
client.RPush("key", "data1")

// 支持一次插入任意个数据
err := client.RPush("key", 1,2,3,4,5).Err()
if err != nil {
	panic(err)
}
复制代码

5.RPushX

跟RPush的区别是,仅当列表存在的时候才插入数据, 他们用法一样

6.LPop

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

val, err := client.LPop("key").Result()
if err != nil {
	panic(err)
}

fmt.Println(val)
复制代码

7.LLen

返回列表的大小

val, err := client.LLen("key").Result()
if err != nil {
	panic(err)
}

fmt.Println(val)
复制代码

8.LRange

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

// 返回从0开始到-1位置之间的数据,意思就是返回全部数据
vals, err := client.LRange("key",0,-1).Result()
if err != nil {
	panic(err)
}
fmt.Println(vals)
复制代码

9.LRem

删除列表中的数据

// 从列表左边开始,删除100, 如果出现重复元素,仅删除1次,也就是删除第一个
dels, err := client.LRem("key",1,100).Result()
if err != nil {
	panic(err)
}

// 如果存在多个100,则从列表左边开始删除2个100
client.LRem("key",2,100)


// 如果存在多个100,则从列表右边开始删除2个100
// 第二个参数负数表示从右边开始删除几个等于100的元素
client.LRem("key",-2,100)

// 如果存在多个100,第二个参数为0,表示删除所有元素等于100的数据
client.LRem("key",0,100)
复制代码

10.LIndex

根据索引坐标,查询列表中的数据

// 列表索引从0开始计算,这里返回第6个元素
val, err := client.LIndex("key",5).Result()
if err != nil {
	panic(err)
}

fmt.Println(val)
复制代码

11.LInsert

在指定位置插入数据

// 在列表中5的前面插入4
// before是之前的意思
err := client.LInsert("key","before", 5, 4).Err()
if err != nil {
	panic(err)
}

// 在列表中 tizi365 元素的前面插入 欢迎你
client.LInsert("key","before", "tizi365", "欢迎你")

// 在列表中 tizi365 元素的后面插入 2019
client.LInsert("key","after", "tizi365", "2019")
复制代码

8.set的相关用法

redis的set类型(集合)是string类型数值的无序集合,并且集合元素唯一。

下面介绍go redis的集合用法。

go redis 集合(set)常用函数列表

  • SAdd - 添加集合元素
  • SCard - 获取集合元素个数
  • SIsMember - 判断元素是否在集合中
  • SMembers - 获取集合中所有的元素
  • SRem - 删除集合元素
  • SPop,SPopN - 随机返回集合中的元素,并且删除返回的元素

1.SAdd

添加集合元素

// 添加100到集合中
err := client.SAdd("key",100).Err()
if err != nil {
	panic(err)
}

// 将100,200,300添加到集合中
client.SAdd("key",100, 200, 300)
复制代码

2.SCard

获取集合元素个数

size, err := client.SCard("key").Result()
if err != nil {
	panic(err)
}
fmt.Println(size)
复制代码

3.SIsMember

判断元素是否在集合中

// 检测100是否包含在集合中
ok, _ := client.SIsMember("key", 100).Result()
if ok {
	fmt.Println("集合包含指定元素")
}
复制代码

4.SMembers

获取集合中所有的元素

es, _ := client.SMembers("key").Result()
// 返回的es是string数组
fmt.Println(es)
复制代码

5.SRem

删除集合元素

// 删除集合中的元素100
client.SRem("key", 100)

// 删除集合中的元素tizi和2019
client.SRem("key", "tizi", "2019")
复制代码

6.SPop,SPopN

随机返回集合中的元素,并且删除返回的元素

// 随机返回集合中的一个元素,并且删除这个元素
val, _ := client.SPop("key").Result()
fmt.Println(val)

// 随机返回集合中的5个元素,并且删除这些元素
vals, _ := client.SPopN("key", 5).Result()
fmt.Println(vals)
复制代码

9.zset的相关用法

Redis 有序集合(sorted set)和集合一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一个double类型的分数,这个分数主要用于集合元素排序。

下面介绍golang redis 有序集合的用法

go redis有序集合常用函数:

  • ZAdd - 添加一个或者多个元素到集合,如果元素已经存在则更新分数
  • ZCard - 返回集合元素个数
  • ZCount - 统计某个分数范围内的元素个数
  • ZIncrBy - 增加元素的分数
  • ZRange,ZRevRange - 返回集合中某个索引范围的元素,根据分数从小到大排序
  • ZRangeByScore,ZRevRangeByScore - 根据分数范围返回集合元素,元素根据分数从小到大排序,支持分页。
  • ZRem - 删除集合元素
  • ZRemRangeByRank - 根据索引范围删除元素
  • ZRemRangeByScore - 根据分数范围删除元素
  • ZScore - 查询元素对应的分数
  • ZRank, ZRevRank - 查询元素的排名

1.ZAdd

添加一个或者多个元素到集合,如果元素已经存在则更新分数

// 添加一个集合元素到集合中, 这个元素的分数是2.5,元素名是tizi
err := client.ZAdd("key", redis.Z{2.5,"tizi"}).Err()
if err != nil {
	panic(err)
}
复制代码

下面是redis.Z结构体说明:

type Z struct {
	Score  float64 // 分数
	Member interface{} // 元素名
}
复制代码

2.ZCard

返回集合元素个数

size, err := client.ZCard("key").Result()
if err != nil {
	panic(err)
}
fmt.Println(size)
复制代码

3.ZCount

统计某个分数范围内的元素个数

// 返回: 1<=分数<=5 的元素个数, 注意:"1", "5"两个参数是字符串
size, err := client.ZCount("key", "1","5").Result()
if err != nil {
	panic(err)
}
fmt.Println(size)

// 返回: 1<分数<=5 的元素个数
// 说明:默认第二,第三个参数是大于等于和小于等于的关系。
// 如果加上( 则表示大于或者小于,相当于去掉了等于关系。
size, err := client.ZCount("key", "(1","5").Result()
复制代码

4.ZIncrBy

增加元素的分数

// 给元素5,加上2分
client.ZIncrBy("key", 2,"5")
复制代码

5.ZRange,ZRevRange

返回集合中某个索引范围的元素,根据分数从小到大排序

// 返回从0到-1位置的集合元素, 元素按分数从小到大排序
// 0到-1代表则返回全部数据
vals, err := client.ZRange("key", 0,-1).Result()
if err != nil {
	panic(err)
}

for _, val := range vals {
	fmt.Println(val)
}
复制代码

ZRevRange用法跟ZRange一样,区别是ZRevRange的结果是按分数从大到小排序。

6.ZRangeByScore

根据分数范围返回集合元素,元素根据分数从小到大排序,支持分页。

// 初始化查询条件, Offset和Count用于分页
op := redis.ZRangeBy{
	Min:"2", // 最小分数
	Max:"10", // 最大分数
	Offset:0, // 类似sql的limit, 表示开始偏移量
	Count:5, // 一次返回多少数据
}
	
vals, err := client.ZRangeByScore("key", op).Result()
if err != nil {
	panic(err)
}

for _, val := range vals {
	fmt.Println(val)
}
复制代码

7.ZRevRangeByScore

用法类似ZRangeByScore,区别是元素根据分数从大到小排序。

8.ZRangeByScoreWithScores

用法跟ZRangeByScore一样,区别是除了返回集合元素,同时也返回元素对应的分数

// 初始化查询条件, Offset和Count用于分页
op := redis.ZRangeBy{
	Min:"2", // 最小分数
	Max:"10", // 最大分数
	Offset:0, // 类似sql的limit, 表示开始偏移量
	Count:5, // 一次返回多少数据
}

vals, err := client.ZRangeByScoreWithScores("key", op).Result()
if err != nil {
	panic(err)
}

for _, val := range vals {
	fmt.Println(val.Member) // 集合元素
	fmt.Println(val.Score) // 分数
}
复制代码

9.ZRem

删除集合元素

// 删除集合中的元素tizi
client.ZRem("key", "tizi")

// 删除集合中的元素tizi和xiaoli
// 支持一次删除多个元素
client.ZRem("key", "tizi", "xiaoli")
复制代码

10.ZRemRangeByRank

根据索引范围删除元素

// 集合元素按分数排序,从最低分到高分,删除第0个元素到第5个元素。
// 这里相当于删除最低分的几个元素
client.ZRemRangeByRank("key", 0, 5)

// 位置参数写成负数,代表从高分开始删除。
// 这个例子,删除最高分数的两个元素,-1代表最高分数的位置,-2第二高分,以此类推。
client.ZRemRangeByRank("key", -1, -2)
复制代码

11.ZRemRangeByScore

根据分数范围删除元素

// 删除范围: 2<=分数<=5 的元素
client.ZRemRangeByScore("key", "2", "5")

// 删除范围: 2<=分数<5 的元素
client.ZRemRangeByScore("key", "2", "(5")
复制代码

12.ZScore

查询元素对应的分数

// 查询集合元素tizi的分数
score, _ := client.ZScore("key", "tizi").Result()
fmt.Println(score)
复制代码

13.ZRank

根据元素名,查询集合元素在集合中的排名,从0开始算,集合元素按分数从小到大排序

rk, _ := client.ZRank("key", "tizi").Result()
fmt.Println(rk)
复制代码

ZRevRank的作用跟ZRank一样,区别是ZRevRank是按分数从大到小排序。

10.发布订阅

Redis提供了发布订阅功能,可以用于消息的传输,Redis的发布订阅机制包括三个部分,发布者,订阅者和Channel。

发布订阅架构图:

img

发布者和订阅者都是Redis客户端,Channel则为Redis服务器端,发布者将消息发送到某个的频道,订阅了这个频道的订阅者就能接收到这条消息。Redis的这种发布订阅机制与基于主题的发布订阅类似,Channel相当于主题。

下面介绍golang如何使用redis的发布订阅功能。

go redis发布订阅常用函数:

  • Subscribe - 订阅channel
  • PSubscribe - 订阅channel支持通配符匹配
  • Publish - 将信息发送到指定的channel。
  • PubSubChannels - 查询活跃的channel
  • PubSubNumSub - 查询指定的channel有多少个订阅者

1.Subscribe

订阅channel

例子1:

// 订阅channel1这个channel
sub := client.Subscribe("channel1")

// 读取channel消息
iface, err := sub.Receive()
if err != nil {
    // handle error
}

// 检测收到的消息类型
switch iface.(type) {
case *redis.Subscription:
    // 订阅成功
case *redis.Message:
    // 处理收到的消息
    // 这里需要做一下类型转换
    m := iface.(redis.Message)
    // 打印收到的小
	fmt.Println(m.Payload)
case *redis.Pong:
    // 收到Pong消息
default:
    // handle error
}
复制代码

例子2:
使用golang channel的方式处理消息

// 订阅channel1这个channel
sub := client.Subscribe("channel1")

// sub.Channel() 返回go channel,可以循环读取redis服务器发过来的消息
for msg := range sub.Channel() {
	// 打印收到的消息
	fmt.Println(msg.Channel)
	fmt.Println(msg.Payload)
}
复制代码

例子3:
取消订阅

// 订阅channel1这个channel
sub := client.Subscribe("channel1")

// 忽略其他处理逻辑
 
// 取消订阅
sub.Unsubscribe("channel1")
复制代码

2.PSubscribe

用法跟Subscribe一样,区别是PSubscribe订阅通道(channel)支持模式匹配。

例子:

// 订阅channel1这个channel
sub := client.PSubscribe("ch_user_*")
// 可以匹配ch_user_开头的任意channel
复制代码

3.Publish

将消息发送到指定的channel

// 将"message"消息发送到channel1这个通道上
client.Publish("channel1","message")
复制代码

4.PubSubChannels

查询活跃的channel

// 没有指定查询channel的匹配模式,则返回所有的channel
chs, _ := client.PubSubChannels("").Result()
for _, ch := range chs {
	fmt.Println(ch)
}

// 匹配user_开头的channel
chs, _ := client.PubSubChannels("user_*").Result()
复制代码

5.PubSubNumSub

查询指定的channel有多少个订阅者

// 查询channel1,channel2两个通道的订阅者数量
chs, _ := client.PubSubNumSub("channel1", "channel2").Result()
for ch, count := range chs {
	fmt.Println(ch) // channel名字
	fmt.Println(count) // channel的订阅者数量
}
复制代码

11.事务相关

redis事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

下面介绍golang redis事务用法。

go redis事务常用函数:

  • TxPipeline - 以Pipeline的方式操作事务
  • Watch - redis乐观锁支持

1.TxPipeline

以Pipeline的方式操作事务

// 开启一个TxPipeline事务
pipe := client.TxPipeline()

// 执行事务操作,可以通过pipe读写redis
incr := pipe.Incr("tx_pipeline_counter")
pipe.Expire("tx_pipeline_counter", time.Hour)

// 上面代码等同于执行下面redis命令
//
//     MULTI
//     INCR pipeline_counter
//     EXPIRE pipeline_counts 3600
//     EXEC

// 通过Exec函数提交redis事务
_, err := pipe.Exec()

// 提交事务后,我们可以查询事务操作的结果
// 前面执行Incr函数,在没有执行exec函数之前,实际上还没开始运行。
fmt.Println(incr.Val(), err)
复制代码

2.watch

redis乐观锁支持,可以通过watch监听一些Key, 如果这些key的值没有被其他人改变的话,才可以提交事务。

// 定义一个回调函数,用于处理事务逻辑
fn := func(tx *redis.Tx) error {
		// 先查询下当前watch监听的key的值
		v, err := tx.Get("key").Result()
		if err != nil && err != redis.Nil {
			return err
		}

		// 这里可以处理业务
		fmt.Println(v)

		// 如果key的值没有改变的话,Pipelined函数才会调用成功
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			// 在这里给key设置最新值
			pipe.Set("key", "new value", 0)
			return nil
		})
		return err
	}

// 使用Watch监听一些Key, 同时绑定一个回调函数fn, 监听Key后的逻辑写在fn这个回调函数里面
// 如果想监听多个key,可以这么写:client.Watch(fn, "key1", "key2", "key3")
client.Watch(fn, "key")
复制代码