redislru的简单介绍

本篇文章给大家谈谈redislru,以及对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

Redis的缓存淘汰策略LRU与LFU

Redis缓存淘汰策略与Redis键的过期删除策略并不完全相同,前者是在Redis内存使用超过一定值的时候(一般这个值可以配置)使用的淘汰策略;而后者是通过定期删除+惰性删除两者结合的方式淘汰内存过期键的。

这里参照官方文档的解释重新叙述一遍过期删除策略:当某个key被设置了过期时间之后,客户端每次对该key的访问(读写)都会事先检测该key是否过期,如果过期就直接删除;但有一些键只访问一次,因此需要主动删除,默认情况下redis每秒检测10次,检测的对象是所有设置了过期时间的键集合,每次从这个集合中随机检测20个键查看他们是否过期,如果过期就直接删除,如果删除后还有超过25%的集合中的键已经过期,那么继续检测过期集合中的20个随机键进行删除。这样可以保证过期键最大只占所有设置了过期时间键的25%。

在Java中LRU的实现方式是使用HashMap结合双向链表,HashMap的值是双向链表的节点,双向链表的节点也保存一份key value。

LFU是在Redis4.0后出现的,LRU的最近最少使用实际上并不精确,考虑下孙歼面的情况,如果在|处删除,那么A距离的时间最久,但实际上A的使用频率要比B频繁,所以合理的淘汰策略应该是淘汰B。LFU就是为应对这种情况昌则而耐凯棚生的。

[img]

Redis的内存优化

一. redisObject对象

二. 缩减键值对象

三. 共享对象池

四. 字符串优化

五. 编码优化

六. 控制key的数量

Redis存储的所有值对象在内部定义为redisObject结构体,内部结构如下图所示。

表示当前对象使用的数据类型,Redis主要支持5种数据类型:string,hash,list,set,zset。可以使用type {key}命令查看对象所属类型,type命令返回的是值对象类型,键都是string类型。

表示Redis内部编码类型,encoding在Redis内部使用,代表当前对象内部采用哪种数据结构实现。理解Redis内部编码方式对于优化内存非常重要 ,同一个对象采用不同的编码实现内存占用存在明显差异,具体细节见之后编码优化部分。

记录对象最后一次被访问的时间,当配置了 maxmemory和maxmemory-policy=volatile-lru | allkeys-lru 时, 用于辅助LRU算法删除键数据。可以使用object idletime {key}命令在不更新lru字段情况下查看当前键的空闲时纯茄答间。

记录当前对象被引用的次数,用于通过引用次数回收内存,当refcount=0时,可以安全回收当前对象空间。使用object refcount {key}获取当前对象引用。当对象为整数且范围在[0-9999]时,Redis可以使用共享对象的方式来节省内存。具体细节见之后共享对象池部分。

与对象的数据内容相关,如果是整数直接存储数据,否则表示指向数据的指针。Redis在3.0之后对值对象是字符串且长度=39字节的数据,内部编码为embstr类型,字符串sds和redisObject一起分配,从而只要一次内存操作。

降低Redis内存使用最直接的方式就是缩减键(key)和值(value)的长度。

其中java-built-in-serializer表示JAVA内置序列化方式,更多数据见jvm-serializers项目: ,其它语言也有各自对应的高效序列化工具。

值对象除了存储二进制数据之外,通常还会使用通用格式存储数据比如:json,xml等作为字符串存储在Redis中。这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json,xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间。

对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。

整数对象池在Redis中通过变量REDIS_SHARED_INTEGERS定义,不能通过配置修改做慧。可以通过object refcount 命令查看对象引用数验证是否启用整数对象池技术,如下:

设置键foo等于100时,直接使用共享池内整数对象,因此引用数是2,再设置键bar等于100时,引用数又变为3,如下图所示。

使用整数对象池究竟能降低多少内存?让我们通过测试来对比对象池的内存优化效果,如下表所示。

使用共享对象池后,相同的数据内存使用降低30%以上。可见当数据大量使用[0-9999]的整数时,纳烂共享对象池可以节约大量内存。需要注意的是对象池并不是只要存储[0-9999]的整数就可以工作。当设置maxmemory并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池,测试命令如下:

