一個golang編寫的redis記憶體分析工具rma4go

winjeg發表於2018-12-12

redis 記憶體分析工具 rma4go 簡介

redis是一個很有名的記憶體型資料庫,這裡不做詳細介紹。而rma4go (redis memory analyzer for golang) 是一個redis的記憶體分析工具,這個工具的主要作用是針對執行時期的redis進行記憶體的分析,統計redis中key的分佈情況, 各種資料型別的使用情況,key的size,大key的數量及分佈, key的過期狀況分佈等一些有助於定位redis使用問題的工具,希望這能夠給應用開發者提供便利排查生產中所遇到的實際問題。

rma4go的應用場景

redis是目前很流行的一個記憶體型資料庫,很多企業都在使用。 但由於業界並沒有很多對於redis使用上的規範,或者是有一些規範並沒有被很好的遵循, 存在很多redis使用上的問題,我這邊就列舉一些例子:

  1. redis 存用滿了, 不知道key的分佈情況,不知道來源於那個應用
  2. redis 被block了,不知道什麼原因導致的block,是哪個應用裡的什麼key的操作導致的
  3. 想遷移redis資料,或者調整一些設定,但不知道要不要對redis裡的資料進行保留,以及不知道什麼業務在使用等
  4. 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聚合的部分了, 這裡面有很多取捨, 由於作者我本人不是專攻演算法的, 而且沒有找到合適的庫, 因此只能動手自己想了一種方式。 基本的思路是:

壓縮的演算法
  1. 對於每個新的key的元資訊, 新增到老的key分析物件裡去
  2. 對這個key從後往前縮短, 去除尾部,看是否已經包含這個key的統計資訊,如果包含, 則把key的資訊累加上去, 如果不包含則建立一個新的紀錄。
  3. 當記錄的個數新增到一定數量的時候, 對物件的個數進行一次壓縮
    • 壓縮的演算法也是從字串的末尾往字串首部進行壓縮
    • 當壓縮不能增加這個pattern 的key的個數的時候使用原來的key(壓縮前的key)
    • 當壓縮可以增加這個pattern的key的個數的時候,進行key的合併,把pattern設定成壓縮後的pattern
    • 當記錄的條數超過指定的條數就迴圈往復,直到壓縮到小於指定的條數為止
    • 如果對於key的最小長度(就算再壓縮也要保留一兩位)有要求, 有一些壓縮到字串的最小長度的引數可以進行調整與設定, 進行一定的取捨。
  4. 直到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專案

rma4go

github.com/winjeg/rma4…

這是一個我已經寫好的專案, 它使用起來非常簡單, 二進位制包已經準備好, 大家可以點以下連結下載 Linux MAC

構建方法

  1. 構建之前請確保golang sdk 已經安裝, 並且版本 >=1.11.0
  2. 請確保已經具備翻牆的環境, 因為它要下載一些依賴,可能來自牆外 翻牆方法如下
// linux/osx
export http_proxy=somehost:port
export https_proxy=somehost:port
// windows
set http_proxy=somehost:port
set https_proxy=somehost:port

複製程式碼
  1. 構建
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 維護(主要陣地)

  1. 歡迎其他開發者加入
  2. 歡迎提issue 反饋問題
  3. 歡迎任何有意義的建議
  4. 另外歡迎star,不建議fork,建議直接提交PR ; )

相關文章