golang-redis系列——返回值助手函數(shù)(二)

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多億)。

Redis Hash

隨著我們操作復(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ù)控乾。

4、相關(guān)文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娜遵,一起剝皮案震驚了整個(gè)濱河市蜕衡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌设拟,老刑警劉巖慨仿,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纳胧,居然都是意外死亡镰吆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門跑慕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來万皿,“玉大人,你說我怎么就攤上這事核行±喂瑁” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵钮科,是天一觀的道長唤衫。 經(jīng)常有香客問我,道長绵脯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任休里,我火速辦了婚禮蛆挫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妙黍。我一直安慰自己悴侵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布拭嫁。 她就那樣靜靜地躺著可免,像睡著了一般抓于。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浇借,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天捉撮,我揣著相機(jī)與錄音,去河邊找鬼妇垢。 笑死巾遭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闯估。 我是一名探鬼主播灼舍,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涨薪!你這毒婦竟也來了骑素?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤刚夺,失蹤者是張志新(化名)和其女友劉穎献丑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體光督,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阳距,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了结借。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筐摘。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖船老,靈堂內(nèi)的尸體忽然破棺而出咖熟,到底是詐尸還是另有隱情,我是刑警寧澤柳畔,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布馍管,位于F島的核電站,受9級特大地震影響薪韩,放射性物質(zhì)發(fā)生泄漏确沸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一俘陷、第九天 我趴在偏房一處隱蔽的房頂上張望罗捎。 院中可真熱鬧,春花似錦拉盾、人聲如沸桨菜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倒得。三九已至泻红,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霞掺,已是汗流浹背谊路。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留根悼,地道東北人凶异。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像挤巡,于是被迫代替她去往敵國和親剩彬。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容