
内存相关策略和内存碎片1、maxmemory配置2、数据淘汰策略3、LRU算法4、LFU算法5、数据过期删除策略6、主从架构和数据持久化对数据的过期处理7、内存碎片7.1、Redis内存碎片的形成7.2、内存使用情况7.3、清理内存碎片1、maxmemory配置maxmemory配置项用于为Redis数据集设置可使用的最大内存量。用户可以使用redis.conf文件来设置这个配置项或者使用config set命令来直接设置。例如为了配置100 MB的内存使用量可以在redis.conf文件内部使用以下命令// 直接修改redis.conf文件 maxmemory 100mb // 直接连接Redis执行如下命令 configsetmaxmemory 100mb // 通过命令查看当前配置的最大内存量 config get maxmemory如果把maxmemory设置为零则表示没有内存使用量的限制这是64位系统的默认设置而32位系统默认的内存使用限制为3GB。当达到设置的内存使用量限制时可以选择不同的操作策略Redis可能只返回有关命令的错误信息或者淘汰某些旧数据。2、数据淘汰策略当内存使用量超过了maxmemory配置的限制时Redis可以使用以下策略来淘汰数据noeviction默认策略当达到内存使用限制且客户端尝试执行可能会使用更多内存的命令时返回错误。volatile-lru删除设置了过期时间且最近最少使用的键LRU淘汰算法。allkeys-lru删除所有最近最少使用的键LRU淘汰算法。volatile-lfu删除设置了过期时间且最不经常使用的键LFU淘汰算法。allkeys-lfu删除所有最不经常使用的键LFU淘汰算法。volatile-random随机淘汰设置了过期时间的键。allkeys-random随机淘汰所有键。volatile-ttl根据过期时间淘汰设置了过期时间的键越早过期越早淘汰。3、LRU算法LRULeast Recently Used最近最少使用算法是根据数据在一段时间内是否被使用的记录来淘汰数据该算法的核心思想是如果数据最近被使用那么将来被使用的概率更高。可以把LRU算法理解为一个链表会将新数据放在链表头部在一段时间内只要数据被使用就移到链表头部链表满的时候就从链表尾部移出数据即链表尾部的数据被淘汰。Redis中使用的算法近似于LRU算法比如修改配置文件maxmemory–samples5表示在Redis中随机采样5个键然后从中淘汰最少使用的键因此采样键的数量与Redis库中键的数量越接近淘汰的规则就越接近LRU算法。官方默认使用5个键最多不超过10个键越大就越消耗CPU的资源。采用LRU算法如果一个热点数据在内存在LRU算法约定的时间段内没有被使用非热点数据在这个时间段内反而被使用了就可能误把热点数据淘汰了而留下非热点数据因此在Redis 4版本后新增了LFU算法来解决这类问题。4、LFU算法LFULeast Frequently Used最近不常使用算法根据数据使用的次数来淘汰数据该算法的核心思想是如果数据过去被使用多次那么将来被使用的频率更高。LFU算法反映了一个键的热度不会像LRU算法中那样偶尔一次被使用就被误认为是热点数据。同样可以把LFU算法理解为一个链表新数据放在链表尾部链表中的数据按照被使用的次数降序排列被使用次数相同的数据按最近使用的时间降序排列链表满的时候从链表尾部移出数据。这样就避免了LRU算法可能存在的问题。需要注意的是LFU算法中数据使用的时间和使用次数(counter)。counter的衰减和两个配置有关。如果一个键长时间没有被使用counter就会按衰减因子的值来减少在衰减因子为1的情况下N分钟没有访问就要减N。// redis.conf文件 lfu-log-factor10// 概率因子 lfu-decay-time1// 衰减因子从下图可以看出默认server.lfu_log_factor为10的情况下8 bits的counter可以表示100万的访问频率。5、数据过期删除策略当内存没占满时在Redis中过期的键是通过惰性删除和定期删除来进行优化的。惰性删除当键被访问时检查该键的过期时间如果过期就删除此键。未过期但是没有被访问的键仍存放在内存中自然就继续占用内存资源。定期删除每隔一段时间随机检查设置了过期的键并删除已过期的键。Redis每10秒进行一次过期扫描。第一步随机取20个设置了过期策略的键。第二步检查这20个键删除其中已过期的键。第三步如果有超过25%的键已过期则重复第一步。// redis.conf文件默认是10秒 hz 106、主从架构和数据持久化对数据的过期处理在RDB持久化模式中采用全量持久化所以在持久化的时候会过滤掉已经过期的键。在AOF持久化模式中当出现键过期时就会给AOF文件发送删除命令。需要注意的是在主从架构中从节点从来不会主动删除过期的数据。为了保证数据一致性主节点会发送删除命令给从节点来处理已经过期的键。7、内存碎片我们在实际使用Redis中可能会发现一个问题删除数据后内存占用率依然很高。这是因为删除数据后Redis释放的内存空间会由内存分配器管理并不会立即返回给操作系统所以操作系统仍然记录着给Redis分配了大量内存。这往往还伴随着一个潜在的风险Redis释放的内存空间可能并不是连续的这些不连续的内存空间很可能处于一种闲置的状态。这就会导致Redis无法使用这些释放的空闲空间来保存数据这种情况不仅会减少Redis可保存的数据量还会降低Redis对机器内存的使用率。7.1、Redis内存碎片的形成Redis内存碎片的形成可能由两方面引起一个是操作系统的内存分配机制另一个是Redis的负载特征。内存分配机制内存分配器的分配策略决定了操作系统无法做到按需分配。这是因为内存分配器一般是按固定大小来分配内存的而不是完全按照应用程序申请的内存空间大小来给程序分配内存的。Redis可以使用libc、jemalloc、tcmalloc多种内存分配器来分配内存默认使用jemalloc。jemalloc的分配策略之一是按照固定的大小划分内存空间比如8字节、16字节、32字节、48字节等。当程序申请的内存大小最接近某个固定值时jemalloc会给它分配相应大小的空间。如果Redis每次向分配器申请的内存大小不一样那么这种分配方式就会形成内存碎片。Redis的负载特征在业务中我们会对键值对进行修改和删除等操作产生对内存空间的申请和释放操作。如果修改后的键值对变大或变小了就需要占用额外的内存空间或者释放不用的内存空间或者删除的键值对不再需要内存空间了此时会把内存空间释放出来形成空闲的内存空间。7.2、内存使用情况可以用info memory命令来查看Redis的内存状态执行结果如图所示。// Redis为了保存数据实际申请使用的内存空间单位为字节 used_memory // 用户数据所占用的内存空间就是缓存数据的大小 used_memory_human // 操作系统实际分配给Redis的物理内存空间里面就包含了内存碎片 used_memory_rss // Redis内存使用的峰值 used_memory_peak // 用户缓存数据的峰值大小 used_memory_peak_human // 执行Lua脚本所占用的内存空间 used_memory_lua // 内存碎片率 mem_fragmentation_ratio内存碎片率的计算公式如下内存碎片率mem_fragmentation_ratio大于1且小于1.5是比较合理的造成内存碎片的原因是无法避免的。mem_fragmentation_ratio大于1.5时表明内存碎片率超过了50%这时就需要采取一些措施来降低内存碎片率。7.3、清理内存碎片在Redis 4版本之前我们只能重启Redis服务但是这有可能出现下面两个问题如果Redis中的数据没有持久化那么重启之后数据会丢失。即使Redis数据持久化了也需要通过AOF或RDB进行恢复恢复时间比较长那么恢复阶段将无法提供服务。相关参数如下activedefrag no是否启用碎片整理默认是no。active-defrag-ignore-bytes 100mb内存碎片的字节数达到100MB时开始清理碎片。active-defrag-threshold-lower 10内存碎片空间占操作系统分配给Redis的总内存空间的比例达到10%时开始清理碎片。active-defrag-cycle-min 1自动清理过程所用CPU时间的比例不低于1%以保证能正常清理。active-defrag-cycle-max 25自动清理过程所用CPU时间的比例不高于25%一旦超过就停止清理从而避免在清理时大量的内存复制阻塞了Redis导致Redis服务响应缓慢。