当前位置: 首页 > 科技观察

Redis和Memcached有很大区别吗?缓存只选一个,我们应该选哪个呢?

时间:2023-03-13 12:57:03 科技观察

学过两者的同学大概有这样一个印象:1.与memcached相比,redis只支持简单的key-value数据类型,还提供了list、set、zset、hash等数据结构的存储;2、Redis支持数据备份,即主从模式的数据备份;3、redis支持数据持久化,可以把内存中的数据保存在磁盘上,重启的时候可以重新加载使用等等。貌似redis比memcached更强大,是这样吗?存在就是合理的,下面我们根据几个不同点来一一比较。网络IO模型Memcached是一个多线程、非阻塞IO多路复用的网络模型,分为监控主线程和worker子线程。监控线程监控网络连接。接受请求后,将连接描述字管道传递给工作线程进行读写。IO层和网络层使用libevent封装的事件库。多线程模型可以起到多核的作用,但是引入了缓存一致性和锁问题。比如:memcached最常用的stats命令,实际上所有的memcached操作都必须锁定这个全局变量,进行技术等工作,带来了性能损失。Redis采用单线程IO多路复用模型,并封装了一个简单的AeEvent事件处理框架。主要实现了epoll、kqueue和select。对于single-memoryonlyIO操作,单线程可以最大化速度优势,但是redis也提供了一些简单的计算功能,比如排序,聚合等,对于这些操作,单线程模型会严重影响整体的吞吐量。在CPU计算过程中,整个IO调度都是阻塞的。数据支持类型memcached采用key-value形式存储和访问数据,在内存中维护了一个庞大的HashTable,将数据查询的时间复杂度降低到O(1),保证了数据的高性能访问。开篇提到:redis与memcached相比,只支持简单的key-value数据类型,还提供了list、set、zset、hash等数据结构的存储;详见《Redis内存使用优化与存储》Redis的内存管理机制对于Memcached这样基于内存的数据库系统,内存管理的效率是影响系统性能的关键因素。传统C语言中的malloc/free函数是最常用的分配和释放内存的方法,但是这种方法有很大的缺陷:第一,不匹配的malloc和free很可能对开发者造成内存泄漏;其次,频繁调用会造成大量内存碎片无法回收再利用,降低内存利用率;***作为系统调用,其系统开销远大于一般的函数调用。因此,为了提高内存管理效率,高效的内存管理方案不会直接使用malloc/free调用。Redis和Memcached都使用了自己设计的内存管理机制,但是实现方式却大相径庭。下面将分别介绍两者的内存管理机制。Memcached默认使用SlabAllocation机制来管理内存。其主要思想是将分配的内存按照预定的大小划分成特定长度的块,存储相应长度的key-value数据记录,从而彻底解决内存碎片问题。SlabAllocation机制只是为了存储外部数据而设计的,也就是说所有的key-value数据都存储在SlabAllocation系统中,而Memcached的其他内存请求都是通过普通的malloc/free来申请的,因为这些请求的数量和频率决定了它们不会影响整个系统的性能。SlabAllocation的原理很简单。如图所示,它首先向操作系统申请一大块内存,将其分成大小不一的chunk,再将大小相同的block分成若干组SlabClasses。其中,Chunk是用来存储key-value数据的最小单位。每个SlabClass的大小可以通过在Memcached启动时设置GrowthFactor来控制。假设图中GrowthFactor的值为1.25,如果第一组Chunk的大小为88字节,则第二组Chunk的大小为112字节,以此类推。当Memcached接收到客户端发送的数据时,首先根据接收到的数据大小选择最合适的SlabClass,然后通过查询保存的SlabClass中的空闲Chunks列表,找到一个可以用来存储数据的通过内存缓存。大块。当一个数据库过期或被丢弃时,记录所占用的Chunk可以被回收并添加到空闲列表中。从上面的过程我们可以看出Memcached的内存管理系统是高效的,不会造成内存碎片,但是它最大的缺点就是会导致空间的浪费。因为每个Chunk都分配了特定长度的内存空间,变长数据无法充分利用这块空间。如图所示,如果在一个128字节的Chunk中缓存了100字节的数据,剩下的28字节就会被浪费掉。Redis的内存管理主要是通过源码中的zmalloc.h和zmalloc.c这两个文件来实现的。Redis为了方便内存管理,在分配一块内存后,会将内存的大小存放在内存块的头部。如图,real_ptr是redis调用malloc后返回的指针。Redis在header中存储内存块的大小,知道size占用的内存大小,就是size_t类型的长度,然后返回ret_ptr。当需要释放内存时,ret_ptr被传递给内存管理器。通过ret_ptr,程序可以很方便的计算出real_ptr的值,然后将real_ptr传递给free来释放内存。Redis通过定义一个长度为ZMALLOC_MAX_ALLOC_STAT的数组来记录所有的内存分配。数组的每个元素代表当前程序分配的内存块的个数,内存块的大小是元素的下标。在源码中,这个数组就是zmalloc_allocations。zmalloc_allocations[16]表示分配的内存块的个数,长度为16字节。zmalloc.c中有一个静态变量used_memory,用于记录当前分配内存的总大小。所以一般情况下,Redis使用封装好的mallc/free,比Memcached的内存管理方式要简单的多。在Redis中,并非所有数据都始终存储在内存中。与Memcached相比,这是一个巨大的差异。当物理内存用完时,Redis可以将一些长时间不用的值交换到磁盘上。Redis只会缓存所有的关键信息。如果Redis发现内存使用量超过了一定的阈值,就会触发swap操作。Redis根据“swappability=age*log(size_in_memory)”计算出哪些key对应于value。交换到磁盘。然后将这些key对应的值持久化到磁盘,同时在内存中清空。这个特性允许Redis保存超过本机内存大小的数据。当然,机器本身的内存必须能够容纳所有的密钥,毕竟这些数据是不会被交换的。同时,当Redis将内存中的数据交换到磁盘时,提供服务的主线程和执行交换操作的子线程会共享这部分内存,所以如果需要的数据swapped被更新,Redis会阻塞这个操作,直到子线程完成swap操作后才能修改。当从Redis读取数据时,如果读取的key对应的value不在内存中,那么Redis需要从swap文件中加载对应的数据,然后返回给请求者。这里有一个I/O线程池问题。默认情况下,Redis会被阻塞,即直到所有交换文件加载完毕后才会响应。这种策略比较适合客户端数量少,批量操作的情况。但是如果Redis应用在一个大型的网站应用中,这显然不能满足大并发的情况。所以Redis允许我们设置I/O线程池的大小,对需要从swap文件加载相应数据的读请求进行并发操作,减少阻塞时间。Memcached使用预先分配的内存池,使用不同大小的slab和chunk来管理内存,并根据Item的大小选择合适的chunk存储。内存池方式可以节省申请/释放内存的开销,可以减少内存碎片的产生,但是这种方式也会带来一定程度的空间浪费,当内存中还有很多空间时,新的数据也可能被删除。原因可以参考Timyang的文章:http://timyang.net/data/Memcached-lru-evictions/Redis采用现场申请内存的方式存储数据,很少使用free-list等方式来存储数据优化内存分配,一定程度上会有内存碎片。Redis根据数据存储命令参数。有过期时间的数据会单独存储在一起,称为临时数据。非临时数据永远不会被移除,即使物理内存不够用,swap也不会移除任何非临时数据(但会尽量排除一些临时数据),Redis更适合存储而不是缓存。数据存储和持久化memcached不支持内存数据的持久化操作,所有数据都以in-memory的形式存储。Redis支持持久化操作。Redis提供了两种不同的持久化方式来将数据存储在硬盘中。一种是快照(snapshotting),可以将某一时刻存在的所有数据写入硬盘。另一种方式叫做append-onlyfile(AOF),在执行写命令的时候会把执行的写命令复制到硬盘中。数据一致性Memcached提供了cas命令,可以保证对同一数据的多个并发访问操作的一致性。Redis没有提供cas命令,所以不能保证这一点,但是Redis提供了事务功能,可以保证一系列命令的原子性,中间不会被任何操作打断。集群管理不同。Memcached是一个全内存数据缓冲系统。Redis虽然支持数据持久化,但全内存毕竟是其高性能的本质。作为基于内存的存储系统,机器物理内存的大小是系统所能容纳的最大数据量。如果要处理的数据量超过了单机的物理内存大小,就需要构建分布式集群来扩展存储容量。Memcached本身不支持分布式,所以Memcached的分布式存储只能在客户端通过一致性哈希等分布式算法实现。下图是Memcached的分布式存储实现架构。客户端在向Memcached集群发送数据之前,会先通过内置的分布式算法计算出数据的目标节点,然后将数据直接发送到该节点进行存储。但是,客户端在查询数据时,还需要计算出查询数据所在的节点,然后直接向该节点发送查询请求,获取数据。与只能在客户端实现分布式存储的Memcached相比,Redis更倾向于在服务端构建分布式存储。最新版本的Redis已经支持分布式存储。RedisCluster是Redis的高级版本,实现了分布式,允许单点故障。它没有中心节点,具有线性可扩展性。RedisCluster分布式存储架构,节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据放置策略上,RedisCluster将整个key-value域划分为4096个hash槽,每个节点可以存储一个或多个hash槽,也就是说RedisCluster目前支持的最大节点数为4096个。分布式RedisCluster使用的算法也很简单:crc16(key)%HASH_SLOTS_NUMBER。RedisCluster为了保证单点故障下的数据可用性,引入了Master节点和Slave节点。在RedisCluster中,每个Master节点都会对应两个Slave节点来实现冗余。这样,在整个集群中,任意两个节点宕机都不会造成数据不可用。当Master节点退出时,集群会自动选择一个Slave节点成为新的Master节点。