LRU算法需要获取对象最后被访问时间,以便淘汰最长未访问数据,每个对象最后访问时间存储在redisObject对象的lru字段。对象共享意味着多个引用共享同一个redisObject,这时lru字段也会被共享,导致无法获取每个对象的最后访问时间。如果没有设置maxmemory,直到内存被用尽Redis也不会触发内存回收,所以共享对象池可以正常工作。

综上所述,共享对象池与maxmemory+LRU策略冲突,使用时需要注意。 对于ziplist编码的值对象,即使内部数据为整数也无法使用共享对象池,因为ziplist使用压缩且内存连续的结构,对象共享判断成本过高,ziplist编码细节后面内容详细说明。

首先整数对象池复用的几率最大,其次对象共享的一个关键操作就是判断相等性,Redis之所以只有整数对象池,是因为整数比较算法时间复杂度为O(1),只保留一万个整数为了防止对象池浪费。如果是字符串判断相等性,时间复杂度变为O(n),特别是长字符串更消耗性能(浮点数在Redis内部使用字符串存储)。对于更复杂的数据结构如hash,list等,相等性判断需要O(n2)。对于单线程的Redis来说,这样的开销显然不合理,因此Redis只保留整数共享对象池。

字符串对象是Redis内部最常用的数据类型。所有的键都是字符串类型, 值对象数据除了整数之外都使用字符串存储。比如执行命令:lpush cache:type “redis” “memcache” “tair” “levelDB” ,Redis首先创建”cache:type”键字符串,然后创建链表对象,链表对象内再包含四个字符串对象,排除Redis内部用到的字符串对象之外至少创建5个字符串对象。可见字符串对象在Redis内部使用非常广泛,因此深刻理解Redis字符串对于内存优化非常有帮助:

Redis没有采用原生C语言的字符串类型而是自己实现了字符串结构,内部简单动态字符串(simple dynamic string),简称SDS。结构下图所示。

Redis自身实现的字符串结构有如下特点:

因为字符串(SDS)存在预分配机制,日常开发中要小心预分配带来的内存浪费,例如下表的测试用例。

从测试数据可以看出,同样的数据追加后内存消耗非常严重,下面我们结合图来分析这一现象。阶段1每个字符串对象空间占用如下图所示。

阶段1插入新的字符串后,free字段保留空间为0,总占用空间=实际占用空间+1字节,最后1字节保存‘\0’标示结尾,这里忽略int类型len和free字段消耗的8字节。在阶段1原有字符串上追加60字节数据空间占用如下图所示。

追加操作后字符串对象预分配了一倍容量作为预留空间,而且大量追加操作需要内存重新分配,造成内存碎片率(mem_fragmentation_ratio)上升。直接插入与阶段2相同数据的空间占用,如下图所示。

阶段3直接插入同等数据后,相比阶段2节省了每个字符串对象预分配的空间,同时降低了碎片率。

字符串之所以采用预分配的方式是防止修改操作需要不断重分配内存和字节数据拷贝。但同样也会造成内存的浪费。字符串预分配每次并不都是翻倍扩容,空间预分配规则如下:

字符串重构:指不一定把每份数据作为字符串整体存储,像json这样的数据可以使用hash结构,使用二级结构存储也能帮我们节省内存。同时可以使用hmget,hmset命令支持字段的部分读取修改,而不用每次整体存取。例如下面的json数据:

分别使用字符串和hash结构测试内存表现,如下表所示。

根据测试结构,第一次默认配置下使用hash类型,内存消耗不但没有降低反而比字符串存储多出2倍,而调整hash-max-ziplist-value=66之后内存降低为535.60M。因为json的videoAlbumPic属性长度是65,而hash-max-ziplist-value默认值是64,Redis采用hashtable编码方式,反而消耗了大量内存。调整配置后hash类型内部编码方式变为ziplist,相比字符串更省内存且支持属性的部分操作。下一节将具体介绍ziplist编码优化细节。

Redis对外提供了string,list,hash,set,zet等类型,但是Redis内部针对不同类型存在编码的概念,所谓编码就是具体使用哪种底层数据结构来实现。编码不同将直接影响数据的内存占用和读写效率。使用object encoding {key}命令获取编码类型。如下:

Redis针对每种数据类型(type)可以采用至少两种编码方式来实现,下表表示type和encoding的对应关系。

了解编码和类型对应关系之后,我们不禁疑惑Redis为什么需要对一种数据结构实现多种编码方式?

