golang常用庫包:緩存redis操作庫go-redis使用(02)-Redis5種基本數據類型操作

前面一篇介紹Redis基本數據結構和其他特性,以及 go-redis 連接到Redis:https://www.cnblogs.com/jiujuan/p/17207166.html

這篇接着講基本數據類型的操作。

四、Redis基本數據類型代碼示例

這裏示例使用 go-redis v8 ,不過 go-redis latest 是 v9

安裝v8:go get github.com/go-redis/redis/v8

我們根據 Redis doc data-type 來進行相應分類操作示例。

也可以看我前面的文章:https://www.cnblogs.com/jiujuan/p/10407955.html,redis 命令分類,僅供參考。

Redis 5 種基本數據類型:

image-20230315030328400

最基本的Set/Get操作

setget.go

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()
	// 設置 key 的值,0 表示永不過期
	err = rdb.Set(ctx, "setkey-1", "value-1", 0).Err()
	if err != nil {
		panic(err)
	}

	// 設置 key 的值的過期時間爲 30 秒
	err = rdb.Set(ctx, "setkey-2", "value-2", time.Second*30).Err()
	if err != nil {
		panic(err)
	}

	// 獲取key的值
	val, err := rdb.Get(ctx, "setkey-1").Result()
	if err == redis.Nil { // 如果返回 redis.Nil 說明key不存在
		fmt.Println("key not exixt")
	} else if err != nil {
		fmt.Println("Get Val error: ", err)
		panic(err)
	}
	fmt.Println("Get Val: ", val)

	val, _ = rdb.Get(ctx, "setkey-2").Result()
	fmt.Println("Get Val setkey-2: ", val)
}

string(s)字符串類型

可以從 redis docs 的 COMMANDS 查詢 string 的所有命令。

String 類型命令文檔:https://redis.io/commands/?group=string,更具體的用法比如詳細語法,參數設置等,點擊每一個命令然後進去查看,進去後 SET 的詳細語法:

SET key value [NX | XX] [GET] [EX seconds | PX milliseconds |
  EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

具體命令用法可以搜索上面 command 然後點進去查看,這裏主要是代碼示例,就不介紹具體命令用法了。

string 的底層是 SDS 數據結構,原理簡析可以看這篇文章:https://www.cnblogs.com/jiujuan/p/15828302.html。

string 常用命令:

1.SET/GET:設置獲取值。

2.SETNX/EXISTS:SETNX設置並指定過期時間僅key不存在,EXISTS 檢查某個 key 是否存在。

3.MSET/MGET:批量設置獲取值。

4.DEL:刪除值。

5.INCR/INCRBY - DECR/DECRBY:INCR 原子加1,INCRBY 加具體數值。DECR/DECRBY 剛好相反。

6.GetRange:字符串截取,返回字符串的總長度。

7.Expire/ExpireAt/TTL:設置值的過期時間端/點。TTL 獲取過期的時間。

8.StrLen:獲取key的值長度

  • 1、SET/GET:設置和獲取值,見最上面SET/GET例子
  • 2、SETNX/EXISTS

SETNX:設置並指定過期時間,僅當 key 不存在時候才設置有效。

EXISTS: 檢查某個 key 是否存在

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()

	// SetNX, 設置並指定過期時間,僅當 key 不存在時候才設置有效
	err = rdb.SetNX(ctx, "setnx-key", "setnx-val", 0).Err()
	if err != nil {
		fmt.Println("setnx value failed: ", err)
		panic(err)
	}

	// 這裏用SetNX設置值,第二次運行後 val2 返回 false,因爲第二次運行時 setnx-key2 已經存在
	val2, err := rdb.SetNX(ctx, "setnx-key2", "setnx-val2", time.Second*20).Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("val2: %v \n", val2)

	// Exists, 檢查某個key是否存在
	n, _ := rdb.Exists(ctx, "setnx-key").Result()
	if n > 0 {
		fmt.Println("n: ", n)
		fmt.Println("set nx key exists")
	} else {
		fmt.Println("set nx key not exists")
	}

	val, _ := rdb.Get(ctx, "setnx-key").Result()
	fmt.Println(val)
}
  • 3、MSET/MGET:批量設置值,批量獲取值
