共计 4709 个字符,预计需要花费 12 分钟才能阅读完成。
这篇文章主要介绍“Redis 内存满了然后去优化”的相关知识,丸趣 TV 小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Redis 内存满了然后去优化”文章能帮助大家解决问题。
Redis 内存满了怎么办?怎么优化内存?MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
Redis 主要消耗什么物理资源?
内存。
Redis 的内存用完了会发生什么?
如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以配置内存淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。
谈谈缓存数据的淘汰机制
Redis 缓存有哪些淘汰策略?
不进行数据淘汰的策略,只有 noeviction 这一种。
会进行淘汰的 7 种策略,我们可以再进一步根据淘汰候选数据集的范围把它们分成两类:
在设置了过期时间的数据中进行淘汰,包括 volatile-random、volatile-ttl、volatile-lru、volatile-lfu 四种。
在所有数据范围内进行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu 三种。
策略规则 volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。volatile-random 在设置了过期时间的键值对中,进行随机删除。volatile-lru 使用 LRU 算法筛选设置了过期时间的键值对 volatile-lfu 使用 LFU 算法选择设置了过期时间的键值对策略规则 allkeys-random 从所有键值对中随机选择并删除数据;allkeys-lru 使用 LRU 算法在所有数据中进行筛选 vallkeys-lfu 使用 LFU 算法在所有数据中进行筛选谈谈 LRU 算法
是按照最近最少使用的原则来筛选数据,最不常用的数据会被筛选出来,而最近频繁使用的数据会留在缓存中。
那具体是怎么筛选的呢?LRU 会把所有的数据组织成一个链表,链表的头和尾分别表示 MRU 端和 LRU 端,分别代表最近最常使用的数据和最近最不常用的数据。
LRU 算法背后的想法非常朴素:它认为刚刚被访问的数据,肯定还会被再次访问,所以就把它放在 MRU 端;长久不访问的数据,肯定就不会再被访问了,所以就让它逐渐后移到 LRU 端,在缓存满时,就优先删除它。
问题:LRU 算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销。而且,当有数据被访问时,需要在链表上把该数据移动到 MRU 端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。
解决:
在 Redis 中,LRU 算法被做了简化,以减轻数据淘汰对缓存性能的影响。具体来说,Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。
当需要再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合。这儿的挑选标准是:能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值。当有新数据进入候选数据集后,如果候选数据集中的数据个数达到了 maxmemory-samples,Redis 就把候选数据集中 lru 字段值最小的数据淘汰出去。
使用建议:
优先使用 allkeys-lru 策略。这样,可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。如果你的业务数据中有明显的冷热数据区分,我建议你使用 allkeys-lru 策略。
如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行。
如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用 volatile-lru 策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU 规则进行筛选。
如何处理被淘汰的数据?
一旦被淘汰的数据选定后,如果这个数据是干净数据,那么我们就直接删除;如果这个数据是脏数据,我们需要把它写回数据库。
那怎么判断一个数据到底是干净的还是脏的呢?
干净数据和脏数据的区别就在于,和最初从后端数据库里读取时的值相比,有没有被修改过。干净数据一直没有被修改,所以后端数据库里的数据也是最新值。在替换时,它可以被直接删除。
而脏数据就是曾经被修改过的,已经和后端数据库中保存的数据不一致了。此时,如果不把脏数据写回到数据库中,这个数据的最新值就丢失了,就会影响应用的正常使用。
即使淘汰的数据是脏数据,Redis 也不会把它们写回数据库。所以,我们在使用 Redis 缓存时,如果数据被修改了,需要在数据修改时就将它写回数据库。否则,这个脏数据被淘汰时,会被 Redis 删除,而数据库里也没有最新的数据了。
Redis 怎么优化内存?
1、控制 key 的数量:当使用 Redis 存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis 本质是一个数据结构服务器,它为我们提供多种数据结构,如 hash,list,set,zset 等结构。使用 Redis 时不要进入一个误区,大量使用 get/set 这样的 API,把 Redis 当成 Memcached 使用。对于存储相同的数据内容利用 Redis 的数据结构降低外层键的数量,也可以节省大量内存。
2、缩减键值对象,降低 Redis 内存使用最直接的方式就是缩减键(key)和值(value)的长度。
key 长度:如在设计键时,在完整描述业务情况下,键值越短越好。
value 长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入 Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。
3、编码优化。Redis 对外提供了 string,list,hash,set,zet 等类型,但是 Redis 内部针对不同类型存在编码的概念,所谓编码就是具体使用哪种底层数据结构来实现。编码不同将直接影响数据的内存占用和读写效率。
1、redisObject 对象
type 字段:
利用集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面。
encoding 字段:
采用不同的编码实现内存占用存在明显差异
lru 字段:
开发提示:可以使用 scan + object idletime 命令批量查询哪些键长时间未被访问,找出长时间不访问的键进行清理降低内存占用。
refcount 字段:
当对象为整数且范围在 [0-9999] 时,Redis 可以使用共享对象的方式来节省内存。
ptr 字段 :
开发提示:高并发写入场景中,在条件允许的情况下建议字符串长度控制在 39 字节以内,减少创建 redisObject 内存分配次数从而提高性能。
2、缩减键值对象
降低 Redis 内存使用最直接的方式就是缩减键(key)和值(value)的长度。
可以使用通用压缩算法压缩 json,xml 后再存入 Redis,从而降低内存占用
3、共享对象池
对象共享池指 Redis 内部维护[0-9999] 的整数对象池。创建大量的整数类型 redisObject 存在内存开销,每个 redisObject 内部结构至少占 16 字节,甚至超过了整数自身空间消耗。所以 Redis 内存维护一个 [0-9999] 的整数对象池,用于节约内存。除了整数值对象,其他类型如 list,hash,set,zset 内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。
当设置 maxmemory 并启用 LRU 相关淘汰策略如:volatile-lru,allkeys-lru 时,Redis 禁止使用共享对象池。
为什么开启 maxmemory 和 LRU 淘汰策略后对象池无效?
LRU 算法需要获取对象最后被访问时间,以便淘汰最长未访问数据,每个对象最后访问时间存储在 redisObject 对象的 lru 字段。对象共享意味着多个引用共享同一个 redisObject,这时 lru 字段也会被共享,导致无法获取每个对象的最后访问时间。如果没有设置 maxmemory,直到内存被用尽 Redis 也不会触发内存回收,所以共享对象池可以正常工作。
综上所述,共享对象池与 maxmemory+LRU 策略冲突,使用时需要注意。
为什么只有整数对象池?
首先整数对象池复用的几率最大,其次对象共享的一个关键操作就是判断相等性,Redis 之所以只有整数对象池,是因为整数比较算法时间复杂度为 O(1),只保留一万个整数为了防止对象池浪费。如果是字符串判断相等性,时间复杂度变为 O(n),特别是长字符串更消耗性能(浮点数在 Redis 内部使用字符串存储)。对于更复杂的数据结构如 hash,list 等,相等性判断需要 O(n2)。对于单线程的 Redis 来说,这样的开销显然不合理,因此 Redis 只保留整数共享对象池。
4、字符串优化
Redis 没有采用原生 C 语言的字符串类型而是自己实现了字符串结构,内部简单动态字符串,简称 SDS。
字符串结构:
特点:
O(1)时间复杂度获取:字符串长度,已用长度,未用长度。
可用于保存字节数组,支持安全的二进制数据存储。
内部实现空间预分配机制,降低内存再分配次数。
惰性删除机制,字符串缩减后的空间不释放,作为预分配空间保留。
预分配机制:
开发提示: 尽量减少字符串频繁修改操作如 append,setrange, 改为直接使用 set 修改字符串,降低预分配带来的内存浪费和内存碎片化。
字符串重构:基于 hash 类型的二级编码方式。
二级编码怎么用?
二级编码方法中采用的 ID 长度是有讲究的。
涉及到一个问题–Hash 类型底层结构小于设定值时使用压缩列表,大于设定值时使用哈希表。
一旦从压缩列表转为了哈希表,Hash 类型会一直用哈希表进行保存,而不会再转回压缩列表。
在节省内存空间方面,哈希表就没有压缩列表那么高效。为能充分使用压缩列表的精简内存布局,一般要控制保存在 Hash 中的元素个数。
5. 编码优化
使用压缩列表 ziplist 编码的 hash 类型依然比使用 hashtable 编码的集合节省大量内存。
6. 控制 key 的数量
开发提示:使用 ziplist+hash 优化 keys 后,如果想使用超时删除功能,开发人员可以存储每个对象写入的时间,再通过定时任务使用 hscan 命令扫描数据,找出 hash 内超时的数据项删除即可。
当 Redis 内存不足时,首先考虑的问题不是加机器做水平扩展,应该先尝试做内存优化。当遇到瓶颈时,再去考虑水平扩展。即使对于集群化方案,垂直层面优化也同样重要,避免不必要的资源浪费和集群化后的管理成本。
关于“Redis 内存满了然后去优化”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注丸趣 TV 行业资讯频道,丸趣 TV 小编每天都会为大家更新不同的知识点。