主要原因是Redis作者想通过不同编码实现效率和空间的平衡。比如当我们的存储只有10个元素的列表,当使用双向链表数据结构时,必然需要维护大量的内部字段如每个元素需要:前置指针,后置指针,数据指针等,造成空间浪费,如果采用连续内存结构的压缩列表(ziplist),将会节省大量内存,而由于数据长度较小,存取操作时间复杂度即使为O(n2)性能也可满足需求。

Redis内存优化

编码类型转换在Redis写入数据时自动完成,这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。例如:

以上命令体现了list类型编码的转换过程,其中Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU,得不偿失。以上示例用到了list-max-ziplist-entries参数,这个参数用来决定列表长度在多少范围内使用ziplist编码。当然还有其它参数控制各种数据类型的编码,如下表所示:

掌握编码转换机制,对我们通过编码来优化内存使用非常有帮助。下面以hash类型为例,介绍编码转换的运行流程,如下图所示。

理解编码转换流程和相关配置之后,可以使用config set命令设置编码相关参数来满足使用压缩编码的条件。对于已经采用非压缩编码类型的数据如hashtable,linkedlist等,设置参数后即使数据满足压缩编码条件,Redis也不会做转换,需要重启Redis重新加载数据才能完成转换。

ziplist编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。ziplist编码是应用范围最广的一种,可以分别作为hash、list、zset类型的底层数据结构实现。首先从ziplist编码结构开始分析,它的内部结构类似这样:….。一个ziplist可以包含多个entry(元素),每个entry保存具体的数据(整数或者字节数组),内部结构如下图所示。

ziplist结构字段含义:

根据以上对ziplist字段说明,可以分析出该数据结构特点如下:

下面通过测试展示ziplist编码在不同类型中内存和速度的表现,如下表所示。

测试数据采用100W个36字节数据,划分为1000个键,每个类型长度统一为1000。从测试结果可以看出:

intset编码是集合(set)类型编码的一种,内部表现为存储有序,不重复的整数集。当集合只包含整数且长度不超过set-max-intset-entries配置时被启用。执行以下命令查看intset表现:

以上命令可以看出intset对写入整数进行排序,通过O(log(n))时间复杂度实现查找和去重操作,intset编码结构如下图所示。

intset的字段结构含义:

根据以上测试结果发现intset表现非常好,同样的数据内存占用只有不到hashtable编码的十分之一。intset数据结构插入命令复杂度为O(n),查询命令为O(log(n)),由于整数占用空间非常小,所以在集合长度可控的基础上,写入命令执行速度也会非常快,因此当使用整数集合时尽量使用intset编码。上表测试第三行把ziplist-hash类型也放入其中,主要因为intset编码必须存储整数,当集合内保存非整数数据时,无法使用intset实现内存优化。这时可以使用ziplist-hash类型对象模拟集合类型,hash的field当作集合中的元素,value设置为1字节占位符即可。使用ziplist编码的hash类型依然比使用hashtable编码的集合节省大量内存。

当使用Redis存储大量数据时,通常会存在大量键,过多的键同样会消耗大量内存。Redis本质是一个数据结构服务器,它为我们提供多种数据结构,如hash,list,set,zset 等结构。使用Redis时不要进入一个误区,大量使用get/set这样的API,把Redis当成Memcached使用。对于存储相同的数据内容利用Redis的数据结构降低外层键的数量,也可以节省大量内存。如下图所示,通过在客户端预估键规模,把大量键分组映射到多个hash结构中降低键的数量。

hash结构降低键数量分析:

通过这个测试数据,可以说明:

关于hash键和field键的设计:

使用hash结构控制键的规模虽然可以大幅降低内存,但同样会带来问题,需要提前做好规避处理。如下:

本文主要讲解Redis内存优化技巧,Redis的数据特性是”ALL IN MEMORY”,优化内存将变得非常重要。对于内存优化建议读者先要掌握Redis内存存储的特性比如字符串,压缩编码,整数集合等,再根据数据规模和所用命令需求去调整,从而达到空间和效率的最佳平衡。建议使用Redis存储大量数据时,把内存优化环节加入到前期设计阶段,否则数据大幅增长后,开发人员需要面对重新优化内存所带来开发和数据迁移的双重成本。当Redis内存不足时,首先考虑的问题不是加机器做水平扩展,应该先尝试做内存优化。当遇到瓶颈时,再去考虑水平扩展。即使对于集群化方案,垂直层面优化也同样重要,避免不必要的资源浪费和集群化后的管理成本。

