0%

Redis内存杂谈

Redis持久化RDB方式,是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后再替换之前的文件,用二进制压缩存储。

一、内存分配

  1. Redis在编译时便会指定内存分配器,内存分配器可以是libc、jemalloc、tcmalloc,默认是libc(Mac下)。
    • libc:泛指,凡是符合实现了C标准规定的内容都是一种libc,如glic。
      • Redis默认内存分配器(Mac下)
      • glibc是Linux下C标准库的实现,全称GNU C Library。glibc本身是GNU旗下的C标准库,后来逐渐成为了Linux的标准C库,而Linux原来的标准C库libc逐渐不再被维护。
        • GNU计划,有译为“革奴计划”,是由理查德·斯托曼在1983年9月27日公开发起的自由软件集体协作计划,它的目标是创建一套完全自由的操作系统GNU。
    • jemalloc:FreeBSD的内存分配器,最大优势在于多线程情况下的高性能以及内存碎片的减少,在Facebook有广泛应用。
      • 安装时可指定make MALLOC=jemalloc
    • tcmalloc:google开发的内存分配器,据称它的内存分配速度是glibc2.3中实现的malloc的数倍。
      • 安装时可指定(Mac下)
        • brew install gperftools
        • make MALLOC=tcmalloc
  1. 源码README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Allocator
---------

Selecting a non-default memory allocator when building Redis is done by setting
the `MALLOC` environment variable. Redis is compiled and linked against libc
malloc by default, with the exception of jemalloc being the default on Linux
systems. This default was picked because jemalloc has proven to have fewer
fragmentation problems than libc malloc.

To force compiling against libc malloc, use:

% make MALLOC=libc

To compile against jemalloc on Mac OS X systems, use:

