Memcached 是什么?Redis 和 Memcached 的区别详解

摘要:Memcached 是一套高性能的分布式内存对象缓存系统,遵循 BSD 协议,它通过在内存中缓存数据和对象,减少读取数据库的次数,从而减轻了数据库负载,提高了动态 Web 应用的访问速度、可扩展性。

简介

Memcached 是一套高性能的分布式内存对象缓存系统,遵循 BSD 协议,它通过在内存中缓存数据和对象,减少读取数据库的次数,从而减轻了数据库负载,提高了动态 Web 应用的访问速度、可扩展性。

工作流程

Memcached 工作流程

  • 用户向服务器发起请求,服务器接收后,检查 Memcached 缓存中是否有用户请求的数据;
  • 数据存在,服务器直接返回用户请求数据,不请求数据库;
  • 数据不存在,则请求数据库,从数据库中获取数据后,服务器返回用户请求数据,同时把数据存储到 Memcached 缓存中(需要程序明确实现);
  • 当数据库数据发生变化时(如修改、删除),也会更新 Memcached 中的旧数据,从而保证数据一致性,确保用户不会在缓存取到旧数据;
  • 当分配给 Memcached 内存空间用完之后,Memcached 自身会使用 LRU 算法 + 过期失效策略,失效的数据首先被替换掉,然后是最近最少使用的数据被替换掉。

LRU 算法

LRU 是 Least Recently Used 的缩写,译为最近最少使用。它的理论基础为:

“最近使用的数据会在未来一段时期内仍然被使用,已经很久没有使用的数据大概率在未来很长一段时间仍然不会被使用。”

由于该思想非常契合业务场景 ,并且可以解决很多实际开发中的问题,所以我们经常通过 LRU 的思想来作缓存,一般也将其称为 LRU 缓存机制。

关于 LRU 算法的更多说明请浏览 LRU 缓存机制和常见的页面置换算法

特点

Memcached 作为高性能的分布式内存对象缓存系统,具有以下的特点:

  • 协议简单
  • 基于 Libevent 的事件处理
  • 内置内存存储方式
  • Memcached 不互相通信的分布式

协议

Memcached 的服务器客户端通信并不使用复杂的 XML 等格式,而使用简单的基于文本行的协议。

因此,通过 telnet 也能在 Memcached 上保存数据、取得数据。

事件处理

Libevent 是一个用 C 语言编写的、轻量级的开源高性能事件通知库,适用于 Windows、Linux、BSD、Solaris 等多种平台,内部使用 select、epoll、kqueue、IOCP(输入输出完成端口)等系统调用管理事件机制。

它将 Linux 的 epoll、BSD 类操作系统的 kqueue 等事件处理功能封装成统一的接口,即使对服务器的连接数增加,也能发挥 O(1) 的性能。

Memcached 使用这个 Libevent 库,因此能在 Linux、BSD、Solaris 等操作系统上发挥其高性能。

存储方式

为了提高性能,Memcached 中保存的数据,都存储在 Memcached 内置的内存存储空间中。

  • 由于数据仅存在于内存中,因此重启 Memcached、重启操作系统会导致全部数据消失;
  • 另外,内容容量达到指定值之后,就基于 LRU(Least Recently Used)算法自动删除不使用的缓存。
  • Memcached 本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。

    注:Memcached 也是一种服务器,是内存缓存服务器,就像 Apache 一样,都是搭建 Web 服务器的开源软件。

通信分布式

Memcached 尽管是 "分布式" 缓存服务器,但服务端并没有分布式功能,各个 Memcached 不会互相通信以共享信息。

那么,怎样进行分布式呢?这完全取决于客户端的实现,即通过内置算法制定目标数据的节点,如下图所示:

Memcached 通信分布式

注意:本文的服务端和客户端是狭义的,是相对 Memcached 来说,不是广义上的服务端和客户端,这里的客户端不是指用户所在端。

因为 Memcached 分为服务端和客户端,从表现形式来讲服务端为 memcached.exe,客户端为 memcache.dll,都是安装在服务器上的。

内存管理机制

对于像 Redis 和 Memcached 这种基于内存的数据库系统来说,内存管理的效率高低是影响系统性能的关键因素。

malloc/free

传统 C 语言中的 malloc/free 函数,是最常用的分配和释放内存的方法。

  • malloc:全称 memory allocation(动态内存分配),其作用是在内存的动态存储区中分配一个长度为 size 的连续空间;
  • free:释放 ptr 指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用 malloc、realloc 以及 calloc 函数来再分配。

但是这种方法存在着很大的缺陷:

  • 首先,对于开发人员来说不匹配的 malloc 和 free 容易造成内存泄露;
  • 其次频繁调用会造成大量内存碎片无法回收重新利用,降低内存利用率;
  • 最后作为系统调用,其系统开销远远大于一般函数调用。

