redis五种数据结构Redis和Memcached的区别(数据类型、内存管理、数据持久
化、集管理)
Redis的作者Salvatore Sanfilippo曾经对这两种基于内存的数据存储系统进⾏过⽐较:
1. Redis⽀持服务器端的数据操作:Redis相⽐Memcached来说,拥有更多的数据结构和并⽀持更丰富的数据操作,通常在Memcached
⾥,你需要将数据拿到客户端来进⾏类似的修改再set回去。这⼤⼤增加了⽹络IO的次数和数据体积。在Redis中,这些复杂的操作通常和⼀般的GET/SET⼀样⾼效。所以,如果需要缓存能够⽀持更复杂的结构和操作,那么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⽀持的数据类型要丰富得多。最为常⽤的数据类型主要由五种: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只会缓存所有的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个字节的数据缓存到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会采取延迟fsyn
c的策略,再等⼀秒钟。也就是在两秒后再进⾏fsync,这⼀次的fsync就不管会执⾏多长时间都会进⾏。这时候由于在fsync时⽂件描述符会被阻塞,所以当前的写操作就会阻塞。所以结论就是,在绝⼤多数情况
下,Redis会每隔⼀秒进⾏⼀次fsync。在最坏的情况下,两秒钟会进⾏⼀次fsync操作。这⼀操作在⼤多数数据库系统中被称为group commit,就是组合多次写操作的数据,⼀次性将⽇志写到磁盘。
appednfsync always 当设置appendfsync为always时,每⼀次写操作都会调⽤⼀次fsync,这时数据是最安全的,当然,由于每次都会执⾏fsync,所以其性能也会受到影响。
对于⼀般性的业务需求,建议使⽤RDB的⽅式进⾏持久化,原因是RDB的开销并相⽐AOF⽇志要低很多,对于那些⽆法忍数据丢失的应⽤,建议使⽤AOF⽇志。
4、集管理的不同
Memcached是全内存的数据缓冲系统,Redis虽然⽀持数据的持久化,但是全内存毕竟才是其⾼性能的本质。作为基于内存的存储系统来说,机器物理内存的⼤⼩就是系统能够容纳的最⼤数据量。如果需要处理的数据量超过了单台机器的物理内存⼤⼩,就需要构建分布式集来扩展存储能⼒。
Memcached本⾝并不⽀持分布式,因此只能在客户端通过像⼀致性哈希这样的分布式算法来实现Mem
cached的分布式存储。下图给出了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节点。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论