Redis内存消耗
1.内存使用统计
info memory
属性名 | 属性说明 |
---|---|
used_memory | Redis分配器分配的内存量,也就是实际存储数据的内存总量 |
used_momory_human | 以可读格式返回Redis使用的内存总量 |
used_momory_rss | 从操作系统的角度,Redis进程占用的总物理内存 |
used_memory_peak | 内存分配器分配的最大内存,代表used_memory的历史峰值 |
used_memory_peak_human | 以可读的格式显示内存消耗峰值 |
used_memory_lua | Lua引擎所消耗的内存 |
mem_fragmentation_ratio | used_memory_rss/used_memory比值,表示内存碎片率 |
mem_allocator | Redis所使用的内存分配器,默认:jemalloc |
2.内存消耗划分
used_memory的组成部分
- 自身内存(800K左右)
缓冲内存
- 客户端缓冲区
- 复制缓冲区
- AOF缓冲区
对象内存
- Key对象
- Value对象
- Lua内存
- 内存碎片 = used_memory_rss - used_memory(申请内存的会超过使用内存,内存预留与内存暂未释放)
3.子进程内存消耗
- redis在bgsave与bgrewriteaof时候都会使用fork创建出子进程,fork是copy-on-write的,当发生写操作时,就会复制出一份内存
优化方式
- 去除THP特性:在kernel 2.6.38中新增的特性。这个特性可以加快fork的速度,但是在复制内存页的时候,会比原来的内存页扩大了512倍。例如原来是4K,扩大后为2M。当写入量比较大时,会造成不必要的阻塞与内存的暴增。
- overcommit_memory=1 可以保证fork顺利完成
客户端缓冲区
#查看已连接客户端的基本信息
info clients
字段名 | 含义 |
---|---|
connected_clients | 已连接客户端的数量(不包括通过从属服务器 |
client_longest_output_list | 当前连接的客户端当中,最长的输出列表 |
client_longest_input_buf | 当前连接的客户端当中,最大的输入缓存 |
blocked_clients | 正在等待阻塞命令(BLPOP、BRPOP)的客户端数量 |
#查看所有Redis-Server的所有客户端的详细信息
client list
字段名 | 含义 |
---|---|
id | 客户端连接的唯一标识 |
addr | 客户端的地址和端口 |
fd | 套接字所使用的文件描述符,-1表示该客户端为redis内部客户端 |
age | 以秒计算的已连接时长 |
idle | 以秒计算的空闲时长 |
flags | 客户端flag<br/>O:客户端是monitor模式下的附属节点(slave)<br/>S: 客户端是一般模式(normal)下的附属节点<br/>M:客户端是主节点(master)<br/>x: 客户端正在执行事务<br/>b: 客户端正在等待阻塞事件<br/>i: 客户端正在等待VM I/O操作(已废弃)<br/>d: 一个受监视(watched)的键已被修改,EXEC命令将失败<br/>c: 将恢复完整地写出之后,关闭连接<br/>u: 客户端未被阻塞(unblocked)<br/>A: 尽可能快地关闭连接 N:未设置任何flag |
db | 该客户端正在使用的数据库ID |
sub | 已订阅频道的数量 |
psub | 已订阅模式的数量 |
multi | 在事务中被执行的命令数量 |
qbuf | 查询缓冲区的长度(单位为字节,0表示没有分配查询缓冲区) |
qbuf-free | 查询缓冲区剩余空间的长度(单位为字节,0表示没有剩余空间) |
obl | 输出缓冲区的长度(单位为字节,0表示没有分配输出缓冲区) |
oll | 输出列表包含的对象数量(当输出缓冲区没有剩余空间时候,<br/> 命令回复会以字符串对象的形式被入队到这个队列里。 |
omem | 输出缓冲区和输出列表占用的内存总量 |
events | 文件描述符事件<br/>r: 客户端套接字(在事件loop中)是可读的(readable)<br/>w:客户端套接字(在事件loop中)是可写的(writeable) |
cmd | 最近一次执行的命令 |
一、输入缓冲区
- 含义:各个客户端执行的Redis命令会存储在Redis的输入缓冲区中,由单线程排队执行
- 注意:输入缓冲区最大1GB,超过后会被强制断开,不可动态配置
二、输出缓冲区
client-output-buffer-limit ${class} ${hard limit} ${soft limit} ${soft seconds}
## class 客户端类型
## normal 普通客户端
## slave 从节点用于复制,伪装成客户端
## pubsub 发布订阅客户端
## hardLimit 如果客户端使用的输出缓冲区大于hardLimit,客户端会被立即关闭
## softLimit 如果客户端使用的输出缓冲区超过了softLimit并且持续了softSeconds秒,客户端会立即关闭
## 其中hardLimit与softLimit为0时不做任何限制
1.普通客户端
- 默认配置:client-output-buffer-limit normal 0 0 0
- 注意:需要防止大命令或者monitor,这两种情况会导致输出缓冲区暴增
2.slave客户端
- 默认配置:client-output-buffer-limit slave 256mb 64mb 60
- 建议调大:可能主从延迟较高,或者从节点数量过多时,主从复制容易发生阻塞,缓冲区会快速打满。最终导致全量复制
- 注意:在主从网络中,从节点不要超过2个
3.pubsub客户端
- 默认配置:client-output-buffer-limit pubsub 32mb 8mb 60
- 阻塞原因:生产速度大于消费速度
- 注意:需要根据实际情况进行调试
缓冲内存
1.复制缓冲区
此部分内存独享,默认1MB,考虑部分复制,可以设置更大,可以设置为100MB,牺牲部分内存避免全量复制。
2.AOF缓冲区
无论使用always、everysecs还是no的策略,都会进行刷盘,刷盘前的数据存放在AOF缓冲区中。
另外在AOF重写期间,会分配一块AOF重写缓冲区,重写时的写数据会存放在重写缓冲区当中。
为了避免数据不一致的发生,在重写完成后,会将AOF重写缓冲区的内容,同步到AOF缓冲区中。
AOF相关的缓冲区没有容量限制。
对象内存
- Key:不要过长,量大不容忽视,建议控制在39字节之内。(embstr)
- Value:尽量控制其内部编码为压缩编码。
内存碎片
- 在jemalloc中必然存在内存碎片
原因
- 与jemalloc有关,jemalloc会将内存空间划分为小、大、巨大三个范围;每个范围又划分了许多小的内存块单位;存储数据的时候,会选择大小最合适的内存块进行存储。
| 类别 | 间距 | 尺寸 |
| ----- | ---- | ------------------------ |
| Small | 8 | [8] |
| | 16 | [16、32、48、...、128] |
| | 32 | [160、192、224、256] |
| | 64 | [320、384、448、512] |
| | 128 | [640、768、896、1024] |
| | 256 | [1280、1536、1792、2048] |
| | 512 | [2560、3072、3584] |
| Large | 4KB | [4KB、8KB、12KB、...、4072KB] |
| Huge | 4MB | [4MB、8MB、12MB、...] |- redis作者为了更好的性能,在redis中实现了自己的内存分配器来管理内存,不用的内存不会马上返还给OS,从而实现提高性能。
- 修改cache的值,且修改后的value与原先的value大小差异较大。
优化方法
- 重启redis服务,
- redis4.0以上可以设置自动清理
config set activedefrag yes
,也可以通过memory purge
手动清理,配置监控使用性能最佳。 - 修改分配器(不推荐,需要对各个分配器十分了解)
- 避免频繁更新操作
内存设置上限
- 定义实例最大内存,便于管理机器内存,一般要保留30%
- 修改maxmemory选项,不修改会无限使用
config set maxmemory 2GB
config rewrite
内存回收策略
1.删除过期键值
惰性删除
- 访问key
- expired dict
- del key
- 定时删除:每秒运行10次,采样删除
2.maxmemory-policy
- Noevition:默认策略,不会删除任何数据,拒绝所有写入操作并返回错误信息。
- volatile-lru:根据lru算法删除设置了超时属性的key,直到腾出空间为止。如果没有可删除的key,则将退回到noevition。
- allkeys-lru:根据lru算法删除key,不管数据有没有设置超时属性,直到腾出足够空间为止。
- allkeys-random:随机删除所有键,直到腾出足够空间为止。
- volatile-random:随机删除过期键,直到腾出足够空间为止。
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期的数据。如果没有,回退到noevicition。
内存优化
1.了解内存消耗划分
2.合理选择数据结构
- 需求:计算网站每天独立用户数
解决方案
- 如果需要返回具体哪些用户访问过系统,那么可以采用集合存储。
- 如果允许存在误差值,则可以使用HyperLogLog存储
- 如果不允许存在误差,那么可以对每个userId对应到位图中
客户端缓冲区优化
- 案例:一次线上事故,主从节点配置的maxmemory都是4GB,发现主节点的使用内存达到了4GB即内存已打满。而从节点只有2GB。
思考方向
- 考虑Redis的内存自身组成
主从节点之前数据传输不一致(会导致对象内存不一致)
- dbsize
- 查看info replication中的slave_repl_offset
查看缓冲区方面
- 通过info clients查看最大的缓冲区占用
- 通过client list查看所有客户端的详细信息
问题发现:
- 存在一个monitor客户端对命令进行监听,由于Redis的QPS极高,monitor客户端无法及时处理,占用缓冲区。
问题预防
- 运维层面:线上禁用monitor(rename monitor "")
- 运维层面:适度限制缓冲区大小
- 开发层面:理解monitor原理,可以短暂寻找热点key
- 开发层面:使用CacheCloud可以直接监控到相关信息
内存优化其他建议
- 不要忽视key的长度:1个亿的key,每个字节都是节省
- 序列化和压缩方法:拒绝Java原生序列化,可以采用Protobuf、kryo等
不建议使用Redis的场景
- 数据:大且冷的数据
- 功能性:关系型查询、消息队列