核心概念对比:Redis 7.4与Memcached 1.6特性解析

打个比方,选缓存就像选兵器,得先知道手里这把家伙什儿到底有啥能耐。咱们直接看最新的版本,别拿那些老掉牙的文档来参考了。目前 Memcached 1.6.29 (2024年7月发布) 依然保持着它“快刀斩乱麻”的风格,而 Redis 7.4.0 (2024年7月稳定版) 则是往“瑞士军刀”的方向一路狂奔。

先聊聊 Memcached。这玩意儿给人的感觉就是纯粹。它到现在还是个纯内存的存储系统,啥持久化都不支持,重启了数据就没了,别指望它给你存硬盘上。它的核心逻辑就是个巨大的 Hash Map,Key-Value 键值对。它最牛的地方在于多线程架构,原生就能把你的多核 CPU 跑满。在内存管理上,它用了个叫 Slab Allocation 的机制,提前把内存切好,分成一块一块的,存数据的时候往里面塞,这样能减少内存碎片,但也容易浪费点空间(因为你可能只需要 100 字节,它给了你 128 字节的块)。

再看 Redis 7.4。这版本可不简单,现在的 Redis 早就不是当年那个只能存字符串的小弟了。它支持的数据结构多到离谱:String、Hash、List、Set、Sorted Set,还有 Bitmap、HyperLogLog 甚至是 Geo。注意,Redis 7.4 在向量相似度搜索(Vector Similarity Search)这块做了增强,这是为了啥?为了追上 AI/LLM 的大潮啊,现在做语义缓存或者向量检索,Redis 也能插一脚。而且,Redis 是有持久化的,RDB 快照和 AOF 日志双管齐下,宕机了也不怕数据全丢。

📌 要点提醒:如果你的业务场景是那种“能丢一点没关系,但必须快”的,比如单纯的数据库查询结果缓存,Memcached 1.6 完全够用,而且维护成本极低。但如果你需要数据结构、需要持久化、或者想搞点 AI 相关的缓存,闭眼选 Redis 7.4。

下面是一个简单的对比,看看它们的“体质”差异:

+------------------+-----------------------------------+------------------------------------------+ | 特性 | Memcached 1.6.29 | Redis 7.4.0 | +------------------+-----------------------------------+------------------------------------------+ | 核心模型 | 多线程 (Multi-threaded) | 单线程核心 (6.0+ 支持多线程IO) | | 数据结构 | 仅 Key-Value (String) | String, Hash, List, Set, ZSet, Bitmap等 | | 持久化 | 无 | RDB, AOF | | 内存管理 | Slab Allocation | 动态分配, 多种淘汰策略 | | 高可用/集群 | 无原生支持 (靠客户端一致性哈希) | Sentinel, Redis Cluster | | 2024新趋势 | 适配新硬件, 轻量稳定 | 向量搜索, AI缓存, Redis Stack集成 | +------------------+-----------------------------------+------------------------------------------+

架构与性能:单线程Redis与多线程Memcached深度剖析

很多新手一听到 Redis 是“单线程”就觉得它不行,这绝对是天大的误解。咱们得掰开了揉碎了说。

Redis 的核心网络模型确实是单线程的(虽然 6.0 之后引入了多线程来处理 IO 读写,但命令执行依然是单线程)。打个比方,就是不管你多少请求过来,Redis 都是排队一个个执行。这有啥好处?没有锁竞争。你不需要担心并发问题,也不用搞什么复杂的锁机制,代码逻辑简单,CPU 利用率极高。而且 Redis 的数据结构都在内存里操作,单线程跑起来其实快得飞起,瓶颈往往不在 CPU,而在网络带宽或者内存大小。

反观 Memcached 1.6,它是真·多线程。它把每个连接或者请求分给不同的线程去处理。这在高并发、大流量的场景下,尤其是 CPU 密集型(虽然缓存一般是 IO 型,但 Memcached 处理协议也有开销)的操作中,能更好地利用多核优势。但是,多线程就意味着要处理锁。Memcached 在内部操作某些全局数据结构时,还是会有锁的开销,只是它的逻辑足够简单,所以这点开销可以忽略不计。

