Redis数据库——内存分配器

Redis数据库——内存分配器

大家好,这里是编程Cookbook。本文详细介绍Redis数据库的内存分配器,这是redis为什么这么快的原因,以及其作为内存数据库的内存管理策略。

Redis 的内存分配器

Redis 的内存管理设计对性能和内存利用率有重要影响。其内存分配依赖底层的内存分配器和自身的一些优化机制。

内存分配器的作用

Redis 是一个基于内存的数据库,需要频繁地进行内存分配和释放操作。内存分配器的性能和设计直接影响 Redis 的整体性能。

  • 内存分配器的主要职责
    • 高效分配和释放内存
    • 尽量减少内存碎片
    • 提供线程安全的并发支持(多线程场景)。

Redis 支持的内存分配器

Redis 支持多种内存分配器,常见的有以下三种:

分配器 优点 缺点 使用场景
jemalloc 高性能、低碎片率、多线程支持【内存利用率高 小对象(<4KB)分配速度略低于 tcmalloc Redis 默认,推荐使用
libc 简单易用,操作系统默认 性能和碎片控制较差 非高并发、低性能需求场景
tcmalloc 极高并发性能、多线程优化 【内存分配速度高 内存利用率略低于 jemalloc(因线程缓存冗余) 极高并发场景,可替代 jemalloc

Redis 的内存分配器设计和优化是其性能的重要保障。正确选择分配器并监控内存使用,可进一步提升系统稳定性和效率。

  • tcmalloc:适合 短生命周期小对象
  • jemalloc:适合 长生命周期中型对象

jemalloc

默认分配器

  • 自 Redis 3.0 起默认使用 jemalloc(由 Facebook 优化维护的高性能分配器)。

核心特点

  1. 内存碎片控制
    • 采用 大小分级(Size Class) 管理,减少外部碎片。
    • 通过 延迟释放(Decay-Based Purge) 策略合并空闲内存。
  2. 多线程优化
    • 使用 线程本地缓存(Thread-Specific Cache) 降低锁竞争。
    • 适合 Redis 的 多连接长生命周期 场景。
  3. 稳定性
    • 长期运行后内存利用率仍保持较高水平。

优点

  • 高并发下性能优异,尤其适合 频繁分配/释放中型对象(如 Redis 的键值对)
  • 内存碎片率显著低于 libc。

缺点

  • 小对象(<4KB)分配速度略低于 tcmalloc(因 tcmalloc 的线程缓存更激进)。

libc(glibc malloc)

简介

  • 操作系统默认的通用内存分配器(如 glibc 的 ptmalloc2)。

核心问题

  1. 性能瓶颈
    • 依赖全局锁,多线程竞争时性能骤降
  2. 内存碎片
    • 频繁分配/释放后易产生碎片,导致 内存浪费OOM
  3. 扩展性差
    • 无法有效利用多核 CPU。

使用场景

  • 仅推荐用于 低并发、短生命周期进程(如命令行工具)。

优点

  • 无需额外依赖,兼容性极佳。

缺点

  • 绝对禁止用于 Redis 等高性能服务

tcmalloc(Thread-Caching Malloc)

简介

  • Google 开发,专为高并发场景优化(如 Golang 运行时默认使用)。

核心特点

  1. 线程本地缓存
    • 小对象(<256KB)分配无锁吞吐量极高
  2. 中央堆管理
    • 大对象直接由全局堆分配,减少碎片。
  3. 动态调整
    • 自动收缩线程缓存,避免内存浪费。

优点

  • 小对象分配性能碾压 jemalloc(适合海量微请求场景)。
  • 短生命周期对象 更友好(如 HTTP 请求临时内存)。

缺点

  • 长期运行可能积累碎片(需定期重启或手动释放)。
  • 内存利用率略低于 jemalloc(因线程缓存冗余)。

使用场景

  • 替代 jemalloc 的场景:
    • 需要极致并发性能(如每秒百万级分配)。
    • 对象生命周期短(如微服务、Go 程序)。

tcmalloc vs jemalloc 核心区别总结

对比维度 tcmalloc jemalloc
设计目标 极致高并发的小对象分配速度 平衡性能与内存碎片控制
线程缓存上限 单线程可缓存 ≤256KB 内存块 默认单线程缓存 ≤32KB(可配置调大)
小对象分配 <256KB 完全无锁,速度极快 <32KB 无锁,更大对象需竞争 Arena
大对象处理 ≥256KB 走中央堆(全局锁) 通过多 Arena 分区减少竞争
内存碎片控制 较差(依赖定期回收线程缓存) 优秀(延迟合并策略 + 主动碎片整理)
适用对象大小 <256KB 短生命周期小对象(如 HTTP 请求) 32KB~1MB 中长期对象(如数据库键值)
典型场景 Go 微服务、高频短生命周期任务 Redis、MySQL、RocksDB 等长期运行服务
内存利用率 较低(线程缓存更激进) 较高(严格限制缓存膨胀)
配置调优 TCMALLOC_MAX_THREAD_CACHE_BYTES MALLOC_CONF="lg_tcache_max:18"
一句话总结 “速度疯魔”,适合瞬发小对象 “稳定至上”,适合扛压的中大型对象

  1. 选 tcmalloc:是 高频、超小对象(<256KB)、能容忍内存浪费(如 Go 程序)。
  2. 选 jemalloc:需要 长期稳定运行、处理中型对象、严格控制碎片(如 Redis/数据库)。

