当前位置:首页 > 技术 > 正文内容

Memcached 与 Redis 核心机制深度解析

访客 技术 2026年7月23日 1

内存缓存系统的设计初衷

数据库磁盘 I/O 的固有限制在高并发场景下会成为显著瓶颈。将热点数据迁移至内存并以键值对形式组织,能够大幅削减查询延迟。Memcached 与 Redis 正是基于这一思路诞生的内存级缓存中间件,通过前置缓存层降低后端数据库压力,提升整体系统吞吐量。

网络服务架构

两者均以独立守护进程形态运行,支持跨网络访问。通信层面基于 TCP 协议实现请求响应,同时兼容 UDP 以降低部分场景下的协议开销。当客户端与服务端位于同一主机时,还可通过 Unix Domain Socket 规避网络协议栈开销,获得更低延迟。

I/O 事件处理模型

现代网络服务普遍采用 epoll 替代传统的 select/poll,Redis 与 Memcached 亦不例外。Redis 在 epoll 之外保留了 select 与 poll 的可选支持,并在 BSD 平台集成 kqueue。Memcached 则依托 libevent 封装底层事件通知,其内部同样基于 epoll 实现。

Redis 采用单线程事件循环(Event Loop)架构,仅通过后台线程执行持久化等辅助任务。其事件分发为典型的 Reactor 模式, noteworthy 的设计在于客户端连接管理:由于 Redis 可配置最大连接数上限,且文件描述符(fd)在生命周期内唯一,Redis 直接使用 fd 作为数组索引,以 O(1) 复杂度定位客户端上下文,避免了红黑树等复杂结构的维护成本。此方案适用于连接规模可控的场景,Nginx 等需要处理海量动态连接的 HTTP 服务器则仍需依赖红黑树。

Memcached 采用 Master-Worker 多线程模型。主线程负责端口监听与连接建立,随后通过轮询或负载策略将连接分发给工作线程。线程间通过管道(Pipe)进行指令传递:主线程将新连接置入对应工作线程的就绪队列,并向其管道写入通知;工作线程在事件循环中监听管道读端,接收到通知后从队列取出连接处理。该模型能够充分利用多核并行能力,但引入了锁、条件变量等同步机制,实现复杂度显著高于单线程方案。

内存管理策略

Memcached 实现了私有内存池机制,预先向系统申请大块内存,后续分配均从池中获取,减少系统调用频率。内存池按 Slab 分级管理,不同 Slab 对应不同尺寸的内存块(Chunk),相同尺寸的 Chunk 归属同一 Slab Class。新数据写入时,根据数据大小选择"能容纳该数据的最小 Chunk",由此产生的内部碎片是空间换时间的权衡。

Redis 则采用即时分配策略,需要时直接向操作系统申请,释放时归还系统,内存管理完全委托给内核。为优化分配效率,Redis 支持链接 tcmalloc 替代 glibc 的默认 malloc,利用更高效的内存分配算法降低碎片率。这种设计简化了 Redis 的核心逻辑,使其能将重心置于数据结构实现与持久化机制。

数据存储引擎对比

Memcached 的存储机制

Memcached 仅支持简单的键值映射,值类型为二进制安全字符串。其存储单元称为 Item,包含元信息、键与值。为加速查找,Memcached 维护一张 Hash 表,采用链地址法处理冲突,每个桶存储指向 Item 的指针链表。

Hash 表支持渐进式扩容:当 Item 数量超过桶数的 1.5 倍时触发,由独立后台线程将旧表数据迁移至新表(桶数翻倍)。扩容期间查询可能涉及新旧两张表,需根据桶位置与迁移进度判断数据归属。

Item 的分配源自 Slab 体系。每个 Slab Class 管理若干 Slab,Slab 由等尺寸 Chunk 组成。空闲 Chunk 通过指针串联成空闲列表,分配时直接取用。为淘汰冷数据,每个 Slab Class 维护一条 LRU 链表,按访问时间排序,空间不足时从尾部开始回收。