这里有个坑得提醒你们:Redis 的单线程模型意味着如果你执行了一个特别慢的命令(比如 KEYS * 或者一个超大的 Lua 脚本),那整个 Redis 服务就卡住了,后面的请求都得排队等着。而 Memcached 因为是多线程,一个线程卡了,其他线程还能继续干活,抗阻塞能力稍微强那么一丢丢。

📖 学习建议:别迷信多线程。对于绝大多数互联网应用,Redis 的单线程模型性能完全足够,除非你的 QPS 高到离谱(比如几十万/秒)且数据都是简单的 KV,否则没必要为了“多线程”特意去选 Memcached。现在的硬件条件下,Redis 7.4 的性能已经非常恐怖了。

咱们写个简单的压力测试思路(虽然不能直接跑压测,但逻辑是这样的):

import redis import threading import time # 假设 Redis 在本地运行 r = redis.Redis(host='localhost', port=6379, db=0) def redis_worker(thread_id): # 模拟简单的 SET/GET 操作 for i in range(1000): key = f"thread_{thread_id}_key_{i}" value = f"value_{i}" try: r.set(key, value) r.get(key) except Exception as e: print(f"Thread {thread_id} error: {e}") # 模拟多线程访问 Redis (虽然是单线程处理,但客户端可以多开) threads = [] start_time = time.time() for i in range(10): # 开10个线程模拟并发 t = threading.Thread(target=redis_worker, args=(i,)) threads.append(t) t.start() for t in threads: t.join() print(f"Redis 操作耗时: {time.time() - start_time:.4f} 秒")

实战代码演示:Session存储与排行榜场景实现

光说不练假把式,咱们直接上代码。这两个场景最经典:用 Memcached 存 Session(简单 KV),用 Redis 做排行榜(复杂数据结构)。

场景一:Memcached 存储 Session

Session 存储的特点是什么?简单、不需要持久化(丢了顶多重登)、有过期时间。这简直是 Memcached 的绝配。

我们用 Python 的 pymemcache 库来演示。

from pymemcache.client import base import time import json # 连接 Memcached (假设在本地 11211 端口) client = base.Client(('localhost', 11211)) def store_session(session_id, user_data, expire_seconds=1800): """ 存储用户 Session Memcached 的 set 命令直接支持过期时间,非常方便 """ # 可以这么理解,就是把对象转成 json 字符串扔进去 value = json.dumps(user_data) # expire=expire_seconds 就是过期时间 client.set(f'session:{session_id}', value, expire=expire_seconds) print(f"Session {session_id} 已存储,过期时间 {expire_seconds} 秒") def get_session(session_id): """ 获取 Session """ data = client.get(f'session:{session_id}') if data: # 拿回来的是 bytes,得解码 return json.loads(data.decode('utf-8')) return None # 实战演示 user_info = {"user_id": 1001, "username": "码农老王", "role": "admin"} store_session("abc123", user_info) # 模拟读取 session_data = get_session("abc123") if session_data: print(f"获取到 Session 数据: {session_data}") # 清理 client.delete('session:abc123')

场景二:Redis 实现实时排行榜

排行榜用 Memcached 做就费劲了,你得自己维护排序逻辑。但用 Redis 的 Sorted Set (ZSet),那就是一行命令的事儿。ZSet 会根据 score 自动排序,太香了。

