Memcached与Redis比较

  上一篇文章我们详细介绍了分布式缓存服务器Memcached,本文比较了Memcached与另一个缓存服务器Redis。

一、服务方式

  Memcached和Redis均可在本地作为独立进程提供服务,也可以在远端提供服务。在本地服务时,支持进程间通信,在远端服务时,支持tcp和udp协议。

二、事件模型

  本质上,Memcached和Redis都使用epoll做事件循环,Redis是单线程的,而Memcached是多线程的。Redis的事件模型很简单,只有一个event loop,是简单的reactor实现。而Memcached是多线程的,使用master-worker的方式,主线程监听端口,建立连接,然后顺序分配给各个工作线程。 每一个工作线程都有一个event loop,它们服务不同的客户端。master线程和worker线程之间使用管道通信,每一个工作线程都会创建一个管道,然后保存写端和读端,并且将读端加入event loop,监听可读事件。

三、内存分配

  Memcached有自己的内存池 ,即预先分配一大块内存,然后接下来分配内存就从内存池中分配,这样可以减少内存分配的次数,提高效率。 Redis没有自己的内存池,直接使用时分配,即什么时候需要什么时候分配,内存管理的事交给操作系统,自己只负责取和释放。不过Redis支持使用tcmalloc来替换glibc的malloc,前者是Google的产品,比glibc的malloc快。
  由于Redis没有自己的内存池,所以内存申请和释放的管理就简单很多,直接malloc和free即可,十分方便。而Memcached是支持内存池的,所以内存申请是从内存池中获取,而free也是还给内存池,所以需要很多额外的管理操作。

四、数据库实现

4.1 Memcached数据库实现

4.1.1 key-value对存储

  Memcached只支持存储key-value对,一个key对应一个value,在内存中也是以key-value对的形式存储。如图1,Memcached将key-value对存储在一个item结构中。


图1 存储key-value对的item结构
图1 存储key-value对的item结构

  当有多个key-value对时,需要快速查找对应的item。Memcached通过维护一个hash表实现item的快速查找。hash表使用开链法解决冲突,每一个hash表节点存储一个链表,链表的节点就是item的指针,如图1中的h_next就是指向下一个节点的指针。当item的数量是hash节点数量的1.5倍以上时,hash表需要扩容。将old_hashtable=primary_hashtable,然后primary_hashtable设置为新的hash表,依次将old_hashtable中的节点移动到新的primary_hashtable,同时用expand_bucket记录移动了多少个节点,最后free old_hashtable。在扩容过程中,查找一个item可能会在old_hashtable也可能会在primary_hashtable,需要根据item对应的节点位置和expand_bucket大小来确定在哪个hashtable。

4.1.2 slab分配内存

  上一篇博客中,我们介绍了Memcached的Slab Allocation机制。如图2,每个slab中的chunk大小相同,不同的slab中chunk的大小按比例递增,为新的item分配一个最小的能放下item的chunk。


图2 Slab分配内存
图2 Slab分配内存

  slabclass有一个指针slot,保存未分配的item和已经free的item,有item被free时,就放在slot头部,在需要slab分配item时,就从slot中取。
  slabclass还对应一个链表,有头尾节点head和tail。链表中的节点即该slabclass已分配的item,新的放在头部,越靠近tail表示越没用被使用。当slabclass内存不足时,可从尾部开始删除节点,该链表实现了LRU(least recently used)。链表中的节点通过指针与hash表中的节点互指,查找item时使用hash,删除item时使用该链表。

4.1.3 多线程

  每次需要新分配item时,先从slabclass对应的链表尾部往前找,看是否有item过期,有则直接分配,没有则从slab中分配chunk,如果slab中没有chunk,则根据LRU释放已分配的chunk。
  Memcached支持多线程,因此需要进行并发控制,维持数据一致性。比如,A改了数据,之后B也改了数据,但A不知道B改了,认为数据是他改完后的值,继续执行任务就可能产生问题。Memcached使用CAS协议解决多线程的问题。

4.2 Redis数据库实现

4.2.1 key-value对存储

  Redis也存储key-value对,但Memcached的value只支持sting,而Redis支持string、list、set、sorted set、hash table五种数据结构。
  如图3,为了实现五种数据结构,Redis定义了抽象的对象Redis Object。每个对象有类型,表示string、list、set、sorted set、hash table中的一种。为了提高效率,Redis为每种类型准备了多种实现方式,根据应用场景选择合适的实现,encoding即表示队形的实现方式。


图3 Redis抽象对象数据结构
图3 Redis抽象对象数据结构

4.2.2 内存管理与多线程

  与Memcached相比,Redis不支持内存管理和多线程。Redis直接通过malloc和free操作内存,将内存管理的任务交给操作系统完成。Redis只有一个event loop,不支持多线程。

4.2.3 数据持久化

  Redis最突出的特点,就是支持数据持久化。

  • RDB持久化——Redis database

  RDB持久化的核心思想就是把整个数据库保存在文件里。

REDIS db_version database EOF check_sum

  首先写入一个REDIS字符串,起验证作用,表示是RDB文件,然后保存Redis的版本信息,然后是具体的数据库,然后存储结束符EOF,最后用检验和。databases中,每个数据库存储方式如下。

SELECTDB db_number key_value_pairs

  首先一个1字节的常量SELECTDB,表示切换DB了,然后是数据库的编号,它的长度是可变的,然后接下来就是具体的key-value对的数据了。
  key_value_pairs存储方式如下。

EXPIRETIME_MS ms TYPE key value

  首先用EXPIRETIME_MS和ms存储expire time,然后存储value的类型,然后存储key和value。
  保存了RDB文件,在Redis启动的时候,可根据RDB文件恢复数据库。

  • AOF持久化——ApeendOnly File

  AOF持久化的核心思想就是保存所有建立数据库的命令。相比于RDB持久化,AOF在持久化过程中开销很小,但在恢复的数据库的时候开销很大。

4.2.4 Redis事务

  Redis另一个比Memcached强大的地方,是它支持简单的事务。Redis保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。但不同于关系型数据库,Redis的事务机制不支持回滚,它的事务只保证命令依次被执行,即使中间一条命令出错也会继续往下执行。
  执行Redis事务,首先执行multi命令,表示开始事务,然后输入需要执行的命令,最后输入exec执行事务。一般情况下Redis在接受到一个client发来的命令后会立即处理并返回处理结果,但是当一个client在一个连接中发出multi命令后,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当收到exec命令后,Redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client。

五、小结

  本文从服务方式、事件模型、内存分配、数据库实现四个方面比较了Memcached和Redis,着重比较了它们在数据库实现上的异同。总而言之,Memcached专注与存储key-value数据,而Redis能提供更丰富的数据结构和其他功能。

参考文献

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章