% make MALLOC=jemalloc
  1. 使用

    • 同时安装三个类型分配器,端口依次为6379、7379、8379
    • 使用info memory查看信息(6.2.5),传送门
      • used_memory:由Redis分配器分配的内存总量,包含了redis进程内部的开销和数据占用的内存,以字节为单位。
      • used_memory_human:以人类更直观的单位展示分配的内存总量。
      • used_memory_rss:向操作系统申请的内存大小,值一般大于used_memory,因为Redis的内存分配器会产生内存碎片。
      • used_memory_rss_human:以人类更直观的单位展示向操作系统申请的内存大小。
      • used_memory_peak:Redis的内存消耗峰值,以字节为单位。
      • used_memory_peak_human:以人类更直观的单位展示内存消耗峰值。
      • used_memory_peak_perc:使用内存达到峰值内存的百分比,即(used_memory/used_memory_peak)*100%
      • used_memory_overhead:Redis维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制backlog等。
      • used_memory_startup:服务启动时消耗的内存。
      • used_memory_dataset:数据占用的内存大小,即used_memory-used_memory_overhead
      • used_memory_dataset_perc:数据占用的内存大小的百分比,(used_memory_dataset/(used_memory-used_memory_startup))*100%
      • allocator_allocated:Total bytes allocated form the allocator, including internal-fragmentation. Normally the same as used_memory.
      • allocator_active:Total bytes in the allocator active pages, this includes external-fragmentation.
      • allocator_resident:Total bytes resident (RSS) in the allocator, this includes pages that can be released to the OS (by MEMORY PURGE, or just waiting).
      • total_system_memory:系统内存总量,以字节为单位。
      • total_system_memory_human:以人类更直观的单位展示系统内存总量。
      • used_memory_lua:Lua脚本存储占用的内存,以字节为单位。
      • used_memory_lua_human:Lua脚本存储占用的内存,以MB/KB为单位。
      • used_memory_scripts:Number of bytes used by cached Lua scripts.
      • used_memory_scripts_human:Human readable representation of previous value
      • number_of_cached_scripts:
      • maxmemory:Redis实例的最大内存配置,设置的最大内存。
      • maxmemory_human:Redis实例的最大内存配置,设置的最大内存。
      • maxmemory_policy:当达到maxmemory时的淘汰策略。
      • allocator_frag_ratio:Ratio between allocator_active and allocator_allocated. This is the true (external) fragmentation metric (not mem_fragmentation_ratio).
      • allocator_frag_bytes:Delta between allocator_active and allocator_allocated. See note about mem_fragmentation_bytes.
      • allocator_rss_ratio:Ratio between allocator_resident and allocator_active. This usually indicates pages that the allocator can and probably will soon release back to the OS.
      • allocator_rss_bytes:Delta between allocator_resident and allocator_active
      • rss_overhead_ratio:Ratio between used_memory_rss (the process RSS) and allocator_resident. This includes RSS overheads that are not allocator or heap related.
      • rss_overhead_bytes:Delta between used_memory_rss (the process RSS) and allocator_resident.
      • mem_fragmentation_ratio:碎片率,约等于used_memory_rss/used_memory
      • mem_fragmentation_bytes: Delta between used_memory_rss and used_memory. Note that when the total fragmentation bytes is low (few megabytes), a high ratio (e.g. 1.5 and above) is not an indication of an issue.
      • mem_not_counted_for_evict:
      • mem_replication_backlog:
      • mem_clients_slaves:
      • mem_clients_normal:
      • mem_aof_buffer:
      • mem_allocator:内存分配器。
      • active_defrag_running:0表示没有活动的defrag任务正在运行,1则相反(defrag意为内存碎片整理)。
      • lazyfree_pending_objects:0表示不存在延迟释放的挂起对象,1则相反。
      • lazyfreed_objects:
    参数 libc jemalloc tcmalloc
    used_memory 1125632 1068768 1074984
    used_memory_human 1.07M 1.02M 1.03M
    used_memory_rss 2105344 2621440 2367488
    used_memory_rss_human 2.01M 2.50M 2.26M
    used_memory_peak 1186016 1128640 1139000
    used_memory_peak_human 1.13M 1.08M 1.09M
    used_memory_peak_perc 94.91% 94.70% 94.38%
    used_memory_overhead 1080032 1027264 1033472
    used_memory_startup 1062592 1006664 1012976
    used_memory_dataset 45600 41504 41512
    used_memory_dataset_perc 72.34% 66.83% 66.95%
    allocator_allocated 1080512 1237680 1033952
    allocator_active 2067456 1581056 2329600
    allocator_resident 2067456 4722688 2329600
    total_system_memory 17179869184 17179869184 17179869184
    total_system_memory_human 16.00G 16.00G 16.00G
    used_memory_lua 37888 37888 37888
    used_memory_lua_human 37.00K 37.00K 37.00K
    used_memory_scripts 0 0 0
    used_memory_scripts_human 0B 0B 0B
    number_of_cached_scripts 0 0 0
    maxmemory 0 0 0
    maxmemory_human 0B 0B 0B
    maxmemory_policy noeviction noeviction noeviction
    allocator_frag_ratio 1.91 1.28 2.25
    allocator_frag_bytes 986944 343376 1295648
    allocator_rss_ratio 1.00 2.99 1.00
    allocator_rss_bytes 0 3141632 0
    rss_overhead_ratio 1.02 0.56 1.02
    rss_overhead_bytes 37888 -2101248 37888
    mem_fragmentation_ratio 1.95 2.55 2.29
    mem_fragmentation_bytes 1024832 1593696 1333536
    mem_not_counted_for_evict 0 0 0
    mem_replication_backlog 0 0 0
    mem_clients_slaves 0 0 0
    mem_clients_normal 17440 20496 20496
    mem_aof_buffer 0 0 0
    mem_allocator libc jemalloc-5.1.0 tcmalloc-2.9
    active_defrag_running 0 0 0
    lazyfree_pending_objects 0 0 0
    lazyfreed_objects 0 0 0
  2. 使用redis-benchmark压测对比内存碎片率

    TODO

  3. 参考

