经常使用命令:html
set,get,decr(减一),incr(加一),mget(返回全部(一个或多个)给定 key 的值) 等。redis
应用场景:数组
String 是最经常使用的一种数据类型,普通的 key/value 存储均可以归为此类,这里就不所作解释了。数据结构
实现方式:并发
String 在 redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr,decr 等操做时会转成数值型进行计算,此时 redisObject 的 encoding 字段为int。性能
经常使用命令:优化
hget,hset,hgetall 等。线程
应用场景:指针
咱们简单举个实例来描述下 Hash 的应用场景,好比咱们要存储一个用户信息对象数据,包含如下信息:code
用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息,若是用普通的 key/value 结构来存储,主要有如下2种存储方式:
第一种方式将用户 ID 做为查找 key,把其余信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增长了序列化/反序列化的开销,而且在须要修改其中一项信息时,须要把整个对象取回,而且修改操做须要对并发进行保护,引入CAS等复杂问题。
第二种方法是这个用户信息对象有多少成员就存成多少个 key-value 对儿,用用户 ID +对应属性的名称做为惟一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,可是用户 ID 为重复存储,若是存在大量这样的数据,内存浪费仍是很是可观的。
那么 Redis 提供的 Hash 很好的解决了这个问题,Redis 的 Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口,以下图:
也就是说,Key 仍然是用户 ID,value 是一个 Map,这个 Map 的 key 是成员的属性名,value 是属性值,这样对数据的修改和存取均可以直接经过其内部 Map 的 Key(Redis 里称内部 Map 的 key 为 field),也就是经过 key(用户 ID) + field(属性标签)就能够操做对应属性数据了,既不须要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。
这里同时须要注意,Redis 提供了接口(hgetall)能够直接取到所有的属性数据,可是若是内部 Map 的成员不少,那么涉及到遍历整个内部 Map 的操做,因为 Redis 单线程模型的缘故,这个遍历操做可能会比较耗时,而另其它客户端的请求彻底不响应,这点须要格外注意。
实现方式:
上面已经说到 Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有2种不一样实现,这个 Hash 的成员比较少时 Redis 为了节省内存会采用相似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap,当成员数量增大时会自动转成真正的 HashMap,此时 encoding 为 ht。
经常使用命令:
lpush,rpush,lpop,rpop,lrange等。
应用场景:
Redis list 的应用场景很是多,也是 Redis 最重要的数据结构之一,好比 twitter 的关注列表,粉丝列表等均可以用 Redis 的 list 结构来实现,比较好理解,这里再也不重复。
实现方式:
Redis list 的实现为一个双向链表,便可以支持反向查找和遍历,更方便操做,不过带来了部分额外的内存开销,Redis 内部的不少实现,包括发送缓冲队列等也都是用的这个数据结构。
经常使用命令:
sadd,spop,smembers,sunion 等。
应用场景:
Redis set 对外提供的功能与 list 相似是一个列表的功能,特殊之处在于 set 是能够自动排重的,当你须要存储一个列表数据,又不但愿出现重复数据时,set 是一个很好的选择,而且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。
实现方式:
set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是经过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的缘由。
经常使用命令:
zadd,zrange,zrem,zcard等
使用场景:
Redis sorted set 的使用场景与 set 相似,区别是 set 不是自动有序的,而 sorted set 能够经过用户额外提供一个优先级(score)的参数来为成员排序,而且是插入有序的,即自动排序。当你须要一个有序的而且不重复的集合列表,那么能够选择 sorted set 数据结构,好比 twitter 的 public timeline 能够以发表时间做为 score 来存储,这样获取时就是自动按时间排好序的。
实现方式:
Redis sorted set 的内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序,HashMap 里放的是成员到 score 的映射,而跳跃表里存放的是全部的成员,排序依据是 HashMap 里存的 score,使用跳跃表的结构能够得到比较高的查找效率,而且在实现上比较简单。
经过咱们上面的一些实现上的分析能够看出 redis 实际上的内存管理成本很是高,即占用了过多的内存,做者对这点也很是清楚,因此提供了一系列的参数和手段来控制和节省内存,咱们分别来讨论下。
首先最重要的一点是不要开启 Redis 的 VM 选项,即虚拟内存功能,这个原本是做为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,可是其内存管理成本也很是的高,而且咱们后续会分析此种持久化策略并不成熟,因此要关闭 VM 功能,请检查你的 redis.conf 文件中 vm-enabled 为 no。
其次最好设置下 redis.conf 中的 maxmemory 选项,该选项是告诉 Redis 当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的 Redis 不会由于使用了过多的物理内存而致使 swap,最终严重影响性能甚至崩溃。
另外 Redis 为不一样数据类型分别提供了一组参数来控制内存使用,咱们在前面详细分析过 Redis Hash 是 value 内部为一个 HashMap,若是该 Map 的成员数比较少,则会采用相似一维线性的紧凑格式来存储该 Map,即省去了大量指针的内存开销,这个参数控制对应在 redis.conf 配置文件中下面2项:
hash-max-zipmap-entries 64 hash-max-zipmap-value 512 hash-max-zipmap-entries
含义是当 value 这个 Map 内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即 value 内部有64个如下的成员就是使用线性紧凑存储,超过该值自动转成真正的 HashMap。
hash-max-zipmap-value 含义是当 value 这个 Map 内部的每一个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。
以上2个条件任意一个条件超过设置值都会转换成真正的 HashMap,也就不会再节省内存了,那么这个值是否是设置的越大越好呢,答案固然是否认的,HashMap 的优点就是查找和操做的时间复杂度都是 O(1) 的,而放弃 Hash 采用一维存储则是 O(n) 的时间复杂度,若是成员数量不多,则影响不大,不然会严重影响性能,因此要权衡好这个值的设置,整体上仍是最根本的时间成本和空间成本上的权衡。
一样相似的参数还有: 1 list-max-ziplist-entries 512 说明:list 数据类型多少节点如下会采用去指针的紧凑存储格式。 1 list-max-ziplist-value 64 说明:list 数据类型节点值大小小于多少字节会采用紧凑存储格式。 1 set-max-intset-entries 512 说明:set 数据类型内部数据若是所有是数值型,且包含多少节点如下会采用紧凑格式存储。