Redis 是互聯(lián)網(wǎng)產(chǎn)品開發(fā)中不可缺少的常備武器,它性能高、數(shù)據(jù)結(jié)構(gòu)豐富、簡單易用,但同時也是因為太容易用了,我們的開發(fā)同學不管什么數(shù)據(jù)、不管這數(shù)據(jù)有多大、不管數(shù)據(jù)有多少通通塞進去,最后導致的問題就是 Redis 內(nèi)存使用持續(xù)上升,但是又不知道里面的數(shù)據(jù)是不是有用,是否可以拆分和清理。 為了更好地使用 Redis,除了對 Redis 做一些使用規(guī)范,還需要對線上使用的 Redis 有充分的了解。那么問題來了:一個 Redis 的實例用了那么大的內(nèi)存,里邊到底存了啥?都有哪些 key?每個 key 占用了多少空間? 雪球當前有幾十個 Redis 集群,近千個 Redis 實例,5T 的內(nèi)存數(shù)據(jù),我們也想要分析業(yè)務(wù)是否有誤用,以提高資源利用率。當然,曾經(jīng)我們也深深地被這個問題所困擾,今天我就來和大家分享下我是如何解決這個問題的,希望能給各位一些啟發(fā)。 那有沒有什么辦法讓我們安全高效的看到 Redis 內(nèi)存消耗的詳細報表呢?辦法總比問題多,有需求就有解決方案。雪球 SRE 團隊針對這個問題實現(xiàn)了一個 Redis 數(shù)據(jù)可視化平臺 RDR (Redis Data Reveal)。RDR 可以非常方便的對 Reids 的內(nèi)存進行分析,了解一個 Redis 實例里都有哪些 key,哪類 key 占用的空間是多少,最耗內(nèi)存的 key 有哪些,占比如何,非常直觀。 我們先梳理下,有什么辦法可以拿到 Redis 的所有數(shù)據(jù)。從我的角度看,大概有以下幾種方法,我們分析一下個字的優(yōu)缺點: 1. 先通過 keys * 命令,拿到所有的 key,然后根據(jù) key 再獲取所有的內(nèi)容。
2. 開啟 aof,通過 aof 文件獲取到所有數(shù)據(jù)。
3. 使用 bgsave,獲取 rdb 文件,解析后獲取數(shù)據(jù)。
以上幾種方式我們評估之后,決定采用低峰期在從節(jié)點做 bgsave 獲取 rdb 文件,相對安全可靠,也可以覆蓋所有業(yè)務(wù)的 Redis 集群。也就是說每個實例每天在低峰期自動生成一個 rdb 文件,即使報表數(shù)據(jù)有一天的延遲也是可以接受的。 拿到了 rdb 文件就相當于拿到了 Redis 實例的所有數(shù)據(jù),接下來就是生成報表的過程了:
邏輯很簡單,所以設(shè)計思路很清晰。數(shù)據(jù)流圖如下: 我們再看下具體該如何實現(xiàn),首先是語言選型,雪球 SRE 自研的組件基本都是用 Go 語言做的后端,所以語言選型沒什么糾結(jié),直接用 Go。然后就是剛剛說的那幾個流程。 按照 Redis 的協(xié)議來做就可以了,這個在 GitHub 上搜索 parse rdb 就可以找到許多解析 rdb 文件的庫,拿過來使用即可。我們使用了 https://github.com/cupcake/rdb 。 一條記錄會有哪些內(nèi)存使用呢? 我們知道 Redis 的實現(xiàn)里面有一些基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),就是用這些結(jié)構(gòu)來實現(xiàn)了對外暴露的各種數(shù)據(jù)類型:比如 sds、dict、intset、zipmap、adlist、ziplist、quicklist、skiplist 等等。只要根據(jù)這條記錄的數(shù)據(jù)類型,找出使用了哪些數(shù)據(jù)結(jié)構(gòu),再計算出這些基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的內(nèi)存消耗,再加上數(shù)據(jù)的內(nèi)存使用,以及一些額外開銷比如過期時間等,就可以估算出一條記錄到底使用了多少內(nèi)存。 但是由于 Redis 做了非常多的優(yōu)化,同樣的一種數(shù)據(jù)類型,在不同場景下使用的數(shù)據(jù)結(jié)構(gòu)有可能是不同的。比如 List ,比較老版本的 Redis,會根據(jù) list 元素的數(shù)量來決定來使用哪種結(jié)構(gòu),較短的時候使用 adlist,長之后使用 ziplist,數(shù)值可以通過 list-max-ziplist-entries 來配置。 3.2 版本以后全都使用了 quicklist。而不同結(jié)構(gòu)對于內(nèi)存的使用其實是有區(qū)別的,我們計算的時候也沒辦法拿到具體的配置,所以都按默認配置來計算,最后得出的值是一個估算的值,不過也基本可以反應使用情況了。如果大家對于 Redis 使用的各種數(shù)據(jù)結(jié)構(gòu)感興趣,想了解其設(shè)計及適用場景,可以多搜索一下相關(guān)的資料以及閱讀 Redis 源碼。 舉個計算內(nèi)存使用的例子: 假如我們通過解析 rdb,獲取到了一個 key 為 hello,value 為 world,類型為 string ,ttl 為 1440 的一條記錄,它的內(nèi)存使用是這樣的:
前四項基本是存儲任何一個 key 都需要消耗的,最后一項根據(jù) value 的數(shù)據(jù)結(jié)構(gòu)不同而不同。
我們根據(jù)以上信息可以算出,向操作系統(tǒng)申請這些內(nèi)存,真正需要多少內(nèi)存。由于 Redis 支持多種 malloc 算法,我們就按 jemalloc 的分配方式算,這里也是可能存在誤差的點。 所以最后 key 為 hello 的這條記錄在 64 位操作系統(tǒng)上一共會消耗 92 字節(jié)。 其他類型的計算也大致是同樣的思路,只不過根據(jù)不同的數(shù)據(jù)結(jié)構(gòu)需要計算不同的內(nèi)存消耗,計算的時候要記得考慮內(nèi)存對齊的情況。還有由于 zset 的算法涉及到了隨機生成層數(shù),我們也使用同樣的算法來隨機,但是算出來的值肯定不是精確的,也是一個誤差點。 終于可以拿到任何一個 key 的內(nèi)存使用了,哪些是最有意義最有價值的數(shù)據(jù)呢?
這幾個需求實現(xiàn)起來也都很容易:
可以每天打開個網(wǎng)頁就可以看到某個 Redis 實例的內(nèi)存使用的詳細情況,是件非常幸福的事情,Redis 的內(nèi)存使用再也不是黑盒。 這個系統(tǒng)上線一年以來對我們優(yōu)化 Redis 資源使用、提高效率、節(jié)約成本提供了非常重要的數(shù)據(jù)支撐,而且在內(nèi)部完全自動化,開發(fā)同學自己就可以看到當前 Redis 的使用情況是否符合預期,對于保障業(yè)務(wù)穩(wěn)定也起到了非常重要的作用。這也是雪球的工程師團隊一貫的做法,SRE 提供高效的工具,開發(fā)工程師可以自己運維自己的業(yè)務(wù)系統(tǒng),可以極大的提高生產(chǎn)效率。 這個項目參考了 redis-rdb-tool 這個開源項目,但是性能上比它高效幾倍,為了回饋社區(qū),也希望有機會幫到大家,所以我們決定開源出來。 雪球的內(nèi)部系統(tǒng)根據(jù)自己的特殊場景做了自動化獲取 rdb 文件并備份的邏輯,開源出來的版本去除了定制化,只保留了獲取到 rdb 之后的分析邏輯以及頁面。 |
|