二、内存分析管理优化

  1. 启动redis服务后,进入命令模式,输入info memory得到如下结果

    • used_memory: Total number of bytes allocated by Redis using its allocator (either standard libc, jemalloc, or an alternative allocator such as tcmalloc),翻译过来就是分配的内存总量,即存储的所有数据占用的内存。
      • used_memory_human: Human readable representation of previous value,以可读格式返回使用的内存量。
    • used_memory_rss: Number of bytes that Redis allocated as seen by the operating system (a.k.a resident set size). This is the number reported by tools such as top(1) and ps(1),从操作系统角度显示Redis进程占用的物理内存总量(操作系统分配给redis的实际内存大小)。
      • used_memory_rss_human: Human readable representation of previous value,以可读格式返回Redis进程占用的物理内存总量。
    • used_memory_peak: Peak memory consumed by Redis (in bytes),内存使用的最大值,表示used_memory峰值。
      • used_memory_peak_human: Human readable representation of previous value,以可读格式返回内存使用的最大值。
    • used_memory_peak_perc: The percentage of used_memory_peak out of used_memory,使用内存达到峰值内存的百分比,即(used_memory/ used_memory_peak) * 100%
    • used_memory_overhead: The sum in bytes of all overheads that the server allocated for managing its internal data structures,Redis为了维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog。
    • used_memory_startup: Initial amount of memory consumed by Redis at startup in bytes,Redis服务器启动时消耗的内存。
    • used_memory_dataset: The size in bytes of the dataset (used_memory_overhead subtracted from used_memory),数据占用的内存大小,即used_memory - used_memory_overhead
    • used_memory_dataset_perc: The percentage of used_memory_dataset out of the net memory usage (used_memory minus used_memory_startup),数据占用的内存大小的百分比,(used_memory_dataset/(used_memory - used_memory_startup)) * 100%
    • total_system_memory: The total amount of memory that the Redis host has,系统总内存。
      • total_system_memory_human: Human readable representation of previous value,以更直观的可读格式显示整个系统内存。
    • used_memory_lua: Number of bytes used by the Lua engine
      • used_memory_lua_human: Human readable representation of previous value
    • used_memory_scripts: Number of bytes used by cached Lua scripts,Lua脚本存储占用的内存。
      • used_memory_scripts_human: Human readable representation of previous value,以更直观的可读格式显示Lua脚本存储占用的内存。
    • maxmemory: The value of the maxmemory configuration directive,Redis实例的最大内存配置。
      • maxmemory_human: Human readable representation of previous value,以更直观的可读格式显示Redis实例的最大内存配置。
    • maxmemory_policy: The value of the maxmemory-policy configuration directive,当达到maxmemory时的淘汰策略。
    • mem_fragmentation_ratio: Ratio between used_memory_rss and used_memory,内存的碎片率,used_memory_rss/used_memory,4.0版本之后可以使用memory purge手动回收内存。
      • 当mem_fragmentation_ratio>1时,说明used_memory_rss-used_memory多出的部分内存并没有用于数据存储,而是被内存碎片所消耗,如果两者相差很大,说明碎片率严重。
      • 当mem_fragmentation_ratio<1时,这种情况一般出现在操作系统把Redis内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘速度远远慢于内存,Redis性能会变得很差。
    • mem_allocator: Memory allocator, chosen at compile time,内存分配器。
    • active_defrag_running: Flag indicating if active defragmentation is active,表示没有活动的defrag任务正在运行,1表示有活动的defrag任务正在运行(defrag:表示内存碎片整理)。
    • lazyfree_pending_objects: The number of objects waiting to be freed (as a result of calling UNLINK, or FLUSHDB and FLUSHALL with the ASYNC option),表示redis执行lazy free操作,在等待被实际回收内容的键个数。
  2. 分析:Redis进程内的内存主要包括:自身内存 + 对象内存 + 缓冲内存 + 内存碎片,其中自身内存 + 对象内存 + 缓冲内存 = used_memory,操作系统实际分配给redis的内存used_memory_rss - used_memory = 内存碎片

    • 自身内存:消耗很少
    • 对象内存:Redis中最占内存的一块,存储着用户所有数据,包括key和value对象的内存(尽量避免过长的key和合理使用value的类型)。
    • 缓冲内存:分为客户端缓冲、复制积压缓冲区、AOF缓冲区。
      • 客户端缓冲:指的是所有连接到Redis的服务器tcp连接输入输出缓冲,输入缓冲无法控制,最大空间1G;输出缓冲可通过client-output-buffer-limit控制。
        • 普通客户端:client-output-buffer-limit normal 0 0 0,普通客户端默认并没有对输出缓冲区做限制。但是如果当有大量的慢连接客户端接入时,这部分消耗就不能忽略了,因为消费的很慢,在成输出缓冲区数据积压。所以可以设置maxclients做限制。
        • 从客户端:client-output-buffer-limit slave 256mb 64mb 60,主节点会每一个从节点单独建立一条连接用于命令复制。当主节点网络延迟较高或主节点挂载大量的从节点时,这部分内存消耗将占用很大一部分,建议主节点挂载从节点最好不要超过2个。
        • 订阅客户端:client-output-buffer-limit pubsub 32mb 8mb 60,当生产消息的速度快于消费的速度时,输出缓冲区容易积压消息
      • 复制积压缓冲区:一个可重用的固定大小缓冲区用于实现部分复制功能,根据repl-backlog-size参数控制,默认1MB。对于复制积压缓区,主节点有一个,所有从节点啊共享这个缓冲区,因此可以设置较大的值,比如100MB,这部分投入是有价值的,可以有效避免全量复制。
      • AOF缓冲区:用于AOF重写期间保存最近写入的命令,等待被刷到磁盘。
    • 内存碎片:以下场景容易出现高内存碎片问题:
      • 频繁更新,对已经存在的key进行append、set、range操作
      • 大量过期键删除,键对象过期删除后,释放的空间无法得到拆分利用
      • 执行AOF/RDB重写时Redis创建的子进程,也会产生碎片
  3. 内存管理

    • 设置最大可用内存config set maxmemory 6GB和设置内存溢出策略控制config set maxmemory-pol-icy{policy} 动态配置,由于内存碎片实际占用系统内存更大,超过最大可用内存,redis将执行内存溢出控制策略。
      • noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not al-lowed when used memory,此时Redis只响应读操作。
      • volatile-lru:根据LRU(最近最少使用)算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
      • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
      • allkeys-random:随机删除所有键,直到腾出足够空间为止。
      • volatile-random:随机删除过期键,直到腾出足够空间为止。
      • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
    • 删除过期键
      • 惰性删除:当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空。但这种方式下,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。
      • 定时删除:Redis内部维护一个定时任务,默认每秒运行10次,采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键。
  4. 内存优化

    • 缩减键值对象
    • 控制键的数量
    • 字符串优化
    • 编码优化
    • 共享对象池
  5. 参考

