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 < 1h
ExpireInDay int64 `json:"expireInDay"` // >= 1h < 24h
ExpireInWeek int64 `json:"expireInWeek"` // >= 1d < 7d
ExpireOutWeek int64 `json:"expireOutWeek"` // >= 7d
}
複製程式碼
實現細節
key元資訊
type KeyMeta struct {
Key string
KeySize int64
DataSize int64
Ttl int64
Type 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 = 128
compactNum = 30
maxLeftNum = 150
minKeyLenLower = 2
minKeyLen = 5
)
func (stat *KeyStat) compact() {
distMap := stat.Distribution
tmpMap := make(map[string][]string, defaultSize)
shrinkTo := compactNum
for k := range distMap {
compactedKey := k
if 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 {
// shrink
if 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 沒有使得這個集合的元素數量增加, 就使用原來的key
for 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 = tnMap
shrinkTo --
}
dists := make(map[string]Distribution, defaultSize)
for k, v := range tmpMap {
if len(v) > 1 {
var nd Distribution
for _, dk := range v {
d := distMap[dk]
nd.KeyPattern = k + "*"
nd.KeyCount += d.KeyCount
nd.KeySize += d.KeySize
nd.DataSize += d.DataSize
nd.ExpireInHour += d.ExpireInHour
nd.ExpireInWeek += d.ExpireInWeek
nd.ExpireInDay += d.ExpireInDay
nd.ExpireOutWeek += d.ExpireOutWeek
nd.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專案
這是一個我已經寫好的專案, 它使用起來非常簡單, 二進位制包已經準備好, 大家可以點以下連結下載 Linux MAC
構建方法
- 構建之前請確保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 string
address of a redis (default "localhost")
-a string
password/auth of the redis
-d int
db of the redis to analyze
-h help content
-p int
port of the redis (default 6379)
-r string
address 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 := 6379
cli := client.BuildRedisClient(client.ConnInfo{
Host: h,
Auth: a,
Port: p,
}, cmder.GetDb())
stat := analyzer.ScanAllKeys(cli)
// print in command line
stat.Print()
// the object is ready to use
}
複製程式碼
github 維護(主要陣地)
- 歡迎其他開發者加入
- 歡迎提issue 反饋問題
- 歡迎任何有意義的建議
- 另外歡迎star,不建議fork,建議直接提交PR ; )