国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > 数据库 > 数据库应用 > Redis和Memcached的区别

Redis和Memcached的区别

来源:程序员人生   发布时间:2015-04-27 09:06:53 阅读次数:4220次

Redis的作者Salvatore Sanfilippo曾对这两种基于内存的数据存储系统进行过比较:

  1. Redis支持服务器真个数据操作:Redis相比Memcached来讲,具有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和1般的GET/SET1样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那末Redis会是不错的选择。
  2. 内存使用效力对照:使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采取hash结构来做key-value存储,由于其组合式的紧缩,其内存利用率会高于Memcached。
  3. 性能对照:由于Redis只使用单核,而Memcached可使用多核,所以平均每个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。

具体为何会出现上面的结论,以下为搜集到的资料:

1、数据类型支持不同

与Memcached仅支持简单的key-value结构的数据记录不同,Redis支持的数据类型要丰富很多。最为经常使用的数据类型主要由5种:String、Hash、List、Set和Sorted Set。Redis内部使用1个redisObject对象来表示所有的key和value。redisObject最主要的信息如图所示:

redisObject

type代表1个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储方式,比如:type=string代表value存储的是1个普通字符串,那末对应的encoding可以是raw或是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,固然条件是这个字符串本身可以用数值表示,比如:”123″ “456”这样的字符串。只有打开了Redis的虚拟内存功能,vm字段字段才会真实的分配内存,该功能默许是关闭状态的。

1)String

  • 经常使用命令:set/get/decr/incr/mget等;
  • 利用场景:String是最经常使用的1种数据类型,普通的key/value存储都可以归为此类;
  • 实现方式:String在redis内部存储默许就是1个字符串,被redisObject所援用,当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

2)Hash

  • 经常使用命令:hget/hset/hgetall等
  • 利用场景:我们要存储1个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获得该用户的姓名或年龄或生日;
  • 实现方式:Redis的Hash实际是内部存储的Value为1个HashMap,并提供了直接存取这个Map成员的接口。如图所示,Key是用户ID, value是1个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就能够操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采取类似1维数组的方式来紧凑存储,而不会采取真实的HashMap结构,这时候对应的value的redisObject的encoding为zipmap,当做员数量增大时会自动转成真实的HashMap,此时encoding为ht。
  • hash
3)List
  • 经常使用命令:lpush/rpush/lpop/rpop/lrange等;
  • 利用场景:Redis list的利用场景非常多,也是Redis最重要的数据结构之1,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现;
  • 实现方式:Redis list的实现为1个双向链表,便可以支持反向查找和遍历,更方便操作,不过带来了部份额外的内存开消,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
4)Set
  • 经常使用命令:sadd/spop/smembers/sunion等;
  • 利用场景:Redis set对外提供的功能与list类似是1个列表的功能,特殊的地方在于set是可以自动排重的,当你需要存储1个列表数据,又不希望出现重复数据时,set是1个很好的选择,并且set提供了判断某个成员是不是在1个set集合内的重要接口,这个也是list所不能提供的;
  • 实现方式:set 的内部实现是1个 value永久为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断1个成员是不是在集合内的缘由。

5)Sorted Set

  • 经常使用命令:zadd/zrange/zrem/zcard等;
  • 利用场景:Redis sorted set的使用处景与set类似,区分是set不是自动有序的,而sorted set可以通过用户额外提供1个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要1个有序的并且不重复的集合列表,那末可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获得时就是自动按时间排好序的。
  • 实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映照,而跳跃表里寄存的是所有的成员,排序根据是HashMap里存的score,使用跳跃表的结构可以取得比较高的查找效力,并且在实现上比较简单。

2、内存管理机制不同

在Redis中,其实不是所有的数据都1直存储在内存中的。这是和Memcached相比1个最大的区分。当物理内存用完时,Redis可以将1些很久没用到的value交换到磁盘。Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某1个阀值,将触发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文件中加载相应数据,然后再返回给要求方。 这里就存在1个I/O线程池的问题。在默许的情况下,Redis会出现阻塞,即完成所有的swap文件加载后才会相应。这类策略在客户真个数量较小,进行批量操作的时候比较适合。但是如果将Redis利用在1个大型的网站利用程序中,这明显是没法满足大并发的情况的。所以Redis运行我们设置I/O线程池的大小,对需要从swap文件中加载相应数据的读取要求进行并发操作,减少阻塞的时间。

