Redis PK Memcached,哪个更牛叉?

说到 redis 就会联想到 memcached,反之亦然。了解过两者的同学有那么个大致的印象:

  • redis 与 memcached 相比,不仅支持简单的 key-value 数据类型,同时还提供 list,set,zset,hash 等数据结构的存储;
  • redis 支持数据的备份,即 master-slave 模式的数据备份;
  • redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用等等。

这似乎看起来 redis 比 memcached 更加牛X一些,那么事实上是不是这样的呢?

存在即合理,我们来根据几个不同点来一一比较一下。

网络IO模型

memcached 是多线程,非阻塞 IO 复用的网络模型,分为监听主线程和 worker子线程,监听线程监听网络连接,接受请求后,将连接描述字 pipe 传递给 worker 线程,进行读写 IO。

网络层使用 libevent 封装的事件库,多线程模型可以发挥多核作用,但是引入了 cache coherency 和锁的问题,比如:memcached 最常用的 stats 命令,实际 memcached 所有操作都要对这个全局变量加锁,进行技术等工作,带来了性能损耗。

redis 使用单线程的 IO 复用模型,自己封装了一个简单的 AeEvent 事件处理框架,主要实现了 epoll, kqueue 和 select,对于单存只有 IO 操作来说,单线程可以将速度优势发挥到最大。

但是 redis 也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型施加会严重影响整体吞吐量,CPU 计算过程中,整个 IO 调度都是被阻塞的。

数据支持类型

memcached 使用 key-value 形式存储和访问数据,在内存中维护一张巨大的 HashTable,使得对数据查询的时间复杂度降低到O(1),保证了对数据的高性能访问。

正如开篇所说:redis 与 memcached 相比,比仅支持简单的 key-value 数据类型,同时还提供 list,set,zset,hash 等数据结构的存储;详细可以翻阅《Redis 内存使用优化与存储》

http://blog.csdn.net/u013256816/article/details/51133134

内存管理机制

对于像 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 的内存管理方法来说,要简单很多。

在 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 文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间。

Memcached 使用预分配的内存池的方式,使用 slab 和大小不同的 chunk 来管理内存,Item 根据大小选择合适的 chunk 存储,内存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数据也可能会被剔除,原因可以参考 Timyang 的文章:http://timyang.net/data/Memcached-lru-evictions/

Redis 使用现场申请内存的方式来存储数据,并且很少使用 free-list 等方式来优化内存分配,会在一定程度上存在内存碎片,Redis 跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致 swap 也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上 Redis 更适合作为存储而不是 cache。

数据存储及持久化

memcached 不支持内存数据的持久化操作,所有的数据都以 in-memory 的形式存储。

redis 支持持久化操作。redis 提供了两种不同的持久化方法来讲数据存储到硬盘里面,一种是快照(snapshotting),它可以将存在于某一时刻的所有数据都写入硬盘里面。另一种方法叫只追加文件(append-only file, AOF),它会在执行写命令时,将被执行的写命令复制到硬盘里面。

数据一致性问题

Memcached 提供了 cas 命令,可以保证多个并发访问操作同一份数据的一致性问题。

Redis 没有提供 cas 命令,并不能保证这点,不过 Redis 提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断。

集群管理不同

Memcached 是全内存的数据缓冲系统,Redis 虽然支持数据的持久化,但是全内存毕竟才是其高性能的本质。作为基于内存的存储系统来说,机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小,就需要构建分布式集群来扩展存储能力。

Memcached 本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现 Memcached 的分布式存储。下图给出了 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 节点。

来源:blog.csdn.net/u013256816/article/details/51146314

往期干货推荐

1. Java 10 实战第 1 篇:局部变量类型推断

2. Java 11 要来了,编译运行一个命令搞定

3. 图解 https 单向认证和双向认证

4. Intellij IDEA 那些隐藏好用的小技巧

5. MySQL数据库开发的 36 条军规

原文发布于微信公众号 - Java技术栈(javastack)

原文发表时间:2018-06-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端杂货铺

Nodejs cluster模块深入探究

由表及里 HTTP服务器用于响应来自客户端的请求,当客户端请求数逐渐增大时服务端的处理机制有多种,如tomcat的多线程、nginx的事件循环等。而对于nod...

55610
来自专栏mini188

Openfire集群源码分析

如果用户量增加后为了解决吞吐量问题,需要引入集群,在openfire中提供了集群的支持,另外也实现了两个集群插件:hazelcast和clustering。为了...

2329
来自专栏Java进阶架构师

9个提升逼格的redis命令

既然keys命令不允许使用,那么有什么代替方案呢?有!那就是scan命令。如果把keys命令比作类似select * from users where user...

1294
来自专栏张首富-小白的成长历程

测试NFS+rsync+sersync+SMTP+shell脚本+cron

1,nfs创建一个目录名为/data/web的目录作为web服务器的后端存储,然后共享使用虚拟用户webuser,因为公司访问量不是很大,所以可以直接用sync...

1102
来自专栏Seebug漏洞平台

WordPress REST API 内容注入漏洞分析

0x00 漏洞简述 1. 漏洞简介 在REST API自动包含在Wordpress4.7以上的版本,WordPress REST API提供了一组易于使用的HT...

3117
来自专栏Linux Python 加油站

什么是爬虫?python爬虫基本知识

把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛。 网络蜘蛛是通过网页的链接地址来寻找网页的。

1723
来自专栏linux驱动个人学习

Linux进程退出详解(do_exit)--Linux进程的管理与调度(十四)

exit是c语言的库函数,他最终调用_exit。在此之前,先清洗标准输出的缓存,调用用atexit注册的函数等, 在c语言的main函数中调用return就等价...

3693
来自专栏北京马哥教育

Python爬虫基本知识:什么是爬虫?

豌豆贴心提醒,本文阅读时间5分钟 一、网络爬虫的定义 网络爬虫,即Web Spider,是一个很形象的名字。 把互联网比喻成一个蜘蛛网,那么Spider就是...

3456
来自专栏文武兼修ing——机器学习与IC设计

流水线式p2p接口的分析与实现

P2P接口是一种双向握手接口,传输的前级和后级各提供一个数据有效信号valid和忙信号busy信号,只有当两个信号达成某种指定情况时,握手完成,数据传输完成,否...

1002
来自专栏H2Cloud

FFrpc python客户端lib

摘要:          Ffrpc可以很方便的构建c++ server, 在网游服务器程序开发中,进程间通讯非常的重要,比如gateserver和gamese...

4257

扫码关注云+社区

领取腾讯云代金券