Redis内存满了会怎么样?

1、通过配置文件配置

通过在Redis安装目录下面的redis.conf配置文件中添加以下配置设置内存大小

redis的配置文件不一定使用的是安装目录下面的redis.conf文件,启动redis服务的时候是可以传一个参数指定redis的配置文件的

2、通过命令修改

Redis支持运行时通过命令动态修改内存大小

既然可以设置Redis最大占用内存大小,那么配置的内存就有用完的时候。那在内存用完的时候,还继续往Redis里面添加数据不就没内存可用了吗?

实际上Redis定义了几种策略用来处理这种情况:

当使用volatile-lru、volatile-random、volatile-ttl这三种策友派略时,如果没有key可以被淘汰,则和noeviction一样返回错误

获取当前内存淘汰策略:

通过配置文件设置淘汰策略(修改redis.conf文件):

通过命令修改淘汰策略:

近似LRU算法

Redis使用的是近似LRU算法,它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据,每次随机出5(默认)个key,从里面淘汰掉最近最少使用的key。

可以通过maxmemory-samples参数修改采样数量:

例:maxmemory-samples 10

maxmenory-samples配置的越大,淘汰的结果越接近于严格的LRU算法

Redis为了实现近似LRU算法,给每个key增加了一个额外增加了一个24bit的字段,用来存储该key最后一次被访问的时间。

Redis3.0对近似LRU的优化

Redis3.0对近似LRU算法进行了一些优化。新算法会维护一个候选池(大小为16),池中的数据根据访问时间进行排序,第一次随机选取的key都会放入池中,随后每次随机选取的key只有在访世告岩问时间小于池中最小的时间才会放入池中,直到候选池被放满。当放满后,如果有新的key需要放入,则将池中最后访问时间最大(最近被访问)的移除。

当需要淘汰的时候,则直接从池中选取最近访问时间最小(最久没被访问)的key淘汰掉就行。搜御

redis 如何实现 LRU

LRU 是一个缓存置换算法,在缓存有限的情况下,如果有新的数据加载至缓存,则需要考虑将不会再继续被访问的数据剔除掉,但是缓存是否会被访问是没有办法预测的,所以,LRU 是基于一个假设实现:

这也是 LRU 实现的一个思路,它首先实现一个双向链表,当一个 key 被访问时,则将这个 key 放到双向链表的头部,当时缓存不可用时,从尾部逐个剔除

如果按照这样的假设实现,会存在一些缺陷,假设我们现在有一张数据表,执行如下 SQL 语句:

上面这条 SQL 的作用是将数据表中的所有数据读取出来,我们再将该数据表中的所有数据读取出后就不再继续使用,那么对于 LRU 的双向链表在头部会有大量数据占用,导致热点数据被逐出缓存以致于会出现大铅盯宏量磁盘 I/O

MySQL Innodb 的 buufer pool 实现了一个改进版的 LRU,它将 LRU 的双向链表分为两部分,一个是 newlist 另一个是 oldlist,newlist 主要是用于存放头部热点数据,oldlist 用于存放非热点数据,当首次加载一个 page 时,会将数据放到 oldlist 的头部,再次访问的时候会移动到 newlist

而对于 redis ,redis 整体是一个大的 dict,如果要想实现双向链表,需要给每一个 key 新增两个指针,占用 16 个字节大小,并且需要一个额外的 list 结构存储双向链表的头尾节点信息,这样会占用一定的内存空间,导致 redis 性能下降,所以,redis 并没有实现双向链表

redis 整体是一个大的 dict,每一个槐册 value 被保存为一个 redisobj 结构,每一个 redisobj 结构都包含有一个 lru 字段,该字段存储的是一个时间戳,当根据 key 获取值的时候,会调用 lookup 函数,如果开启了 LRU 模式,则该函数会将 lru 的替换成当前秒级的时间戳,然后 redis 再使用随机采样法,从 dict 中筛选出 5 个key(注意:这里的 5 个 key 是可以修改的,由 maxmemory-smples 控制),比较 lru 值的大小,淘汰掉最小则迹的那个