对像Redis和Memcached这类基于内存的数据库系统来讲,内存管理的效力高低是影响系统性能的关键因素。传统C语言中的malloc/free函数是最经常使用的分配和释放内存的方法,但是这类方法存在着很大的缺点:首先,对开发人员来讲不匹配的malloc和free容易造成内存泄漏;其次频繁调用会造成大量内存碎片没法回收重新利用,下降内存利用率;最后作为系统调用,其系统开消远远大于1般函数调用。所以,为了提高内存的管理效力,高效的内存管理方案都不会直接使用malloc/free调用。Redis和Memcached均使用了本身设计的内存管理机制,但是实现方法存在很大的差异,下面将会对二者的内存管理机制分别进行介绍。

Memcached默许使用Slab Allocation机制管理内存,其主要思想是依照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。Slab Allocation机制只为存储外部数据而设计,也就是说所有的key-value数据都存储在Slab Allocation系统里,而Memcached的其它内存要求则通过普通的malloc/free来申请,由于这些要求的数量和频率决定了它们不会对全部系统的性能造成影响Slab Allocation的原理相当简单。 如图所示,它首先从操作系统申请1大块内存,并将其分割成各种尺寸的块Chunk,并把尺寸相同的块分成组Slab Class。其中,Chunk就是用来存储key-value数据的最小单位。每一个Slab Class的大小,可以在Memcached启动的时候通过制定Growth Factor来控制。假定图中Growth Factor的取值为1.25,如果第1组Chunk的大小为88个字节,第2组Chunk的大小就为112个字节,依此类推。

Slab-Allocation

当Memcached接收到客户端发送过来的数据时首先会根据收到数据的大小选择1个最适合的Slab Class,然后通过查询Memcached保存着的该Slab Class内空闲Chunk的列表就能够找到1个可用于存储数据的Chunk。当1条数据库过期或抛弃时,该记录所占用的Chunk就能够回收,重新添加到空闲列表中。从以上进程我们可以看出Memcached的内存管理制效力高,而且不会造成内存碎片,但是它最大的缺点就是会致使空间浪费。由于每一个Chunk都分配了特定长度的内存空间,所以变长数据没法充分利用这些空间。如图 所示,将100个字节的数据缓存到128个字节的Chunk中,剩余的28个字节就浪费掉了。

Chunk

Redis的内存管理主要通过源码中zmalloc.h和zmalloc.c两个文件来实现的。Redis为了方便内存的管理,在分配1块内存以后,会将这块内存的大小存入内存块的头部。如图所示,real_ptr是redis调用malloc后返回的指针。redis将内存块的大小size存入头部,size所占据的内存大小是已知的,为size_t类型的长度,然后返回ret_ptr。当需要释放内存的时候,ret_ptr被传给内存管理程序。通过ret_ptr,程序可以很容易的算出real_ptr的值,然后将real_ptr传给free释放内存。

zmalloc

Redis通过定义1个数组来记录所有的内存分配情况,这个数组的长度为ZMALLOC_MAX_ALLOC_STAT。数组的每个元素代表当前程序所分配的内存块的个数,且内存块的大小为该元素的下标。在源码中,这个数组为zmalloc_allocations。zmalloc_allocations[16]代表已分配的长度为16bytes的内存块的个数。zmalloc.c中有1个静态变量used_memory用来记录当前分配的内存总大小。所以,总的来看,Redis采取的是包装的mallc/free,相较于Memcached的内存管理方法来讲,要简单很多。

3、数据持久化支持

Redis虽然是基于内存的存储系统,但是它本身是支持内存数据的持久化的,而且提供两种主要的持久化策略:RDB快照和AOF日志。而memcached是不支持数据持久化操作的。

1)RDB快照

Redis支持将当前数据的快照存成1个数据文件的持久化机制,即RDB快照。但是1个延续写入的数据库如何生成快照呢?Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出1个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。我们可以通过Redis的save指令来配置RDB快照生成的时机,比如配置10分钟就生成快照,也能够配置有1000次写入就生成快照,也能够多个规则1起实行。这些规则的定义就在Redis的配置文件中,你也能够通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。

