本文主要从3个方面分析:
- Redis什么地方都消耗内存?
- Redis中怎么对内存进行管理的?
- 如何优化内存?
一、内存消耗
查看内存消耗
可以用info memory命令来查看内存消耗的关键指标。其中最重要的指标是used_memory_rss和used_memory以及他们的比值mem_fragmentation_ratio。
- used_memory_rss:从操作系统角度显示Redis进程占用的 物理内存总量。
- used_memory:Redis分配器分配的内存总量。
- mem_fragmentation_ratio:>1,说明used_memory_rss-used_memory多出的部分并没用来存储数据,而是被内存碎片消耗;<1,一般是操作系统将Redis内存交换到硬盘所致,要格外关注,此时redis性能变得很差,甚至僵死。
内存消耗划分
Redis进程内存消耗主要包括:自身内存、对象内存、缓冲内存、内存碎片,其中Redis空进程自身内存消耗比较小,可以忽略不计。
- 对象内存
对象内存是 Redis内存占用最大的一块,存储着所有的用户数据。redis中所有的数据都采用 key-value类型,每次创建键值对时,至少创建2个对象:key对象和value对象 。键对象都是字符串,应该避免键过长。value主要包含5个主要的数据类型:List、set、zset、hash、string。其他数据类型都是建立在基本数据类型上的。 - 内存缓冲
内存缓冲区主要包括:客户端缓冲区、复制积压缓冲区、AOF缓冲区。
客户端缓冲区指的是所有接入到Redis服务器TCP连接的输入输出缓冲。
输入缓冲区无法控制,最大空间为 1G,如果超出将断开链接。
普通客户端。Redis并没有对普通客户端进的输入缓冲区做限制,一般的客户端内存消耗可以忽 略不计,但是当有大量慢连接客户端接入时,这部分内存消耗就不能忽略了。
从客户端。从客户端主节点会为从节点单独建立一条连接用于主从复制。当主从节点之间网 络延迟较高或直接点挂载大量从节点时这部分 内存消耗将占很大一大部分。
订阅客户端。当使用发布订阅功能时,连接客户端使用单独的输出缓冲区。当订阅服务的消息生产快于消费速度时,输出缓冲区会产生挤压造成输出缓冲区溢出。
复制积压缓冲区:用于实现部分复制功能。对于复制积压缓冲区整个主节点只有一个,所有从节点共享此缓冲区,复制积压缓冲区可有效减少全量复制。
AOF缓冲区:这部分缓冲区用于在Redis重写期间保存最近写入的命令。 - 内存碎片
频繁做更新操作,大量过期键删除,键对象过期后,释放的空间无法得到充分利用,导致碎片率较高。
解决方法:数据对齐,安全重启。
二、内存管理
内存管理主要有:设置内存上限、动态调整内存上限、内存回收策略等。
内存回收策略。有定期删除和惰性删除2种。
- 定期删除
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。
- Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
- 从过期字典中随机 20 个 key;
- 删除这 20 个 key 中已经过期的 key;如果过期的 key 比率超过 1/4,那就重复步骤 1;
- 惰性删除
除了定期遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。
定期删除是集中处理,惰性删除是零散处理。
为什么要采用定期删除+惰性删除2种策略呢?
如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。但是问题是,定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下。
通过上述两种手段结合起来,保证过期的key一定会被干掉。所以说用了上述2种策略后,下面这种现象就不难解释了:数据明明都过期了,但是还占有着内存。
内存淘汰策略:
这个问题可能有小伙伴们遇到过,放到Redis中的数据怎么没了?
因为Redis将数据放到内存中,内存是有限的,比如redis就只能用10个G,你要是往里面写了20个G的数据,会咋办?当然会干掉10个G的数据,然后就保留10个G的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
Redis提供的内存淘汰策略有如下几种:
1、noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
2、volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。(这个是使用最多的)
3、volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
4、volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
5、allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
6、allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。
三、内存优化
内存优化包括:
- 缩减键值对象
- 共享对象池
- 字符串优化
- 编码优化
- 控制键的数量等