从Redis2.2开始,许多数据类型都被优化到必定大小,从而减小了占用内存空间。Hash
,List
,由整数组成的Set
,和Sorted set
,当他们的元素数量和单个元素所占内存分别小于给定值时,这些汇集类型会被以很是搞笑利用内存的方式存储,最高能够节省10倍内存(平都可以节省5倍内存)。html
从用户和API的角度来看,这些优化是彻底透明的。由于这是CPU/内存的这种,因此咱们能够在redis.conf
配置文件中以下指令,经过修改某个特定类型下元素最大数量和元素自身最大内存的值来调优。redis
hash-max-zipmap-entries 512 (hash-max-ziplist-entries for Redis >= 2.6)
hash-max-zipmap-value 64 (hash-max-ziplist-value for Redis >= 2.6)
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
复制代码
当一个特殊优化的数据超过配置的最大值时,Redis会自动将其编码类型转为该类型数据的常规编码类型。json
使用32位target编译的redis下,每一个key能少用不少内存,由于指针会小不少,可是最大使用内存也会被限制在4G。要想编译成32位二进制的redis,可使用make 32bit
。RDB
和AOF
在32位和64位实例上是兼容的(固然在大端和小段之间上也是兼容的),因此你能够从32位切换到64位,反过来也同样。数组
Redis 2.2 引进了新的字节和位级别的操做: GETRANGE
,SETRANGE
, GETBIT
,SETBIT
。使用这些指令,你能够把string类型当作能够随机访问的数组。好比说,你有一个应用,应用里用户经过一个惟一的渐进整数来识别,你可使用位图来保存用户的性别信息,能够在位上女性置1,男性置0,或者用其余方式。在Redis实例里,10亿用户的性别数据只须要12M RAM
。你可使用GETRANGE
,SETRANGE
来存储一个字节长度的每一个用户信息。这里只是个例子,可是咱们有可能使用这些新指令解决不少小空间时面临的问题。缓存
小的hash会使用占用很小空间的编码格式,因此你应该可能的使用hash
来存储你的数据。好比说你有一个网站用户对象,你应该使用单个hash来存储全部的字段,而非把用户的名字,性别,年龄等都存储成不一样的k-v。ruby
我理解这节的话题有点吓人,可是我会详细解释它。bash
基本上,可使用redis对普通的键值存储进行建模,其中的值能够只是字符串,这不只比redis普通的键值更节省内存,并且比memcached更节省内存。数据结构
让咱们从一个事实开始:少许的键比包含少许字段的哈希的单个键使用更多的内存。这怎么可能?咱们使用了一个技巧。从理论上讲,为了保证咱们在常数时间内执行查找(在大O符号中也称为O(1)),须要在平均状况下使用具备常数时间复杂度的数据结构,例如哈希表。memcached
可是不少时候,哈希只包含不多的字段。当哈希比较小的时候,咱们可使用O(N)
复杂度的数据结构对其编码,好比带有长度前缀的键值对线性数组。由于咱们只在N
比较小的时候才这么作,HGET
和HSET
的均摊时间仍然是O(1)
,当哈希包含的元素个数增加到足够大时 (你能够在redis.conf里配置这个限制),它将被转换为真正的哈希表。测试
从时间复杂度的角度看,这并不能很好的工做,但从常量时间的角度来看却相反,由于经过CPU缓存,一个键值对的线性数组刚好可以工做的很好。
可是,由于哈希的字段(field
)和值(value
)(一般)不能像Redis对象那样表现出全部的特性,哈希的字段没有相似Redis键那样与之关联的生存时间(过时时间),仅仅包含一个字符串。但这对咱们来讲是没问题的,这就是设计哈希数据结构API时的意图(咱们相信简单优于特性,因此不运行内嵌数据结构,单个字段的过时属性也是不支持的)。
因此,哈希是内存高效的。因此,在表示对象或者对包含一组相关字段的问题建模时使用哈希是颇有用的。可是对于只有普通键值对的业务又该如何呢?
好比咱们使用Redis存储一些小对象,能够是json编码的对象、小的HTML块,简单的键->布尔值对等等。本质上,都是小型键和小型值的字符串->字符串的映射。
如今咱们假设要缓存的对象是数字的,好比:
Ojbect:1234
最终会被分为:Ojbect:12
34
因此咱们用处理最后两个字符之外的字符做为key
,而最后两个字符做为哈希的字段名,为了设置咱们的键,可使用以下命令: HSET object:12 34 somevalue
正如你看到的,全部的哈希最终都能包含100个(00-99)字段(field
),这是CPU和内存之间的最佳折中。
还有另一个很重要的事须要注意,使用这个模式时,不管咱们有多少数量的对象要缓存,咱们都只能缓存100个左右的字段。这是由于,咱们的对象最终都是以数字结尾,而不是随机字符串。在某种程度上,最后的数字能够看做是隐式预分片(pre-sharding
)的一种方式。
那么小数字呢?好比Object:2
? 咱们能够把Object:
看成键名,整个数字看成哈希的字段名。因此Object:10
和Object:2
都以Ojbect:
为键名,可是前者以``0看成字段名,后者则是
2`。
这种方式下咱们能节省多少内存?
我使用下面的Ruby程序来测试这是如何工做的:
require 'rubygems'
require 'redis'
UseOptimization = true
def hash_get_key_field(key)
s = key.split(":")
if s[1].length > 2
{:key => s[0]+":"+s[1][0..-3], :field => s[1][-2..-1]}
else
{:key => s[0]+":", :field => s[1]}
end
end
def hash_set(r,key,value)
kf = hash_get_key_field(key)
r.hset(kf[:key],kf[:field],value)
end
def hash_get(r,key,value)
kf = hash_get_key_field(key)
r.hget(kf[:key],kf[:field],value)
end
r = Redis.new
(0..100000).each{|id|
key = "object:#{id}"
if UseOptimization
hash_set(r,key,"val")
else
r.set(key,"val")
end
}
复制代码
这是在一个Redis2.2版本的64位实例上运行的结果:
UseOptimization
选项为true
的时候: 使用了1.7 MB的内存UseOptimization
选项为false
的时候: 使用了11 MB的内存这是一个数量级(an order of magnitude)的差距,我认为这或多或少的使Redis成为最省内存的普通键值存储。
警告 为了使其工做,你须要保证在redis.conf文件里存在以下设置:
hash-max-zipmap-entries 256
还要记得根据你键值对的最大值来设置下面的字段:
hash-max-zipmap-value 1024
每当哈希超出设置的最大数量,或者值超出了设置的最大致量,哈希都会被转化为真正的哈希表,内存节省的特性也就随之丢失。
你也许会问,为何不在普通键空间上显示的作这些,有两个缘由:一个是咱们倾向于显式地权衡,而这里正是一个显式地权衡:CPU,内存,最大元素大小。另外一个缘由就是顶级的键空间须要支持许多有趣的特性,好比过时时间,LRU数据等等,因此普世地使用这种方法并不实用。
可是redis的方式是用户必须理解Reids是如何工做的,从而可以更好的选择折衷方案,以及可以更好的了解系统确切行为方式。
Special encoding of small aggregate data types
内存消耗分析
info memory
used_memory_rss
: redis进程所占用内存used_memory
: redis内部存储的全部数据内存占有量used_fragmentation_ratio
: used_memory_rss
/used_memory
,表示内存碎片率,大于1表示有碎片,小于1则存在内存交互到硬盘的状况