Redis的RDB文件不会坏掉,由于其写操作是在1个新进程中进行的,当生成1个新的RDB文件时,Redis生成的子进程会先将数据写到1个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任什么时候候出现故障,Redis的RDB文件都总是可用的。同时,Redis的RDB文件也是Redis主从同步内部实现中的1环。RDB有他的不足,就是1旦数据库出现问题,那末我们的RDB文件中保存的数据其实不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。在某些业务下,这是可以忍耐的。

2)AOF日志

AOF日志的全称是append only file,它是1个追加写入的日志文件。与1般数据库的binlog不同的是,AOF文件是可辨认的纯文本,它的内容就是1个个的Redis标准命令。只有那些会致使数据产生修改的命令才会追加到AOF文件。每条修改数据的命令都生成1条日志,AOF文件会愈来愈大,所以Redis又提供了1个功能,叫做AOF rewrite。其功能就是重新生成1份AOF文件,新的AOF文件中1条记录的操作只会有1次,而不像1份老文件那样,可能记录了对同1个值的屡次操作。其生成进程和RDB类似,也是fork1个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的进程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志1次性写入到临时文件中。然后调用原子性的rename命令用新的AOF文件取代老的AOF文件。

AOF是1个写文件操作,其目的是将操作日志写到磁盘上,所以它也一样会遇到我们上面说的写操作的流程。在Redis中对AOF调用write写入后,通过appendfsync选项来控制调用fsync将其写到磁盘上的时间,下面appendfsync的3个设置项,安全强度逐步变强。

  • appendfsync no 当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这1切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行1次fsync,将缓冲区中的数据写到磁盘上。
  • appendfsync everysec 当设置appendfsync为everysec的时候,Redis会默许每隔1秒进行1次fsync调用,将缓冲区中的数据写到磁盘。但是当这1次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等1秒钟。也就是在两秒后再进行fsync,这1次的fsync就不管会履行多长时间都会进行。这时候候由于在fsync时文件描写符会被阻塞,所以当前的写操作就会阻塞。所以结论就是,在绝大多数情况下,Redis会每隔1秒进行1次fsync。在最坏的情况下,两秒钟会进行1次fsync操作。这1操作在大多数数据库系统中被称为group commit,就是组合屡次写操作的数据,1次性将日志写到磁盘。
  • appednfsync always 当设置appendfsync为always时,每次写操作都会调用1次fsync,这时候数据是最安全的,固然,由于每次都会履行fsync,所以其性能也会遭到影响。

对1般性的业务需求,建议使用RDB的方式进行持久化,缘由是RDB的开消并相比AOF日志要低很多,对那些没法忍数据丢失的利用,建议使用AOF日志。

4、集群管理的不同

Memcached是全内存的数据缓冲系统,Redis虽然支持数据的持久化,但是全内存毕竟才是其高性能的本质。作为基于内存的存储系统来讲,机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小,就需要构建散布式集群来扩大存储能力。

Memcached本身其实不支持散布式,因此只能在客户端通过像1致性哈希这样的散布式算法来实现Memcached的散布式存储。下图给出了Memcached的散布式存储实现架构。当客户端向Memcached集群发送数据之前,首先会通过内置的散布式算法计算出该条数据的目标节点,然后数据会直接发送到该节点上存储。但客户端查询数据时,一样要计算出查询数据所在的节点,然后直接向该节点发送查询要求以获得数据。

Memcached-node

 

相较于Memcached只能采取客户端实现散布式存储,Redis更偏向于在服务器端构建散布式存储。最新版本的Redis已支持了散布式存储功能。Redis Cluster是1个实现了散布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。下图给出Redis Cluster的散布式存储架构,其中节点与节点之间通过2进制协议进行通讯,节点与客户端之间通过ascii协议进行通讯。在数据的放置策略上,Redis Cluster将全部key的数值域分成4096个哈希槽,每一个节点上可以存储1个或多个哈希槽,也就是说当前Redis Cluster支持的最大节点数就是4096。Redis Cluster使用的散布式算法也很简单:crc16( key ) % HASH_SLOTS_NUMBER。

Redis-Cluster

 

为了保证单点故障下的数据可用性,Redis Cluster引入了Master节点和Slave节点。在Redis Cluster中,每一个Master节点都会有对应的两个用于冗余的Slave节点。这样在全部集群中,任意两个节点的宕机都不会致使数据的不可用。当Master节点退出后,集群会自动选择1个Slave节点成为新的Master节点。

Redis-Cluster-2

 

参考资料:

  • http://www.redisdoc.com/en/latest/
  • http://memcached.org/
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生