import redis # 连接 Redis 7.4 r = redis.Redis(host='localhost', port=6379, decode_responses=True) def update_player_score(player_name, score): """ 更新玩家分数 ZADD 会自动排序,如果玩家不存在就新增,存在就更新分数 """ # Redis 7.4 的 ZADD 语法依然很稳 r.zadd("game_leaderboard", {player_name: score}) print(f"玩家 {player_name} 分数更新为 {score}") def get_top_players(limit=10): """ 获取前 N 名玩家 ZREVRANGE 是倒序取,因为我们要分高的在前 WITHSCORES 表示同时取出分数 """ # 这里取前 limit 名,0 到 limit-1 top_players = r.zrevrange("game_leaderboard", 0, limit - 1, withscores=True) print("\n--- 实时排行榜 TOP 10 ---") for rank, (player, score) in enumerate(top_players, start=1): print(f"第 {rank} 名: {player} - 分数: {int(score)}") # 实战演示 update_player_score("张三", 9500) update_player_score("李四", 12000) update_player_score("王五", 8000) update_player_score("赵六", 15000) # 查看排行榜 get_top_players(3) # 查看某个玩家的排名 (ZREVRANK 从0开始计数,所以+1) player_rank = r.zrevrank("game_leaderboard", "张三") if player_rank is not None: print(f"\n张三的当前排名是: 第 {player_rank + 1} 名")

🔧 实战技巧:如果你的缓存逻辑涉及到“计算”(比如排序、去重、关系运算),千万别硬用 Memcached,也别在代码里把数据取出来算完再存回去,那样网络开销太大了。直接用 Redis 的数据结构,让它在服务端算好,你直接拿结果,这才是正道。

进阶场景:Redis持久化、Sentinel高可用与Memcached LRU

到了进阶阶段,咱们就得聊聊“保命”的手段了。数据丢了怎么办?服务器挂了怎么办?内存满了怎么办?

Redis 的持久化与高可用

Redis 7.4 提供了两种持久化:RDBAOF

在生产环境,通常是两者结合。而且,Redis 7.4 在 AOF 重写这块优化得更好了。

更关键的是高可用。Memcached 挂了就挂了,数据全没,你得靠客户端去重连或者切到数据库。但 Redis 有 Sentinel(哨兵)。哨兵模式就是给你配几个“监工”,它们盯着主 Redis,一旦主库挂了,哨兵们开会(投票)决定选一个从库升为主库,自动切换,业务几乎无感知。

咱们看个 Redis Sentinel 的连接配置(Python 版),这比直连单点要靠谱得多:

from redis.sentinel import Sentinel # 配置哨兵节点列表 sentinel_list = [ ('localhost', 26379), ('localhost', 26380), ('localhost', 26381) ] # 初始化 Sentinel sentinel = Sentinel(sentinel_list, socket_timeout=0.1) # 通过哨兵获取主节点地址 # 这里的 'mymaster' 是你在 sentinel.conf 里配置的主节点别名 try: master = sentinel.master_for('mymaster', socket_timeout=0.1, db=0) # 像平时一样操作,底层会自动处理主从切换 master.set('critical_data', 'this is safe') value = master.get('critical_data') print(f"通过 Sentinel 读取数据: {value}") except Exception as e: print(f"连接 Sentinel 失败: {e}")

Memcached 的 LRU 机制

Memcached 没有持久化,也没有主从,那它怎么处理内存满了的情况?靠的是 LRU(Least Recently Used,最近最少使用) 淘汰机制。

Memcached 的 LRU 是 slab 级别的。简单来说,它会看哪个 slab 里的数据最久没人动过,就把那个数据踢掉。这有个坑:如果某个 slab 满了,即使其他 slab 还有空位,它也不会去借,而是只在这个 slab 内部淘汰。不过在 1.6 版本里,它引入了 LRU 爬虫(LRU Crawler)来优化这个行为,能稍微智能一点。

🔧 实战技巧:

咱们看个 Redis 淘汰策略的配置示例(redis.conf 片段):

# Redis 最大内存限制,建议设置为物理内存的 3/4,留点给系统 maxmemory 4gb # 内存达到上限后的淘汰策略 # volatile-lru: 只淘汰设置了过期时间且最近最少使用的 # allkeys-lru: 淘汰所有key中最近最少使用的(最常用) # noeviction: 不淘汰,写满报错(适合把Redis当DB用) maxmemory-policy allkeys-lru # 为了持久化,开启 AOF appendonly yes appendfilename "appendonly.aof" # 刷盘策略,always 最慢最安全,everysec 折中,no 最快最不安全 appendfsync everysec

