Redis 的作者 Salvatore Sanfilippo 曾经对这两种基于内存的资料储存系统进行过比较:
Redis 支援站群服务器端的资料操作:Redis 相比 Memcached 来说,拥有更多的资料结构和并支援更丰富的资料操作,通常在 Memcached 里,你需要将资料拿到客户端来进行类似的修改再 set 回去。这大大增加了互联网 IO 的次数和资料体积。在 Redis 中,这些复杂的操作通常和一般的 GET/SET 一样高效。所以,如果需要 WordPress 加速缓存能够支援更复杂的结构和操作,那么 Redis 会是不错的选择。
内存使用效率对比:使用简单的 key-value 储存的话,Memcached 的内存利用率更高,而如果 Redis 采用 hash 结构来做 key-value 储存,由于其组合式的压缩,其内存利用率会高于 Memcached 。
效能对比:由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在储存小资料时比 Memcached 效能更高。而在 100k 以上的资料中,Memcached 效能要高于 Redis,虽然 Redis 最近也在储存大资料的效能上进行优化,但是比起 Memcached,还是稍有逊色。
具体为什么会出现上面的结论,以下为收集到的资料:
1 、资料型别支援不同
与 Memcached 仅支援简单的 key-value 结构的资料记录不同,Redis 支援的资料型别要丰富得多。最为常用的资料型别主要由五种:String 、 Hash 、 List 、 Set 和 Sorted Set 。 Redis 内部使用一个 redisObject 物件来表示所有的 key 和 value 。 redisObject 最主要的资讯如图所示:
type 代表一个 value 物件具体是何种资料型别,encoding 是不同资料型别在 redis 内部的储存方式,比如:type=string 代表 value 储存的是一个普通字串,那么对应的 encoding 可以是 raw 或者是 int,如果是 int 则代表实际 redis 内部是按数值型类储存和表示这个字串的,当然前提是这个字串本身可以用数值表示,比如:”123″ “456” 这样的字串。只有开启了 Redis 的虚拟内存功能,vm 栏位栏位才会真正的分配内存,该功能预设是关闭状态的。
1)String
常用命令:set/get/decr/incr/mget 等;
应用场景:String 是最常用的一种资料型别,普通的 key/value 储存都可以归为此类;
实现方式:String 在 redis 内部储存预设就是一个字串,被 redisObject 所引用,当遇到 incr 、 decr 等操作时会转成数值型进行计算,此时 redisObject 的 encoding 栏位为 int 。
2)Hash
常用命令:hget/hset/hgetall 等
应用场景:我们要储存一个使用者资讯物件资料,其中包括使用者 ID 、使用者姓名、年龄和生日,通过使用者 ID 我们希望获取该使用者的姓名或者年龄或者生日;
实现方式:Redis 的 Hash 实际是内部储存的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的介面。如图所示,Key 是使用者 ID, value 是一个 Map 。这个 Map 的 key 是成员的属性名,value 是属性值。这样对资料的修改和存取都可以直接通过其内部 Map 的 Key(Redis 里称内部 Map 的 key 为 field), 也就是通过 key(使用者 ID) + field(属性标签) 就可以操作对应属性资料。当前 HashMap 的实现有两种方式:当 HashMap 的成员比较少时 Redis 为了节省内存会采用类似一维阵列的方式来紧凑储存,而不会采用真正的 HashMap 结构,这时对应的 value 的 redisObject 的 encoding 为 zipmap,当成员数量增大时会自动转成真正的 HashMap, 此时 encoding 为 ht 。
3)List
常用命令:lpush/rpush/lpop/rpop/lrange 等;
应用场景:Redis list 的应用场景非常多,也是 Redis 最重要的资料结构之一,比如 twitter 的关注列表,粉丝列表等都可以用 Redis 的 list 结构来实现;
实现方式:Redis list 的实现为一个双向连结串列,即可以支援反向查询和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲伫列等也都是用的这个资料结构。
4)Set
常用命令:sadd/spop/smembers/sunion 等;
应用场景:Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要储存一个列表资料,又不希望出现重复资料时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要介面,这个也是 list 所不能提供的;
实现方式:set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。
5)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, 使用跳跃表的结构可以获得比较高的查询效率,并且在实现上比较简单。
2 、内存管理机制不同
在 Redis 中,并不是所有的资料都一直储存在内存中的。这是和 Memcached 相比一个最大的区别。当实体内存用完时,Redis 可以将一些很久没用到的 value 交换到磁碟。 Redis 只会 WordPress 加速缓存所有的 key 的资讯,如果 Redis 发现内存的使用量超过了某一个阀值,将触发 swap 的操作,Redis 根据 “swappability = age*log(size_in_memory)” 计算出哪些 key 对应的 value 需要 swap 到磁碟。然后再将这些 key 对应的 value 持久化到磁碟中,同时在内存中清除。这种特性使得 Redis 可以保持超过其机器本身内存大小的资料。当然,机器本身的内存必须要能够保持所有的 key,毕竟这些资料是不会进行 swap 操作的。同时由于 Redis 将内存中的资料 swap 到磁碟中的时候,提供服务的主执行绪和进行 swap 操作的子执行绪会共享这部分内存,所以如果更新需要 swap 的资料,Redis 将阻塞这个操作,直到子执行绪完成 swap 操作后才可以进行修改。当从 Redis 中读取资料的时候,如果读取的 key 对应的 value 不在内存中,那么 Redis 就需要从 swap 档案中载入相应资料,然后再返回给请求方。 这里就存在一个 I/O 执行绪池的问题。在预设的情况下,Redis 会出现阻塞,即完成所有的 swap 档案载入后才会相应。这种策略在客户端的数量较小,进行批量操作的时候比较合适。但是如果将 Redis 应用在一个大型的网站应用程式中,这显然是无法满足大并发的情况的。所以 Redis 执行我们设定 I/O 执行绪池的大小,对需要从 swap 档案中载入相应资料的读取请求进行并发操作,减少阻塞的时间。
对于像 Redis 和 Memcached 这种基于内存的资料库系统来说,内存管理的效率高低是影响系统效能的关键因素。传统 C 语言中的 malloc/free 函式是最常用的分配和释放内存的方法,但是这种方法存在著很大的缺陷:首先,对于开发人员来说不匹配的 malloc 和 free 容易造成内存泄露;其次频繁呼叫会造成大量内存碎片无法回收重新利用,降低内存利用率;最后作为系统呼叫,其系统开销远远大于一般函式呼叫。所以,为了提高内存的管理效率,高效的内存管理方案都不会直接使用 malloc/free 呼叫。 Redis 和 Memcached 均使用了自身设计的内存管理机制,但是实现方法存在很大的差异,下面将会对两者的内存管理机制分别进行介绍。
Memcached 预设使用 Slab Allocation 机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以储存相应长度的 key-value 资料记录,以完全解决内存碎片问题。 Slab Allocation 机制只为储存外部资料而设计,也就是说所有的 key-value 资料都储存在 Slab Allocation 系统里,而 Memcached 的其它内存请求则通过普通的 malloc/free 来申请,因为这些请求的数量和频率决定了它们不会对整个系统的效能造成影响 Slab Allocation 的原理相当简单。 如图所示,它首先从操作系统申请一大块内存,并将其分割成各种尺寸的块 Chunk,并把尺寸相同的块分成组 Slab Class 。其中,Chunk 就是用来储存 key-value 资料的最小单位。每个 Slab Class 的大小,可以在 Memcached 启动的时候通过制定 Growth Factor 来控制。假定图中 Growth Factor 的取值为 1.25,如果第一组 Chunk 的大小为 88 个位元组,第二组 Chunk 的大小就为 112 个位元组,依此类推。
当 Memcached 接收到客户端传送过来的资料时首先会根据收到资料的大小选择一个最合适的 Slab Class,然后通过查询 Memcached 储存著的该 Slab Class 内空闲 Chunk 的列表就可以找到一个可用于储存资料的 Chunk 。当一条资料库过期或者丢弃时,该记录所占用的 Chunk 就可以回收,重新新增到空闲列表中。
从以上过程我们可以看出 Memcached 的内存管理制效率高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。因为每个 Chunk 都分配了特定长度的内存空间,所以变长资料无法充分利用这些空间。如图 所示,将 100 个位元组的资料 WordPress 加速缓存到 128 个位元组的 Chunk 中,剩余的 28 个位元组就浪费掉了。
Redis 的内存管理主要通过原始码中 zmalloc.h 和 zmalloc.c 两个档案来实现的。 Redis 为了方便内存的管理,在分配一块内存之后,会将这块内存的大小存入内存块的头部。如图所示,real_ptr 是 redis 呼叫 malloc 后返回的指标。 redis 将内存块的大小 size 存入头部,size 所占据的内存大小是已知的,为 size_t 型别的长度,然后返回 ret_ptr 。当需要释放内存的时候,ret_ptr 被传给内存管理程式。通过 ret_ptr,程式可以很容易的算出 real_ptr 的值,然后将 real_ptr 传给 free 释放内存。
Redis 通过定义一个阵列来记录所有的内存分配情况,这个阵列的长度为 ZMALLOC_MAX_ALLOC_STAT 。阵列的每一个元素代表当前程式所分配的内存块的个数,且内存块的大小为该元素的下标。在原始码中,这个阵列为 zmalloc_allocations 。 zmalloc_allocations[16] 代表已经分配的长度为 16bytes 的内存块的个数。 zmalloc.c 中有一个静态变数 used_memory 用来记录当前分配的内存总大小。所以,总的来看,Redis 采用的是包装的 mallc/free,相较于 Memcached 的内存管理方法来说,要简单很多。
3 、资料持久化支援
Redis 虽然是基于内存的储存系统,但是它本身是支援内存资料的持久化的,而且提供两种主要的持久化策略:RDB 快照和 AOF 日志。而 memcached 是不支援资料持久化操作的。
1)RDB 快照
Redis 支援将当前资料的快照存成一个资料档案的持久化机制,即 RDB 快照。但是一个持续写入的资料库如何生成快照呢?Redis 借助了 fork 命令的 copy on write 机制。在生成快照时,将当前程序 fork 出一个子程序,然后在子程序中回圈所有的资料,将资料写成为 RDB 档案。我们可以通过 Redis 的 save 指令来配置 RDB 快照生成的时机,比如配置 10 分钟就生成快照,也可以配置有 1000 次写入就生成快照,也可以多个规则一起实施。这些规则的定义就在 Redis 的配置档案中,你也可以通过 Redis 的 CONFIG SET 命令在 Redis 执行时设定规则,不需要重启 Redis 。
Redis 的 RDB 档案不会坏掉,因为其写操作是在一个新程序中进行的,当生成一个新的 RDB 档案时,Redis 生成的子程序会先将资料写到一个临时档案中,然后通过原子性 rename 系统呼叫将临时档案重新命名为 RDB 档案,这样在任何时候出现故障,Redis 的 RDB 档案都总是可用的。同时,Redis 的 RDB 档案也是 Redis 主从同步内部实现中的一环。 RDB 有他的不足,就是一旦资料库出现问题,那么我们的 RDB 档案中储存的资料并不是全新的,从上次 RDB 档案生成到 Redis 停机这段时间的资料全部丢掉了。在某些业务下,这是可以忍受的。
2)AOF 日志
AOF 日志的全称是 append only file,它是一个追加写入的日志档案。与一般资料库的 binlog 不同的是,AOF 档案是可识别的纯文字,它的内容就是一个个的 Redis 标准命令。只有那些会导致资料发生修改的命令才会追加到 AOF 档案。每一条修改资料的命令都生成一条日志,AOF 档案会越来越大,所以 Redis 又提供了一个功能,叫做 AOF rewrite 。其功能就是重新生成一份 AOF 档案,新的 AOF 档案中一条记录的操作只会有一次,而不像一份老档案那样,可能记录了对同一个值的多次操作。其生成过程和 RDB 类似,也是 fork 一个程序,直接遍历资料,写入新的 AOF 临时档案。在写入新档案的过程中,所有的写操作日志还是会写到原来老的 AOF 档案中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时档案中。然后呼叫原子性的 rename 命令用新的 AOF 档案取代老的 AOF 档案。
AOF 是一个写档案操作,其目的是将操作日志写到磁碟上,所以它也同样会遇到我们上面说的写操作的流程。在 Redis 中对 AOF 呼叫 write 写入后,通过 appendfsync 选项来控制呼叫 fsync 将其写到磁碟上的时间,下面 appendfsync 的三个设定项,安全强度逐渐变强。
appendfsync no 当设定 appendfsync 为 no 的时候,Redis 不会主动呼叫 fsync 去将 AOF 日志内容同步到磁碟,所以这一切就完全依赖于操作系统的除错了。对大多数 Linux 操作系统,是每 30 秒进行一次 fsync,将缓冲区中的资料写到磁碟上。
appendfsync everysec 当设定 appendfsync 为 everysec 的时候,Redis 会预设每隔一秒进行一次 fsync 呼叫,将缓冲区中的资料写到磁碟。但是当这一次的 fsync 呼叫时长超过 1 秒时。 Redis 会采取延迟 fsync 的策略,再等一秒钟。也就是在两秒后再进行 fsync,这一次的 fsync 就不管会执行多长时间都会进行。这时候由于在 fsync 时档案描述符会被阻塞,所以当前的写操作就会阻塞。所以结论就是,在绝大多数情况下,Redis 会每隔一秒进行一次 fsync 。在最坏的情况下,两秒钟会进行一次 fsync 操作。这一操作在大多数资料库系统中被称为 group commit,就是组合多次写操作的资料,一次性将日志写到磁碟。
appednfsync always 当设定 appendfsync 为 always 时,每一次写操作都会呼叫一次 fsync,这时资料是最安全的,当然,由于每次都会执行 fsync,所以其效能也会受到影响。
对于一般性的业务需求,建议使用 RDB 的方式进行持久化,原因是 RDB 的开销并相比 AOF 日志要低很多,对于那些无法忍资料丢失的应用,建议使用 AOF 日志。
4 、丛集管理的不同
Memcached 是全内存的资料缓冲系统,Redis 虽然支援资料的持久化,但是全内存毕竟才是其高效能的本质。作为基于内存的储存系统来说,机器实体内存的大小就是系统能够容纳的最大资料量。如果需要处理的资料量超过了单台机器的实体内存大小,就需要构建分散式丛集来扩充套件储存能力。
Memcached 本身并不支援分散式,因此只能在客户端通过像一致性杂凑这样的分散式演算法来实现 Memcached 的分散式储存。下图给出了 Memcached 的分散式储存实现架构。当客户端向 Memcached 丛集传送资料之前,首先会通过内建的分散式演算法计算出该条资料的目标节点,然后资料会直接传送到该节点上储存。但客户端查询资料时,同样要计算出查询资料所在的节点,然后直接向该节点传送查询请求以获取资料。
相较于 Memcached 只能采用客户端实现分散式储存,Redis 更偏向于在站群服务器端构建分散式储存。最新版本的 Redis 已经支援了分散式储存功能。 Redis Cluster 是一个实现了分散式且允许单点故障的 Redis 高阶版本,它没有中心节点,具有线性可伸缩的功能。下图给出 Redis Cluster 的分散式储存架构,其中节点与节点之间通过二进位制协议进行通讯,节点与客户端之间通过 ascii 协议进行通讯。在资料的放置策略上,Redis Cluster 将整个 key 的数值域分成 4096 个杂凑槽,每个节点上可以储存一个或多个杂凑槽,也就是说当前 Redis Cluster 支援的最大节点数就是 4096 。 Redis Cluster 使用的分散式演算法也很简单:crc16( key ) % HASH_SLOTS_NUMBER 。
为了保证单点故障下的资料可用性,Redis Cluster 引入了 Master 节点和 Slave 节点。在 Redis Cluster 中,每个 Master 节点都会有对应的两个用于冗余的 Slave 节点。这样在整个丛集中,任意两个节点的宕机都不会导致资料的不可用。当 Master 节点退出后,丛集会自动选择一个 Slave 节点成为新的 Master 节点。
参考资料:
http://www.redisdoc.com/en/latest/
http://memcached.org/
原文来自:http://h2ex.com/1223
本文地址:http://www.linuxprobe.com/redisVSmemcached.html