所以,为了提高内存的管理效率,高效的内存管理方案都不会直接使用 malloc/free 调用。

Redis 和 Memcached 均使用了自身设计的内存管理机制,但是实现方法存在很大的差异,下面将会对两者的内存管理机制分别进行介绍。

Slab Allocation

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 来控制。

Slab Allocation

假定图中 Growth Factor 的取值为 1.25,如果第一组 Chunk 的大小为 88 个字节,第二组 Chunk 的大小就为 112 个字节,依此类推。

  • 当 Memcached 接收到客户端发送过来的数据时,首先会根据收到数据的大小,选择一个最合适的 Slab Class;
  • 然后通过查询 Slab Class 内空闲 Chunk 的列表,就可以找到一个可用于存储数据的 Chunk;
  • 当一条数据库过期或者丢弃时,该记录所占用的 Chunk 就可以回收,重新添加到空闲列表中。

从以上过程我们可以看出 Memcached 的内存管理制效率高,而且不会造成内存碎片,但是它最大的缺点就是会导致空间浪费。

因为每个 Chunk 都分配了特定长度的内存空间,所以长数据无法充分利用到由短数据造成的多余空间。

如图所示,将 100 个字节的数据缓存到 128 个字节的 Chunk 中,剩余的 28 个字节就浪费掉了。

Chunk 空间浪费

Memory Block

Redis 的内存管理主要通过源码中 zmalloc.h 和 zmalloc.c 两个文件来实现的。

为了方便内存的管理,Redis 在分配一块内存之后,会将这块内存的大小存入 Memory Block(内存块)的头部。

如图所示,real_ptr 是 redis 调用 malloc 后返回的指针。

Memory Block 内存块

  • 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] 代表已经分配的长度为 16 bytes 的内存块的个数;
  • zmalloc.c 中有一个静态变量 used_memory 用来记录当前分配的内存总大小。

所以,总的来看,Redis 采用的是包装的 malloc/free,相较于 Memcached 的内存管理方法来说,要简单很多。

swap

在 Redis 中,并不是所有的数据都一直存储在内存中的,这是和 Memcached 相比一个最大的区别。

如果 Redis 发现内存的使用量超过了某一个阀值,将触发 swap(交换)的操作,将一些很久没用到的 value 交换到磁盘。

  • 根据 “swappability = age*log(size_in_memory)” 计算出哪些 key 对应的 value 需要 swap 到磁盘;
  • 然后再将这些 key 对应的 value 持久化到磁盘中,同时在内存中清除。

这种特性使得 Redis 可以保持超过其机器本身内存大小的数据。

  • 当然,机器本身的内存必须要能够保持所有的 key,毕竟这些数据是不会进行 swap 操作的;
  • 同时由于 Redis 将内存中的数据 swap 到磁盘中的时候,提供服务的主线程和进行 swap 操作的子线程会共享这部分内存。

所以如果更新需要 swap 的数据,Redis 将阻塞这个操作,直到子线程完成 swap 操作后才可以进行修改。

I/O 线程池

当从 Redis 中读取数据的时候,如果读取的 key 对应的 value 不在内存中,那么 Redis 就需要从 swap 文件中加载相应数据,然后再返回给请求方。

这里就存在一个 I/O 线程池的问题:

  • 在默认的情况下,Redis 会出现阻塞,即完成所有的 swap 文件加载后才会相应。这种策略在客户端的数量较小,进行批量操作的时候比较合适;
  • 但是如果将 Redis 应用在一个大型的网站应用程序中,这显然是无法满足大并发的情况的。

所以 Redis 运行我们设置 I/O 线程池的大小,对需要从 swap 文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间。

总结

Memcached 使用预分配的内存池的方式,使用 slab 和大小不同的 chunk 来管理内存,Item(项目)根据大小选择合适的 chunk 存储,内存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生。

但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数据也可能会被剔除。

Redis 使用现场申请内存的方式来存储数据,并且很少使用 free-list 等方式来优化内存分配,会在一定程度上存在内存碎片。

Redis 根据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据。

非临时数据是永远不会被剔除的,即便物理内存不够,导致 swap 也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上 Redis 更适合作为存储。

Redis vs. Memcached

从本质上讲,Memcached 是一个简单的 key-value 内存 Cache,对可靠性无要求;而 Redis 更倾向于内存数据库,支持多种数据类型,对可靠性方面要求比较高。

下面我们对这两种基于内存的数据存储系统,根据几个不同点来一一比较一下。

Redis vs. Memcached

数据类型

  • Memcached 使用 key-value 形式存储和访问数据,在内存中维护一张巨大的 HashTable(哈希表),使得对数据查询的时间复杂度降低到 O(1),保证了对数据的高性能访问;
  • Redis 与 Memcached 相比,比仅支持简单的 key-value 数据类型,同时还提供 list、set、zset、hash 等数据结构的存储。

