转载 https://www.jianshu.com/p/e94fa7340923
简介
memcached和redis都属于内存(memory)键-值(key-value)数据库,在设计和思想上有许多相同之处,功能和应用在很多场合(如分布式缓存服务)也相似。它们都从属于数据库解决方案中的nosql家族,由于两者都将数据存储在内存中,自然而然,它们都是非常理想的缓存实现方案。
memcached最初是由Brad Fitzpatrick于2003年开发而成。而redis则由Salvatore Sanfilippo于2009年创建,它本身也从memcached上吸取借鉴大量宝贵经验教训,被称为“强化版memcached”。确实,redis在功能多样性方面要胜过memcached,虽然强大且更具灵活性,但复杂程度也比memcached更甚。
基本架构和思想
两者的架构和设计思想
memcached
memcached采用客户端-服务器(C/S)的架构,客户端和服务器端的通讯使用自定义的协议标准,只要满足协议格式要求,客户端Library可以用任何语言实现。
从用户的角度来说,服务器维护了一个键-值关系的数据表,服务器之间相互独立,互相之间不共享数据也不做任何通讯操作。也就是说,memcached本身不支持分布式扩展。客户端需要知道所有的服务器,并自行负责管理数据在各个服务器间的分配。
在服务器端,内部的数据存储,使用基于Slab的内存管理方式,有利于减少内存碎片和频繁分配销毁内存所带来的开销。各个Slab按需动态分配一个page的内存(和4Kpage的概念不同,这里默认page为1M),page内部按照不同slab class的尺寸再划分为内存chunk供服务器存储KV键值对使用。
memcached的基本应用模型:
redis
redis的基本应用模式和上图memcached的基本相似,不难发现网上到处都是关于redis是否可以完全替代memcached使用的问题。
redis内部的数据结构最终也会落实到key-value对应的形式,不过从暴露给用户的数据结构来看,要比memcached丰富,除了标准的通常意义的键值对,redis还支持List,Set, Hashes,Sorted Set等数据结构。
基本命令
memcached的命令或者说通讯协议非常简单,server所支持的命令基本就是对特定key的添加,删除,替换,原子更新,读取等,具体包括 set, get, add, replace, append, inc/dec 等等。
memcached的通讯协议包括文本格式和二进制格式,用于满足简单网络客户端工具(如telnet)和对性能要求更高的客户端的不同需求。
redis的命令在kv(string类型)上提供与memcached类似的基本操作,在其它数据结构上也支持基本类似的操作(当然还有这些数据结构所特有的操作,如set的union,list的pop等)而支持更多的数据结构,在一定程度上也就意味着更加广泛的应用场合
除了多种数据结构的支持,redis相比memcached还提供了许多额外的特性,比如subscribe/publish命令,以支持发布/订阅模式这样的通知机制等等,这些额外的特性同样有助于拓展它的应用场景。
redis的客户端-服务器通讯协议完全采用文本格式(在将来可能的服务器间通讯会采用二进制格式)。
事务
redis通过multi / watch / exec等命令可以支持事务的概念,原子性的执行一批命令。在2.6以后的版本中由于添加了对script脚本的支持,而脚本固有的是以transaction事务的方式执行的,并且更加易于使用,所以不排除将来取消multi等命令接口的可能性。
memcached的应用模式中,除了increment/decrement这样的原子操作命令,不存在对事务的支持。
数据备份,有效性,持久化等
memcached不保证存储的数据的有效性,slab内部基于LRU也会自动淘汰旧数据,客户端不能假设数据在服务器端的当前状态,这应该说是memcached的feature设定,用户不必太多关心或者自己管理数据的淘汰更新工作,当然是否适合你的应用,取决于具体的需求,它也可能成为你需要精确自行控制cache生命周期的一个障碍。
memcached也不做数据的持久化工作,但是有许多基于memcached协议的项目实现了数据的持久化,例如memcache DB使用BerkeleyDB进行数据存储,但本质上它已经不是一个cache server,而只是一个兼容memcached的协议key-valueData Store了
redis可以以master-slave的方式配置服务器,slave节点对数据进行replica备份,slave节点也可以充当read only的节点分担数据读取的工作
redis内建支持两种持久化方案,snapshot快照和AOF增量Log方式。快照顾名思义就是隔一段时间将完整的数据dump下来存储在文件中。AOF增量Log则是记录对数据的修改操作(实际上记录的就是每个对数据产生修改的命令本身),两种方案可以并存,也各有优缺点,具体参见http://redis.io/topics/persistence
以上Redis的数据备份持久化方案等,如果不需要,为了提高性能,也完全可以Disable
性能
memcached
memcached自身并不主动定期检查和标记哪些数据需要被淘汰,只有当再次读取相关数据时才检查时间戳,或者当内存不够使用需要主动淘汰数据时进一步检查LRU数据。
redis
redis为了减少大量小数据CMD操作的网络通讯时间开销 RTT (Round Trip Time),支持pipeline和script技术。
所谓的pipeline就是支持在一次通讯中,发送多个命令给服务器批量执行,带来的代价是服务器端需要更多的内存来缓存查询结果。
redis内嵌了LUA解析器,可以执行lua脚本,脚本可以通过eval等命令直接执行,也可以使用script load等方式上传到服务器端的script cache中重复使用。
这两种方式都可以有效地减少网络通讯开销,增加数据吞吐率。
对于KV的操作,memcached和redis都支持multiple的get和set命令(memcached的multiple set命令貌似只在二进制的协议中支持),这同样有利于性能的提升。
实际性能方面,网上有很多测试比较,给出的结果各不相同,这无疑和各种测试的测试用例,测试环境,和测试时具体使用的客户端Library实现有关。但是总体看下来,比较靠谱的结论是在kv类操作上,两者的性能接近,memcached的结构更加简单,理论上应该会略微快一些。
集群
memcached的服务器端互相完全独立,客户端通常通过对键值应用hash算法决定数据的分区,为了减少服务器的增减对hash结果的影响,导致大面积的缓存失效,多数客户端实现了一致性hash算法。
redis计划在服务器端内建对集群的支持,在此之前,同样可以认为每个redis服务器实例相互之间是完全独立的,需要依靠客户端处理分区算法和可用服务器列表管理的工作。
redis官方推荐的用于sharding的客户端程序库是Twitter的开源项目Twemproxy, Twemproxy同时支持Memcached和Redis的文本通讯协议。
需要注意的是,redis的许多命令在集群环境下是不能正确运行的,例如set的交集,以及跨节点的事务操作等等,因为目前的redis集群设计,根本目标也就是服务器之间互相汇报一下存活状态,以及对数据做荣誉备份平衡负载等而已,本质上对数据的跨节点操作并不提供任何额外支持,所以在数据服务的层面上来说,各个服务器依旧是完全独立的。
这些操作如果一定要实现,当然可以通过客户端代码来实现(效率有多高且不说),类似的问题memcached集群当然也会遇上,但是原本memcached就不支持复杂的操作和数据类型,许多运算逻辑原本就是由客户端代码或应用程序自己处理的。
如何选择
何时应该使用memcached
相对memcached而言,redis的面世时间更晚且具备更多功能,因此开发人员通常将其视为默认性首选方案。不过有两类特殊场景仍然是memcached的一家天下。首先就是对小型静态数据进行缓存处理,最具代表性的例子就是HTML代码片段。memcached的内部内存管理机制虽然不像redis的那样复杂,但却更具实际效率——这是因为memcached在处理元数据时所消耗的内存资源相对更少。作为memcached所支持的惟一一种数据类型,字符串非常适合用于保存那些只需要进行读取操作的数据,因为字符串本身无需进行进一步处理。
除此之外,memcached在横向扩展方面也比redis更具优势。由于其在设计上的思路倾向以及相对更为简单的功能设置,memcached在实现扩展时的难度比redis低得多。不过根据我们了解到的情况,目前已经有多种经过测试且切实有效的方案能够将redis扩展至多台服务器之上,而其即将发布的3.0版本(感兴趣的朋友可以点击此处查看其候选版本说明)将包含专门针对横向扩展场景的内置集群化机制。
何时应该使用redis
除非大家需要考虑某种限定性条件(例如处理传统应用程序)对于memcached的特殊依赖性,或者自己的实际用例属于前面提到的两类场景中的一种,否则请直接选择redis并加以运用。凭借着redis所带来的卓越缓存方案,我们将拥有强大的处理能力——例如对缓存内容及持久性进行细节调整的能力——以及出色的整体执行效率。
redis几乎在缓存管理工作中的每一个侧面都表现出显而易见的优越性。这套缓存方案采用所谓数据回收机制,能够将陈旧数据从内存中删除以提供新数据所必需的缓存空间。memcached的数据回收机制使用的是LRU(即最低近期使用量)算法,而且往往会比较武断地直接删除掉与新数据体系相近的原有内容。相比之下,redis允许用户更为精准地进行细化控制,利用六种不同回收策略确切提高缓存资源的实际利用率。redis还采用更为复杂的内存管理与回收对象备选方案。
redis还能为我们带来最大程度的灵活性空间,从而保证管理员在打理缓存对象时拥有充裕的施展平台。在这方面,memcached将键名限制在250字节,值也被限制在不超过1MB,且只适用于普通字符串。相比之下,redis则将键名与值的最大上限各自设定为512MB,且支持二进制格式。redis支持六种数据类型,因此能够更加智能地对数据进行缓存处理及操作,这相当于为应用程序开发人员敞开了一道通往无尽可能性的大门。
相对于将对象保存为序列化字符串,redis允许开发人员以散列方式将对象域及值加以保存,并利用单一键对其进行管理。redis散列机制的存在保证开发人员无需经历获取完整字符串、反序列化、更新值、对象重新序列化并在每次值更新后利用其替代缓存内完整字符串这一系列复杂的流程——这也意味着资源消耗量得以降低、性能表现迎来显著提升。redis所支持的其它数据类型,例如lists以及sets——也可被用于实现更加复杂的缓存管理模式。
redis的另一大重要优势在于,它所保存的数据具备透明化特性,也就是说服务器能够直接对这些数据进行操作。redis当中提供160多种可用命令,其中大部分用于实现数据处理操作并通过服务器端脚本将逻辑嵌入至数据存储体系当中。这些内置命令及用户脚本带来了极大的灵活性优势,足以帮助大家直接在redis内部完成数据处理任务——而不必将数据在网络中的其它专门处理系统之间来回移动。
redis还提供可选而且能够具体调整的数据持久性方案,其设计目的在于在发生规划内停机或者计划外故障之后对缓存内容进行重新引导。虽然我们更倾向于强调缓存内数据的易失性与暂时性,但将数据在磁盘中加以持久保存在某些缓存场景当中仍然极具现实意义。这种机制能够在设备重启之后快速将保存在磁盘上的数据重新载入至缓存当中,从而大大缩短缓存预热周期并根据主数据存储内容对当前缓存内容进行重新评估。
最后但也同样重要的一点是,redis能够提供复制功能。复制功能旨在帮助缓存体系实现高可用性配置方案,从而在遭遇故障的情况下继续为应用程序提供不间断的缓存服务。很明显,一套成熟的缓存方案应该能够在应用程序发生故障时略微甚至完全不给用户体验或者应用程序性能表现带来任何影响,而这种对缓存内容及服务可用性的有力保障在大多数情况下也成为缓存解决方案的一大主要优势。
小结
redis的作者Salvatore Sanfilippo曾经对这两种基于内存的数据存储系统进行过比较,总体来看还是比较客观的,现总结如下:
- 性能对比:由于redis只使用单核,而memcached可以使用多核,所以平均每一个核上redis在存储小数据时比memcached性能更高。而在100k以上的数据中,memcached性能要高于redis,虽然redis最近也在存储大数据的性能上进行优化,但是比起memcached,还是稍有逊色。
- 内存使用效率对比:使用简单的key-value存储的话,memcached的内存利用率更高,而如果redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于memcached。另外,memcached使用预分配的内存池的方式,带来一定程度的空间浪费 并且在内存仍然有很大空间时,新的数据也可能会被剔除,而redis使用现场申请内存的方式来存储数据,不会剔除任何非临时数据 redis更适合作为存储而不是cache。
- redis支持服务器端的数据操作:redis相比memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么redis会是不错的选择。
另外,贴一些前辈们使用redis的经验和教训:
- 要进行master-slave配置,出现服务故障时可以支持切换。
- 在master侧禁用数据持久化,只需在slave上配置数据持久化。
- 物理内存+虚拟内存不足,这个时候dump一直死着,时间久了机器挂掉。这个情况就是灾难。
- 当redis物理内存使用超过内存总容量的3/5时就会开始比较危险了,就开始做swap,内存碎片大。
- 当达到最大内存时,会清空带有过期时间的key,即使key未到过期时间。
- redis与DB同步写的问题,先写DB,后写redis,因为写内存基本上没有问题。