过期检查采用惰性策略:访问时比对时间戳,过期即失效,不主动扫描。此举节省 CPU,但可能导致过期数据长期驻留内存。并发更新通过 CAS(Compare-And-Swap)协议保证:每个 Item 携带 64 位版本号,更新前校验客户端提交的版本与服务器当前版本是否一致,防止写覆盖。

Redis 的数据结构实现

Redis 支持 String、List、Set、Sorted Set、Hash 五种数据结构,通过 redisObject 统一抽象:

typedef struct redisObject {
    unsigned type:4;        // 对象类型
    unsigned encoding:4;  // 底层编码
    unsigned lru:LRU_BITS; // 最近访问时间
    int refcount;         // 引用计数
    void *ptr;            // 指向实际数据
} robj;

每种类型对应多种底层实现,运行时根据数据特征自动选择最优编码。例如 String 可采用 RAWINTEMBSTR;List 可在双向链表与压缩列表间切换;Sorted Set 由跳表(Skip List)或压缩列表实现,同时辅以 Hash 表加速按成员查询。

Redis 的字符串实现为 SDS(Simple Dynamic String):

struct sdshdr {
    int len;      // 已用长度
    int free;     // 剩余容量
    char buf[];   // 柔性数组,存储实际字符
};

键值映射通过自研字典(Dict)实现,采用双 Hash 表设计支持平滑扩容:

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];      // 主表与副表
    int rehashidx;     // -1 表示未在扩容
    int iterators;
} dict;

扩容(或缩容)时,新表申请完毕,后续每次字典操作附带迁移一个桶的数据,将一次性开销均摊至多次请求,避免服务卡顿。迁移完成后交换主副表指针。

Redis 默认启用 16 个独立数据库,客户端可切换使用。过期时间通过独立的 Expire Dict 维护,键映射至 64 位时间戳。删除策略结合惰性删除与定时随机采样,在内存占用与回收及时性间取得平衡。

持久化机制

RDB 快照

RDB 以二进制形式保存某一时刻的完整数据库状态。触发方式包括手动执行 SAVE(阻塞主线程)或 BGSAVE(Fork 子进程后台执行),以及配置条件自动触发(如 "900 秒内 1 次变更")。

子进程写入临时文件,完成后通知父进程原子替换。由于 Copy-On-Write 机制,Fork 后的内存页共享,仅修改时复制,但期间的数据变更不会反映至快照,故 RDB 为时间点一致性而非实时一致性。

AOF 日志

AOF 记录每条修改命令的文本序列,重启时重放以重建状态。同步策略可选:每次写入后 fsync、每秒后台 fsync、或交由操作系统决定。

AOF 重写(Rewrite)机制解决日志膨胀问题:Fork 子进程遍历当前数据库生成等价命令集写入新文件,期间父进程将新命令追加至重写缓冲区,子进程结束后合并入新文件。此设计避免了进程间复杂通信,以简单缓冲区实现增量同步。

事务与并发控制

Redis 提供 MULTI/EXEC 事务块,命令入队后批量执行,不支持回滚。执行期间不会被其他客户端打断,但无法防止隔离性问题。引入 WATCH 命令实现乐观锁:监视指定键,若事务提交前被其他客户端修改,则事务执行失败。实现上通过客户端标记 REDIS_DIRTY_CAS 与数据库的 Watcher 列表联动完成。

发布订阅功能

Redis 支持频道(Channel)与模式(Pattern)订阅。服务端维护 pubsub_channels 字典映射频道至客户端链表,以及 pubsub_patterns 列表存储模式订阅关系。消息发布时,先向精确匹配频道广播,再遍历模式列表进行正则匹配广播,同一客户端可能多次接收。

分布式路由:一致性 Hash

传统取模 Hash 在节点扩容时导致大规模缓存失效。一致性 Hash 将节点映射至 2^32 的环形空间,数据 Key 同样映射至环上,顺时针寻找首个节点即为路由目标。新增节点仅影响环上相邻区间,大幅缩小学动范围。节点规模越大,扩容影响面越小,配合虚拟节点技术可进一步优化负载均衡。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。