三、内存查看

  1. 几种方式

    • 方式一:bigkeys方式:redis-cli -h localhost -p 6379 -a your_passwod --bigkeys -i -i 0.1,使用scan方式对key进行统计不会对redis造成阻塞

      • -------- summary -------以上部分显示了扫描过程

      • -------- summary -------部分

        • 每种数据结构中最大的Key
        • string类型以字节长度为衡量标准,统计较为准确
        • list,set,zset,hash等都是以元素个数作为衡量标准,元素个数多不能说明其占的内存就一定多
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

        # Scanning the entire keyspace to find biggest keys as well as
        # average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
        # per 100 SCAN commands (not usually needed).

        [00.00%] Biggest string found so far '"str"' with 6 bytes
        [00.00%] Biggest stream found so far '"testStream"' with 1 entries
        [00.00%] Biggest string found so far '"testHyperLogLog"' with 24 bytes
        [00.00%] Biggest zset found so far '"myZset"' with 3 members
        [00.00%] Biggest zset found so far '"testZset"' with 4 members
        [00.00%] Biggest set found so far '"testSet"' with 2 members
        [00.00%] Biggest list found so far '"list"' with 2 items

        -------- summary -------

        Sampled 8 keys in the keyspace!
        Total key length in bytes is 60 (avg len 7.50)

        Biggest list found '"list"' has 2 items
        Biggest string found '"testHyperLogLog"' has 24 bytes
        Biggest stream found '"testStream"' has 1 entries
        Biggest set found '"testSet"' has 2 members
        Biggest zset found '"testZset"' has 4 members

        1 lists with 2 items (12.50% of keys, avg size 2.00)
        0 hashs with 0 fields (00.00% of keys, avg size 0.00)
        2 strings with 30 bytes (25.00% of keys, avg size 15.00)
        1 streams with 1 entries (12.50% of keys, avg size 1.00)
        1 sets with 2 members (12.50% of keys, avg size 2.00)
        3 zsets with 9 members (37.50% of keys, avg size 3.00)
    • 方式二:debug object key方式,如:debug object testZset(4.0版本以前查看某个key的内存占用方式)

      • Value at:key的内存地址
      • refcount:引用次数
      • encoding:编码类型
      • serializedlength:序列化长度
      • lru_seconds_idle:空闲时间
      1
      Value at:0x7fc51d728ae0 refcount:1 encoding:ziplist serializedlength:47 lru:6556545 lru_seconds_idle:182
    • 方式三:memory usage key方式(4.0版本开始支持)

      • 分配的内存总字节数
      1
      2
      memory usage somekey
      (integer) 221
    • 方式四:rdbtools方式(需要安装)

      • rdb
      • redis-memory-for-key
  2. rdbtools使用

    • 安装

      • pip安装pip install rdbtools python-lzf
      • 源码安装,省略
    • 使用rdb --help

      • rdb转储json rdb -c memory /usr/local/redis/dump.rdb -f ~/Desktop/dump.json
      • 生成内存报告 rdb -c memory /usr/local/redis/dump.rdb -b 10 -t hash -f ~/Desktop/ddd.csv
      1
      2
      3
      4
      5
      6
      7
      8
      database:key在redis的db
      type:key类型
      key:key值
      size_in_bytes:key的内存大小(byte)
      encoding:value的存储编码形式
      num_elements:key中的value的个数
      len_largest_element:key中的value的长度
      expiry:key过期时间
    • 将快照写入csv:rdb -c memory /usr/local/var/db/redis/dump.rdb > ~/Desktop/dump.csv

    • 根据第四列排序:sort -t, -k4nr ~/Desktop/dump.csv -o ~/Desktop/sort.csv

    • 统计key占用内存:awk -F ',' -v sum=0 '{if($3~/^dm/){sum+=$4}}END{print sum}' dump.csv

    • 查看具体某个key的内存情况 redis-memory-for-key -s localhost -p 6379 key_name

      • 对于<4.0版本的redis可使用此方法以获得较精确结果
      1
      2
      3
      4
      5
      6
      Key				        key_name
      Bytes 418836.0
      Type hash
      Encoding hashtable
      Number of Elements 12
      Length of Largest Element 73600
  3. 参考

