《redis开发与运维》笔记
一:内存消耗
需要重点关注的指标有:used_memory_rss和used_memory以及它们的比值mem_fragmentation_ratio。
当mem_fragmentation_ratio>1时,说明used_memory_rss-used_memory多出的部分内存并没有用于数据存储,而是被内存碎片所消耗,如果两者相差很大,说明碎片率严重。
当mem_fragmentation_ratio<1时,这种情况一般出现在操作系统把Redis内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘速度远远慢于内存,Redis性能会变得很差,甚至僵死。
内存消耗分析
Redis进程内消耗主要包括:自身内存,对象内存,缓冲内存,内存碎片,
其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右。
缓存内存:客户端缓存区,复制积压缓存区,AOF缓存区
子进程的内存消耗
子进程内存消耗主要指执行AOF/RDB重写时Redis创建的子进程内存消耗。
Redis产生的子进程并不需要消耗1倍的父进程内存,实际消耗根据期间写入命令量决定,但是依然要预留出一些内存防止溢出。
需要设置sysctl vm.overcommit_memory=1允许内核可以分配所有的物理内存,防止Redis进程执行fork时因系统剩余内存不足而失败。
排查当前系统是否支持并开启THP,如果开启建议关闭,防止copy-on-write期间内存过度消耗。
二:内存管理
Redis主要通过控制内存上限和回收策略实现内存管理。
控制内存上限:Redis使用maxmemory参数限制最大可用内存。
主要目的有:用于缓存场景,当超出内存上线的时候使用LRU等删除策略释放内存。防止内存超过物理内存。
需要注意,maxmemory限制的是Redis实际使用的内存量。
动态控制内存上限:Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。
Redis-1>config set maxmemory 6GB
Redis-2>config set maxmemory 2G
内存回收策略:
删除到达过期时间的键对象:惰性删除(用到时检测然后删除),定时任务删除
内存使用达到maxmemory上限时触发内存溢出控制策略:6种策略
三:内存优化
1,redisObject对象
redis存储的数据都使用redisObject来封装,包括String,hash,list,set,zset在内的所有数据类型。
type字段:表示对象使用的数据类型(type{key}查看)
encoding字段:表示内部编码类型
lru字段:记录对象最后一次被访问的时间,当配置了maxmemory和maxmemory-policy=volatile-lru或者allkeys-lru时,用于辅助LRU算法删除键数据。可以使用object idletime{key}命令在不更新lru字段情况下查看当前键的空闲时间。
refcount字段:记录对象被引用的次数
ptr字段:与对象的数据内容相关,如果是整数,直接存储数据;否则表示指向数据的指针(高并发写入场景中,在条件允许的情况下,建议字符串长度控制在39字节以内,减少创建redisObject内存分配次数,从而提高性能)
2,缩减键值对象
降低内存使用最直接的方式就是缩减键值的长度
key:越短越好
value:可以转换为二进制数组存放
3,共享对象池
定义:共享对象池是指redis内部维护【0-9999】的整数对象池,用于节约内存,除了整数值的对象,其他类型的元素也可以使用整数对象池。
只能查看:整数对象池在Redis中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount命令查看对象引用数验证是否启用整数对象池技术,
冲突:当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池
冲突原因:LRU算法需要获取对象最后被访问时间,以便淘汰最长未访问数据,每个对象最后访问时间存储在redisObject对象的lru字段。对象共享意味着多个引用共享同一个redisObject,这时lru字段也会被共享,导致无法获取每个对象的最后访问时间。如果没有设置maxmemory,直到内存被用尽Redis也不会触发内存回收,所以共享对象池可以正常工作
不能用:对于ziplist编码的值对象,即使内部数据为整数也无法使用共享对象池,因为ziplist使用压缩且内存连续的结构,对象共享判断成本过高。
4,字符串优化
字符串是最常用的数据类型,所有键都是字符串类型。
4.1 字符串结构
redis自己实现了字符串结构
Redis自身实现的字符串结构有如下特点:
·O(1)时间复杂度获取:字符串长度、已用长度、未用长度。
·可用于保存字节数组,支持安全的二进制数据存储。
·内部实现空间预分配机制,降低内存再分配次数。
·惰性删除机制,字符串缩减后的空间不释放,作为预分配空间保留。
4.2 预分配机制
字符串之所以采用预分配的方式是防止修改操作需要不断重分配内存和字节数据拷贝。但同样也会造成内存的浪费。字符串预分配每次并不都是翻倍扩容,空间预分配规则如下:
1)第一次创建len属性等于数据实际大小,free等于0,不做预分配。
2)修改后如果已有free空间不够且数据小于1M,每次预分配一倍容量。如原有len=60byte,free=0,再追加60byte,预分配120byte,总占用空间:60byte+60byte+120byte+1byte。
3)修改后如果已有free空间不够且数据大于1MB,每次预分配1MB数据。如原有len=30MB,free=0,当再追加100byte,预分配1MB,总占用空间:1MB+100byte+1MB+1byte。
所以尽量减少字符串的操作,比如append等,改为用set修改字符串
4.3 字符串重构
定义:不一定把每份数据当做字符串整天存储,像json一样的可以用hash结构,使用二级结构存储节省空间。
5,编码优化
编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换
理解编码转换流程和相关配置之后,可以使用config set命令设置编码相关参数来满足使用压缩编码的条件。对于已经采用非压缩编码类型的数据如hashtable、linkedlist等,设置参数后即使数据满足压缩编码条件,Redis也不会做转换,需要重启Redis重新加载数据才能完成转换。
6,控制键的数量
通过在客户端预估键规模,把大量键分组映射到多个hash结构中。
重点:
1)Redis实际内存消耗主要包括:键值对象、缓冲区内存、内存碎片。
2)通过调整maxmemory控制Redis最大可用内存。当内存使用超出时,
根据maxmemory-policy控制内存回收策略。
3)内存是相对宝贵的资源,通过合理的优化可以有效地降低内存的使
用量,内存优化的思路包括:
·精简键值对大小,键值字面量精简,使用高效二进制序列化工具。
·使用对象共享池优化小整数对象。
·数据优先使用整数,比字符串类型更节省空间。
·优化字符串使用,避免预分配造成的内存浪费。
·使用ziplist压缩编码优化hash、list等结构,注重效率和空间的平衡。
·使用intset编码优化整数集合。
·使用ziplist编码的hash结构降低小对象链规模。