💬 1、简述
随着高并发、低延迟的业务需求不断增长,缓存系统成为后端架构中的核心组件。在众多缓存方案中,Redis 与 Memcached 是最常见的两个选择。但如今,绝大多数项目都更倾向于使用 Redis,而不是 Memcached。
本文将从底层原理、数据结构、持久化等方面对比两者,并给出集成 Redis 的实战案例。
| 特性 | Redis | Memcached | 
|---|---|---|
| 数据结构 | 支持5种核心数据结构+扩展模块 | 仅Key-Value存储 | 
| 持久化 | RDB/AOF两种机制 | 不支持 | 
| 集群模式 | Redis Cluster原生支持 | 依赖客户端分片 | 
| 内存模型 | 支持LRU/LFU等多种淘汰策略 | 单一LRU策略 | 
| 线程模型 | 单线程(避免锁竞争) | 多线程 | 
| 事务支持 | 支持ACID特性的事务 | 不支持 | 
| 地理空间索引 | 原生支持 | 不支持 | 
| 发布订阅 | 完整实现 | 不支持 | 
| Lua脚本 | 支持服务器端执行 | 不支持 | 

✍️ 2、Redis不可替代的五大场景
✅ 2.1 复杂数据结构应用
实践样例:电商购物车
# Redis实现(Hash结构)
import redis
r = redis.Redis()
user_id = "user_1001"
# 添加商品
r.hset(f"cart:{user_id}", "item_123", 2)  # 商品ID:数量
r.hset(f"cart:{user_id}", "item_456", 1)
# 获取全部商品
cart = r.hgetall(f"cart:{user_id}")
print(cart)  # {b'item_123': b'2', b'item_456': b'1'}
# 修改数量
r.hincrby(f"cart:{user_id}", "item_123", 1)
对比Memcached:需要序列化整个购物车对象,无法单独修改某个商品数量
✅ 2.2 持久化需求场景
实践样例:用户会话存储
// Redis配置(Spring Boot)
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
    @Bean
    public RedisConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory("redis-host", 6379);
    }
}
// 自动实现会话持久化,重启不丢失
Memcached局限:服务重启后所有会话数据丢失,需要重建
✅ 2.3 实时排行榜系统
实践样例:游戏积分榜
// Redis ZSET实现
const Redis = require('ioredis');
const redis = new Redis();
// 更新玩家分数
await redis.zadd('game:leaderboard', 1500, 'player1');
await redis.zadd('game:leaderboard', 1800, 'player2');
// 获取TOP10
const topPlayers = await redis.zrevrange('game:leaderboard', 0, 9, 'WITHSCORES');
console.log(topPlayers); 
// ['player2', '1800', 'player1', '1500']
Memcached方案:需要额外维护排序数据结构,性能低下
✅ 2.4 分布式锁实现
实践样例:秒杀系统锁
// Redis分布式锁(RedLock算法)
public class RedisLock {
    private final RedisTemplate<String, String> redisTemplate;
  
    public boolean tryLock(String lockKey, String lockValue, long expireTime) {
        return redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.MILLISECONDS);
    }
  
    public boolean unlock(String lockKey, String lockValue) {
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "   return redis.call('del', KEYS[1]) " +
            "else " +
            "   return 0 " +
            "end";
    
        return redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            lockValue
        ) == 1;
    }
}
Memcached缺陷:没有原子性删除操作,无法实现安全的锁释放
✅ 2.5 实时消息系统
实践样例:聊天室
# Redis发布/订阅
import redis
pubsub = redis.Redis().pubsub()
pubsub.subscribe('chat_room')
def handle_message(message):
    print(f"收到消息: {message['data'].decode()}")
# 发布消息
redis.Redis().publish('chat_room', 'Hello World!')
# 接收线程
for message in pubsub.listen():
    if message['type'] == 'message':
        handle_message(message)