ctx = context.Background()
// MSet 設置值
err = rdb.MSet(ctx, "mset-key1", "mset-val1", "mset-key2", "mset-val2", "mset-key3", "mset-val3").Err()
if err != nil {
    fmt.Println("MSet ERROR : ", err)
}
// MGet 獲取值
vals, err := rdb.MGet(ctx, "mset-key1", "mset-key2", "mset-key3").Result()
if err != nil {
    fmt.Println("MGet ERROR: ", err)
    panic(err)
}
fmt.Println("vals: ", vals)
  • 4、DEL:刪除操作,支持刪除多個 key 的操作
ctx = context.Background()
n, err := rdb.Del(ctx, "setkey", "setnx-key").Result()
if err != nil {
    panic(err)
}
fmt.Println("del nums: ", n)
  • 5、INCR/INCRBY - DECR/DECRBY

INCR:對數字進行原子加 1 操作;INCRBY:加某個數值。

同理 DECR 都是相反操作

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()

	err = rdb.SetNX(ctx, "nums", 2, 0).Err()
	if err != nil {
		panic(err)
	}
	fmt.Println("set nums : ", 2)

	// Incr
	val, err := rdb.Incr(ctx, "nums").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("incr: ", val)

	// IncrBy
	val, err = rdb.IncrBy(ctx, "nums", 10).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("incrby: ", val)

	//Decr
	val, _ = rdb.Decr(ctx, "nums").Result()
	fmt.Println("desc: ", val)

	//DecrBy
	val, _ = rdb.DecrBy(ctx, "nums", 5).Result()
	fmt.Println("decrby: ", val)
}
  • 6、GetRange:字符串截取,返回字符串的總長度
// GetRange,字符串截取操作,返回字符串截取後的值
val, _ = rdb.GetRange(ctx, "setkey-1", 1, 3).Result()
fmt.Println("get range: ", val)

可以放到GET/SET代碼裏去測試

  • 7、Expire/ExpireAt/TTL 設置過期時間

Expire:設置某個時間段後過期。

ExpireAt:設置某個時間點過期。

TTL:獲取剩餘時間

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()

	rdb.Set(ctx, "setkey-expire-1", "value-expire-1", 0).Err()
	rdb.Set(ctx, "setkey-expire-2", "value-expire-2", time.Second*40).Err()

	// Expire, 設置key在某個時間段後過期
	val1, _ := rdb.Expire(ctx, "setkey-expire-1", time.Second*20).Result()
	fmt.Println("expire: ", val1)

	// ExpireAt,設置key在某個時間點後過期
	val2, _ := rdb.ExpireAt(ctx, "setkey-expire-2", time.Now().Add(time.Second*50)).Result()
	fmt.Println("expire at: ", val2)

	// TTL
	expire, err := rdb.TTL(ctx, "setkey-expire-1").Result()
	fmt.Println(expire, err)
}
/**
expire:  true
expire at:  true
20s <nil>
**/
  • 8、StrLen 獲取key的值的長度
// STRLEN,獲取key的值的長度
strlen, _ := rdb.StrLen(ctx, "setkey-1").Result()
fmt.Println("strlen: ", strlen)

list列表類型

list 類型的操作命令文檔:https://redis.io/commands/?group=list

list 列表是一個字符串列表,可以從頭部或尾部插入元素。

image-20230312224413127

list 的源碼簡析可以看這篇文章:https://www.cnblogs.com/jiujuan/p/15839269.html。

list 常用命令:

1.LPUSH:list頭部(左邊)插入值,最後的值在最前面。LPUSHX 僅當列表值存在時才插入值

2.LPOP:移除列表的頭部值並返回這個值

3.RPUSH:list尾部(右邊)插入值。RPUSHX 僅當列表值存在才插入值

4.RPOP:移除列表的尾部值並返回這個值

5.LRANGE:返回key列表指定區間的值

6.BLPOP: 語法 BLPOP key [key ...] timeout,從 key 列表頭部彈出一個值,沒有就阻塞 timeout 秒,如果 timeout=0 則一直阻塞

7.BRPOP:與上面 BLPOP 用法相似,只不過 BRPOP 是從尾部彈出一個值

