理解内存
为什么要理解内存呢?
redis所有的数据都存在内存中
如何高效利用内存,实现用更少的内存存更多的数据,从而降低成本
如何统计内存使用?
info memory可以获取内存相关指标,如下:
used_memory:redis分配器分配的内存总量
used_memory_human:以可读格式返回used_memory
used_memory_rss:从操作系统的角度显示redis占用的物理内存总量
used_memory_peak:内存使用的最大值,表示used_memory的峰值
used_memory_peak_human:以可读格式返回used_memory_peak
used_memory_lua:Lua引擎所消耗的内存大小
mem_fragmentation_ratio:used_memory_rss/used_memory比值,表示内存碎片率
mem_allocator:redis所使用的内存分配器,默认为jemalloc
(注:mem_fragmentation_ratio>1出现内存碎片,mem_fragmentation_ratio<1出现操作系统把redis内存Swap到硬盘现象)
内存消耗
自身内存
对象内存(sizeof(keys)+sizeof(values))
缓冲内存
客户端缓冲(所有接入到redis服务器TCP连接的输入输出缓冲,输入缓冲无法控制,最大空间1G,超过将断开连接。输出缓冲通过参数client-output-buffer-limit控制)
普通客户端(除了复制和订阅的客户端之外的所有连接)
从客户端(主节点会为每个从节点单独建立一条连接用于命令复制)
订阅客户端(当使用发布订阅功能时,连接客户端使用单独的输出缓冲区)
复制积压缓冲区(一个可重用的固定大小缓冲区用于实现部分复制功能,repl-backlog-size)
AOF缓冲区(用于redis重写期间保存最近的写入命令)
内存碎片
内存分配策略(小,大,巨大)
容易出现内存碎片的场景(频繁做更新操作、大量过期键删除)
如何解决(数据对齐、安全重启)
子进程消耗(执行AOF/RDB重写时redis创建的子进程内存消耗)
内存管理
设置内存上限(maxmemory)
动态调整内存上限(config set maxmemory 6GB)
内存回收策略
删除到达过期时间的键对象
惰性删除(当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,节省CPU,存在内存泄漏问题)
定时任务删除(自适应算法,键的过期比例、使用快慢两种模式)
内存使用达到maxmemory上限时触发内存溢出控制策略(maxmemory-policy控制)
noeviction:不会删除任何数据,拒绝所有写入操作并返回客户端错误信息
volatile-lru:根据LRU算法删除设置了超时属性的键,直到腾出足够空间为止,如果没有删除的键,回退到noeviction
allkeys-lru:根据LRU算法删除所有键,直到腾出足够空间为止
allkeys-random:随机删除所有键,直到腾出足够空间为止
volatile-random:随机删除过期键,直到腾出足够空间为止
volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据,如果没有,回退到noeviction
内存优化
首先了解一下RedisObject对象
type字段:表示当前对象使用的数据类型
encoding字段:表示redis内部编码类型
lru字段:记录对象最后一次被访问的时间(object idletime {key}查看键的空闲时间)
refcount字段:记录当前对象被引用的次数(object refcount {key}获取当前对象的引用)
*ptr字段:与对象的数据内容有关,如果是整数,直接存储数据,否则表示指向数据的指针
有哪些具体优化手段?
缩减键值对象(key尽可能短,value采用序列化或者压缩算法)
共享对象池(redis内部维护[0-9999]的整数对象池)
注意:当设置maxmemory并启用LRU相关淘汰策略时,redis禁止使用共享对象池;对于ziplist编码的值对象,即使内部数据为整数也无法使用共享对象池
字符串优化
SDS(字符串长度、已用长度、未用长度)
预分配机制(减少字符串频繁修改操作)
字符串重构
编码优化
什么是编码?
具体使用哪种底层数据结构来实现
string--raw
embstr
int
hash----hashtable
ziplist(value<=hash-max-ziplist-value and count(field)<=hash-max-ziplist-entries)
list----linkedlist
ziplist(value<=list-max-ziplist-value and 链表长度<=list-max-ziplist-entries)
quicklist
set-----hashtable
intset(元素为整数 and 集合长度<=set-max-intset-entries)
zset----skiplist
ziplist(value<=zset-max-ziplist-value and 有序集合长度<=zset-max-ziplist-entries)
(hashtable、ziplist、linkedlist、quicklist、intset、skiplist)
注意:小编码可以转大编码,大不能转小
ziplist(所有数据采用线性连续的内存结构,节约内存)
zlbytes:记录整个压缩列表所占字节长度,方便重新调整ziplist空间。类型是int-32,长度为4字节
zltail:记录距离尾节点的偏移量,方便尾节点弹出操作。类型int-32,4字节
zllen:记录压缩链表节点数量,类型是int-16,2字节
entry:记录具体的节点
prev_entry_bytes_length:记录前一个节点所占空间,用于快速定位上一个节点,可实现列表反向迭代
encoding:当前节点编码和长度,前两位表示类型,其余表示长度
contents:节点的值
zlend:记录列表结尾,1字节
intset(存储有序、不重复的整数集)
encoding:表示类型
length:集合元素个数
contents:整数数组,从小到大顺序保存
控制键的数量
针对自己现在使用的模式,分析其内存消耗和可优化的地方。