赞
踩
1、内存使用统计
可通过执行info memory命令获取内存相关指标。
下表为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 |
2、内存消耗划分
Redis进程内消耗主要包括:自身内存+对象内存+缓冲内存+内存碎片,其中Redis空进程自身内存消耗非常少,通常used_memory_rss在3MB左右,used_memory在800KB左右,一个空的Redis进程消耗内存可以忽略不计。
另外三种内存消耗:
1)对象内存
2)缓存内存
3)内存碎片
出现高内存碎片问题时常见的解决方式如下:
3、子进程内存消耗
子进程内存消耗总结如下:
4、设置内存上限
Redis主要通过控制内存上限和回收策略实现内存管理。
Redis使用maxmemory参数限制最大可用内存。限制内存的目的主要有:
需要注意,maxmemory限制的是Redis实际使用的内存量,也就是used_memory统计项对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。
通过设置内存上限可以非常方便地实现一台服务器部署多个Redis进程的内存控制。
得益于Redis单线程架构和内存限制机制,即使没有采用虚拟化,不同的Redis进程之间也可以很好地实现CPU和内存的隔离性。
5、动态调整内存上限
Redis的内存上限可以通过config set maxmemory进行动态修改,即修改最大可用内存。
Redis-1> config set maxmemory 6GB
Redis-2> config set maxmemory 2GB
6、内存回收策略
Redis的内存回收机制主要体现在以下两个方面:
1)删除到达过期时间的键对象。
2)内存使用达到maxmemory上限时触发内存溢出控制策略。
7、内存优化-redisObject对象
8、内存优化-缩减键值对象
降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。
key长度: 如在设计键时,在完整描述业务情况下,键值越短越好。如 user:{uid}:friends:notify:{fid}可以简化为u:{uid}:fs:nt:{fid}。
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis。
值对象除了存储二进制数据之外,通常还会使用通用格式存储数据,比如:json、xml等作为字符串存储在Redis中。这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json、xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。
当频繁压缩解压json等文本数据时,开发人员需要考虑压缩速度和计算开销成本,这里推荐使用Google的Snappy压缩工具,在特定的压缩率情况下效率远远高于GZIP等传统压缩工具,且支持所有主流语言环境。
9、内存优化-共享对象池
整数对象池在Redis 中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改。可以通过object refcount命令查看对象引用数验证是否启用整数对象池技术。
// 设置键foo等于100时,直接使用共享池内整数对象,因此引用数是2,再设置键bar等于100时,引用数又变为3
redis> set foo 100
OK
redis> object refcount foo
(integer) 2
redis> set bar 100
OK
redis> object refcount bar
(integer) 3
需要注意的是对象池并不是只要存储[0-9999]的整数就可以工作。当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池。
为什么开启maxmemory和LRU淘汰策略后对象池无效?
为什么只有整数对象池?
10、内存优化-字符串优化
所有的键都是字符串类型,值对象数据除了整数之外都使用字符串存储。
1)字符串结构
2)预分配机制
3)字符串重构
11、内存优化-编码优化
1)了解编码
编码不同将直接影响数据的内存占用和读写效率。使用object encoding{key}命令获取编码类型。
type和encoding对应关系如下表:
类型 | 编码方式 | 数据结构 |
---|---|---|
string | raw | 动态字符串编码 |
string | embstr | 优化内存分配的字符串编码 |
string | int | 整数编码 |
hash | hashtable | 散列表编码 |
hash | ziplist | 压缩列表编码 |
list | linkedlist | 双向链表编码 |
list | ziplist | 压缩列表编码 |
list | quicklist | 3.2版本新的列表编码 |
set | hashtable | 散列表编码 |
set | intset | 整数集合编码 |
zset | skiplist | 跳跃表编码 |
zset | ziplist | 压缩列表编码 |
2)控制编码类型
编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。
Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU,得不偿失。
hash、list、set、zset内部编码配置如下表:
类型 | 编码 | 决定条件 |
---|---|---|
hash | ziplist | 满足所有条件:value最大空间(字节)<=hash-max-ziplist-value field个数<=hash-max-ziplist-entries |
hash | hashtable | 满足任意条件:value最大空间(字节)>hash-max-ziplist-value field个数>hash-max-ziplist-entries |
list | ziplist | 满足所有条件:value最大空间(字节)<=list-max-ziplist-value 链表长度<=list-max-ziplist-entries |
list | linkedlist | 满足任意条件:value最大空间(字节)>list-max-ziplist-value 链表长度>list-max-ziplist-entries |
list | quicklist | 3.2版本新编码: 废弃 list-max-ziplist-entries和list-max-ziplist-entries 配置 使用新配置: list-max-ziplist-size:表示最大压缩空间或长度 最大空间使用[-5-1]范围配置,默认-2表示8KB 正整数表示最大压缩长度 list-compress-depth:表示最大压缩深度,默认=0不压缩 |
set | intset | 满足所有条件: 元素必须为整数 集合长度<=set-max-intlist-entries |
set | hashtable | 满足任意条件: 元素非整数类型 集合长度>hash-max-ziplist-entries |
zset | ziplist | 满足所有条件: value最大空间(字节)<=zset-max-ziplist-value 有序集合长度<=zset-max-ziplist-entries |
zset | skiplist | 满足任意条件: value最大空间(字节)>zset-max-ziplist-value 有序集合长度>zset-max-ziplist-entries |
3)ziplist编码
ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。
ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。
首先从ziplist编码结构开始分析,它的内部结构类似这样: <…> 。
一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组)。
ziplist结构字段含义:
ziplist数据结构的特点:
使用ziplist可以分别作为hash、list、zset数据类型实现。使用ziplist编码类型可以大幅降低内存占用。ziplist实现的数据类型相比原生结构,命令操作更加耗时,不同类型耗时排序:list<hash<zset。
ziplist压缩编码的性能表现跟值长度和元素个数密切相关,正因为如此Redis提供了{type}-max-ziplist-value和{type}-max-ziplist-entries相关参数来做控制ziplist编码转换。最后再次强调使用ziplist压缩编码的原则:追求空间和时间的平衡。
针对性能要求较高的场景使用ziplist,建议长度不要超过1000 ,每个元素大小控制在512字节以内。
命令平均耗时使用info Commandstats命令获取,包含每个命令调用次数、总耗时、平均耗时,单位为微秒。
3)intset编码
intset编码是集合(set)类型编码的一种,内部表现为存储有序、不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。
intset的字段结构含义:
intset保存的整数类型根据长度划分,当保存的整数超出当前类型时,将会触发自动升级操作且升级后不再做回退。升级操作将会导致重新申请内存空间,把原有数据按转换类型后拷贝到新数组。
使用intset编码的集合时,尽量保持整数范围一致,如都在int-16范围内。防止个别大整数触发集合升级操作,产生内存浪费。
当使用整数集合时尽量使用intset编码。
使用ziplist编码的hash类型依然 比使用hashtable编码的集合节省大量内存。
12、内存优化-控制键的数量
当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis本质是一个数据结构服务器,它为我们提供多种数据结构,如hash、list、set、zset等。使用Redis时不要进入一个误区,大量使用get/set这样的API,把Redis当成Memcached使用。对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。
hash结构降低键数量分析:
同样的数据使用ziplist编码的hash类型存储比string类型节约内存。节省内存量随着value空间的减少越来越明显。hash-ziplist类型比string类型写入耗时,但随着value空间的减少,耗时逐渐降低。
使用hash重构后节省内存量这种内存优化技巧的关键点:
关于hash键和field键的设计:
当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键。如:key=1948480哈希key=group#️⃣1948,哈希field=480。
当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值。
尽量减少hash键和field的长度,如使用部分键内容。
客户端需要预估键的规模并设计hash分组规则,加重客户端开发成本。
hash重构后所有的键无法再使用超时(expire)和LRU淘汰机制自动删除,需要手动维护删除。
对于大对象,如1KB以上的对象,使用hash-ziplist结构控制键数量反而得不偿失。
对于大量小对象的存储场景,非常适合使用ziplist编码的hash类型控制键的规模来降低内存。
使用ziplist+hash优化keys后,如果想使用超时删除功能,开发人员可以存储每个对象写入的时间,再通过定时任务使用hscan命令扫描数据,找出hash内超时的数据项删除即可。
13、【汇总】内存优化的思路包括:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。