8.LLEN:返回列表的長度

9.LINSERT:在指定位置插入數據

10.LREM:刪除列表中的數據

11.LINDEX:根據索引查詢列表中的值

12.LSET:根據索引設置列表中的某個值

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()
	// LPUSH 從頭部(左邊)插入數據,最後的值在最前面
	count, err := rdb.LPush(ctx, "listkeyone", "one", "two", "three", "four").Result()
	if err != nil {
		fmt.Println("lpush err:", err)
	}
	fmt.Println("lpush count: ", count)

	// LRANGE 返回列表範圍數據。例子中返回 0 到 -1,就是返回所有數據
	rangeval, err := rdb.LRange(ctx, "listkeyone", 0, -1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("LRange values: ", rangeval)

	// LLen 返回列表數據大小
	len, err := rdb.LLen(ctx, "listkeyone").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("llen: ", len)

	// LInsert 在指定位置插入數據
	err = rdb.LInsert(ctx, "listkeyone", "before", "two", 2).Err()
	if err != nil {
		panic(err)
	}

	vals, _ := rdb.LRange(ctx, "listkeyone", 0, -1).Result()
	fmt.Println("LInsert val: ", vals)

	// RPUSH 在 list 尾部插入值
	count, err := rdb.RPush(ctx, "listkeyone", "six", "five").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("RPush count: ", count)

	// RPOP 刪除list列表尾部(右邊)值
	val, err := rdb.RPop(ctx, "listkeyone").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("rpop val: ", val)
	vals, _ = rdb.LRange(ctx, "listkeyone", 0, -1).Result()
	fmt.Println("(rpop)lrange val: ", vals)

	// LPOP 刪除list列表頭部(左邊)值
	val, err = rdb.LPop(ctx, "listkeyone").Result()
	fmt.Println("rpop val: ", val)

	// LIndex 根據索引查詢值,索引是從0開始
	val1, _ := rdb.LIndex(ctx, "listkeyone", 3).Result()
	fmt.Println("LIndex val: ", val1)

	// LSET 根據索引設置某個值,索引從0開始
	val2, _ := rdb.LSet(ctx, "listkeyone", 3, "han").Result()
	fmt.Println("lset: ", val2)

	// LREM 刪除列表中的數據
	del, err := rdb.LRem(ctx, "listkeyone", 1, 5) // 從列表左邊開始刪除值 5,出現重複元素只刪除一次
	if err != nil {
		panic(err)
	}
	fmt.Println("del : ", del)

	rdb.LRem(ctx, "listkeyone", 2, 5) // 從列表頭部(左邊)開始刪除值 5,如果存在多個值 5,則刪除 2 個 5

	rdb.LRem(ctx, "listkeyone", -3, 6) // 從列表尾部(右邊)開始刪除值 6,如果存在多個值 6, 則刪除 3 個 6

}

hash哈希表類型

hash類型數據操作命令:https://redis.io/commands/?group=hash。

hash類型原理簡析:https://www.cnblogs.com/jiujuan/p/15944061.html。

hash 數據結構圖:

image-20230314172026854

hash 哈希表數據類型常用命令,redisdoc.com 這個地址把Redis分類列出來了,還有詳細解釋:

1.HSET 單個設置值。

2.HGET 單個獲取值。

3.HMSET 批量設置。

4.HMGET 批量獲取值。

5.HGETALL 獲取所有值。

6.HDEL 刪除字段,支持刪除多個字段。

7.HLEN 獲取hash表中key的值數量。

8.HEXISTS 判斷元素是否存在。

9.HINCRBY 根據key的field字段的整數值加減一個數值。

10.HSETNX 如果某個字段不存在則設置該字段值。

更多命令請查看:https://redis.io/commands/?group=hash

