redis 进阶 - 内存优化

Redis内存优化可以从多个角度进行,包括:

  1. 数据结构优化
  2. 内存配置优化
  3. 数据过期策略
  4. 编码化
  5. 内用外部存储

内存结构分析

内存组成

# 查看内存详情
redis-cli> INFO memory
# 关键指标:
# used_memory: 11845632     # Redis分配器分配的内存
# used_memory_human: 11.30M
# used_memory_rss: 12419072  # 操作系统角度看的内存
# used_memory_peak: 15832432 # 内存使用峰值
# mem_fragmentation_ratio: 1.05 # 内存碎片率
# used_memory_overhead: 1021120 # Redis内部管理开销
# used_memory_dataset: 9832448 # 实际数据占用的内存

# 内存开销结构
- Redis进程本身:约1-3MB
- 内部数据结构开销:每个键值对约96字节
- 数据实际存储:取决于数据类型和编码
- 复制缓冲区、AOF缓冲区等

内存分配器

Redis 使用 Jemalloc 作为默认内存分配器:

# 查看分配器信息
redis-cli> INFO memory
# 相关参数:
# mem_allocator: jemalloc-5.1.0
# active_defrag_running: 0
# lazyfree_pending_objects: 0

# Jemalloc优点:
# 1. 减少内存碎片
# 2. 多线程性能好
# 3. 提供详细的内存统计

数据结构优化

键名优化

# 不好的做法:键名太长
"user:session:1234567890:profile:settings:theme"
# 内存占用:约60字节

# 优化做法:使用缩写或编码
"u:s:1234567890:p:st:th"
# 内存占用:约25字节

# 极致优化:使用数字ID
"u:1234567890"
# 内存占用:约15字节

# 注意:键名共享机制(Redis 7.0+)
# 相同的键名前缀会共享内存

字符串优化

# 场景:存储数字
# 不好的做法:存储为字符串
SET counter "1000000"  # 占用7字节 + Redis开销

# 优化做法:存储为整数
SET counter 1000000    # 占用4字节 + Redis开销

# 检查编码方式
redis-cli> OBJECT ENCODING counter
# "int" - 更节省内存

# 场景:存储短字符串
# Redis内部使用SDS(Simple Dynamic String)
# SDS结构:
# struct sdshdr {
#     int len;     // 4字节
#     int free;    // 4字节  
#     char buf[];  // 实际数据
# }
# 总开销:数据长度 + 8字节 + 1字节(结束符)

Hash优化

# Hash的两种编码方式:
# 1. ziplist(压缩列表):元素少且值小
# 2. hashtable(哈希表):元素多或值大

# 配置参数(redis.conf):
hash-max-ziplist-entries 512    # 元素数量阈值
hash-max-ziplist-value 64       # 单个元素值大小阈值

# 优化示例:
# 存储用户信息
HMSET user:1000 name "John" age 30 city "NY" job "Engineer"

# 检查编码
redis-cli> OBJECT ENCODING user:1000
# "ziplist" - 如果满足条件

# 优化建议:
# 1. 字段名尽量短
# 2. 数值存储为整数
# 3. 控制字段数量
# 4. 大Hash拆分成小Hash

List优化

# List的三种编码:
# 1. ziplist:元素少且小
# 2. linkedlist:双向链表
# 3. quicklist(Redis 3.2+):ziplist组成的链表

# 配置参数:
list-max-ziplist-size -2  # 每个quicklist节点大小限制
# -2: 8KB, -1: 4KB, 正数: 元素个数

list-compress-depth 0     # 压缩深度
# 0: 不压缩, 1: 首尾各1个节点不压缩, ...

# 优化建议:
# 1. 列表元素较多时,使用quicklist
# 2. 大列表考虑分片
# 3. 使用LPUSH/RPOP代替LRANGE获取所有元素

集合 Set优化

# Set的两种编码:
# 1. intset:元素都是整数且数量少
# 2. hashtable:其他情况

# 配置参数:
set-max-intset-entries 512

# 优化示例:
# 存储用户标签
SADD user:1000:tags 1 2 3 4 5  # 使用数字ID代替字符串

# 检查编码
redis-cli> OBJECT ENCODING user:1000:tags
# "intset" - 更节省内存

有序集合 Sorted Set优化

# ZSet的两种编码:
# 1. ziplist:元素少且小
# 2. skiplist+dict:跳表+字典

