Redis bigkey解決方案

木马不是马發表於2024-04-11

什麼是大key

查詢bigkey

叢集模式檢視bigkey

redis-cli排查

cluster模式排查大key
因為clister叢集模式下查詢bigkey時,因為鍵會分散在不同的槽(slot)和不同的節點上,因此需要分別連到各個主節點進行檢查,或者在命令新增-c引數
首先需要檢視cluster各個節點,連線其中任意一個節點執行以下命令

CLISTER NODES

檢視叢集中每個節點的資訊,返回結果如下,是以空格分割的CSV格式

07c37dfeb235213a872192d90877d0cd55635b91 127.0.0.1:30004@31004,hostname4 slave e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 0 1426238317239 4 connected
67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 127.0.0.1:30002@31002,hostname2 master - 0 1426238316232 2 connected 5461-10922
292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f 127.0.0.1:30003@31003,hostname3 master - 0 1426238318243 3 connected 10923-16383
6ec23923021cf3ffec47632106199cb7f496ce01 127.0.0.1:30005@31005,hostname5 slave 67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 0 1426238316232 5 connected
824fe116063bc5fcf9f4ffd895bc17aee7731ac3 127.0.0.1:30006@31006,hostname6 slave 292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f 0 1426238317741 6 connected
e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 127.0.0.1:30001@31001,hostname1 myself,master - 0 0 1 connected 0-5460

每行格式如下:

<id> <ip:port@cport[,hostname]> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>

其中可以區分是否主從節點
master 主節點
slave 從節點
myself 當前節點
fail 當前節點處於失戀狀態
如果是從節點,展示主節點的ID,如果是主節點,則顯示-,可以看出誰是誰的主節點,誰是誰的從節點
表示當前節點的hash槽
以三主三從為例可以看到當前叢集的節點資訊

透過以上獲取到各個節點資訊,可以檢視每個節點的bigkey情況
執行命令

redis-cli -h 節點ip -p 埠 --bigkeys

返回的並非就是bigkey, 而是每個型別的top1,同時給出每種資料型別的鍵值個數和平均大小, 大致衡量大小為:string型別大於10kb,hash,zset,list,set元素個數超過5000個,
以三主三從為例,如果卡槽為0-5460、5461-10922可能會出現以下提示

意味著訪問的key被移到另一卡槽了可以手動重定向到提示的MOVE節點進行嘗試

除了上述方法,也可以使用下面命令檢視某個key的大小

MEMORY USAGE key [SAMPLES count]

返回結果為所佔位元組大小,如果key不存在resps2返回nil,resp3返回null

解決方案

如何是key的名稱較大,可以使用md5進行摘要演算法生成固定的長度

如果是value較大,可以拆分為多個子key,比如一個大的list,拆分成多個小的list

再利用管道操作可以一定程度上保證事務

public void saveBigKey() {
        ArrayList<Object> list = new ArrayList<>();
        List<List<Object>> partList = Lists.partition(list, 100);
        String masterKey = "master_key";
        redisTemplate.executePipelined((RedisCallback<Object>) con -> {
            con.del(rawKey(masterKey));
            for (int i = 0; i < partList.size(); i++) {
                String partKey = String.format("CHILD_KEY_%s", i);
                con.set(rawKey(partKey), rawValue(JsonUtil.dumpObject(partList.get(i))));
                con.expire(rawKey(partKey), TimeUnit.DAYS.toSeconds(2));
                con.zAdd(rawKey(masterKey), i, rawValue(partKey));
            }
            con.expire(rawKey(masterKey), TimeUnit.DAYS.toSeconds(1));
            return null;
        });
    }

    RedisSerializer keySerializer() {
        return redisTemplate.getKeySerializer();
    }

    RedisSerializer valueSerializer() {
        return redisTemplate.getValueSerializer();
    }

    @SuppressWarnings("unchecked")
    byte[] rawKey(Object key) {

        Assert.notNull(key, "non null key required");

        if (keySerializer() == null && key instanceof byte[]) {
            return (byte[]) key;
        }

        return keySerializer().serialize(key);
    }

    @SuppressWarnings("unchecked")
    byte[] rawValue(Object value) {

        if (valueSerializer() == null && value instanceof byte[]) {
            return (byte[]) value;
        }

        return valueSerializer().serialize(value);
    }

相關文章