代碼示例:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()
	// HSET,根據key設置field字段值
	err = rdb.HSet(ctx, "hashkey", "field-val", "value-one").Err()
	if err != nil {
		panic(err)
	}
	_ = rdb.HSet(ctx, "hashkey", "field1", "value1", "field2", "value2").Err()
	_ = rdb.HSet(ctx, "hashkey", map[string]interface{}{"field3": "value3", "field4": "value4"}).Err()
	_ = rdb.HSet(ctx, "hashkey-two", []string{"field0", "value0", "field1", "value1"}).Err()

	// HSETNX,如果某個字段不存在則設置值
	ok, err := rdb.HSetNX(ctx, "hashkey", "field1", "oneval").Result() // 字段 field1 已存在,所以返回ok值爲false
	if err != nil {
		panic(err)
	}
	fmt.Println("HSetNX bool: ", ok)

	// HGET,根據key和field查詢值
	val, err := rdb.HGet(ctx, "hashkey", "field-val").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("HGet: ", val)

	val, _ = rdb.HGet(ctx, "hashkey-two", "field0").Result()
	fmt.Println("HGet hashkey-two: ", val)

	// HGETALL,獲取key的所有field-val值
	fieldvals, err := rdb.HGetAll(ctx, "hashkey").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("HGetAll: ", fieldvals) // 返回 map 類型

	// HMSET,根據hash key設置多個字段值,與上面的 HSet 設置多個值很像
	fieldvalues := make(map[string]interface{})
	fieldvalues["age"] = 23
	fieldvalues["firstname"] = "Chare"
	fieldvalues["lastname"] = "Jimmy"
	err = rdb.HMSet(ctx, "hmsetkey", fieldvalues).Err()
	if err != nil {
		panic(err)
	}
	/*//也可以像上面HSet直接設置map值

	rdb.HMSet(ctx, "hmsetkey", map[string]interface{}{"age":23,"firstname":"Chare","LastName":"Jimmy"}).Err()
	*/

	// HMGET, 根據hash key和多個字段獲取值
	vals, err := rdb.HMGet(ctx, "hmsetkey", "age", "lastname").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("HMGET vals: ", vals)

	// HEXISTX,某個hashkey中字段field否存在
	ok, _ = rdb.HExists(ctx, "hmsetkey", "lastname").Result()
	fmt.Println("HExists: ", ok) // HExists: true

	// HLen,獲取hashkey的字段多少
	len, _ := rdb.HLen(ctx, "hashkey").Result()
	fmt.Println("HLen hashkey: ", len) // HLen hashkey: 5

	// HIncrBy,根據key的field字段的整數值加減一個數值
	age, err := rdb.HIncrBy(ctx, "hmsetkey", "age", -3).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("HIncrBy : ", age) // HIncrBy :  20

	// HDel,刪除字段,支持刪除多個字段
	rdb.HSet(ctx, "hashkeydel", map[string]interface{}{"field10": "value10", "field11": "value11", "field12": "value12", "field13": "value13"}).Err()
	rdb.HDel(ctx, "hashkeydel", "field10", "field12") //刪除多個字段

	delvals, err := rdb.HGetAll(ctx, "hashkeydel").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("HGetAll hashkeydel: ", delvals)

}

/**
HSetNX bool:  false
HGet:  value-one
HGet hashkey-two:  value0
HGetAll:  map[field-val:value-one field1:value1 field2:value2 field3:value3 field4:value4]
HMGET vals:  [23 Jimmy]
HExists:  true
HLen hashkey:  5
HIncrBy :  20
HGetAll hashkeydel:  map[field11:value11 field13:value13]
 * */

set集合類型

Redis 中的 set 集合類型是一個無序的唯一值集合,也就是說一個set集合中的值唯一,它的存儲順序不會按照插入順序進行存儲。

set與list的區別:

  1. list 可以存儲重複的元素,而 set 不能
  2. list 可以按照元素先後順序存儲,而 set 不能

image-20230314184953189

set 集合操作命令文檔:

set 常用命令:

  1. SADD:SADD key member [member...],將一個或多個元素數據添加到集合 key 中
  2. SISMEMBER: SISMEMBER key member,判斷 member 是否是集合 key 的成員
  3. SMEMBERS: SMEMBERS key,獲取 key 中的所有元素數據
  4. SREM:SREM key member [member ...],刪除 key 中的一個或多個數據
  5. SPOP:SPOP key,隨機移除集合中的一個數據並返回
  6. SCARD:SCARD key,獲取集合 key 中元素的數量
  7. SDIFF:SDIFF key [key...],計算多個集合的差集
  8. SUNION:SUNION key [key...],計算多個集合的並集
  9. SINTER:SINTER key [key...],計算多個集合的交集

