Redis內(nèi)存管理之BigKey問(wèn)題及解決過(guò)程
Java中的Redis BigKey問(wèn)題解析
一、BigKey 定義與危害分析
1.1 核心定義
BigKey 是指 Redis 中 Value 體積異常大的 Key,通常表現(xiàn)為:
- 字符串類(lèi)型:Value 超過(guò) 10KB
- 集合類(lèi)型:元素?cái)?shù)量超過(guò) 1 萬(wàn)(List/Set)或 5 千(Hash/ZSet)
- 流類(lèi)型:Stream 包含數(shù)萬(wàn)條消息
1.2 危害全景圖
1.3 典型業(yè)務(wù)場(chǎng)景
場(chǎng)景 | 錯(cuò)誤用法 | 推薦方案 |
---|---|---|
社交用戶(hù)畫(huà)像存儲(chǔ) | 單個(gè)Hash存儲(chǔ)用戶(hù)所有標(biāo)簽 | 分片存儲(chǔ) + 二級(jí)索引 |
電商購(gòu)物車(chē)設(shè)計(jì) | 單個(gè)List存儲(chǔ)百萬(wàn)級(jí)商品 | 分頁(yè)存儲(chǔ) + 冷熱分離 |
實(shí)時(shí)消息隊(duì)列 | 單個(gè)Stream累積數(shù)月數(shù)據(jù) | 按時(shí)間分片 + 定期歸檔 |
二、BigKey 檢測(cè)方法論
2.1 內(nèi)置工具檢測(cè)
2.1.1 redis-cli --bigkeys
# 掃描耗時(shí)型操作,建議在從節(jié)點(diǎn)執(zhí)行 redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.1 # 輸出示例 [00.00%] Biggest string found 'user:1024:info' has 12 bytes [12.34%] Biggest hash found 'product:8888:spec' has 10086 fields
2.1.2 MEMORY USAGE
// 計(jì)算Key內(nèi)存占用 Long memUsage = redisTemplate.execute( (RedisCallback<Long>) connection -> connection.serverCommands().memoryUsage("user:1024:info".getBytes()) );
2.2 自定義掃描方案
2.2.1 SCAN + TYPE 組合掃描
public List<Map.Entry<String, Long>> findBigKeys(int threshold) { List<Map.Entry<String, Long>> bigKeys = new ArrayList<>(); Cursor<byte[]> cursor = redisTemplate.execute( (RedisCallback<Cursor<byte[]>>) connection -> connection.scan(ScanOptions.scanOptions().count(100).build()) ); while (cursor.hasNext()) { byte[] keyBytes = cursor.next(); String key = new String(keyBytes); DataType type = redisTemplate.type(key); long size = 0; switch (type) { case STRING: size = redisTemplate.opsForValue().size(key); break; case HASH: size = redisTemplate.opsForHash().size(key); break; // 其他類(lèi)型處理... } if (size > threshold) { bigKeys.add(new AbstractMap.SimpleEntry<>(key, size)); } } return bigKeys; }
2.2.2 RDB 文件分析
# 使用rdb-tools分析 rdb -c memory dump.rdb --bytes 10240 > bigkeys.csv # 輸出示例 database,type,key,size_in_bytes,encoding,num_elements,len_largest_element 0,hash,user:1024:tags,1048576,hashtable,50000,128
2.3 監(jiān)控預(yù)警體系
2.3.1 Prometheus 配置
# redis_exporter配置 - name: redis_key_size rules: - record: redis:key_size:bytes expr: redis_key_size{job="redis"} labels: severity: warning
2.3.2 Grafana 看板指標(biāo)
監(jiān)控項(xiàng) | 查詢(xún)表達(dá)式 | 報(bào)警閾值 |
---|---|---|
大Key數(shù)量 | count(redis_key_size > 10240) | >10 |
最大Key內(nèi)存占比 | max(redis_key_size) / avg(…) | >5倍 |
三、BigKey 處理全流程
3.1 分治法處理
3.1.1 Hash 拆分
public void splitBigHash(String originalKey, int batchSize) { Map<Object, Object> entries = redisTemplate.opsForHash().entries(originalKey); List<List<Map.Entry<Object, Object>>> batches = Lists.partition( new ArrayList<>(entries.entrySet()), batchSize ); for (int i = 0; i < batches.size(); i++) { String shardKey = originalKey + ":shard_" + i; redisTemplate.opsForHash().putAll(shardKey, batches.get(i).stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) ); } redisTemplate.delete(originalKey); }
3.1.2 List 分頁(yè)
public List<Object> getPaginatedList(String listKey, int page, int size) { long start = (page - 1) * size; long end = page * size - 1; return redisTemplate.opsForList().range(listKey, start, end); }
3.2 漸進(jìn)式刪除
3.2.1 非阻塞刪除方案
public void safeDeleteBigKey(String key) { DataType type = redisTemplate.type(key); switch (type) { case HASH: redisTemplate.execute( "HSCAN", key, "0", "COUNT", "100", (result) -> { // 分批刪除字段 return null; }); break; case LIST: while (redisTemplate.opsForList().size(key) > 0) { redisTemplate.opsForList().trim(key, 0, -101); } break; // 其他類(lèi)型處理... } redisTemplate.unlink(key); }
3.2.2 Lua 腳本控制
-- 分批次刪除Hash字段 local cursor = 0 repeat local result = redis.call('HSCAN', KEYS[1], cursor, 'COUNT', 100) cursor = tonumber(result[1]) for _, field in ipairs(result[2]) do redis.call('HDEL', KEYS[1], field) end until cursor == 0
3.3 數(shù)據(jù)遷移方案
3.3.1 集群環(huán)境下處理
public void migrateBigKey(String sourceKey, String targetKey) { RedisClusterConnection clusterConn = redisTemplate.getConnectionFactory() .getClusterConnection(); int slot = ClusterSlotHashUtil.calculateSlot(sourceKey); RedisNode node = clusterConn.clusterGetNodeForSlot(slot); try (Jedis jedis = new Jedis(node.getHost(), node.getPort())) { // 分批遷移數(shù)據(jù) ScanParams params = new ScanParams().count(100); String cursor = "0"; do { ScanResult<Map.Entry<String, String>> scanResult = jedis.hscan(sourceKey, cursor, params); List<Map.Entry<String, String>> entries = scanResult.getResult(); // 分批寫(xiě)入新Key Map<String, String> batch = entries.stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); jedis.hmset(targetKey, batch); cursor = scanResult.getCursor(); } while (!"0".equals(cursor)); } }
四、Java 開(kāi)發(fā)規(guī)范與最佳實(shí)踐
4.1 數(shù)據(jù)建模規(guī)范
數(shù)據(jù)類(lèi)型 | 反例 | 正例 |
---|---|---|
String | 存儲(chǔ)10MB的JSON字符串 | 拆分成多個(gè)Hash + Gzip壓縮 |
Hash | 存儲(chǔ)用戶(hù)所有訂單信息 | 按訂單日期分片存儲(chǔ) |
List | 存儲(chǔ)10萬(wàn)條聊天記錄 | 按時(shí)間分片+消息ID索引 |
4.2 客戶(hù)端配置優(yōu)化
4.2.1 JedisPool 配置
JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(200); // 最大連接數(shù) config.setMaxWaitMillis(1000); // 最大等待時(shí)間 config.setTestOnBorrow(true); // 獲取連接時(shí)驗(yàn)證
4.2.2 Lettuce 調(diào)優(yōu)
ClientOptions options = ClientOptions.builder() .autoReconnect(true) .publishOnScheduler(true) .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1))) .build();
4.3 監(jiān)控與熔斷
@CircuitBreaker(name = "redisService", fallbackMethod = "fallback") public Object getData(String key) { return redisTemplate.opsForValue().get(key); } private Object fallback(String key, Throwable t) { return loadFromBackup(key); }
五、生產(chǎn)環(huán)境案例
5.1 社交平臺(tái)用戶(hù)關(guān)系案例
問(wèn)題:?jiǎn)蝹€(gè)Set存儲(chǔ)50萬(wàn)粉絲導(dǎo)致節(jié)點(diǎn)內(nèi)存溢出
解決方案:
- 按粉絲ID范圍拆分成100個(gè)Set
- 使用SINTERSTORE合并多個(gè)Set查詢(xún)
- 新增反向索引(粉絲 -> 關(guān)注列表)
5.2 電商商品屬性案例
問(wèn)題:Hash存儲(chǔ)10萬(wàn)條商品規(guī)格導(dǎo)致HGETALL阻塞
改造方案:
- 按屬性類(lèi)別拆分Hash
- 使用HMGET獲取指定字段
- 增加緩存版本號(hào)控制
六、開(kāi)發(fā)方向
- AI 智能分片:基于機(jī)器學(xué)習(xí)預(yù)測(cè)數(shù)據(jù)增長(zhǎng)趨勢(shì)
- Serverless 存儲(chǔ):自動(dòng)彈性伸縮的Key分片服務(wù)
- 新型數(shù)據(jù)結(jié)構(gòu):使用RedisJSON模塊處理大文檔
- 內(nèi)存壓縮算法:ZSTD 壓縮算法集成優(yōu)化
通過(guò)全流程的預(yù)防、檢測(cè)、處理體系建設(shè),結(jié)合智能化的監(jiān)控預(yù)警,可有效應(yīng)對(duì) BigKey 挑戰(zhàn),保障 Redis 高性能服務(wù)能力。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于Redis實(shí)現(xiàn)短信驗(yàn)證碼登錄項(xiàng)目示例(附源碼)
手機(jī)登錄驗(yàn)證在很多網(wǎng)頁(yè)上都得到使用,本文主要介紹了基于Redis實(shí)現(xiàn)短信驗(yàn)證碼登錄項(xiàng)目示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05完美解決linux上啟動(dòng)redis后配置文件未生效的問(wèn)題
今天小編就為大家分享一篇完美解決linux上啟動(dòng)redis后配置文件未生效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Redis如何實(shí)現(xiàn)計(jì)數(shù)統(tǒng)計(jì)
這篇文章主要介紹了Redis如何實(shí)現(xiàn)計(jì)數(shù)統(tǒng)計(jì)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04Redis持久化方式之RDB和AOF的原理及優(yōu)缺點(diǎn)
在Redis中,數(shù)據(jù)可以分為兩類(lèi),即內(nèi)存數(shù)據(jù)和磁盤(pán)數(shù)據(jù),Redis?提供了兩種不同的持久化方式,其中?RDB?是快照備份機(jī)制,AOF?則是追加寫(xiě)操作機(jī)制,本文將詳細(xì)給大家介紹Redis?持久化方式RDB和AOF的原理及優(yōu)缺點(diǎn),感興趣的同學(xué)可以跟著小編一起來(lái)學(xué)習(xí)2023-06-06