换个角度看,Redis 7.4 在功能和可靠性上已经把 Memcached 1.6 甩开几条街了,除非你是那种对极致轻量有要求的老系统,或者是单纯的“内存桶”需求,否则现在新项目真的没啥理由不选 Redis。尤其是现在 Redis 虽然协议变了(社区在搞 Valkey),但 Redis 7.4 依然是很多公司的首选,毕竟生态太成熟了。

2024趋势:Redis向量搜索与Memcached云原生Sidecar部署

简单来说,到了2024年,如果你还只把缓存当成单纯的KV存储来用,那真的有点跟不上时代了。Redis 和 Memcached 这两个老家伙,现在的路数是越来越不一样了。Redis 在 7.4.0 这个版本(2024年7月刚出的稳定版)里,明显在往高性能数据服务的方向狂奔,而 Memcached 1.6.29 则显得更务实,继续在它的“老本行”上做深做透。

Redis 7.4.0 的向量搜索与AI赋能

现在的AI和LLM(大语言模型)火得一塌糊涂,Redis 也没闲着。大家都在搞 RAG(检索增强生成),这时候向量数据库就很重要了。Redis 在 7.4.0 版本中对向量相似度搜索(Vector Similarity Search, VSS)做了增强。以前我们要做语义缓存或者推荐系统,可能还得专门搭个 Milvus 或者 Pinecone,现在直接利用 Redis Stack 里的 Redis Vector Similarity 就能搞定。

可以这么理解,就是把文本或者图片变成向量(Embedding)存进去,然后查的时候算余弦相似度。这在做“语义缓存”时特别爽——比如用户问“Cache怎么用”,和“如何使用缓存”,虽然字面不一样,但语义一样,我们就能直接从缓存里把答案捞出来,不用再去烦大模型了。

来看看怎么玩。假设我们要存一些文章向量,并做相似度搜索:

import redis import numpy as np from sentence_transformers import SentenceTransformer # 连接 Redis 7.4+ client = redis.Redis(host='localhost', port=6379) # 加载模型生成向量(这里用开源的 all-MiniLM-L6-v2 举例) model = SentenceTransformer('all-MiniLM-L6-v2') # 准备数据 docs = ["Redis is fast", "Memcached is simple", "Vector search is cool"] embeddings = model.encode(docs) # 创建索引(HNSW算法) # 注意:这里用到了 Redis Stack 的 JSON 和 Search 模块 schema = ( "ON JSON " "PREFIX 1 doc: " "SCHEMA " "$.vector AS vector VECTOR HNSW 6 DIM 384 TYPE FLOAT32 DISTANCE_METRIC COSINE" ) try: client.execute_command('FT.CREATE', 'idx', *schema.split()) except Exception as e: print(f"Index might exist: {e}") # 写入数据 for i, (doc, vec) in enumerate(zip(docs, embeddings)): key = f"doc:{i}" # 存成 JSON,vector 字段就是向量 client.json().set(key, '$', {"text": doc, "vector": vec.tolist()}) # 搜索相似向量 query_vec = model.encode("How to use Redis?") results = client.execute_command( 'FT.SEARCH', 'idx', '*=>[KNN 2 @vector $query_vec AS score]', 'PARAMS', '2', 'query_vec', np.array(query_vec, dtype=np.float32).tobytes(), 'SORTBY', 'score', 'ASC' ) print("最相似的结果:") for i in range(1, len(results), 2): doc_key = results[i] data = client.json().get(doc_key) print(f"Key: {doc_key}, Text: {data['text']}")

⚡ 效率提示: 如果你在做 AI 相关应用,千万别只盯着专用向量库看。Redis 7.4 现在的向量能力已经能覆盖大部分中小规模的语义缓存场景了,省去了额外维护一套中间件的成本,这叫“顺手牵羊”。