在 redis 3.0 以后对该算法进行了一个升级,新的算法维护了一个候选池(pool),首次筛选出来的 key 会被全部放入到候选池中,在后续的筛选过程中只有 lru 小于候选池中最小的 lru 才能被放入到候选池,直至候选池放满,当候选池满了的时候,如果有新的数据继续放入,则需要将候选池中 lru 字段最大值取出

然后在淘汰的时候,只需要将候选池中 lru 字段值最小的淘汰掉即可

Redis内存配置和淘汰策略

1.在redis安装目录下找到redis.conf,打开找到如下行:

其中的maxmemory bytes即为最大内存配置项,默认是注释掉的会采用 默认的最大内存大小 :在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB。

2.在客户端通过命令行查看

这里结果为0表示未手动指定过最大内存,采用默认的最大内存。

一般推荐Redis设置内存为最大物理内存的四分之三。

1.在配置文件redis.conf中指定maxmemory参数,例如,如果最大内存是200M,则在配置文件中添加 maxmemory 209751200 ;

2.通过命令 config set maxmemory 209751200 设置,注意,这里如果是通过命令行设置的最大内存大小,在配置文件redis.conf中并不会添加 maxmemory 209751200 这一行内容。

当Redis达到最大的可用内存时,再向其中存入数据则会报OOM,因此,要避免无限制存入数据导致OOM,则需要采用合适的内存淘汰策略。

在讨论Redis的内存淘汰策略之前,我们要先对Redis中过期键的删除机制有个大体的了解;实际上,过期键的删除策略有三种,每种策略下过期键的删除时机均不同。

1. 定时删除

所谓定时删除,就是在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即删除对键的删除操作。其能够对过期键进行立即删除,对内存是友好的,但是因为要维护定时器,对cpu是不友好的。

2. 惰性删除

所谓惰性删除,就是放任过期键不管,但每次获取键时,都检查取得的键是否过期,如果过期的汪游话,就删除该键。如果没有过期,就返回该键。惰性删除对cpu友好,但是由于其无法主动删除过期键,当过期键大量积聚时会占用很大内存,对内存不友好。

3. 定期删除

所谓定期删除,是前两种删除策略的一种折中。会每隔一段时间执行一次删除过期键操作,并通过限制操作执行的时长和频率来减少删除操作对cpu时间的影响。

定期删除会周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度,其特点为:

a)CPU占用设置有峰值,检测频度可自定义设置。

b)内存压力有限,长期占用内存的冷数据会被持续清理。

总结下来,定期删除会周期性抽查存储空间(随机抽查、重点抽查)。

定期删除的难点在于如何确定产出操作执行的时长和频率,如果删除操作执行的太过频繁,或者执行的时间太长,定期删除策略就会退化成定时删除策略,以至于将CPU时间过多的消耗在删除键上面。如果删除操作执行的太少,或者执行的时间太短,定期删除策略又会和惰性删除策困返销略一样,出现内存浪费的情况。因此,必须合理的设置定期删除策略的执行时长和执行频率。

定期删除在一定程度上是一种合理有效的过期键删除策略,但是由于其在执行时长和执行频度的局限性,必须要有另一种机制(策略)确保内存能够获得回收,因此,就需要引入内存淘汰策略。

在redis.conf中指出内存淘汰策略有有以下八种:

1. volatile-lru :从已设置过期时间的key中挑选最近最少使用的数据淘汰;

2. allkeys-lru :从全部key中挑选最近最少使用的数据淘汰;

3. volatile-lfu :从已设置过期时间的key中世咐挑选使用频率最低的数据淘汰;

4. allkeys-lfu :从全部key中挑选使用频率最低的数据淘汰;

5. volatile-random :从已设置过期时间的key中任意选择数据淘汰;

6. allkeys-random :从全部key中任意选择数据淘汰

7. volatile-ttl :从已设置过期时间的key中挑选将要过期的数据淘汰;

8. no-enviction :禁止驱逐数据,这也是默认策略。当内存不足以容纳新入数据时,新写入操作就会报错。

内存淘汰策略的设置与查看

redis八种淘汰策略是什么?

redis八种淘汰策略如下:

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

特点:

Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,段好镇Erlang等客户端,使用很方便。

Redis支持主从同步。数据可以从主服务器握粗向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读袜旅取操作的可扩展性和数据冗余很有帮助。

关于redislru和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

标签列表