# 配置参数:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# 优化建议:
# 1. 成员名尽量短
# 2. 分数使用整数
# 3. 大ZSet考虑分片

内存优化配置

内存淘汰策略

# redis.conf配置
maxmemory 4gb  # 设置最大内存
maxmemory-policy volatile-lru  # 淘汰策略

# 淘汰策略选项:
# volatile-lru: 从设置了过期时间的键中淘汰最近最少使用的
# allkeys-lru: 从所有键中淘汰最近最少使用的
# volatile-lfu: 从设置了过期时间的键中淘汰最不经常使用的
# allkeys-lfu: 从所有键中淘汰最不经常使用的
# volatile-random: 从设置了过期时间的键中随机淘汰
# allkeys-random: 从所有键中随机淘汰
# volatile-ttl: 淘汰剩余生存时间最短的
# noeviction: 不淘汰,返回错误(默认)

# 根据场景选择:
# 缓存场景:allkeys-lru 或 volatile-lru
# 持久化数据:noeviction + 监控告警
# 混合场景:volatile-lru + 关键数据不设置过期

内存压缩

# 字符串压缩(Redis 7.0+)
# list、hash、zset的quicklist节点可以压缩
list-compress-depth 1
# 0: 不压缩
# 1: 首尾各1个节点不压缩,其他压缩
# 2: 首尾各2个节点不压缩,其他压缩
# 以此类推

# 内存碎片整理
activedefrag yes
active-defrag-ignore-bytes 100mb      # 内存碎片达到100MB时开始整理
active-defrag-threshold-lower 10      # 碎片率下限10%
active-defrag-threshold-upper 100     # 碎片率上限100%
active-defrag-cycle-min 5             # 最小CPU使用百分比
active-defrag-cycle-max 75            # 最大CPU使用百分比

过期键优化

# 过期键删除策略:
# 1. 定时删除:主动检查(默认每秒10次)
# 2. 惰性删除:访问时检查

# 配置优化:
hz 10  # 提高检查频率(默认10,范围1-500)
# 但会增加CPU使用

# 建议:
# 1. 避免同一时间大量键过期(设置随机过期时间)
# 2. 监控过期键数量
redis-cli> INFO stats
# expired_keys: 已过期的键总数
# expired_stale_perc: 过期比率

高级优化技巧

使用Bitmaps优化布尔型数据

# 场景:用户在线状态、打卡记录等
# 传统方式:为每个用户存储一个键
SET user:1000:online 1  # 约100字节

# Bitmaps方式:
SETBIT online:2023-10-01 1000 1  # 约1位 + 开销
# 1亿用户每天只需约12MB

# 操作示例:
# 设置用户1000在线
SETBIT online:2023-10-01 1000 1
# 检查是否在线
GETBIT online:2023-10-01 1000
# 统计在线人数
BITCOUNT online:2023-10-01

使用HyperLogLog优化基数统计

# 场景:统计UV(独立访客)
# 传统方式:使用Set存储用户ID
SADD uv:2023-10-01 "user1" "user2" "user3"
# 1亿用户需要约3.2GB

# HyperLogLog方式:
PFADD uv:2023-10-01 "user1" "user2" "user3"
# 1亿用户只需约12KB,误差约0.81%

# 获取统计结果
PFCOUNT uv:2023-10-01
# 合并多日数据
PFMERGE uv:week uv:2023-10-01 uv:2023-10-02

使用GEO优化地理位置

# GEO底层使用ZSet存储
# 配置优化:使用ziplist编码
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# 示例:
GEOADD cities 116.405285 39.904989 "北京"
# 内存占用:约50字节/点

# 优化建议:
# 1. 精度控制:根据业务需求选择精度
# 2. 数据分片:按区域分片存储

内存分析

# 1. 查看整体内存
redis-cli> INFO memory

# 2. 查看键内存
redis-cli> MEMORY USAGE key
redis-cli> MEMORY STATS

# 3. 查看大键
redis-cli> --bigkeys
# 或使用scan方式避免阻塞
redis-cli> redis-cli --bigkeys -i 0.1  # 每100ms休息一次

# 4. 内存医生
redis-cli> MEMORY DOCTOR

# 5. 内存分析报告
redis-cli> MEMORY MALLOC-STATS
posted @ 2026-01-19 11:02  py卡卡  阅读(0)  评论(0)    收藏  举报