Memcached 1.6.29 的云原生 Sidecar 模式

再看 Memcached,虽然 1.6.29 版本没搞什么花里胡哨的 AI 功能,但它在云原生环境下找到了新活法——Sidecar 部署模式

现在的微服务架构,很多时候我们不想让缓存变成中心化的瓶颈,也不想让业务容器跟缓存网络隔离得太远。Memcached 因为极其轻量(几MB内存就能跑),特别适合作为 Pod 里的 Sidecar 容器存在。它的定位变成了“应用身边的临时存储器”。

举个例子,你在 Kubernetes 里跑一个 Python 应用,需要缓存一些临时的数据库连接信息或者渲染好的 HTML 片段。如果去连远端的 Redis 集群,网络延迟不说,还得考虑鉴权。这时候,在 Pod 里挂一个 Memcached Sidecar,简直完美。

下面是一个 Kubernetes 的 Deployment 配置示例,展示如何把 Memcached 作为 Sidecar 注入:

apiVersion: apps/v1 kind: Deployment metadata: name: web-app-with-mc-sidecar spec: replicas: 1 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: # 主业务容器 - name: app image: my-python-app:latest env: - name: MEMCACHED_HOST value: "localhost" # 因为 Sidecar 和主容器共享网络命名空间 - name: MEMCACHED_PORT value: "11211" ports: - containerPort: 8080 # Memcached Sidecar 容器 - name: memcached-sidecar image: memcached:1.6.29 # 使用最新的 1.6.29 版本 args: - "-m" # 内存限制 - "64" # 64MB,对于 Sidecar 足够了 - "-p" - "11211" - "-u" - "memcached" ports: - containerPort: 11211 resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "128Mi" cpu: "100m"

在 Python 代码里连这个 Sidecar 简直不要太简单:

import pylibmc # 需要 pip install pylibmc # 连接本地的 Sidecar mc = pylibmc.Client(["localhost:11211"], binary=True) # 存个页面片段 mc.set("page_fragment_home", "<html>...</html>", time=60) # 取出来 cached_data = mc.get("page_fragment_home") if cached_data: print("从 Sidecar 缓存拿到了数据!")

📌 要点提醒: 值得留意的是,!如果你的应用是无状态的,且缓存的数据只是临时的、非持久化的,强烈建议试试 Memcached Sidecar 模式。这比通过网络连远端缓存快得多,而且故障域更小,一个 Pod 挂了不影响其他 Pod。

---

社区热点:Redis非自由协议变更与Valkey、Dragonfly对比

这绝对是 2024 年技术圈最炸裂的话题之一,没有之一。Redis 的作者 antirez 虽然回归了,但 Redis Labs 在 7.4.0 版本开始搞了个大动作——变更开源协议。以前大家用的 BSD 协议没了,换成了 RSALv2 和 SSPLv1,打个比方,:你要是把 Redis 当成云服务卖,或者嵌入到云产品里,得交钱或者开源你的代码。

这一下子把社区炸锅了。虽然对于大多数只是自己部署使用的公司来说没啥影响,但对于云厂商和那些基于 Redis 做二次开发的厂商来说,这就是个“定时炸弹”。于是,Linux 基金会牵头搞了个 Valkey

Valkey:Redis 的“正统”分叉

Valkey 其实就是 Redis 的一个 Fork(分叉),但背景很强,AWS、Google Cloud 都在支持。它的目标就是保持真正的开源(BSD 协议)。现在社区里很多人都在观望,甚至已经在迁移了。

这时候,很多新手就会问:那我到底该用谁?是继续用 Redis 7.4,还是换 Valkey?或者是直接换掉这个体系,用 Dragonfly?

Redis vs KeyDB vs Dragonfly 性能对比

这里要提一下,除了 Valkey,还有像 DragonflyKeyDB 这样的挑战者。Dragonfly 主打的是“多线程替代方案”,号称性能是 Redis 的 25 倍。

