Redis内存消耗

1.内存使用统计

info memory
属性名属性说明
used_memoryRedis分配器分配的内存量,也就是实际存储数据的内存总量
used_momory_human以可读格式返回Redis使用的内存总量
used_momory_rss从操作系统的角度,Redis进程占用的总物理内存
used_memory_peak内存分配器分配的最大内存,代表used_memory的历史峰值
used_memory_peak_human以可读的格式显示内存消耗峰值
used_memory_luaLua引擎所消耗的内存
mem_fragmentation_ratioused_memory_rss/used_memory比值,表示内存碎片率
mem_allocatorRedis所使用的内存分配器,默认: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,牺牲部分内存避免全量复制。

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 4, 2019
如果觉得我的文章对你有用,请随意赞赏