五:理解内存
1.内存消耗
1)对象内存
redis所有数据均采用keyValue数据类型,每次创建键值对时,至少创建两个类型对象:key对象和value对象,对象内存=sizeof(keys)+sizeof(values)
key对象均为字符串,value对象包括:string、hash、list、set、zset。每种value对象类型根据使用规模不同,占用内存不同
2)缓冲内存
主要包括:客户端缓冲、复制挤压缓冲、AOF缓冲
* 客户端缓冲(所有接入到redis服务器TCP连接的输入输出缓冲区。输入缓冲无法控制,最大为1G,超过将断开连接;输出缓冲通过参数client-output-buffer-limit控制,具体值如下:)
3)内存碎片
redis默认的内存分配器是 jemalloc,可选的分配器还有:glibc、tcmalloc
内存分配器为了更好的管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配。
jemalloc在64位操作系统中将内存空间划分为:小、大、巨大三个范围,每个范围又划分多个小的内存块单位
* 小:[8byte][16byte][32byte]...
* 大:[4KB]
[8KB][12KB]...
* 巨:[4MB][8MB][12MB]...
例:当保存5KB的对象时,会采用8KB的块存储,而剩下的3KB空间就成了空间碎片而不能再分配给其他对象
易出现碎片的场景:
* 频繁做更新操作 (例如对已存在的key频繁做append操作)
* 大量过期键删除 (键对象删除后,释放的空间无法得到充分利用)
碎片空间解决方案:
* 数据对齐 (数据计量采用数字类型或者固定长度字符串)
* 安全重启 (重启节点可以将内存碎片重新整理)
4)子进程内存消耗
主要是指执行AOF/RDB重写时redis创建的子进程内存消耗。
redis执行fork操作产生的子进程内存占用量对外表现为与父进程相同,理论上需要一倍的物理内存来完成重写操作。但linux的写时复制技术,父子进程会共享相同的物理内存页
子进程内存消耗总结:
* redis产生的子进程并不需要消耗1倍的父进程内存,实际消耗根据期间写入命令量来决定,但是依然需要预留出一些内存防止溢出
* 需要设置sysct1 vm.overcommit_memrory=1 允许内核可以分配所有的物理内存,防止redis执行fork时因系统剩余内存不足而失败
* 排查当前系统是否支持并开启THP,如果开启建议关闭
2.内存管理
redis主要通过控制内存上线和回收策略实现内存管理
1)设置内存上限
* 设置configure 设置redis.conf里的maxmemory
* 动态设置 config set maxmemory xxGB
2)内存回收策略
* 删除过期key对象
* 内存溢出控制策略删除key对象
内存溢出控制策略如下:
可以在redis.conf中设置,也可以使用 config set maxmemory-policy {policy}来设置
3.内存优化
1)redisObject
redis存储的所有值对象在内部定义为redisObject,该值对象具体属性有:
2)缩减键值对象
尽量去掉不必要的属性字段
3)共享对象池
redis内部维护[0-9999]的整数对象池,当value属于这个值范围,可以使用使用对象池中的数据。
4)字符串优化
* 字符串结构:redis自实现了字符串结构,包括三个属性(int len:已用字节长度 int free:未用字节长度 char buf[]:字节数组)
* 特点:O(1)时间复杂度;可用于保存字节数组,支持安全的二进制数据存储;
内部实现空间预分配机制,降低内存再分配次数;惰性删除,字符串缩减后的空间不释放,作为预分配空间保留
* 预分配机制:由于存在预分配,数据大量追加后会造成内存碎片率上升
* 字符串重构:json这样的数据可以考虑使用hash来存储
5)编码优化
控制编码类型,多用具有压缩功能的编码类型
6)控制键的数量
不要把redis当做单纯的key-value来使用,适当的可以考虑使用其他类型来存储,控制键的数量,降低内存使用