1财破、redigo客戶端的返回值解析
從上一節(jié)的內(nèi)容可知溢吻,Do() 和 Receive() 等方法的返回值,除了 error 外怔毛,是一個(gè) interface{} 類型的返回值员萍,因此當(dāng)我們的復(fù)雜操作返回的不是基本數(shù)據(jù)類型時(shí),就需要我們自己解析返回值拣度,例如碎绎,當(dāng)我們利用 HMGET 方法獲取一批返回值時(shí)螃壤,就需要對返回結(jié)果進(jìn)行解析,具體如下:
var param = map[string][]int64{
"153_35_2": []int64{10, 20, 30},
"153_35_1": []int64{1, 2, 3},
"153_35_3": []int64{100, 200, 300},
}
_, err := rc.Do("HMSET", redis.Args{}.Add("redis-hash").AddFlat(param)...)
if err != nil {
fmt.Println("cacheModelInfo error:", err.Error())
return
}
fmt.Println("insert ok")
reply, _ := rc.Do("HGETALL", "redis-hash")
for i,v := range reply.([]interface{}){
fmt.Println(string(v.([]uint8)))
}
---------------------------------------------------
返回結(jié)果:
insert ok
153_35_2
[10 20 30]
153_35_1
[1 2 3]
153_35_3
[100 200 300]
由于返回值是多條數(shù)據(jù)筋帖,因此需要先將 reply 轉(zhuǎn)成 []interface 類型奸晴,然后在遍歷結(jié)果時(shí)在分別轉(zhuǎn)成 []uint8 (byte數(shù)組), 最后再轉(zhuǎn)成 string 類型日麸。
寫入時(shí) vaule 為 []int64寄啼,為什么讀出來是 string 類型呢?
這是因?yàn)?Redis hash 是一個(gè) string 類型的 field(字段) 和 value(值) 的映射表代箭,hash 特別適合用于存儲對象墩划。
Redis 中每個(gè) hash 可以存儲 232 - 1 鍵值對(40多億)。
隨著我們操作復(fù)雜度嗡综,數(shù)據(jù)解析的工作量也會非常大乙帮,(lua 腳本的使用,會使結(jié)果的解析更為復(fù)雜极景,因?yàn)榭赡艽嬖诙喾N類型的結(jié)果一起返回的情況察净,lua 腳本相關(guān)的內(nèi)容會在下一節(jié)介紹)。
redigo 包中的返回值助手函數(shù)的存在盼樟,就是為了幫助我們完成這些枯燥繁瑣的數(shù)據(jù)解析過程氢卡。
2、redigo 返回值助手函數(shù)
返回值助手函數(shù)相關(guān)源碼路徑為 github.com/gomodule/redigo/redis/reply.go
提供的主要方法如下:
方法 | 說明 |
---|---|
func Int(reply interface{}, err error) (int, error) | 將返回值轉(zhuǎn)為 int 類型數(shù)據(jù) |
Int64(reply interface{}, err error) (int64, error) | 將返回值轉(zhuǎn)為 int64 類型數(shù)據(jù) |
Uint64(reply interface{}, err error) (uint64, error) | 將返回值轉(zhuǎn)為 uint64 類型數(shù)據(jù) |
func Float64(reply interface{}, err error) (float64, error) | 將返回值轉(zhuǎn)為 float64 類型數(shù)據(jù) |
func String(reply interface{}, err error) (string, error) | 將返回值轉(zhuǎn)為 string 類型數(shù)據(jù) |
func Bytes(reply interface{}, err error) ([]byte, error) | 將返回值轉(zhuǎn)為 []byte 類型數(shù)據(jù),通常用于解析某些編碼數(shù)據(jù)恤批,例如protobuf 或者 json等 |
Bool(reply interface{}, err error) (bool, error) | 將返回值轉(zhuǎn)為 bool 類型數(shù)據(jù) |
func Values(reply interface{}, err error) ([]interface{}, error) | 將返回值轉(zhuǎn)為 []interface 類型數(shù)據(jù),一般用于分解復(fù)雜結(jié)構(gòu)异吻,例如 lua 腳本的聚合結(jié)果 |
func Float64s(reply interface{}, err error) ([]float64, error) | 將返回值轉(zhuǎn)為 []float 類型數(shù)據(jù) |
func Strings(reply interface{}, err error) ([]string, error) | 將返回值轉(zhuǎn)為 []string 類型數(shù)據(jù) |
func ByteSlices(reply interface{}, err error) ([][]byte, error) | 將返回值轉(zhuǎn)為 [][]byte 類型數(shù)據(jù),通常用于解析某些編碼數(shù)據(jù)數(shù)組,例如protobuf 或者 json等 |
func Int64s(reply interface{}, err error) ([]int64, error) | 將返回值轉(zhuǎn)為 []int64 類型數(shù)據(jù) |
func Ints(reply interface{}, err error) ([]int, error) | 將返回值轉(zhuǎn)為 []int 類型數(shù)據(jù) |
func StringMap(result interface{}, err error) (map[string]string, error) | 將返回值轉(zhuǎn)為 map[string]string 類型數(shù)據(jù),通常用于 HMGET喜庞、MGET、HGETALL 等場景 |
func IntMap(result interface{}, err error) (map[string]int, error) | 將返回值轉(zhuǎn)為 map[string]int 類型數(shù)據(jù)棋返,通常用于value 類型為 int 的 HMGET延都、MGET、HGETALL |
func Int64Map(result interface{}, err error) (map[string]int64, error) | 將返回值轉(zhuǎn)為 map[string]int64 類型數(shù)據(jù) 通常用于value 類型為 int64 的 HMGET睛竣、MGET晰房、HGETALL |
func Uint64s(reply interface{}, err error) ([]uint64, error) | 將返回值轉(zhuǎn)為 []uint64 類型數(shù)據(jù) |
func Uint64Map(result interface{}, err error) (map[string]uint64, error) | 將返回值轉(zhuǎn)為 map[string]uint64 類型數(shù)據(jù) |
func SlowLogs(result interface{}, err error) ([]SlowLog, error) | 將返回值轉(zhuǎn)為 []SlowLog 類型數(shù)據(jù),主要用于獲取慢查詢?nèi)罩?/td> |
func Positions(result interface{}, err error) ([]*[2]float64, error) | 將返回值轉(zhuǎn)為 []*[2]float64 類型數(shù)據(jù)射沟,主要用于 GEOPOS 命令的返回 |
上述返回值助手函數(shù)的具體使用殊者,應(yīng)該依據(jù)具體的命令進(jìn)行選擇。如果大家還記得上一節(jié)介紹的 Redis 基本數(shù)據(jù)類型验夯,可能會有些疑問猖吴,對于 redis 來說,其數(shù)據(jù)據(jù)存儲本質(zhì)都是 []bytes挥转, 為什么可以解析出 Int海蔽、int64共屈、float等類型的數(shù)據(jù)呢?
我們以 Float64() 為例進(jìn)行說明党窜,具體源碼如下:
func Float64(reply interface{}, err error) (float64, error) {
if err != nil {
return 0, err
}
switch reply := reply.(type) {
case []byte:
n, err := strconv.ParseFloat(string(reply), 64)
return n, err
case nil:
return 0, ErrNil
case Error:
return 0, reply
}
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
}
其實(shí)拗引,返回值助手函數(shù)是將 []byte 類型的原始數(shù)據(jù),利用 strconv.ParseFloat(string(reply), 64)
轉(zhuǎn)換成了 float64類型幌衣,因此在我們使用過程中返回值助手函數(shù)的選擇矾削,應(yīng)該基于業(yè)務(wù)和實(shí)際存儲的數(shù)據(jù)格式為依據(jù)。我們以第一小節(jié)的示例為例豁护,看返回值助手函數(shù)如何降低我們的工作量怔软,具體如下:
reply,_ := redis.StringMap(rc.Do("HGETALL", "redis-hash"))
for k,v := range reply{
fmt.Println(k,":",v)
}
---------------------------------------------------------------
153_35_2 : [10 20 30]
153_35_1 : [1 2 3]
153_35_3 : [100 200 300]
reply,_ := redis.Strings(rc.Do("HGETALL", "redis-hash"))
for _,v := range reply{
fmt.Println(v)
}
---------------------------------------------------------------
153_35_2
[10 20 30]
153_35_1
[1 2 3]
153_35_3
[100 200 300]
3、Scan 方法解析復(fù)雜結(jié)構(gòu)
除了使用返回值助手函數(shù)對上述固定結(jié)構(gòu)的結(jié)果進(jìn)行解析外择镇,redigo 包還提供了一個(gè) Scan()函數(shù)用于解析自定義的復(fù)雜數(shù)據(jù)結(jié)構(gòu)挡逼,我們依然以上一個(gè)示例進(jìn)行說明,具體示例如下:
rc := RedisClientPool.Get()
defer func() {
rcErr := rc.Close()
if rcErr != nil {
fmt.Printf("Cache init redis client close err: %s", rcErr.Error())
}
}()
var param = map[string][]int64{
"153_35_2": []int64{10, 20, 30},
"153_35_1": []int64{1, 2, 3},
"153_35_3": []int64{100, 200, 300},
}
_, err := rc.Do("HMSET", redis.Args{}.Add("redis-hash").AddFlat(param)...)
if err != nil {
fmt.Println("cacheModelInfo error:", err.Error())
return
}
fmt.Println("insert ok")
reply,_ := redis.Values(rc.Do("HGETALL", "redis-hash"))
fmt.Println(reply)
for len(reply) > 0 {
var key string
var value string
reply, err = redis.Scan(reply, &key, &value)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(key,":",value)
}
--------------------------------------------------------------------------------
Output:
153_35_2 : [10 20 30]
153_35_1 : [1 2 3]
153_35_3 : [100 200 300]
Scan 函數(shù)從 src 掃描副本到 dest 指向的值。
dest 指向的值必須是整數(shù)腻豌、浮點(diǎn)數(shù)家坎、布爾值、字符串吝梅、[]byte虱疏、interface{} 或這些類型的切片。掃描使用標(biāo)準(zhǔn)的 strconv 包將字符串轉(zhuǎn)換為數(shù)字和布爾類型苏携。
如果 dest 值為 nil做瞪,則跳過相應(yīng)的 src 值。
如果 src 元素為 nil右冻,則不修改對應(yīng)的 dest 值装蓬。
為了在循環(huán)中輕松使用 Scan,Scan 返回的結(jié)果為 src 切片掃描之后剩余的部分纱扭。
如果返回結(jié)果為結(jié)構(gòu)化切片牍帚,也可以使用 canSlice() 方法,從而簡化 loop 處理的部分乳蛾,具體示例如下:
var result []struct {
Key string
Value string
}
if err := redis.ScanSlice(reply, &result); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v\n", result)
---------------------------------------------------------
Output:
[
{153_35_2 [10 20 30]}
{153_35_1 [1 2 3]}
{153_35_3 [100 200 300]}
]
通過上述的示例暗赶,我們介紹了 scan 函數(shù)的基本用法,但是細(xì)心的同學(xué)可能會發(fā)現(xiàn)嗎肃叶,為什么數(shù)據(jù)寫入時(shí)蹂随,value 的類型為 []int64 但是讀取時(shí)只能按照 string 類型讀取呢。這是因?yàn)?Redis 底層存儲的數(shù)據(jù)本質(zhì)都是 string 類型因惭,岳锁。無論是 HMSET 還是 MSET 最終都只能按照 string 類型讀取,因?yàn)槠浔举|(zhì)都是 hash 結(jié)構(gòu)筛欢,不同之處僅在于 HMSET 是嵌套的 hash類型浸锨。 因此唇聘,[]int64 數(shù)據(jù)在寫入階段,就已經(jīng)被自動處理為 []byte柱搜,寫入 redis 之后迟郎,len 和 類型 屬性會丟失。
如果強(qiáng)行按照 []int64解析將出錯(cuò):
var result []struct {
Key string
Value []int64
}
if err := redis.ScanSlice(reply, &result); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v\n", result)
-----------------------------------------------------------
redigo.ScanSlice: cannot assign element 1 to field Value: cannot convert from Redis bulk string to []int64
如果 value 必須以結(jié)構(gòu)化的數(shù)據(jù)存儲聪蘸,那么可以提前對要寫入的數(shù)據(jù)進(jìn)行編碼宪肖,例如 json、protobuf 等健爬,取出后再進(jìn)行解碼獲得原始數(shù)據(jù)控乾。