一、redis的内存消耗在哪里
如何查看redis的内存消耗情况
info memory
命令
每一项的具体意思可以看博客的图片,标红的要重点关注。
在我们的印象中,我们都会认为redis占用的内存就是我们写入kv占用的内存,但其实还包括bgsave fork出子进程要用的内存,各种缓冲区占用的内存等等。
redis使用的内存分为两大块:
used_memory: 又分为自身内存、对象内存、缓冲内存、lua内存
自身内存:很低,几百k,可以忽略
对象内存:就是我们存储的string、hash、set、list等内容
缓冲内存:包括复制缓冲区、aof缓冲区、客户端缓冲区等等
lua内存:略
used_memory_rss - used_memory: 即内存碎片,即操作系统分配给redis的内存和redis实际使用的内存的差值。比如我存一个字符串 hello, 一共5个字节,但实际操作系统分配给这个key的内存大小要大于5个字节。
内存碎片率 + used_memory(redis实际使用的内存量) = used_memory_rss(总的内存消耗量)
二、缓冲区内存
输入缓冲区:
客户端发送命令给redis,redis在执行这些命令前,这些命令会被临时存放到一个输入缓冲区,也就是之前说的命令队列(由于redis是单线程的所以命令要放在队列中排队顺序执行)。
命令存在于输入缓冲区的时间很短,因为redis处理命令很快,所以输入缓冲区占用的内存基本看到都是0
但是如果redis很忙,或者key是一个bigkey,那么输入缓冲区就会较长时间的被占用。
输入缓冲区不能占用超过1G(无法配置修改),否则就会断开和客户端的连接。
1.客户端缓冲区
分为
普通客户端(redis-cli,php或者python执行命令时的客户端等)
slave客户端(从节点同步主节点时需要一个slave客户端接收主节点同步过来的命令)
pubsub客户端(发布订阅时会生成客户端)
这三个是输出缓冲区(redis服务返回命令所经过的缓冲区)
如果客户端请求的是bigkey,那么普通客户端缓冲区就很容易占用大量内存。
2.复制缓冲区和AOF缓冲区
复制缓冲区是用于主从节点的部分复制而生的,master节点会将写命令写入到复制缓冲区,然后再从复制缓冲区发送给从节点以进行数据同步。为了防止网络抖动造成不必要的全量复制可以将这个缓冲区调大点。这个缓冲区大小默认是1Mb,可以调大到100M的样子。
AOF缓冲区分为aof缓冲区(用于aof日志的生成)和aof重写缓冲区(用于aof重写),这两个缓冲区是没有容量限制的。
3.内存碎片
内存碎片的处理最好解决方式是安全重启。
4.子进程内存消耗
像bgsave和bgrewriteaof的时候,就会fork生成一个子进程从而消耗内存。
三、内存管理
1.设置内存上限
假如一台linux专门用来做redis缓存,共有24G内存,则可以在这台机器上部署多个redis实例,分配2/3的内存给redis,预留1/3的内存给Linux系统和一部分空闲内存以备fork消耗。
可以通过设置在配置文件的 maxmemory 设置redis节点使用的最大内存(这里的最大内存不包含客户端缓冲区)
动态配置:
config set maxmemory 6GB
config rewrite # 配置重启,写入配置文件中
2.内存回收策略
分两块说:
A.对于过期key的回收,redis会使用定期删除 + 惰性删除两种策略结合。
定期好理解,默认每隔一段时间(100秒)就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
为啥不扫描全部设置了过期时间的key呢?
假如Redis里面所有的key都有过期时间,都扫描一遍?那太恐怖了,而且我们线上基本上也都是会设置一定的过期时间的。全扫描跟你去查数据库不带where条件不走索引全表扫描一样,100s一次,Redis累都累死了。
如果一直没随机到很多key,里面不就存在大量的无效key了?
此时就用到了惰性删除:
即当用户查询到某个key的时候,redis会查看这个key是否过期。过期则不返回给用户,直接删掉。
B.当内存溢出的时候(redis中的kv超过了maxmemory内存上限)会触发5种相应的策略。
Noeviction:默认策略,不会删除任何数据,但会拒绝所有写入操作并返回错误信息“OOM command not allowed when used memory”。但是会相应读操作。
Volatile-lru:根据LRU算法(最近最少使用算法)删除设置了超时时间的key(设置了expire的key)直到腾出足够空间为止。如果没有可删除的key则退回到Noeviction默认策略
Volatile-ttl:根据key的ttl属性,删除最近即将过期的key,如果没有就回退到默认策略。
Allkeys-lru:根据LRU算法删除key,不管数据有没有设置超时,直到腾出足够空间为止。
Allkeys-random:随机删除所有key直到腾出足够空间为止
volatile-random:随机删除过期key直到腾出足够空间为止
可以在配置文件的设置 maxmemory-policy 来控制。
当内存溢出的时候,可以分析一下是什么原因造成的,这是要想到redis用到内存的地方(对象内存、缓冲区内存、内存碎片等)
=======================================
value设计
1.拒绝bigkey
string类型超过10k就可以看成bigkey
hash,list,set和zset元素个数超过5000个可以看成bigkey
这个要看不同公司有不同标准
bigkey的危害:
1.网络阻塞
举个例子,有一个key大小是5M,单机的带宽是千兆也就是128Mb,那么当对这1个key的QPS达到25的时候带宽就会占满从而影响了其他很多的业务。
2.慢查询
例如做一些hgetall,lrange,zrange等操作
因为redis是一个单线程,如果单个key的大小非常大,就会阻塞后面的命令的执行。
3.节点数据不均衡
这个问题是出现在分布式redis中的,如果key过大,那么在每个节点的key个数差不多的情况下出现数据倾斜的问题。
4.反序列化消耗
如果string类型存的是一个序列话的bigkey,那么获取的时候,你的应用程序就要对他进行反序列化,这是一个比较吃CPU的操作
如何发现bigkey
可以使用 redis-cli --bigkeys(他也是进行数据的扫描,建议在slave节点中进行)
或者通过网络流量监控,客户端监控(例如python去查redis发现超时)。