四、过期策略和淘汰机制

  1. 过期策略
    • 定时删除:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
      • 优点:保证内存被尽快释放
      • 缺点:
        • 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
        • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
    • 惰性删除:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期则删除并返回null。
      • 优点:删除操作只发生在取出key的时候,而且只删除当前key,对CPU时间的占用是比较少的
      • 缺点:若大量的key在超出超时时间后,很久一段时间内都没有被获取过,那么可能发生内存泄露
    • 定期删除:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key操作
      • 优点:
        • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理”定时删除”的缺点
        • 定期删除过期key–处理”惰性删除”的缺点
      • 缺点
        • 在内存友好方面,不如”定时删除”
        • 在CPU时间友好方面,不如”惰性删除”

总结

  • 定时删除和定期删除为主动删除:Redis会定期主动淘汰一批已过去的key
  • 惰性删除为被动删除:用到的时候才会去检验key是不是已过期,过期就删除
  • 惰性删除+定期删除为redis服务器内置策略
    • 第一、配置redis.conf 的hz选项,默认为10 (即1秒执行10次,100ms一次,值越大说明刷新频率越快,最Redis性能损耗也越大)
    • 第二、配置redis.conf的maxmemory最大值,当已用内存超过maxmemory限定时就会触发主动清理策略
  1. 淘汰机制(主动清理策略)
    • noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息
      • 大多数写命令都会导致占用更多的内存(有极少数会例外, 如DEL)
      • 默认的策略
    • allkeys-lru: 所有key通用
      • 优先删除最近最少使用(less recently used,LRU)的 key。
    • volatile-lru: 只限于设置了 expire 的部分
      • 优先删除最近最少使用(less recently used,LRU)的 key。
    • allkeys-random: 所有key通用
      • 随机删除一部分 key。
    • volatile-random: 只限于设置了 expire 的部分
      • 随机删除一部分 key。
    • volatile-ttl: 只限于设置了 expire 的部分
      • 优先删除剩余时间(time to live,TTL) 短的key。