【redis】ZSet的使用

准备动作

启动docker

1
docker run -d -p 6379:6379 --name redis redis

查看docke状态

1
docker ps

进入redis-cli

1
redis-cli

Zset的基本指令集合

ZADD 添加元素 O(log(N))

1
zadd myzset 1 "1"

ZREM 删除元素

​ O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed.

1
ZREM myzset "one"

ZCARD 查看zset的元素个数 O(1)

1
zcard myzset 

ZCOUNT 查看zset区间的与元素个数(默认为左闭右闭)O(log(N))

1
zcount myzset 1 3#查看大于等于1小于等于3的
1
zcount myzset (1 3 #查看大于1小于等于3的
1
zcount myzset -inf +inf 

ZINCRBY 给元素添加值 O(log(N)

1
ZINCRBY myzset -10 "one"

ZRANK O(log(N))

  • 键存在,返回排名(默认为从小到大排序),最小的排序名词为(0)

  • 键不存在,返回nil

    1
    zrank myzset "one"

ZRANGE 查询区间值的元素 O(log(N)+M)

1
ZRANGE myzset -10 100 WITHSOCRES

ZINTERSTORE

有序集合(Sorted Set)的交集并将结果存储到新有序集合的命令

1
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

参数说明:

  • destination:目标有序集合的键名,用于存储交集结果。
  • numkeys:指定参与计算的有序集合的数量。
  • key [key ...]:要参与计算交集的有序集合的键名列表。
  • WEIGHTS weight [weight ...](可选):用于指定每个有序集合的权重,可以调整每个成员分数的权重。如果不指定权重,则默认为 1。
  • AGGREGATE SUM|MIN|MAX(可选):用于指定计算交集时的聚合方式,可以是 SUM(求和,默认)、MIN(最小值)或 MAX(最大值)。
1
ZINTERSTORE intersection_max 2 set1 set2 AGGREGATE MAX

ZRANGEBULEX

  • 按序返回指定成员区间的成员

  • 区间成员的分数必须相同

  • 不要在分数不一致的SortSet集合中去使用 ZRANGEBYLEX 指令,因为获取的结果并不准确。

  • 可以使用 “-“ 和 “+” 表示得分最小值和最大值

  • 成员字符串作为二进制数组的字节数进行比较。默认是以ASCII字符集的顺序进行排列。

    1
    2
    3
    4
    5
    6
    7
    8
    zadd zset 0 a 0 aa 0 abc 0 apple 0 b 0 c 0 d 0 d1 0 dd 0 dobble 0 z 0 z1
    ZRANGEBYLEX zset - +

    ZRANGEBYLEX zset - + limit 0 3 # 其中的0是结果的其实位置(offset) 3是结果的数量(count)

    ZRANGEBULEX zset [aa [c

    ZRANGEBULEX zset [aa (c

    使用场景

    • 姓名排序
    zrangebylex
    • 电话号码排序

      onx9Qg.png

go-redis的基本使用

安装

1
go get github.com/go-redis/redis/v8

连接local redis

1
2
3
4
5
6
7
8
func doCommand() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 密码
DB: 0, // 数据库
PoolSize: 20, // 连接池大小
})
}

使用基本的go-redis指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

val, err := rdb.Get(ctx, "key").Result()
fmt.Println(val, err)

cmder := rdb.Get(ctx, "key")
val = cmder.Val()
err = cmder.Err()
fmt.Println(val, err)

err = rdb.Set(ctx, "key1", "val1", time.Hour).Err()

val = rdb.Get(ctx, "key1").Val()
fmt.Println(val, err)

go-redis中提供了redis.Nil 的错误来表示不存在的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 密码
DB: 0, // 数据库
PoolSize: 20, // 连接池大小
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

val, err := rdb.Get(ctx, "key").Result()
if err != nil {
if errors.Is(err, redis.Nil) {
fmt.Printf("redis.Nil")
return
}
fmt.Println(err)
return
}
fmt.Println(val, err)

使用 Client.Do 来实现任意redis指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 密码
DB: 0, // 数据库
PoolSize: 20, // 连接池大小
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

err := rdb.Do(ctx, "set", "key2", "val2", "EX", 3600).Err()
fmt.Println(err)

val, err := rdb.Do(ctx, "get", "key2").Result()
fmt.Println(val, err)

go-redis ZSet 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 密码
DB: 0, // 数据库
PoolSize: 20, // 连接池大小
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

zsetKey := "scores_rank"

rank := []*redis.Z{
{Score: 90.0, Member: "Alice"},
{Score: 90.1, Member: "Bob"},
{Score: 80.2, Member: "Cick"},
{Score: 89.2, Member: "David"},
{Score: 99.1, Member: "Eric"},
{Score: 78.1, Member: "Frank"},
}
if err := rdb.ZAdd(ctx, zsetKey, rank...).Err(); err != nil {
fmt.Println("zadd failed", err)
return
}
fmt.Println("zadd success")

newscore, err := rdb.ZIncrBy(ctx, zsetKey, -10, "Frank").Result()
if err != nil {
fmt.Println("zincrby failed", err)
return
}
fmt.Printf("Frank's score is %f\n", newscore)

res := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Val()
fmt.Println("ZRevRangeWithScores's res :", res)

for _, z := range res {
fmt.Println(z)
}

Redis Pipeline

  • Pipeline 将发送的命令一次性打包发给Redis服务器执行,从而减少了RTT时延

  • Redis服务器会按照添加命令的顺序依次执行它们

  • Pipeline 不适合在这种情况下使用:单个命令可能需要等待前一个命令的结果,或者需要保证命令的顺序性

rdb.Pipeline()

1
2
3
4
5
6
7
8
9
10
11
12
13
pipe := rdb.Pipeline()

incr := pipe.Incr(ctx, "pipeline_counter")
pipe.Expire(ctx, "pipeline_counter", time.Hour)
Get := pipe.Get(ctx, "pipe_test1")
_, err := pipe.Exec(ctx)
if err != nil {
panic(err)
}

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

rdb.Pipelined()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//set
cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < 100; i++ {
pipe.Set(ctx, fmt.Sprintf("key%d", i), fmt.Sprintf("key%d", i), 0)
}
return nil
})
if err != nil {
panic(err)
}
/* for _, cmd := range cmds {
fmt.Println(cmd.(*redis.StringCmd).Val())
}*/
//get
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())
}

go-redis 事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//TxPipelineDemo
pipe := rdb.TxPipeline()
incr := pipe.Incr(ctx, "tx_pipeline_counter")
pipe.Expire(ctx, "tx_pipeline_counter", time.Hour)
_, err := pipe.Exec(ctx)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(incr.Val())

//TxPipelined demo TxPipelined 自动执行 Exec
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)
return

等价指令

1
2
3
4
MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

参考


【redis】ZSet的使用
http://example.com/2023/09/13/【redis】ZSet的使用/
作者
Forrest
发布于
2023年9月13日
许可协议