代碼例子:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()
	// SADD,將一個或多個元素數據添加到集合中
	err = rdb.SAdd(ctx, "setkey:1", 20, "dog").Err()
	if err != nil {
		panic(err)
	}
	rdb.SAdd(ctx, "setkey:1", []string{"hanmeimei", "lilei", "tom", "dog", "one"}) // 切片增加數據,dog只有一個數據
	rdb.SAdd(ctx, "setkey:2", []string{"jimmy", "pig", "dog", "lilei"})

	// SMEMBERS,獲取集合中的所有元素數據
	smembers, err := rdb.SMembers(ctx, "setkey:1").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("SMembers,setkey:1: ", smembers)

	// SCARD,獲取集合中的元素數量
	scards, err := rdb.SCard(ctx, "setkey:2").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("SCard,setkey:2: ", scards)

	// SPOP,隨機移除一個數據並返回這個數據
	rdb.SAdd(ctx, "setkey:3", []string{"one", "two", "three", "four", "six"})
	spop, _ := rdb.SPop(ctx, "setkey:3").Result()
	res, _ := rdb.SMembers(ctx, "setkey:3").Result()
	fmt.Println("spop: ", spop, ", SMembers: ", res)
	// SPOPN,隨機移除多個元素並返回
	spopn, _ := rdb.SPopN(ctx, "setkey:3", 2).Result()
	res, _ = rdb.SMembers(ctx, "setkey:3").Result()
	fmt.Println("spopn: ", spopn, ", SMembers: ", res)

	// SISMEMBER,判斷元素是否在集合中
	ok, err := rdb.SIsMember(ctx, "setkey:3", "two").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("SIsMember, two : ", ok)

	// SDIFF,差集,SDIFF key1,key2 與 SDIFF key1,key2 差集是不同,看下面的例子
	diff, _ := rdb.SDiff(ctx, "setkey:1", "setkey:2").Result()
	fmt.Println("sdiff: ", diff)
	diff2, _ := rdb.SDiff(ctx, "setkey:2", "setkey:1").Result()
	fmt.Println("sdiff2: ", diff2)
	// SUNION,並集
	union, _ := rdb.SUnion(ctx, "setkey:1", "setkey:2").Result()
	fmt.Println("union: ", union)
	// SINTER,交集
	inter, _ := rdb.SInter(ctx, "setkey:1", "setkey:2").Result()
	fmt.Println("inter: ", inter)

	// SREM , 刪除值,返回刪除元素個數
	rdb.SAdd(ctx, "setkey:4", []string{"one", "two", "three"})
	count, err := rdb.SRem(ctx, "setkey:4", "one", "three").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("SRem: ", count)
}

/*
SMembers,setkey:1:  [20 hanmeimei one lilei tom dog]
SCard,setkey:2:  4
spop:  six , SMembers:  [four three one two]
spopn:  [one two] , SMembers:  [four three]
SIsMember, two :  false
sdiff:  [tom 20 hanmeimei one]
sdiff2:  [jimmy pig]
union:  [hanmeimei one jimmy lilei tom dog 20 pig]
inter:  [lilei dog]
SRem:  2
*/

zset有序集合類型

zset 是一個不重複值的有序集合,與 set 集合一樣值是不能重複。

zset 排序集是按照相關分數排序的唯一字符串(成員)的集合。它的底層數據結構是由壓縮列表或跳錶(skiplist)實現的。

如果有序集合元素個數小於 128 且每個元素值小於 64 字節,redis 會使用 壓縮列表作爲 zset 類型的底層數據結構;如果不滿足上面的條件,那麼就用跳錶作爲底層數據結構。

跳錶的原理簡析:https://www.cnblogs.com/jiujuan/p/12884824.html

zset 有序集合命令列表:

zset有序集合圖:

image-20230314204659885

