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
O:客户端是monitor模式下的附属节点(slave)
S: 客户端是一般模式(normal)下的附属节点
M:客户端是主节点(master)
x: 客户端正在执行事务
b: 客户端正在等待阻塞事件
i: 客户端正在等待VM I/O操作(已废弃)
d: 一个受监视(watched)的键已被修改,EXEC命令将失败
c: 将恢复完整地写出之后,关闭连接
u: 客户端未被阻塞(unblocked)
A: 尽可能快地关闭连接
N:未设置任何flag
db 该客户端正在使用的数据库ID
sub 已订阅频道的数量
psub 已订阅模式的数量
multi 在事务中被执行的命令数量
qbuf 查询缓冲区的长度(单位为字节,0表示没有分配查询缓冲区)
qbuf-free 查询缓冲区剩余空间的长度(单位为字节,0表示没有剩余空间)
obl 输出缓冲区的长度(单位为字节,0表示没有分配输出缓冲区)
oll 输出列表包含的对象数量(当输出缓冲区没有剩余空间时候,
命令回复会以字符串对象的形式被入队到这个队列里。
omem 输出缓冲区和输出列表占用的内存总量
events 文件描述符事件
r: 客户端套接字(在事件loop中)是可读的(readable)
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,牺牲部分内存避免全量复制。

20180925214632675.png

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.删除过期键值

  • 惰性删除
    1. 访问key
    2. expired dict
    3. 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的场景

  • 数据:大且冷的数据
  • 功能性:关系型查询、消息队列
Last modification:June 4th, 2019 at 08:48 pm
如果觉得我的文章对你有用,请随意赞赏