redis 內存分析工具 rma4go
redis是一個很有名的內存型數據庫,這里不做詳細介紹。而rma4go
(redis memory analyzer for golang) 是一個redis的內存分析工具,這個工具的主要作用是針對運行時期的redis進行內存的分析,統計redis中key的分布情況, 各種數據類型的使用情況,key的size,大key的數量及分布, key的過期狀況分布等一些有助于定位redis使用問題的工具,希望這能夠給應用開發者提供便利排查生產中所遇到的實際問題。
rma4go
的應用場景
redis是目前很流行的一個內存型數據庫,很多企業都在使用。 但由于業界并沒有很多對于redis使用上的規范,或者是有一些規范并沒有被很好的遵循, 存在很多redis使用上的問題,我這邊就列舉一些例子:
- redis 存用滿了, 不知道key的分布情況,不知道來源于那個應用
- redis 被block了,不知道什么原因導致的block,是哪個應用里的什么key的操作導致的
- 想遷移redis數據,或者調整一些設置,但不知道要不要對redis里的數據進行保留,以及不知道什么業務在使用等
- redis的key的過期情況不明朗, 不知道哪些東西可以刪除或者調整
其實上面的一些問題是我隨便列舉出來的一些,并不是所有的存在的問題,相信也有很多其他場景同樣會用到這樣的一個redis內存分析工具rma4go
rma4go的具體功能
數據維度
對于key的分析我們這個工具會提供如下幾個維度的數據:
- key的數量分布維度
- key的過期分布維度
- key的類型分布維度
- key對應的的數據的大小分布維度
- key的前綴分布維度
- 慢key與大key的維度
當然以后如果發現有更好的緯度也會添加進去,目前先以這幾個緯度為主
數據類型設計
type RedisStat struct {All KeyStat `json:"all"`String KeyStat `json:"string"`Hash KeyStat `json:"hash"`Set KeyStat `json:"set"`List KeyStat `json:"list"`ZSet KeyStat `json:"zset"`Other KeyStat `json:"other"`BigKeys KeyStat `json:"bigKeys"`
}// distributions of keys of all prefixes
type Distribution struct {KeyPattern string `json:"pattern"`Metrics
}// basic metrics of a group of key
type Metrics struct {KeyCount int64 `json:"keyCount"`KeySize int64 `json:"keySize"`DataSize int64 `json:"dataSize"`KeyNeverExpire int64 `json:"neverExpire"`ExpireInHour int64 `json:"expireInHour"` // >= 0h < 1hExpireInDay int64 `json:"expireInDay"` // >= 1h < 24hExpireInWeek int64 `json:"expireInWeek"` // >= 1d < 7dExpireOutWeek int64 `json:"expireOutWeek"` // >= 7d
}
實現細節
key元信息
type KeyMeta struct {Key stringKeySize int64DataSize int64Ttl int64Type string
}
眾所周知, redis里的所有的數據基本都是由key的, 也是根據key進行操作的,那么對redis里的key進行分析我們必須要記錄下來這個key的信息才可以做到, 我們能記錄的信息正如以上結構中的一樣, key本身, key的大小, 數據的大小, 過期時間以及key的類型。這些信息是我們對key進行分析的一個基礎信息,都可以通過一些簡單的redis命令就可以取到。
遍歷redis所有key
要對一個redis進行完整的key分析, 我們就需要有辦法能夠訪問到所有key的源信息, 所幸redis提供了 scan
這么一種方式可以比較輕量的遍歷所有的key,訪問到相應的key的元信息。
這樣對于redis而言, 進行在線key分析的時候造成的壓力也不會非常大,當然key分析不能再QPS高峰期進行, 需要在redis資源余量允許的情況下進行分析。
另外由于redis本身的一個內存清理機制,有25%的過期占用可以在分析key的時候被清理掉, 因此這個分析工具同時兼具了清理一部分內存的作用, 如果redis里面存在過期的而且存在于內存里面的key的話。
對記錄的信息進行分析與匯總
有了遍歷所有key的方法, 又有了元數據, 剩下的事情就是把這些數據進行聚合匯總, 這個主要是一個算法上的工作,
最難的部分要數這個key聚合的部分了, 這里面有很多取舍, 由于作者我本人不是專攻算法的, 而且沒有找到合適的庫, 因此只能動手自己想了一種方式。 基本的思路是:
壓縮的算法
- 對于每個新的key的元信息, 添加到老的key分析對象里去
- 對這個key從后往前縮短, 去除尾部,看是否已經包含這個key的統計信息,如果包含, 則把key的信息累加上去, 如果不包含則創建一個新的紀錄。
-
當記錄的個數添加到一定數量的時候, 對對象的個數進行一次壓縮
- 壓縮的算法也是從字符串的末尾往字符串首部進行壓縮
- 當壓縮不能增加這個pattern 的key的個數的時候使用原來的key(壓縮前的key)
- 當壓縮可以增加這個pattern的key的個數的時候,進行key的合并,把pattern設置成壓縮后的pattern
- 當記錄的條數超過指定的條數就循環往復,直到壓縮到小于指定的條數為止
- 如果對于key的最小長度(就算再壓縮也要保留一兩位)有要求, 有一些壓縮到字符串的最小長度的參數可以進行調整與設置, 進行一定的取舍。
- 直到scan完畢
代碼如下
const (defaultSize = 128compactNum = 30maxLeftNum = 150minKeyLenLower = 2minKeyLen = 5
)func (stat *KeyStat) compact() {distMap := stat.DistributiontmpMap := make(map[string][]string, defaultSize)shrinkTo := compactNumfor k := range distMap {compactedKey := kif orgks, ok := tmpMap[compactedKey]; ok {orgks = append(orgks, k)tmpMap[compactedKey] = orgks} else {ks := make([]string, 0, defaultSize)ks = append(ks, k)tmpMap[compactedKey] = ks}}shrinkTo--for (len(tmpMap) > compactNum && shrinkTo >= minKeyLen) || (len(tmpMap) > maxLeftNum && shrinkTo >= minKeyLenLower) {tnMap := make(map[string][]string, defaultSize)for k := range tmpMap {// shrinkif len(k) > shrinkTo {compactedKey := k[0:shrinkTo]if oik, ok := tnMap[compactedKey]; ok {oik = append(oik, tmpMap[k]...)tnMap[compactedKey] = oik} else {ks := make([]string, 0, defaultSize)ks = append(ks, tmpMap[k]...)tnMap[compactedKey] = ks}} else {tnMap[k] = tmpMap[k]}}// 如果此次shrink 沒有使得這個集合的元素數量增加, 就使用原來的keyfor k := range tmpMap {if len(k) > shrinkTo {ck := k[0:shrinkTo]if len(tnMap[ck]) == len(tmpMap[k]) && len(tnMap[ck]) > 1 {x := make([]string, 0, defaultSize)tnMap[k] = append(x, tnMap[ck]...)delete(tnMap, ck)}}}tmpMap = tnMapshrinkTo --}dists := make(map[string]Distribution, defaultSize)for k, v := range tmpMap {if len(v) > 1 {var nd Distributionfor _, dk := range v {d := distMap[dk]nd.KeyPattern = k + "*"nd.KeyCount += d.KeyCountnd.KeySize += d.KeySizend.DataSize += d.DataSizend.ExpireInHour += d.ExpireInHournd.ExpireInWeek += d.ExpireInWeeknd.ExpireInDay += d.ExpireInDaynd.ExpireOutWeek += d.ExpireOutWeeknd.KeyNeverExpire += d.KeyNeverExpire}dists[k] = nd} else {for _, dk := range v {nd := distMap[dk]nd.KeyPattern = dk + "*"dists[dk] = nd}}}stat.Distribution = dists
}
在線key分析的github項目
rma4go
這是一個我已經寫好的項目, 它使用起來非常簡單
構建方法
- 構建之前請確保golang sdk 已經安裝, 并且版本 >=1.11.0
- 請確保已經具備翻墻的環境, 因為它要下載一些依賴,可能來自墻外
翻墻方法如下
// linux/osx
export http_proxy=somehost:port
export https_proxy=somehost:port
// windows
set http_proxy=somehost:port
set https_proxy=somehost:port
- 構建
git clone git@github.com:winjeg/rma4go.git
cd rma4go
go build .
使用方法
用法如下:rma4go -h
rma4go usage:
rma4go -r some_host -p 6379 -a password -d 0
======================================================-H stringaddress of a redis (default "localhost")-a stringpassword/auth of the redis-d intdb of the redis to analyze-h help content-p intport of the redis (default 6379)-r stringaddress of a redis (default "localhost")
示例輸出
all keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
string keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
list keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
hash keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
set keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
zset keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
other keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
big keys statistics| PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
|---------|---------|----------|-----------|----------------|---------------|----------------|-----------------|--------------|
| total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
rendered by markdown
total count 4004
all keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
TOP_TEN_NEW_XXXXXXXX* | 1 | 20 | 1529 | 0 | 0 | 0 | 0 | 1 |
XXXXXXXXXXXXXX_STATISTICS_MIGRATION_LIST* | 1 | 40 | 7692832 | 0 | 0 | 0 | 0 | 1 |
time-root:* | 23 | 272 | 299 | 0 | 0 | 0 | 0 | 23 |
DS_AXXXXXXXX_CORRECT* | 2 | 45 | 46 | 0 | 0 | 0 | 0 | 2 |
time-2* | 761 | 7528 | 9893 | 0 | 0 | 0 | 0 | 761 |
time-level:* | 537 | 8461 | 6981 | 0 | 0 | 0 | 0 | 537 |
time-9* | 102 | 901 | 1326 | 0 | 0 | 0 | 0 | 102 |
time-7* | 153 | 1372 | 1989 | 0 | 0 | 0 | 0 | 153 |
DS_MAGIC_SUCC_2017-06-22* | 1 | 24 | 415 | 0 | 0 | 0 | 0 | 1 |
tersssss* | 5 | 124 | 0 | 0 | 0 | 0 | 0 | 5 |
appoint_abcdefg_msgid* | 1 | 21 | 0 | 0 | 0 | 0 | 0 | 1 |
BUSSINESSXXXXXXX_STATISTICS_NEED_CALC_RECENT* | 1 | 44 | 1 | 0 | 0 | 0 | 0 | 1 |
switch_abcd_abcde* | 3 | 69 | 3 | 0 | 0 | 0 | 0 | 3 |
abcdeferCounter_201* | 3 | 78 | 0 | 0 | 0 | 0 | 0 | 3 |
diy1234567flag* | 1 | 14 | 1 | 0 | 0 | 0 | 0 | 1 |
DS_PRXXBCD_LIST* | 1 | 15 | 17208 | 0 | 0 | 0 | 0 | 1 |
time-4* | 133 | 1194 | 1729 | 0 | 0 | 0 | 0 | 133 |
datastatistics_switch_version0* | 1 | 30 | 1 | 0 | 0 | 0 | 0 | 1 |
register_count_2_201* | 592 | 15984 | 640 | 0 | 0 | 0 | 0 | 592 |
canVisitNewabcdef1234PageLevels* | 1 | 31 | 0 | 0 | 0 | 0 | 0 | 1 |
YOUR_WEEK_VITALITY_INFO* | 1 | 23 | 75782 | 0 | 0 | 0 | 0 | 1 |
time-8* | 101 | 894 | 1313 | 0 | 0 | 0 | 0 | 101 |
EXPERTS_APPOINT_INFO_MAP* | 1 | 24 | 0 | 0 | 0 | 0 | 0 | 1 |
time-3* | 130 | 1215 | 1690 | 0 | 0 | 0 | 0 | 130 |
time-1* | 943 | 9456 | 12259 | 0 | 0 | 0 | 0 | 943 |
time-64* | 87 | 781 | 1131 | 0 | 0 | 0 | 0 | 87 |
time-5* | 168 | 1516 | 2184 | 0 | 0 | 0 | 0 | 168 |
total | 4004 | 53422 | 7832490 | 0 | 0 | 0 | 0 | 4004 |
string keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
BUSSINESSXXXXXXX_STATISTICS_NEED_CALC_RECENT* | 1 | 44 | 1 | 0 | 0 | 0 | 0 | 1 |
time-5* | 130 | 1174 | 1690 | 0 | 0 | 0 | 0 | 130 |
datastatistics_switch_version0* | 1 | 30 | 1 | 0 | 0 | 0 | 0 | 1 |
time-7* | 39 | 348 | 507 | 0 | 0 | 0 | 0 | 39 |
time-level:* | 567 | 8939 | 7371 | 0 | 0 | 0 | 0 | 567 |
diy1234567flag* | 1 | 14 | 1 | 0 | 0 | 0 | 0 | 1 |
switch_abcd_abcde* | 3 | 69 | 3 | 0 | 0 | 0 | 0 | 3 |
time-2* | 598 | 5918 | 7774 | 0 | 0 | 0 | 0 | 598 |
time-6* | 125 | 1118 | 1625 | 0 | 0 | 0 | 0 | 125 |
time-4* | 136 | 1225 | 1768 | 0 | 0 | 0 | 0 | 136 |
time-8* | 72 | 636 | 936 | 0 | 0 | 0 | 0 | 72 |
time-1* | 1176 | 11814 | 15288 | 0 | 0 | 0 | 0 | 1176 |
time-9* | 100 | 880 | 1300 | 0 | 0 | 0 | 0 | 100 |
time-root:* | 23 | 272 | 299 | 0 | 0 | 0 | 0 | 23 |
register_count_2_201* | 592 | 15984 | 640 | 0 | 0 | 0 | 0 | 592 |
DS_AXXXXXXXX_CORRECT* | 1 | 20 | 20 | 0 | 0 | 0 | 0 | 1 |
TOP_TEN_NEW_tersssss* | 1 | 20 | 1529 | 0 | 0 | 0 | 0 | 1 |
time-3* | 202 | 1925 | 2626 | 0 | 0 | 0 | 0 | 202 |
total | 3989 | 53042 | 46253 | 0 | 0 | 0 | 0 | 3989 |
list keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
XXXXXXXXXXXXXX_STATISTICS_MIGRATION_LIST* | 1 | 40 | 7692832 | 0 | 0 | 0 | 0 | 1 |
DS_MAGIC_SUCC_2017-06-22* | 1 | 24 | 415 | 0 | 0 | 0 | 0 | 1 |
DS_PRXXBCD_LIST* | 1 | 15 | 17208 | 0 | 0 | 0 | 0 | 1 |
total | 3 | 79 | 7710455 | 0 | 0 | 0 | 0 | 3 |
hash keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
tersssss_action_prepage_new* | 1 | 27 | 0 | 0 | 0 | 0 | 0 | 1 |
YOUR_WEEK_VITALITY_INFO* | 1 | 23 | 75782 | 0 | 0 | 0 | 0 | 1 |
EXPERTS_APPOINT_INFO_MAP* | 1 | 24 | 0 | 0 | 0 | 0 | 0 | 1 |
abcdeferCounter_2017-06-11* | 1 | 26 | 0 | 0 | 0 | 0 | 0 | 1 |
tersssssHardTaskCounter* | 1 | 23 | 0 | 0 | 0 | 0 | 0 | 1 |
abcdeferCounter_2018-04-27* | 1 | 26 | 0 | 0 | 0 | 0 | 0 | 1 |
abcdeferCounter_2017-09-01* | 1 | 26 | 0 | 0 | 0 | 0 | 0 | 1 |
tersssssEasyTaskCounter* | 1 | 23 | 0 | 0 | 0 | 0 | 0 | 1 |
total | 8 | 198 | 75782 | 0 | 0 | 0 | 0 | 8 |
set keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
tersssss_bind_phone_phone* | 1 | 25 | 0 | 0 | 0 | 0 | 0 | 1 |
appoint_abcdefg_msgid* | 1 | 21 | 0 | 0 | 0 | 0 | 0 | 1 |
canVisitNewabcdef1234PageLevels* | 1 | 31 | 0 | 0 | 0 | 0 | 0 | 1 |
tersssss_bind_phone_userid* | 1 | 26 | 0 | 0 | 0 | 0 | 0 | 1 |
total | 4 | 103 | 0 | 0 | 0 | 0 | 0 | 4 |
zset keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
other keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
total | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
big keys statistics
PATTERN | KEY NUM | KEY SIZE | DATA SIZE | EXPIRE IN HOUR | EXPIRE IN DAY | EXPIRE IN WEEK | EXPIRE OUT WEEK | NEVER EXPIRE |
---|---|---|---|---|---|---|---|---|
XXXXXXXXXXXXXX_STATISTICS_MIGRATION_LIST* | 1 | 40 | 7692832 | 0 | 0 | 0 | 0 | 1 |
total | 1 | 40 | 7692832 | 0 | 0 | 0 | 0 | 1 |
作為依賴使用
獲取方法如下:
go get github.com/winjeg/rma4go
使用方法如下:
func testFunc() {h := "localhost"a := ""p := 6379cli := client.BuildRedisClient(client.ConnInfo{Host: h,Auth: a,Port: p,}, cmder.GetDb())stat := analyzer.ScanAllKeys(cli)// print in command linestat.Print()// the object is ready to use
}
github 維護(主要陣地)
- 歡迎其他開發者加入
- 歡迎提issue 反饋問題
- 歡迎任何有意義的建議
- 另外歡迎star,不建議fork,建議直接提交PR ; )