Redis 内存分配器的选择

Redis 默认使用 jemalloc,兼顾性能和碎片化控制,是推荐的内存分配器。

选择其他分配器步骤

  1. 更改编译选项
    • 在 Redis 源码目录下,编译时指定 MALLOC 参数即可选择内存分配器。
    • 示例:
      make MALLOC=libc      # 使用 libc malloc
      make MALLOC=tcmalloc  # 使用 tcmalloc
      
  2. 查看当前分配器
    • 编译完成后,通过 ldd redis-server 命令查看当前分配器:
      ldd redis-server | grep malloc
      

jemalloc 的优势

作为默认分配器,jemalloc 在 Redis 中表现优异,具体优势包括:

  • jemalloc 的优势体现在减小内存碎片方面。
  • jemalloc 在 64 位系统中,将内存空间划分为小、大、巨大三个范围
  • 每个范围内又划分了许多小的内存块单位
  • 当 Redis 存储数据时,会选择大小最合适的内存块进行存储。size单位是字节。

r-4-1.png

1. 减少内存碎片

  • jemalloc 按内存块大小将内存分为不同类别(class),分配时选择最合适的类别,避免内存碎片化。

2. 线程本地缓存

  • jemalloc 为每个线程维护独立的分配缓存,减少线程间竞争。

3. 内存分配效率高

  • jemalloc 的分配算法更适合 Redis 的高并发特性,可以快速分配和释放内存。

4. 内存统计

  • jemalloc 提供了详细的内存统计工具,可通过 Redis 的 DEBUG 命令查看:
    redis-cli DEBUG malloc-stats
    

Redis 内存管理机制

除了底层的内存分配器,Redis 还采用了多种机制优化内存使用:

内存预分配

  • Redis 会为某些数据结构(如 SDS)预先分配一定的内存,避免频繁的内存分配操作。
  • 优势
    • 提升性能,减少频繁分配和释放内存的开销。
    • 动态扩容:当内存不足时,会按一定比例扩展,减少扩容次数。

内存碎片处理

  • 问题
    • Redis 在分配和释放内存时,会出现碎片化问题。
  • 指标
    • jemalloc 内置了优化机制,通过内存池减少碎片。
    • 提供 INFO MEMORY 命令,可通过 mem_fragmentation_ratio 指标监控内存碎片率。
      mem_fragmentation_ratio:1.05  # 碎片率 1.05,表示有 5% 的碎片
      
      • 低于 1.2:正常。
      • 高于 1.5:可能存在严重碎片,需分析和优化。
  • 优化方案
    • 如果发现碎片率过高(如超过 1.5),可以尝试以下几种优化方法:
      1. 重启 Redis:重启 Redis 会清理内存中的碎片,重新进行内存分配。
      2. 调整 jemalloc 配置:优化 jemalloc 的参数设置,以提高内存使用效率。
      3. 定期清理空闲内存:在 Redis 长时间运行后,可以使用redis-cli memory purge 手动触发 jemalloc 的内存清理操作,减少碎片化。

内存淘汰机制

当 Redis 内存占用达到上限时,会根据设置的淘汰策略移除一些数据。

  • 常见策略
  1. 针对设置了过期时间的键

    • volatile-lru:仅对设置了过期时间的键,淘汰最近最少使用的键。
    • volatile-ttl:仅对设置了过期时间的键,淘汰剩余时间最短的键。
    • volatile-random:仅对设置了过期时间的键,随机淘汰。
    • volatile-lfu:仅对设置了过期时间的键,淘汰访问频率最低的键。
  2. 针对所有键

    • allkeys-lru:对所有键,淘汰最近最少使用的键。
    • allkeys-random:对所有键,随机淘汰。
    • allkeys-lfu:对所有键,淘汰访问频率最低的键。
  3. 无淘汰策略

    • noeviction:不进行淘汰,当内存超限时直接返回错误。

压缩内存结构

  • Redis 内部使用紧凑型数据结构(如 ziplist、intset)来节省内存。
  • 特点
    • 小规模数据(如小列表、小哈希)会使用压缩存储结构,减少内存开销。

Redis 内存优化建议

Redis 内存监控

Redis 提供了多种命令监控内存使用情况:

  1. 查看内存使用情况

    INFO MEMORY
    

    输出示例:

    used_memory:1048576          # 已使用的内存
    used_memory_peak:2097152     # 内存峰值
    mem_fragmentation_ratio:1.02 # 内存碎片率
    
  2. 查看键的内存占用

    MEMORY USAGE key
    
  3. 查看内存分配统计
    使用 jemalloc 的 jemalloc.stats.print

    redis-cli -p 6379 debug jemalloc stats
    

开启最大内存限制

  • 配置 maxmemory 参数,避免超出物理内存,报OOM(Out of Memory)错误。

分布式存储

  • 在高内存使用场景下,采用 Redis Cluster 扩展容量。