zset 有序集合常用命令:

  1. ZADD:ZADD key score member [[score member] [score member] …],將一個或多個member元素以及score加入到有序集合key中
  2. ZSCORE:ZSCORE key member,返回集合 key 中 member 成員的分數
  3. ZRANGE:ZRANGE key start stop [WITHSCORES],返回集合 key 中指定區間的元素,score 從小到大排序,start 和 stop 都是 0 開始。
  4. ZREVRANGE:ZREVRANGE key start stop [WITHSCORES],與 zrange 相反,返回集合 key 中指定區間元素,score 從大到小排序。
  5. ZRANGEBYSCORE:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count],返回結果的數量區間,score 從小到大排序,LIMIT 參數指定返回結果集的數量和區間,後面可選的 [limit offset count] 像 SQL 中的 select ... limit offset,count。
  6. ZREVRANGEBYSCORE:與上面 ZRANGEBYSCORE 幾乎相同,不同是 score 是從大到小排序
  7. ZREVRANGEBYSCOREWITHSCORES:和 ZRANGEBYSCORE 一樣,區別是它不僅返回集合元素,也返回元素對應分數
  8. ZREM:刪除元素
  9. ZREMRANGEBYRank:根據索引範圍刪除
  10. ZREMRANGEBYSCORE:根據分數區間刪除

還有 ZCOUNT 獲取區間內元素個數;ZCARD 獲取元素個數;ZINCRBY 增加元素分數;ZRANK 根據元素查詢在集合中的排名,分數從小到大排序查詢。

