Redis 内存使用优化与存储

  实现方式:

  上面已经说到 Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有2种不同实现,这个 Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap,当成员数量增大时会自动转成真正的 HashMap,此时 encoding 为 ht。

  List

  常用命令:

  lpush,rpush,lpop,rpop,lrange等。

  应用场景:

  Redis list 的应用场景非常多,也是 Redis 最重要的数据结构之一,比如 twitter 的关注列表,粉丝列表等都可以用 Redis 的 list 结构来实现,比较好理解,这里不再重复。

  实现方式:

  Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

  Set

  常用命令:

  sadd,spop,smembers,sunion 等。

  应用场景:

  Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。

  实现方式:

  set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。

  Sorted 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) 的时间复杂度,如果成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是最根本的时间成本和空间成本上的权衡。