Redis 从 0 到 1(三十):删了一半数据 RSS 不降——内存碎片与 activedefrag 观测

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [Redis 从 0 到 1(三十):删了一半数据 RSS 不降------内存碎片与 activedefrag 观测](#Redis 从 0 到 1(三十):删了一半数据 RSS 不降——内存碎片与 activedefrag 观测)
    • 一、现象:数字对不上的内存
    • [二、碎片治理决策树(收藏点 1)](#二、碎片治理决策树(收藏点 1))
    • [三、Go 实操:制造碎片并观测 activedefrag(收藏点 2)](#三、Go 实操:制造碎片并观测 activedefrag(收藏点 2))
    • [四、activedefrag 配置与代价(收藏点 3)](#四、activedefrag 配置与代价(收藏点 3))
    • 五、命令清单
    • [六、排查步骤:RSS 异常不降](#六、排查步骤:RSS 异常不降)
    • 七、总结

Redis 从 0 到 1(三十):删了一半数据 RSS 不降------内存碎片与 activedefrag 观测

上一篇《Redis 从 0 到 1(二十九):CPU 突然 100%------commandstats 定位法与 6 步排查链路》我们破了 CPU 100% 案,链接:https://blog.csdn.net/2501_92769340/article/details/167400306 。

第二案:运维删了一半历史数据想释放内存,used_memory 确实降了一半,但操作系统的 RSS(实际占用)纹丝不动------服务器内存压力一点没缓解。

一、现象:数字对不上的内存

bash 复制代码
INFO memory | grep -E "used_memory:|rss|ratio"
# used_memory:1.05G          ← Redis 记账:数据只剩一半了
# used_memory_rss:1.90G      ← OS 视角:实际还占着 1.9G
# mem_fragmentation_ratio:1.81

碎片率 1.81:物理内存页里只有 55% 装的是有效数据,其余是"空洞"。成因是 Redis 的内存分配模式:jemalloc 按固定大小档分配,变长 value 频繁增删后,已释放的小块夹杂在存活数据之间,无法归还给 OS。

典型制造碎片的操作:长期运行的实例 + value 大小差异大 + 大量删除(第 22 篇清 bigkey 之后尤其容易触发)。

二、碎片治理决策树(收藏点 1)

复制代码
mem_fragmentation_ratio 多少?
├── < 1.1:健康,什么都不做
├── 1.1 ~ 1.4:观察,监控趋势
├── 1.4 ~ 1.5:
│   └── 低峰期执行 MEMORY PURGE(一次性归还,秒级)
└── > 1.5:
    ├── 方案 A(在线):开 activedefrag,边服务边整理
    │   └── 注意 CPU 代价(第 29 案警告)
    └── 方案 B(根治):主从切换,让新实例从头顺序装载数据
        └── RDB 重放是顺序分配,天然低碎片

三、Go 实操:制造碎片并观测 activedefrag(收藏点 2)

go 复制代码
package main

import (
	"context"
	"fmt"
	"math/rand"
	"strings"
	"time"

	"redis-0to1/redisx"
)

// Ratio 解析 INFO memory 的碎片率(go-redis 无直接字段,从文本提取)
func Ratio(ctx context.Context, rdb *redis.Client) float64 {
	info, _ := rdb.Info(ctx, "memory").Result()
	for _, line := range strings.Split(info, "\r\n") {
		if strings.HasPrefix(line, "mem_fragmentation_ratio:") {
			var f float64
			fmt.Sscanf(line, "mem_fragmentation_ratio:%f", &f)
			return f
		}
	}
	return 0
}

func main() {
	rdb, _ := redisx.NewClient(redisx.Config{Addr: "127.0.0.1:6379", Password: "redis123", PoolSize: 10})
	ctx := context.Background()

	// 1. 制造碎片:写 20 万个变长 value(512B~4KB 随机),
	//    然后随机删一半 ------ jemalloc 产生大量无法归还的空洞
	for i := 0; i < 200000; i++ {
		size := 512 + rand.Intn(3584)
		rdb.SetEx(ctx, fmt.Sprintf("frag:k%d", i), strings.Repeat("x", size), time.Hour)
	}
	for i := 0; i < 100000; i++ {
		rdb.Del(ctx, fmt.Sprintf("frag:k%d", rand.Intn(200000)))
	}
	fmt.Println("删除后碎片率:", Ratio(ctx, rdb)) // 典型 1.3~1.8

	// 2. 观察在线整理(需服务端开启 activedefrag)
	//    CONFIG SET activedefrag yes
	//    每 30 秒打印 Ratio,可见逐步回落到 1.0~1.1
	t := time.NewTicker(30 * time.Second)
	for range t.C {
		r := Ratio(ctx, rdb)
		fmt.Printf("%s 碎片率=%.2f\n", time.Now().Format("15:04:05"), r)
		if r < 1.15 {
			fmt.Println("整理完成,可 CONFIG SET activedefrag no 关闭")
			return
		}
	}
}

四、activedefrag 配置与代价(收藏点 3)

conf 复制代码
# redis.conf ------ 五个参数,别裸开 yes
activedefrag yes
active-defrag-ignore-bytes 100mb      # 碎片小于 100MB 不启动(省 CPU)
active-defrag-threshold-lower 10      # 碎片率 >10% 才启动
active-defrag-threshold-upper 100     # 碎片率 >100% 时最激进
active-defrag-cycle-min 5             # 每轮整理占 CPU 下限 5%
active-defrag-cycle-max 20            # 上限 20%(峰值期压不下来就把 max 调小)

代价就是 CPU(第 29 案的系统级事件之一):整理期间 CPU 会多出 5~20% 的持续消耗。所以低碎片率不开、低峰期开、开完关,是标准姿势。MEMORY PURGE 则是"手动一次整理",适合做完了大批量删除后的收尾。

五、命令清单

命令 作用
INFO memory mem_fragmentation_ratio / rss
MEMORY PURGE 手动触发一次整理
CONFIG SET activedefrag yes/no 在线开关自动整理
LATENCY DOCTOR 确认 CPU 尖峰是否来自 defrag

六、排查步骤:RSS 异常不降

  1. 先确认不是假象:确认删除真的发生了(DBSIZE/used_memory_dataset);
  2. 看 mem_fragmentation_ratio:>1.5 走决策树;
  3. 低峰 MEMORY PURGE 试一次:RSS 明显下降 = 碎片实锤;
  4. 顽固碎片(ratio 仍高)→ activedefrag 在线整理,或安排主从切换根治;
  5. 治本预防:value 大小差异大的场景,考虑定长化或分桶(第 26 篇)。

七、总结

  1. RSS ≠ used_memory:差值是碎片;ratio >1.5 才需要动手;
  2. 碎片是 jemalloc + 变长 value + 大量删除的必然产物,删数据后 RSS 不降不是灵异事件;
  3. 治理阶梯:PURGE(一次性)→ activedefrag(在线,花 CPU)→ 主从切换(根治);
  4. 开 defrag 必配阈值参数,裸开 yes 会偷走你的 CPU。

下一篇预告:单机故障排完了,下一案上分布式------下一篇《主从切换后订单丢了一页------脑裂的 2 个丢数场景与 min-replicas 防御》,哨兵自动切主的隐性代价来了。

相关推荐
csdn_aspnet15 小时前
使用 Docker 和 RedisInsight 设置 Redis 以进行本地开发
redis·docker·容器·redisinsight
用户0942485680320 小时前
第22章:Redis 持久化 RDB 与 AOF:数据不丢的底线
redis·架构·源码
没有气的汽水20 小时前
升级完客户端,redis 又开始大量返回 MOVED:一个埋了七个月的本地配置
redis
csdn_aspnet21 小时前
如何在本地 Docker 容器中运行 Redis,并使用 Redis Insight 和 Redis CLI 进行管理
redis·docker·容器
IT规划师1 天前
Redis 数据类型全解 - Bitfield:99% 开发者没用过的位域操作,把内存再砍 80%(七)
redis·bitfield·位域
用户094248568032 天前
第19章:库存扣减与秒杀系统:Redis 扛峰值流量
redis·架构·源码
爱写Bug的浮游2 天前
RTA高频请求下的Redis短缓存工程闭环
redis
没有气的汽水2 天前
redis加了个分片,把线上写挂了一个多小时
redis