Redis 內存分析方法
背景
線上經常遇到用戶想知道自己 Redis 實例中數據的內存分布情況。為了不影響線上實例的使用,我們一般會采用 bgsave 生成 dump.rdb 文件,再結合 redis-rdb-tools 和 sqlite 來進行靜態分析。總的來說,整個分析的過程簡單而實用,是每一個 Redis 的用戶都非常值得掌握的一個方法。
創建備份
自建 Redis 可在客戶端執行 bgsave 生成 rdb 文件。雲數據庫 Redis 版可以在控制台上可以進行數據備份和下載的操作,下載后的數據為 rdb 格式文件。步驟詳見下圖:

生成內存快照
redis-rdb-tools 是一個 python 的解析 rdb 文件的工具,在分析內存的時候,我們主要用它生成內存快照。主要有以下三個功能:
- 生成內存快照
- 轉儲成 json 格式
- 使用標准的 diff 工具比較兩個 dump 文件
redis-rdb-tools 安裝
redis-rdb-tools 有兩種安裝方式,任選其一即可。
使用 PYPI 安裝
pip install rdbtools
從源碼安裝
git clone https://github.com/sripathikrishnan/redis-rdb-toolscd redis-rdb-toolssudo python setup.py install
使用 redis-rdb-tools 生成內存快照
生成內存快照的命令為:
rdb -c memory dump.rdb > memory.csv
生成 CSV 格式的內存報告。包含的列有:數據庫 ID,數據類型,key,內存使用量(byte),編碼。內存使用量包含 key、value 和其他值。
注意:內存使用量是理論上的近似值,在一般情況下,略低於實際值。memory.csv 例子:
$head memory.csvdatabase,type,key,size_in_bytes,encoding,num_elements,len_largest_element0,string,"orderAt:377671748",96,string,8,80,string,"orderAt:413052773",96,string,8,80,sortedset,"Artical:Comments:7386",81740,skiplist,479,410,sortedset,"pay:id:18029",2443,ziplist,84,160,string,"orderAt:452389458",96,string,8,8
分析內存快照
SQLite 是一款輕型的數據庫。我們可以將前面生成的 csv 導入到數據庫中之后,就可以利用 sql 語句很方便的對 Redis 的內存數據進行各種分析了。
注意:SQLite版本必須是3.16.0以上。
導入方法
sqlite3 memory.dbsqlite> create table memory(database int,type varchar(128),key varchar(128),size_in_bytes int,encoding varchar(128),num_elements int,len_largest_element varchar(128));sqlite>.mode csv memorysqlite>.import memory.csv memory
數據導入以后,接下來想怎么分析就怎么分析了,舉幾個簡單的例子:
查詢key個數
sqlite>select count(*) from memory;
查詢總的內存占用
sqlite>select sum(size_in_bytes) from memory;
查詢內存占用最高的10個 key
sqlite>select * from memory order by size_in_bytes desc limit 10;
查詢成員個數1000個以上的 list
sqlite>select * from memory where type='list' and num_elements > 1000 ;
總結
通過使用 redis-rdb-tools + sqlite 的方式,可以方便的對 redis 實例的內存情況進行靜態的分析。整個過程也比較簡單,獲取到 rdb 之后即可。
rdb -c memory dump.rdb > memory.csv;sqlite3 memory.dbsqlite> create table memory(database int,type varchar(128),key varchar(128),size_in_bytes int,encoding varchar(128),num_elements int,len_largest_element varchar(128));sqlite>.mode csv memorysqlite>.import memory.csv memory
實際使用中,發現過一個 List 積攢了10多 GB 的內容,也發現過43 MB 以上的 string 類型的 value, 往往不僅能解答用戶的疑惑,而且能夠幫助用戶排除業務中潛在的風險點,找到業務性能瓶頸。
游戲玩家排行榜
場景介紹
雲數據庫 Redis 版在功能上與 Redis 基本一致,因此很容易用它來實現一個在線游戲中的積分排行榜功能。
代碼示例
import java.util.ArrayList;import java.util.List;import java.util.Set;import java.util.UUID;import redis.clients.jedis.Jedis;import redis.clients.jedis.Tuple;public class GameRankSample {static int TOTAL_SIZE = 20;public static void main(String[] args){//連接信息,從控制台可以獲得String host = "xxxxxxxxxx.m.cnhz1.kvstore.aliyuncs.com";int port = 6379;Jedis jedis = new Jedis(host, port);try {//實例密碼String authString = jedis.auth("password");//passwordif (!authString.equals("OK")){System.err.println("AUTH Failed: " + authString);return;}//Key(鍵)String key = "游戲名:奔跑吧,阿里!";//清除可能的已有數據jedis.del(key);//模擬生成若干個游戲玩家List<String> playerList = new ArrayList<String>();for (int i = 0; i < TOTAL_SIZE; ++i){//隨機生成每個玩家的IDplayerList.add(UUID.randomUUID().toString());}System.out.println("輸入所有玩家 ");//記錄每個玩家的得分for (int i = 0; i < playerList.size(); i++){//隨機生成數字,模擬玩家的游戲得分int score = (int)(Math.random()*5000);String member = playerList.get(i);System.out.println("玩家ID:" + member + ", 玩家得分: " + score);//將玩家的ID和得分,都加到對應key的SortedSet中去jedis.zadd(key, score, member);}//輸出打印全部玩家排行榜System.out.println();System.out.println(" "+key);System.out.println(" 全部玩家排行榜 ");//從對應key的SortedSet中獲取已經排好序的玩家列表Set<Tuple> scoreList = jedis.zrevrangeWithScores(key, 0, -1);for (Tuple item : scoreList) {System.out.println("玩家ID:"+item.getElement()+", 玩家得分:"+Double.valueOf(item.getScore()).intValue());}//輸出打印Top5玩家排行榜System.out.println();System.out.println(" "+key);System.out.println(" Top 玩家");scoreList = jedis.zrevrangeWithScores(key, 0, 4);for (Tuple item : scoreList) {System.out.println("玩家ID:"+item.getElement()+", 玩家得分:"+Double.valueOf(item.getScore()).intValue());}//輸出打印特定玩家列表System.out.println();System.out.println(" "+key);System.out.println(" 積分在1000至2000的玩家");//從對應key的SortedSet中獲取已經積分在1000至2000的玩家列表scoreList = jedis.zrangeByScoreWithScores(key, 1000, 2000);for (Tuple item : scoreList) {System.out.println("玩家ID:"+item.getElement()+", 玩家得分:"+Double.valueOf(item.getScore()).intValue());}} catch (Exception e) {e.printStackTrace();}finally{jedis.quit();jedis.close();}}}
運行結果
在輸入了正確的雲數據庫 Redis 版實例訪問地址和密碼之后,運行以上 Java 程序,輸出結果如下:
輸入所有玩家玩家ID:9193e26f-6a71-4c76-8666-eaf8ee97ac86, 玩家得分: 3860玩家ID:db03520b-75a3-48e5-850a-071722ff7afb, 玩家得分: 4853玩家ID:d302d24d-d380-4e15-a4d6-84f71313f27a, 玩家得分: 2931玩家ID:bee46f9d-4b05-425e-8451-8aa6d48858e6, 玩家得分: 1796玩家ID:ec24fb9e-366e-4b89-a0d5-0be151a8cad0, 玩家得分: 2263玩家ID:e11ecc2c-cd51-4339-8412-c711142ca7aa, 玩家得分: 1848玩家ID:4c396f67-da7c-4b99-a783-25919d52d756, 玩家得分: 958玩家ID:a6299dd2-4f38-4528-bb5a-aa2d48a9f94a, 玩家得分: 2428玩家ID:2e4ec631-1e4e-4ef0-914f-7bf1745f7d65, 玩家得分: 4478玩家ID:24235a85-85b9-476e-8b96-39f294f57aa7, 玩家得分: 1655玩家ID:e3e8e1fa-6aac-4a0c-af80-4c4a1e126cd1, 玩家得分: 4064玩家ID:99bc5b4f-e32a-4295-bc3a-0324887bb77e, 玩家得分: 4852玩家ID:19e2aa6b-a2d8-4e56-bdf7-8b59f64bd8e0, 玩家得分: 3394玩家ID:cb62bb24-1318-4af2-9d9b-fbff7280dbec, 玩家得分: 3405玩家ID:ec0f06da-91ee-447b-b935-7ca935dc7968, 玩家得分: 4391玩家ID:2c814a6f-3706-4280-9085-5fe5fd56b71c, 玩家得分: 2510玩家ID:9ee2ed6d-08b8-4e7f-b52c-9adfe1e32dda, 玩家得分: 63玩家ID:0293b43a-1554-4157-a95b-b78de9edf6dd, 玩家得分: 1008玩家ID:674bbdd1-2023-46ae-bbe6-dfcd8e372430, 玩家得分: 2265玩家ID:34574e3e-9cc5-43ed-ba15-9f5405312692, 玩家得分: 3734游戲名:奔跑吧,阿里!全部玩家排行榜玩家ID:db03520b-75a3-48e5-850a-071722ff7afb, 玩家得分:4853玩家ID:99bc5b4f-e32a-4295-bc3a-0324887bb77e, 玩家得分:4852玩家ID:2e4ec631-1e4e-4ef0-914f-7bf1745f7d65, 玩家得分:4478玩家ID:ec0f06da-91ee-447b-b935-7ca935dc7968, 玩家得分:4391玩家ID:e3e8e1fa-6aac-4a0c-af80-4c4a1e126cd1, 玩家得分:4064玩家ID:9193e26f-6a71-4c76-8666-eaf8ee97ac86, 玩家得分:3860玩家ID:34574e3e-9cc5-43ed-ba15-9f5405312692, 玩家得分:3734玩家ID:cb62bb24-1318-4af2-9d9b-fbff7280dbec, 玩家得分:3405玩家ID:19e2aa6b-a2d8-4e56-bdf7-8b59f64bd8e0, 玩家得分:3394玩家ID:d302d24d-d380-4e15-a4d6-84f71313f27a, 玩家得分:2931玩家ID:2c814a6f-3706-4280-9085-5fe5fd56b71c, 玩家得分:2510玩家ID:a6299dd2-4f38-4528-bb5a-aa2d48a9f94a, 玩家得分:2428玩家ID:674bbdd1-2023-46ae-bbe6-dfcd8e372430, 玩家得分:2265玩家ID:ec24fb9e-366e-4b89-a0d5-0be151a8cad0, 玩家得分:2263玩家ID:e11ecc2c-cd51-4339-8412-c711142ca7aa, 玩家得分:1848玩家ID:bee46f9d-4b05-425e-8451-8aa6d48858e6, 玩家得分:1796玩家ID:24235a85-85b9-476e-8b96-39f294f57aa7, 玩家得分:1655玩家ID:0293b43a-1554-4157-a95b-b78de9edf6dd, 玩家得分:1008玩家ID:4c396f67-da7c-4b99-a783-25919d52d756, 玩家得分:958玩家ID:9ee2ed6d-08b8-4e7f-b52c-9adfe1e32dda, 玩家得分:63游戲名:奔跑吧,阿里!Top 玩家玩家ID:db03520b-75a3-48e5-850a-071722ff7afb, 玩家得分:4853玩家ID:99bc5b4f-e32a-4295-bc3a-0324887bb77e, 玩家得分:4852玩家ID:2e4ec631-1e4e-4ef0-914f-7bf1745f7d65, 玩家得分:4478玩家ID:ec0f06da-91ee-447b-b935-7ca935dc7968, 玩家得分:4391玩家ID:e3e8e1fa-6aac-4a0c-af80-4c4a1e126cd1, 玩家得分:4064游戲名:奔跑吧,阿里!積分在1000至2000的玩家玩家ID:0293b43a-1554-4157-a95b-b78de9edf6dd, 玩家得分:1008玩家ID:24235a85-85b9-476e-8b96-39f294f57aa7, 玩家得分:1655玩家ID:bee46f9d-4b05-425e-8451-8aa6d48858e6, 玩家得分:1796玩家ID:e11ecc2c-cd51-4339-8412-c711142ca7aa, 玩家得分:1848
