91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

redis實踐及思考

發布時間:2020-08-17 11:50:30 來源:ITPUB博客 閱讀:128 作者:騰訊技術工程 欄目:數據庫

導語:當面臨存儲選型時是選擇關系型還是非關系型數據庫 如果選擇了非關系型的redis,redis常用數據類型占用內存大小如何估算的? redis的性能瓶頸又在哪里?

背景
前段時間接手了一個業務,響應時間達到 10s左右 閱讀源碼后發現,每一次請求都是查詢多個分表數據(task1,task2….),然后再join其他表(course,teacher..), 時間全部花在了大量磁盤I/O上 腦袋一拍,重構,上redis!
為什么選擇redis
拍腦袋做技術方案肯定是不行的,得用數據和邏輯說服別人才可以。

時延

時延=后端發起請求db(用戶態拷貝請求到內核態)+ 網絡時延 + 數據庫尋址和讀取
如果想要降低時延,只能減少請求數(合并多個后端請求)和減少數據庫尋址和讀取得時間。 從降低時延的角度,基于 單線程和內存的redis,每秒10萬次得讀寫性能肯定遠遠勝過磁盤讀寫性能。

數據規模

以redis一組K-V為例(”hello” -> “world”),一個簡單的set命令最終會產生4個消耗內存的結構。

redis實踐及思考

關于Redis數據存儲的細節,又要涉及到內存分配器(如jemalloc),簡單說就是存儲170字節,其實內存分配器會分配192字節存儲。

redis實踐及思考

那么總的花費就是

  • 一個dictEntry,24字節,jemalloc會分配32字節的內存塊

  • 一個redisObject,16字節,jemalloc會分配16字節的內存塊

  • 一個key,5字節,所以SDS(key)需要5+9=14個字節,jemalloc會分配16字節的內存塊

  • 一個value,5字節,所以SDS(value)需要5+9=14個字節,jemalloc會分配16字節的內存塊

綜上,一個dictEntry需要32+16+16+16=80個字節。

上面這個算法只是舉個例子,想要更深入計算出redis所有數據結構的內存大小,可以參考 這篇文章
筆者使用的是哈希結構,這個業務需求大概一年的數據量是200MB,從使用redis成本上考慮沒有問題。

需求特點

筆者這個需求背景讀多寫少,冷數據占比比較大,但數據結構又很復雜(涉及多個維度數據總和),因此只要啟動定時任務離線增量寫入redis,請求到達時直接讀取redis中的數據,無疑可以減少響應時間。

redis實踐及思考
[ 最終方案 ]
redis瓶頸和優化

HGETALL

最終存儲到redis中的數據結構如下圖。

redis實踐及思考

采用同步的方式對三個月(90天)進行HGETALL操作,每一天花費30ms,90次就是2700ms! redis操作讀取應該是ns級別的,怎么會這么慢? 利用多核cpu計算會不會更快?

redis實踐及思考
常識告訴我,redis指令執行速度 >> 網絡通信(內網) > read/write等系統調用。 因此這里其實是I/O密集型場景,就算利用多核cpu,也解決不到根本的問題,最終影響redis性能, **其實是網卡收發數據 用戶態內核態數據拷貝 **

pipeline

這個需求qps很小,所以網卡也不是瓶頸了,想要把需求優化到1s以內,減少I/O的次數是關鍵。 換句話說, 充分利用帶寬,增大系統吞吐量。

于是我把代碼改了一版,原來是90次I/O,現在通過redis pipeline操作,一次請求半個月,那么3個月就是6次I/O。 很開心,時間一下子少了1000ms。

redis實踐及思考
redis實踐及思考

pipeline攜帶的命令數

代碼寫到這里,我不經反問自己,為什么一次pipeline攜帶15個HGETALL命令,不是30個,不是40個? 換句話說,一次pipeline攜帶多少個HGETALL命令才會發起一次I/O?

我使用是golang的 redisgo  的客戶端,翻閱源碼發現,redisgo執行pipeline邏輯是 把命令和參數寫到golang原生的bufio中,如果超過bufio默認最大值(4096字節),就發起一次I/O,flush到內核態。

redis實踐及思考

redisgo編碼pipeline規則 如下圖, *表示后面參數加命令的個數,$表示后面的字符長度 ,一條HGEALL命令實際占45字節。

那其實90天數據,一次I/O就可以搞定了(90 * 45 < 4096字節)!

redis實踐及思考

果然,又快了1000ms,耗費時間達到了1秒以內

redis實踐及思考

對吞吐量和qps的取舍

筆者需求任務算是完成了,可是再進一步思考,redis的pipeline一次性帶上多少HGETALL操作的key才是合理的呢? 換句話說,服務器吞吐量大了,可能就會導致qps急劇下降(網卡大量收發數據和redis內部協議解析,redis命令排隊堆積,從而導致的緩慢),而想要qps高,服務器吞吐量可能就要降下來,無法很好的利用帶寬。
對兩者之間的取舍,同樣是不能拍腦袋決定的,用壓測數據說話!