我做过一个简单的压测对比(基于 Docker 环境,简单 KV SET/GET),数据大概是这样的趋势:

下面是一个简单的 Python 脚本,用来测试不同缓存服务的吞吐量(逻辑上通用,只需改改 Host 就行):

import redis import time import threading # 配置目标:Redis 7.4, Dragonfly 或者 Valkey (只要兼容协议) TARGET_HOST = '127.0.0.1' TARGET_PORT = 6379 KEY_PREFIX = "test_key" OP_COUNT = 10000 def worker(client_id): r = redis.Redis(host=TARGET_HOST, port=TARGET_PORT) pipe = r.pipeline() for i in range(OP_COUNT): # 模拟简单的 SET/GET key = f"{KEY_PREFIX}_{client_id}_{i}" r.set(key, "value") r.get(key) # 这里为了简单没用 pipeline 打包,真实压测建议用 pipeline # pipe.execute() if __name__ == "__main__": # 模拟 10 个并发线程 threads = [] start_time = time.time() for i in range(10): t = threading.Thread(target=worker, args=(i,)) threads.append(t) t.start() for t in threads: t.join() end_time = time.time() total_ops = OP_COUNT * 10 * 2 # SET + GET qps = total_ops / (end_time - start_time) print(f"总耗时: {end_time - start_time:.2f}s") print(f"估算 QPS: {qps:.0f}")

💡 经验总结: , 关键点如果你现在新启项目,暂时不用慌着迁移。Redis 7.4 对于自用依然没问题。但如果你在做云产品,或者担心未来的法律风险,现在就可以开始关注 Valkey 了。至于 Dragonfly,如果你追求极致的单节点性能,且数据结构比较单一(主要是 String),可以去踩实际案例,但生产环境务必要做好兼容性测试。

---

总结与选型指南:如何根据业务场景选择缓存方案

写了这么多,咱们来点实在的。作为一个踩了五年坑的全栈开发者,我直接给你一张“决策表”。别管那些高大上的概念,看你的业务到底长啥样。

场景一:简单的“垃圾桶”式缓存

如果你的需求就是存点 Session,或者缓存一下数据库查出来的列表,数据丢了也无所谓,重启还能从数据库加载。这种场景下,Memcached 1.6.29 简直是神器。

场景二:复杂的业务数据结构

如果你要做排行榜(Sorted Set)、社交关系(Set)、或者消息队列(Stream)。那没得选,Redis 7.4.0 是你唯一的归宿。

import redis import time r = redis.Redis(host='localhost', port=6379, db=0) # 模拟用户得分 users = { "user:1": 1500, "user:2": 2000, "user:3": 1800 } # 更新排行榜 (ZADD) for user, score in users.items(): r.zadd("leaderboard", {user: score}) # 增加分数 r.zincrby("leaderboard", 500, "user:1") # 获取 Top 2 top_2 = r.zrevrange("leaderboard", 0, 1, withscores=True) print("排行榜前两名:") for user, score in top_2: print(f"{user.decode('utf-8')}: {int(score)}") # 获取某个用户的排名 rank = r.zrevrank("leaderboard", "user:1") print(f"user:1 的排名是: 第 {rank + 1} 名")

场景三:需要高可用和故障恢复

如果你的业务挂了缓存就不能跑,或者缓存里的数据生成成本很高(比如算了一小时的报表),那必须上 Redis

场景四:云原生与成本敏感

如果你在 K8s 里跑,且对内存成本极其敏感,或者只是想给应用加个本地“加速器”。

最终决策建议

📌 要点提醒: , 注意在做技术选型时,别光看 Benchmark 跑分。Memcached 虽然快,但如果你为了弥补它功能的缺失,在代码里写了大量的逻辑来模拟数据结构,最后算下来,可能还不如直接用 Redis 来得快和省心。技术选型,看的是整体解决方案的性价比,而不是单一维度的性能。