讨论Redis内存优化以前,有必要先聊下关于物理内存、虚拟内存的概念。html
物理内存:就是主机安装的物理内存条,通常32位的CPU有32条地址线,最大寻址空间就是4G,而64位的CPU则可以支持更大的内存。但是,再大的内存,也有可能知足不了全部应用的内存须要;又或者是,大量使用频率极低的冷数据占用了大部分物理内存。这个时候,就是“虚拟内存”出场了。linux
虚拟内存(简称VM):由操做系统OS管理,本质就是硬盘中的一个特殊格的磁盘分区、或者一个文件(linux下是swap分区、windows下是ib3文件),其写入写出由OS掌管,若是出现以上两种情景,OS会将溢出物理内存部分数据或者部分冷数据交换到虚拟内存,释放更多空间提供其余应用运做。而对于全部应用而言,其运行可以使用的内存理论上是:物理内存 + 虚拟内存。redis
Redis跟全部的应用同样,其内存分配都是要由OS来管理。用户增长key-value对象,Redis则向OS申请内存划分到应用;反之,用户移除key-value对象,Redis向OS返还内存,并由OS决定该将热数据放入物理内存,冷数据交换到硬盘。算法
不同的是,Redis有本身的内存分配器,当key-value对象被移除时,Redis不会立刻向操做系统释放其占用内存(例如,当用户往一个实例填充了5G的数据,移除其中2G数据,但占用内存可能仍会保持在5G左右)。为何Redis要这样处理?有两个缘由:windows
一、OS可能会将释放内存交换到VM,但OS的VM又是物理文件,其IO读写效率较低,从而影响Redis性能表现;缓存
二、OS的VM换入换出是基于Page机制,同一Page内的部分数据对象被释放,但其余数据对象依然被其余应用使用中,致使在该Page内的Redis对象没有被释放。服务器
而Redis做者应该是考虑到以上问题,不但愿Redis由此下降性能,因此在设计上Redis更倾向于本身掌控VM换入的粒度。数据结构
redis-cli > info
启动redis-cli,info
命令能够观察Redis实例的运行状况,其中# Memory
块查看内存使用状况。dom
提示:以上used_memory数据所包含的内存均包含有:分布式
用户定义的数据:内存被用来存储key-value值。
内部开销: 存储内部Redis信息用来表示不一样的数据类型。
再来看看具体每一个数据所指具体含义
used_memory:当前Redis全部key-value值及内部开销理论上要占用的内存
used_memory_human:上一数据的可读版本
used_memory_rss:(Resident Set Size常驻数据集大小),可理解为OS为Redis分配的物理内存总量
used_memory_peak:峰值内存
used_memory_peak_human:峰值内存可读版本
used_memory_lua:lua引擎占用内存
mem_fragmentation_ratio:内存碎片率,used_memory_rss 和 used_memory 之间的比率(如下有更多说明)
mem_allocator: Redis 所使用的内存分配器。能够是 libc 、 jemalloc 或者 tcmalloc
其中mem_fragmentation_ratio(内存碎片率)是分析Redis性能的重要数据指标,计算公式是:
大于1:OS为Redis分配的物理内存 > Redis全部key-value值及内部开销应占用的内存
产生缘由:物理内存多出的部分,Redis内移除对象的占用内存,但这部份内存由Redis自带内存分配器占用,没有向操做系统返回。这一部分就是内存碎片。
小于1:OS为Redis分配的物理内存 < Redis全部key-value值及内部开销理应占用的内存
产生缘由:应占内存比物理内存多出的部分,是被操做系统交换到虚拟内存,说明当前Redis的内存使用已经超出物理内存
问题是,这个ratio比率,多少比值最合理?回顾前文,Redis是自带内存分配器的,自带内存分配器会保留内部已释放的内存,目的是为了不操做系统低性能交换,固然它也是比较灵活的,被释放的内存块是能够被重用。翻阅了下官方文档,也有对应的说明
However allocators are smart and are able to reuse free chunks of memory, so after you freed 2GB of your 5GB data set, when you start adding more keys again, you’ll see the RSS (Resident Set Size) to stay steady and don’t grow more, as you add up to 2GB of additional keys. The allocator is basically trying to reuse the 2GB of memory previously (logically) freed.
所以,内存碎片率保持在1.0至1.5之间是最理想的状态。倘若碎片率超过了1.5,我所知道的最有效解决手段就是重启Redis服务器,释放内存回到操做系统;反之,若碎片率为0.9,说明物理内存已不够用,应增添硬件,或设置Redis最大内存限制maxmemory
。
最大内存限制maxmemory
的设置很是重要,若是不设置maxmemory
,Redis一直会为其分配内存,直至耗尽全部物理内存,直到操做系统进行虚拟内存交换。所以,通常状况下,做者建议仍是把峰值设置设上。开启此配置,当超出限定内存状况发生,Redis会返回异常消息,操做系统不会因内存溢出而奔溃。还有一点建议是,开发者在系统设计之初,就应当制定Redis内存使用划分计划,而划分原则是,为Redis准备系统可能使用的峰值内存,而不是平均使用内存。例如系统大部分状况会以Redis做为分布式缓存写入10G数据,但大部分状况下只会跑到4G,但Redis依然推荐用户为其预留10G内存(used_memory_peak峰值)。
redis.conf--> maxmemory 10000000
maxmemory
的单位是bytes,默认为0,即不限制最大内存。
除maxmemory
之外,仍然须要指定Redis在最大内存溢出后的处理行为——maxmemory-policy
。同时设置了maxmemory
与maxmemory-policy
选项,redis内存使用达到上限。能够经过设置LRU算法(Least Recently Used 近期最少使用算法)来删除部分key,释放空间。相关内容参考:将redis当作使用LRU算法的缓存来使用。
提示:Redis32位实例最大可用内存为3G,64位则无限制,而RDB与AOF持久化文件都兼容支持32位或64位实例,所以能够自由切换在32位与64位之间切换。
volatile-lru -> 根据LRU算法生成的过时时间来删除。
allkeys-lru -> 根据LRU算法删除任何key。
volatile-random -> 根据过时设置来随机删除key。
allkeys-random -> 无差异随机删。
volatile-ttl -> 根据最近过时时间来删除(辅以TTL)
noeviction -> 谁也不删,直接在写操做时返回错误。
String与Hash毫无疑问是最经常使用的两种数据结构,并且AB两个方案互换使用每每均可以知足系统的业务需求,该如何选型,才能在复杂度、性能以及内存间取得一个平衡点?
举个例子,存储如下数据,分别采起String与Hash的形式:
object:1
object:2
object:3
object:……
object:100000
String存储:以object:x
做为key,(1)
Hash存储:以object:x
做为Key,后两位字节xx
做为hash key,(2)
(1)存储结果:
(2)存储结果:
从以上结果能够得出:hash散列比string更节省内存,缘由有3点:
Redis的Key承载了不少特性的,过时时间、LRU、Cluster节点信息、系统关键信息等,所以和String类型的key-value
相比,hash
类型可更大限度地减小key的数量,从而节省内存空间的使用率;
key对应多种数据结构,而hash key对应的value只能是string,这种简单结构比多重复杂结构更加高效。(Redis是不支持嵌套数据的,例如hash中value还嵌套有hash);
hash在额定数量及容积下,将会以一维线性的紧凑格式存储(ziplist),这种存储形式可节省更多空间。
注意一点是:hash只会在额定的要求范围内,才会以ziplist
存储,当超过额定阈值后会转换成真正hashtable
格式并从新存储。而hashtable
的存储对于内存的使用不占优点。关于这个额定值,redis提供了两个可配置参数hash-max-ziplist-entries
与hash-max-ziplist-value
:
hash-max-ziplist-entries
:hash以ziplist存储的最大对象数量,单位:个
hash-max-ziplist-value
:hash以ziplist存储的单个对象占用空间,单位:bytes
默认值是:
# Hashes are encoded using a memory efficient data structure when they have a # small number of entries, and the biggest entry does not exceed a given # threshold. These thresholds can be configured using the following directives. hash-max-ziplist-entries 512 hash-max-ziplist-value 64
修改配置hash-max-ziplist-entries
为20万,再调整以上例子写入10万数据到单一散列中,
结果是,与(2)例子接近,以ziplist格式存储更节省空间,但缺点是ziplist格式读写速度至关低效,10万数据的写入花费了99356毫秒,接近1.5分钟。所以,在实际应用中,对应存储格式的选型,仍是须要根据实际需求做为衡量标准。