Memcached:完全不支持发布订阅机制
📊 3、性能基准测试对比
3.1 测试环境
🔹 硬件:AWS c5.xlarge (4vCPU 8GB)
🔹 数据集:1百万条记录,key大小20字节,value大小200字节
🔹 客户端:50并发线程
3.2 测试结果
| 操作类型 | Redis QPS | Memcached QPS | 差异分析 | 
|---|---|---|---|
| SET操作 | 125,000 | 135,000 | Memcached多线程优势 | 
| GET操作 | 130,000 | 140,000 | 简单查询Memcached略快 | 
| HSET操作 | 118,000 | 不支持 | Redis数据结构优势 | 
| ZADD操作 | 95,000 | 不支持 | 复杂操作Redis独占 | 
| 持久化模式下 | 110,000 | - | AOF everysec配置 | 
🔤 4、企业级应用实践案例
案例1:Twitter的Timeline服务
需求特点:高并发读写,时间序列数据
Redis方案:
🔹 使用ZSET存储推文ID和时间戳
🔹每个用户维护一个Timeline集合
🔹 通过 ZREVRANGE实现分页查询
Memcached局限:无法实现复杂的分页排序需求
案例2:Uber的附近车辆查询
需求特点:地理空间索引,实时更新
Redis方案:
# 添加车辆位置
GEOADD vehicles 13.361389 38.115556 "car_123"
# 查询3公里内车辆
GEORADIUS vehicles 15 37 3 km WITHDIST
Memcached方案:需要额外集成空间索引系统
案例3:Pinterest的计数服务
需求特点:高并发计数器,持久化要求
Redis方案:
// 使用HINCRBY实现多维度计数
public void incrementCount(String entityType, String entityId, String metric) {
    redisTemplate.opsForHash().increment(
        "counters:" + entityType + ":" + entityId,
        metric,
        1
    );
}
优势分析:原子操作+持久化保证数据不丢失
📦 5、何时应该选择Memcached?
虽然Redis在大多数场景更优,但Memcached仍有适用场景:
🔹  纯KV缓存场景:不需要持久化和复杂数据结构
🔹超大规模缓存:Memcached内存管理更简单
🔹 遗留系统兼容:已有Memcached基础设施
典型配置样例:
import memcache
mc = memcache.Client(['memcached1:11211', 'memcached2:11211'])
# 简单缓存操作
mc.set("user_1001", {"name": "John", "age": 30}, time=3600)
user = mc.get("user_1001")
🧠 6、迁移方案:从Memcached到Redis
6.1 双写过渡方案
public class CacheService {
    private MemcachedClient memcached;
    private RedisTemplate<String, Object> redis;
  
    @PostConstruct
    public void warmUp() {
        // 启动时将Memcached数据导入Redis
        Map<String, Object> allData = memcached.getAllKeys();
        redis.opsForValue().multiSet(allData);
    }
  
    public void set(String key, Object value) {
        // 双写
        memcached.set(key, value);
        redis.opsForValue().set(key, value);
    }
}
6.2 客户端分片迁移
class HybridCache:
    def __init__(self):
        self.redis = redis.Redis()
        self.memcached = pylibmc.Client(["memcached1"])
    
    def get(self, key):
        # 先查Redis,不存在则查Memcached并回填
        value = self.redis.get(key)
        if not value:
            value = self.memcached.get(key)
            if value:
                self.redis.set(key, value)
        return value
🔚 7、总结
虽然 Memcached 曾是缓存领域的热门选择,但在多样性、可靠性和灵活性方面,Redis 已成为更现代、更全面的解决方案。除非你只需要非常简单的、纯内存的 KV 缓存,否则 Redis 几乎是首选。
✅ 开发者建议:Redis 功能更全,支持多场景应用;Memcached 适合简单、轻量、高速的只读缓存需求:
🔹 优先选择Redis:除非有明确证据表明Memcached更适合
🔹 数据结构先行:用Hash代替多个Key,用ZSET代替手动排序
🔹 合理配置持久化:AOF everysec通常是平衡选择
🔹 监控内存使用:避免OOM,设置maxmemory-policy
🔹 考虑混合架构:极端场景可Redis+Memcached组合使用
最终建议:在新项目中默认使用Redis,除非遇到Memcached能明确解决的特定瓶颈。Redis的丰富功能和持续创新(如Redis Streams、RedisAI等模块)使其成为现代应用架构的更优选择。