代碼示例:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	rdb := redis.NewClient(&redis.Options{
		Addr:        "localhost:6379",
		Password:    "",
		DB:          0,
		IdleTimeout: 350,
		PoolSize:    50, // 連接池連接數量
	})
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	_, err := rdb.Ping(ctx).Result() // 檢查連接redis是否成功
	if err != nil {
		fmt.Println("Connect Failed: %v \n", err)
		panic(err)
	}

	ctx = context.Background()
	// ZADD,添加一個或多個數據到集合中
	//* 添加一個*/
	n, err := rdb.ZAdd(ctx, "zsetkey", &redis.Z{23.0, "tom"}).Result()
	/* 或把字段寫上
	member := &redis.Z{
		Score:  23.0,
		Member: "tom",
	}

	n, err := rdb.ZAdd(ctx, "zsetkey", member).Result()
	if err != nil {
		panic(err)
	}
	*/
	fmt.Println("zadd: ", n)
	val, _ := rdb.ZRange(ctx, "zsetkey", 0, -1).Result()
	fmt.Println("ZRange, zsetkey: ", val)

	//* ZADD批量增加*/
	fruits_price_z := []*redis.Z{
		&redis.Z{Score: 5.0, Member: "apple"},
		&redis.Z{Score: 3.5, Member: "orange"},
		&redis.Z{Score: 6.0, Member: "banana"},
		&redis.Z{Score: 9.1, Member: "peach"},
		&redis.Z{Score: 19.0, Member: "cherry"},
	}
	num, err := rdb.ZAdd(ctx, "fruits_price", fruits_price_z...).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("zadd : ", num)

	// ZRANGE,索引範圍返回元素,分數從小到大, 0 到 -1 就是所有元素
	vals, err := rdb.ZRange(ctx, "fruits_price", 0, -1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("ZRange,fruits_price: ", vals)

	// ZREVRANGE,分數從大到小
	vals, err = rdb.ZRevRange(ctx, "fruits_price", 0, -1).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("ZRevRange,fruits_price: ", vals)

	// ZRANGEBYSCORE , offset 和 count 可用於分頁
	rangbyscore := &redis.ZRangeBy{
		Min:    "3", // 最小分數
		Max:    "7", // 最大分數
		Offset: 0,   // 開始偏移量
		Count:  4,   // 一次返回多少數據
	}
	vals, err = rdb.ZRangeByScore(ctx, "fruits_price", rangbyscore).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("ZRangeByScore: ", vals)

	// ZCOUNT ,統計某個分數內的元素個數
	count, _ := rdb.ZCount(ctx, "fruits_price", "3", "7").Result()
	fmt.Println("ZCount: ", count)

	// ZREVRANGEBYSCOREWITHSCORES, 和 ZRANGEBYSCORE 一樣,區別是它不僅返回集合元素,也返回元素對應分數
	rangbyscorewithscores := &redis.ZRangeBy{
		Min:    "3", // 最小分數
		Max:    "7", // 最大分數
		Offset: 0,   // 開始偏移量
		Count:  4,   // 一次返回多少數據
	}
	keyvals, err := rdb.ZRangeByScoreWithScores(ctx, "fruits_price", rangbyscorewithscores).Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("ZRangeByScoreWithScores: ", keyvals)

	// ZCRORE, 查詢集合中元素的分數
	score, _ := rdb.ZScore(ctx, "fruits_price", "peach").Result()
	fmt.Println("ZScore: ", score)

	// ZRANK 根據元素查詢在集合中的排名,分數從小到大排序查詢
	rank, _ := rdb.ZRank(ctx, "fruits_price", "peach").Result()
	fmt.Println("ZRank: ", rank)

	// ZREM,根據Member刪除值,一次可以刪除一個或多個
	age_z := []*redis.Z{
		&redis.Z{Score: 20, Member: "tom"},
		&redis.Z{Score: 34, Member: "jim"},
		&redis.Z{Score: 23, Member: "lilei"},
		&redis.Z{Score: 43, Member: "hanxu"},
		&redis.Z{Score: 30, Member: "jimmy"},
		&redis.Z{Score: 55, Member: "MA"},
		&redis.Z{Score: 50, Member: "MB"},
		&redis.Z{Score: 52, Member: "MC"},
		&redis.Z{Score: 54, Member: "MD"},
		&redis.Z{Score: 59, Member: "ME"},
		&redis.Z{Score: 70, Member: "MF"},
		&redis.Z{Score: 75, Member: "MG"},
	}
	rdb.ZAdd(ctx, "people_age", age_z...).Err()

	rdb.ZRem(ctx, "people_age", "jim").Err() // 刪除一個
	// rdb.ZRem(ctx, "people_age", "jim", "jimmy").Err() // 刪除多個
	agevals, _ := rdb.ZRange(ctx, "people_age", 0, -1).Result()
	fmt.Println("ZRem, ZRange age: ", agevals)

	//ZREMRANGEBYSCORE, 根據分數區間刪除
	// rdb.ZRemRangeByScore("people_age", "20", "30").Err()  // 刪除 20<=分數<=30
	rdb.ZRemRangeByScore(ctx, "people_age", "20", "(30").Err() // 刪除 20<=分數<30

	agevals, _ = rdb.ZRange(ctx, "people_age", 0, -1).Result()
	fmt.Println("ZRemRangeByScore, ZRange age: ", agevals)

	// ZREMRANGEBYRANK,根據分數排名刪除
	// 從低分到高分進行排序,然後按照索引刪除
	rdb.ZRemRangeByRank(ctx, "people_age", 6, 7) // 低分到高分排序,刪除第6個元素到第7個元素
	agevals, _ = rdb.ZRange(ctx, "people_age", 0, -1).Result()
	fmt.Println("ZRemRangeByRank, ZRange age: ", agevals)
	// 如果寫成負數,那麼從高分開始刪除
	// rdb.ZRemRangeByRank(ctx, "people_age", -6, -7)

	// ZIncrBy, 增加分數
	rdb.ZIncrBy(ctx, "people_age", 12, "MG").Err()
	score, _ = rdb.ZScore(ctx, "people_age", "MG").Result()
	fmt.Println("ZScore: ", score)
}

/*
zadd:  0
ZRange, zsetkey:  [tom]
zadd :  0
ZRange,fruits_price:  [orange apple banana peach cherry]
ZRevRange,fruits_price:  [cherry peach banana apple orange]
ZRangeByScore:  [orange apple banana]
ZCount:  3
ZRangeByScoreWithScores:  [{3.5 orange} {5 apple} {6 banana}]
ZScore:  9.1
ZRank:  3
ZRem, ZRange age:  [tom lilei jimmy hanxu MB MC MD MA ME MF MG]
ZRemRangeByScore, ZRange age:  [jimmy hanxu MB MC MD MA ME MF MG]
ZRemRangeByRank, ZRange age:  [jimmy hanxu MB MC MD MA MG]
ZScore:  87
*/

五、參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章