簡單寫了一個壓測程序,通過比較請求量和qps的關系,來看一下吞吐量和qps的變化,從而選擇一個適合業務需求的值。

package main
import (
    "crypto/rand"
    "fmt"
    "math/big"
    "strconv"
    "time"
    "github.com/garyburd/redigo/redis"
)
const redisKey = "redis_test_key:%s"
func main() {
    for i := 1; i < 10000; i++ {
        testRedisHGETALL(getPreKeyAndLoopTime(i))
    }
}
func testRedisHGETALL(keyList [][]string) {
    Conn, err := redis.Dial("tcp", "127.0.0.1:6379")
    if err != nil {
        fmt.Println(err)
        return
    }
    costTime := int64(0)
    start := time.Now().Unix()
    for _, keys := range keyList {
        for _, key := range keys {
            Conn.Send("HGETALL", fmt.Sprintf(redisKey, key))
        }
        Conn.Flush()
    }
    end := time.Now().Unix()
    costTime = end - start
    fmt.Printf("cost_time=[%+v]ms,qps=[%+v],keyLen=[%+v],totalBytes=[%+v]",
        1000*int64(len(keyList))/costTime, costTime/int64(len(keyList)), len(keyList), len(keyList)*len(keyList[0])*len(redisKey))
}
//根據key的長度,設置不同的循環次數,平均計算,取除網絡延遲帶來的影響
func getPreKeyAndLoopTime(keyLen int) [][]string {
    loopTime := 1000
    if keyLen < 10 {
        loopTime *= 100
    } else if keyLen < 100 {
        loopTime *= 50
    } else if keyLen < 500 {
        loopTime *= 10
    } else if keyLen < 1000 {
        loopTime *= 5
    }
    return generateKeys(keyLen, loopTime)
}
func generateKeys(keyLen, looTime int) [][]string {
    keyList := make([][]string, 0)
    for i := 0; i < looTime; i++ {
        keys := make([]string, 0)
        for i := 0; i < keyLen; i++ {
            result, _ := rand.Int(rand.Reader, big.NewInt(100))
            keys = append(keys, strconv.FormatInt(result.Int64(), 10))
        }
        keyList = append(keyList, keys)
    }
    return keyList
}
windows上單機版redis結果如下:
redis實踐及思考

擴展 (分布式方案下pipeline操作)
需求最終是完成了,可是轉念一想,現在都是集群版的redis,pipeline批量請求的key可能分布在不同的機器上,但pipeline請求最終可能只被一臺redis server處理,那不就是會讀取數據失敗嗎? 于是,筆者查找幾個通用的redis 分布式方案,看看他們是如何處理這pipeline問題的。

redis cluster

redis cluster 是官方給出的分布式方案。 Redis Cluster在設計中沒有使用一致性哈希,而是使用數據分片(Sharding)引入哈希槽(hash slot)來實現。 一個 Redis Cluster包含16384(0~16383)個哈希槽,存儲在Redis Cluster中的所有鍵都會被映射到這些slot中,集群中的每個鍵都屬于這16384個哈希槽中的一個,集群使用公式slot=CRC16 key/16384來計算key屬于哪個槽。 比如redis cluster有5個節點,每個節點就負責一部分哈希槽, 如果參數的多個key在不同的slot,在不同的主機上,那么必然會出錯。

因此redis cluster分布式方案是不支持pipeline操作,如果想要做,只有客戶端緩存slot和redis節點的關系,在批量請求時,就通過key算出不同的slot以及redis節點,并行的進行pipeline。

github.com/go-redis就是這樣做的,有興趣可以閱讀下源碼。

redis實踐及思考

codis

市面上還流行著一種在客戶端和服務端之間增設代理的方案,比如codis就是這樣。 對于上層應用來說,連接 Codis-Proxy 和直接連接 原生的 Redis-Server 沒有的區別,也就是說codis-proxy會幫你做上面并行分槽請求redis server,然后合并結果在一起的操作,對于使用者來說無感知。
總結
在做需求的過程中,發現了很多東西不能拍腦袋決定,而是前期做技術方案的時候,想清楚,調研好,用數據和邏輯去說服自己。
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

临汾市| 洪洞县| 达孜县| 林甸县| 汝阳县| 岳阳市| 柳林县| 长宁县| 博爱县| 涿州市| 奇台县| 十堰市| 博湖县| 宁波市| 莎车县| 正镶白旗| 京山县| 阜康市| 松江区| 平罗县| 阿荣旗| 仙游县| 夏津县| 瓮安县| 若羌县| 天等县| 和静县| 仁布县| 阿克苏市| 荔波县| 舞阳县| 福州市| 贡山| 驻马店市| 阿拉善盟| 怀来县| 专栏| 富顺县| 南陵县| 和硕县| 河曲县|