内容简介:从Redis2.2开始,许多数据类型都被优化到一定大小,从而减少了占用内存空间。从用户和API的角度来看,这些优化是完全透明的。因为这是CPU/内存的这种,所以我们可以在当一个特殊优化的数据超过配置的最大值时,Redis会自动将其编码类型转为该类型数据的常规编码类型。
从 Redis 2.2开始,许多数据类型都被优化到一定大小,从而减少了占用内存空间。 Hash
, List
,由整数组成的 Set
,和 Sorted set
,当他们的元素数量和单个元素所占内存分别小于给定值时,这些聚集类型会被以非常搞笑利用内存的方式存储,最高可以节省10倍内存(平均可以节省5倍内存)。
从用户和API的角度来看,这些优化是完全透明的。因为这是CPU/内存的这种,所以我们可以在 redis.conf
配置文件中如下指令,通过修改某个特定类型下元素最大数量和元素自身最大内存的值来调优。
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会自动将其编码类型转为该类型数据的常规编码类型。
使用32位实例
使用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
来存储你的数据。比如说你有一个网站用户对象,你应该使用单个hash来存储所有的字段,而非把用户的名字,性别,年龄等都存储成不同的k-v。
在redis上使用哈希抽象出一个非常节省内存的键值存储
我理解这节的话题有点吓人,但是我会详细解释它。
基本上,可以使用redis对普通的键值存储进行建模,其中的值可以只是字符串,这不仅比redis普通的键值更节省内存,而且比 memcached 更节省内存。
让我们从一个事实开始:少量的键比包含少量字段的哈希的单个键使用更多的内存。这怎么可能?我们使用了一个技巧。从理论上讲,为了保证我们在常数时间内执行查找(在大O符号中也称为O(1)),需要在平均情况下使用具有常数时间复杂度的数据结构,例如哈希表。
但是很多时候,哈希只包含很少的字段。当哈希比较小的时候,我们可以使用 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是如何工作的,从而能够更好的选择折衷方案,以及能够更好的了解系统确切行为方式。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Rust代替Elixir实现好友列表下发(体量1100W)
- 多业务线亿级体量,携程是怎么做账务中台的
- 聚集问题,方为敏捷
- MySQL——排序操作与聚集函数
- Mybatis高级-resultMap之collection聚集
- Key Lookup开销过大导致聚集索引扫描
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
测试驱动开发的艺术
Lasse Koskela / 李贝 / 人民邮电出版社 / 20101023 / 59.00元
在传统的软件开发中,开发人员对于代码是否正确心中无底,一切依赖于后期的测试环节。极限编程反其道而行之,主张采用测试驱动开发(TDD)的方法,即通过测试定义所要开发的功能的接口,然后实现功能的开发过程。TDD通过不断地测试推动代码的开发,既简化了代码,又保证了软件质量。 本书采用“手把手”的教学方式,通过大量实例来解释TDD,还专门用几章的篇幅来讲解如何为难于测试的技术编写单元测试。全书内容循......一起来看看 《测试驱动开发的艺术》 这本书的介绍吧!