Redis学习--理解内存
内存消耗
- 内存使用统计
info memory
info memory详细解释
当mem_fragmentation_ratio>1时,说明used_memory_rss-used_memory多出的部分内存并没有用于数据存储,而是被内存碎片所消耗,如果两者相差很大,说明碎片率严重。
当mem_fragmentation_ratio<1时,这种情况一般出现在操作系统把Redis内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘速度远远慢于内存,Redis性能会变得很差,甚至僵死。
-
内存消耗划分
自身内存+对象内存(内存占用最大的一块)+缓冲内存+内存碎片(数据对齐,安全重启)
Redis内存消耗划分 - 子进程内存消耗
1.Redis产生的子进程并不需要消耗1倍的父进程内存,实际消耗根据期间写入命令量决定,但是依然要预留出一些内存防止溢出。
2.需要设置sysctl vm.overcommit_memory=1允许内核可以分配所有的物理内存,防止Redis进程执行fork时因系统剩余内存不足而失败。
3.排查当前系统是否支持并开启THP,如果开启建议关闭,防止copy-onwrite期间内存过度消耗。
内存管理
- 设置内存上限
Redis使用maxmemory参数限制最大可用内存。
服务器分配4个4GB的Redis进程1.用于缓存场景,当超出内存上限maxmemory时使用LRU等删除策略释放空间。
2.防止所用内存超过服务器物理内存。
- 动态调整内存上限
Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。 - 内存回收策略
删除到达过期时间的键对象。
内存使用达到maxmemory上限时触发内存溢出控制策略。
1.删除过期键对象
惰性删除:惰性删除用于当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空
定时任务删除:Redis内部维护一个定时任务,默认每秒运行10次(通过配置hz控制),采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键.
定时任务删除过期键逻辑
- 内存溢出控制策略(Redis支持6种策略)
1)noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作。
2)volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
3)allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
4)allkeys-random:随机删除所有键,直到腾出足够空间为止。
5)volatile-random:随机删除过期键,直到腾出足够空间为止。
6)volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
内存优化
-
redisObject对象
redisObject内部结构 -
缩减键值对象
key长度:如在设计键时,在完整描述业务情况下,键值越短越好。如user:{uid}:friends:notify:{fid}可以简化为u:{uid}:fs:nt:{fid}。
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。
Java常见序列化组件占用内存空间对比
使用GZIP压缩后的json可降低约60%的空间。推荐使用Google的Snappy压缩工具,在特定的压缩率情况下效率远远高于GZIP等传统压缩工具
- 共享对象池
共享对象池是指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。
注:当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池 -
字符串优化
1.字符串结构
字符串结构体SDS
2.预分配机制
字符串内存预分配测试
尽量减少字符串频繁修改操作如append、setrange,改为直接使用set修改字符串,降低预分配带来的内存浪费和内存碎片化。
3.字符串重构
使用字符串和hash结构测试内存表现 - 编码优化
type和encoding对应关系表
hash、list、set、zset内部编码可以按照需要进行配置
ziplist编码:
ziplist内部结构
使用ziplist压缩编码的原则:追求空间和时间的平衡。(降低内存占用)
intset编码:
intset编码是集合(set)类型编码的一种,内部表现为存储有序、不重复的整数集。
intset内部结构 -
控制键的数量
客户端维护哈希分组降低键规模
hash的field可用于记录原始key字符串,方便哈希查找
hash的value保存原始值对象,确保不要超过hash-max-ziplist-value限制。
总结
1)Redis实际内存消耗主要包括:键值对象、缓冲区内存、内存碎片。
2)通过调整maxmemory控制Redis最大可用内存。当内存使用超出时,根据maxmemory-policy控制内存回收策略。
3)内存是相对宝贵的资源,通过合理的优化可以有效地降低内存的使用量,内存优化的思路包括:
- 精简键值对大小,键值字面量精简,使用高效二进制序列化工具。
- 使用对象共享池优化小整数对象。
- 数据优先使用整数,比字符串类型更节省空间。
- 优化字符串使用,避免预分配造成的内存浪费。
- 使用ziplist压缩编码优化hash、list等结构,注重效率和空间的平衡。
- 使用intset编码优化整数集合。
- 使用ziplist编码的hash结构降低小对象链规模。