Redis和Memcached
Redis
和 Memcached
都是基于内存的数据存储系统。Memcached
是高性能分布式内存缓存服务;Redis
是一个开源的key-value
存储系统。与 Memcached
类似,Redis
将大部分数据存储在内存中,支持的数据类型包括:字符串、哈希 表、链表、等数据类型的相关操作。
性能比较
由于 Redis
只使用单核,而 Memcached
可以使用多核多线程,所以平均每一个核上 Redis
在存储小数据时比 Memcached
性能更高。而在100k以上的数据时,Memcached
性能要高于 Redis
,虽然 Redis
最近也在存储大数据的性能上进行优化,但是比起 Memcached
,还是稍有逊色。
Redis
使用的是单进程单线程模型,保证了数据顺序提交,利用队列技术,消除了传统数据库串行控制的开销;Memcached
使用CAS保证数据的一致性。CAS(Check and Set)是一个确保并发一致性的机制,属于“乐观锁”范畴;原理很简单:拿版本号,操作,对比版本号,如果一致就操作,不一致就放弃任何操作。
内存使用效率
使用简单的key-value存储的话,Memcached
的内存利用率更高,而如果 Redis
采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于 Memcached
。
数据存储
Redis
和 Memcached
都是将数据存放在内存中,都是内存数据库。不过 Memcached
还可用于缓存其他东西,例如图片、视频等等。Memcached
把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小;Redis
有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(RDB、AOF),而 Memcached
不支持持久化,超过内存比例的数据,会通过LRU(最近最少使用Least Recently Used抹掉。同时 Redis
并不是所有的数据都一直存储在内存中的,当物理内存用完时,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文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间。
内存管理机制
传统的C语言的malloc/free函数是最常用的分配和释放内存的方法,频繁的调用会造成大量内存碎片无法重新利用,降低内存利用率,另外其作为系统调用,系统开销远远大于一般函数调用。
Memcached
默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。Slab Allocation机制只为存储外部数据而设计,也就是说所有的key-value数据都存储在Slab Allocation系统里,而 Memcached
的其它内存请求则通过普通的malloc/free来申请,因为这些请求的数量和频率决定了它们不会对整个系统的性能造成影响。
如图上图所示,它首先从操作系统申请一大块内存,并将其分割成各种尺寸的块 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个字节就浪费掉了。Memcached
主要的 cache 机制是 LRU(最近最少使用Least Recently Used)算法+超时失效。
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
的内存管理方法来说,要简单很多。
数据备份恢复
Memcached
挂掉后,数据不可恢复;
Redis
有部分数据是存在硬盘上,支持数据的持久化(快照或者AOF日志持久化),所以数据丢失后,可以通过 快照或者AOF日志恢复,Redis
支持数据的备份,即master-slave主从模式的数据备份。
集群、分布式存储
作为基于内存的存储系统来说,机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小,就需要构建分布式集群来扩展存储能力。
Memcached
本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。当客户端向 Memcached
集群发送数据之前,首先会通过内置的分布式算法计算出该条数据的目标节点,然后数据会直接发送到该节点上存储。但客户端查询数据时,同样要计算出查询数据所在的节点,然后直接向该节点发送查询请求以获取数据。Memcached
服务端之间是不相互通信的。
Redis
在2.8版本之前与Memcached
一致,可以在客户端实现分布式,也可以使用代理,twitter曾开源用于 Redis
和Memcached
的代理Twemproxy
,集群架构如下,为避免单点,结合keepalved来实现高可用:
上面的架构通常只有一台Twemproxy在工作,另外一台处于备机,当一台挂掉以后,vip自动漂移,备机接替工作。
在3.0版本以后,Redis
在服务端构建了分布式存储。Redis Cluster
是一个实现了分布式且允许单点故障的 Redis
高级版本,它没有中心节点,各个节点地位一致,具有线性可伸缩的功能。为了保证单点故障下的数据可用性,Redis Cluster引入了Master节点和Slave节点。在Redis Cluster中,每个Master节点都会有对应的两个用于冗余的Slave节点。这样在整个集群中,任意两个节点的宕机都不会导致数据的不可用。当Master节点退出后,集群会自动选择一个Slave节点成为新的Master节点。
如图给出 Redis Cluster
的分布式存储架构,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster
将整个 key的数值域分成16384个哈希槽,每个节点上可以存储一个或多个哈希槽,也就是说当前Redis Cluster
支持的最大节点数就是16384。也就是数据分片和读写分离模型(一个master和多个slave)的融合。