数据操作

Redis 相比 Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作。

  • 通常在 Memcached 里,你需要将数据拿到客户端来进行类似的修改再 set(设置)回去,这大大增加了网络 IO 的次数和数据体积;
  • 在 Redis 中,这些复杂的操作通常和一般的 GET/SET 一样高效。
  • 所以,如果需要缓存能够支持更复杂的结构和操作,那么 Redis 会是不错的选择。

数据一致性

  • Memcached 提供了 cas 命令,可以保证多个并发访问操作同一份数据的一致性问题;
  • Redis 没有提供 cas 命令,并不能保证这点,不过 Redis 提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断。

数据持久化

  • Memcached 不支持数据持久化,所有的数据都以 in-memory(内存)的形式存储,断电或重启后数据消失;
  • Redis 支持数据持久化和数据恢复,提供了两种不同的持久化方法,RDB 快照和 AOF 日志。

数据存储大小

  • Memcached 单个 key-value 大小有限,一个 value 最大只支持 1 MB;
  • Redis 与 Memcached 相比,value 最大支持 512 MB。

内存空间

  • Memcached 可以修改最大内存,采用 LRU 算法;
  • Redis 增加了 VM 的特性,突破了物理内存的限制。
  • 注:只有打开了 Redis 的虚拟内存功能,VM 字段才会真正的分配内存,该功能默认是关闭状态的。

内存利用率

  • 使用简单的 key-value 存储的话,Memcached 的内存利用率更高;
  • 而如果 Redis 采用 hash 结构来做 key-value 存储,由于其组合式的压缩,其内存利用率会高于 Memcached。

内存管理机制

  • Memcached 使用预分配内存的方式来存储数据,能减小内存碎片产生,但会带来一定程度上的空间浪费;
  • Redis 使用现场申请内存的方式来存储数据,不会造成空间浪费,但会存在一定程度上的内存碎片。

网络 IO 模型

Memcached 是多线程,非阻塞 IO 复用的网络模型,分为监听主线程和 worker 子线程。

  • 监听线程监听网络连接,接受请求后,将连接描述字 pipe 传递给 worker 线程,进行读写 IO,网络层使用 Libevent 封装的事件库;
  • 多线程模型可以发挥多核作用,但是引入了 Cache Coherency(缓存一致性)和锁的问题,带来了性能损耗。
    比如:Memcached 最常用的 stats 命令,实际 Memcached 所有操作都要对这个全局变量加锁,进行技术等工作。

Redis 使用单线程的 IO 复用模型,封装了一个简单的 AeEvent 事件处理框架,主要实现了 epoll、kqueue 和 select。

  • 对于单核只有 IO 操作来说,单线程可以将速度优势发挥到最大;
  • 但是 Redis 也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型施加会严重影响整体吞吐量,CPU 计算过程中,整个 IO 调度都是被阻塞的。

性能

  • 由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高;
  • 而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。

分布式

  • Memcached 本身并不支持分布式,因此只能在客户端,通过像一致性哈希这样的分布式算法,来实现 Memcached 的分布式存储;
  • Redis 更偏向于在服务端构建分布式存储,Redis Cluster 是一个实现了分布式且允许单点故障的 Redis 高级版本(3.0),它没有中心节点,各个节点可以相互交流,具有线性可伸缩的功能,可扩展性、可维护性更强大。

应用场景

  • Memcached 适用于动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如大量查询用户信息、好友信息、文章信息等);
  • Redis 适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统。

总结

Memcached 和 Redis 都能很好的满足解决我们的问题,它们性能都很高,总的来说,可以把 Redis 理解为是对 Memcached 的拓展,是更加重量级的实现,提供了更多更强大的功能。

版权声明:本文为博主原创文章,未经博主允许不得转载。http://www.dedenotes.com/html/Memcached.html
(1)
打赏 微信扫一扫 微信 支付宝 QQ 扫码打赏

防止表单重复提交的 4 种方法

Dedenotes 赞(3)

平时开发的项目中可能会出现下面这些情况:由于用户误操作,多次点击表单提交按钮;由于网速等原因造成页面卡顿,用户重复刷新提交页面;黑客或恶意用户使用 Postman 等工具重复恶意提交表单(攻击网站)。

meta

Dedenotes 赞(3)

meta 是 html 语言 head 区的一个辅助性标签,位于文档的头部,不包含任何内容,标签的属性定义了与文档相关联的名称/值对。meta 标签可提供相关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。

HTTP消息结构 HTTP请求报文和响应报文的格式

Dedenotes 赞(3)

HTTP 协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,基于 TCP/IP 通信协议来传递数据(HTML 文件, 图片文件, 查询结果等),所有的 WWW(World Wide Web)文件都必须遵守这个标准。