Memcached vs. Redis?

Memcached vs. Redis?

技术背景

在应用开发中,为了加速应用程序的响应速度,常常会使用内存数据存储工具作为缓存。Memcached 和 Redis 就是其中两款强大且快速的内存数据存储工具,它们能通过缓存数据库结果、HTML 片段等,帮助提升应用性能。

直接对比

读写速度

两者都极为快速。基准测试结果会因工作负载、版本等多种因素而有所不同,但总体显示 Redis 的速度与 Memcached 相当或近乎相同。

内存使用

  • Memcached:需指定缓存大小,随着插入数据,守护进程会快速增长至略大于该指定大小。除非重启 Memcached,否则无法回收已分配的内存。即便所有键过期或清空数据库,它仍会使用配置的全部内存。
  • Redis:可自行设置最大内存使用量。Redis 不会使用超过所需的内存,并且会释放不再使用的内存。

磁盘 I/O 转储

Redis 具有明显优势,它默认支持磁盘转储,且持久化配置灵活。而 Memcached 若不借助第三方工具,则无法实现磁盘转储。

扩展性

在仅作为缓存使用时,两者在单实例情况下都有很大的性能提升空间。不过,Redis 提供了工具以支持进一步扩展,而 Memcached 则没有。

详细介绍

Memcached

Memcached 是一个简单的易失性缓存服务器,允许存储键值对,其中值仅限于最大为 1MB 的字符串。它的读写速度极快,常能使可用网络甚至内存带宽达到饱和。但重启 Memcached 后,数据将丢失,因此不适用于存储重要数据。若需要高性能或高可用性,可借助第三方工具、产品和服务。

Redis

Redis 不仅能完成 Memcached 的工作,而且表现更优。它同样可作为缓存,存储键值对,且值最大可达 512MB。可以选择关闭持久化功能,使 Redis 在重启时丢失数据;也可以开启持久化,让缓存数据在重启后得以保留。此外,Redis 还具备以下特性:

  • 丰富的数据类型:支持字符串、哈希、列表、集合、有序集合、地理数据、位图和 HyperLogLog 等多种数据类型,并提供相应操作命令。
  • 事务和原子性:命令具有原子性,同时支持使用“乐观锁”的事务。
  • 管道技术:可将多个命令一次性发送给 Redis 执行,提高批量操作的吞吐量。
  • 发布/订阅:支持发布/订阅功能,可作为高速消息广播器。

核心代码示例

以下是使用 Java 语言,通过 Jedis 客户端对 Redis 和 Memcached 进行基本操作的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import redis.clients.jedis.Jedis;
import net.spy.memcached.MemcachedClient;
import java.io.IOException;
import java.net.InetSocketAddress;

public class CacheExample {
public static void main(String[] args) throws IOException {
// Redis 操作示例
Jedis jedis = new Jedis("localhost", 6379);
int count = 100000;
long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
jedis.set("u112-" + i, "v51" + i);
}
long endTime = System.currentTimeMillis();
System.out.println("Redis: Time taken to store " + count + " values is =" + (endTime - startTime) + "ms");

startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
jedis.get("u112-" + i);
}
endTime = System.currentTimeMillis();
System.out.println("Redis: Time taken to retrieve " + count + " values is =" + (endTime - startTime) + "ms");
jedis.close();

// Memcached 操作示例
MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("localhost", 11211));
startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
memcachedClient.set("u112-" + i, 0, "v51" + i);
}
endTime = System.currentTimeMillis();
System.out.println("Memcached: Time taken to store " + count + " values is =" + (endTime - startTime) + "ms");

startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
memcachedClient.get("u112-" + i);
}
endTime = System.currentTimeMillis();
System.out.println("Memcached: Time taken to retrieve " + count + " values is =" + (endTime - startTime) + "ms");
memcachedClient.shutdown();
}
}

最佳实践

  • 新应用场景:推荐使用 Redis,因为它功能强大、社区支持好,能满足更多需求。
  • 特定场景:若仅需简单的键值缓存,且对内存使用有严格限制,Memcached 是不错的选择;若需要复杂的数据结构、持久化、事务等功能,则应选择 Redis。

常见问题

内存管理问题

  • Redis:在设置最大内存时,由于内存分配和键的过期机制,可能会出现内存碎片化问题。可通过调整内存分配器或优化键的管理来缓解。
  • Memcached:一旦分配内存,除非重启,否则无法回收,可能导致内存浪费。

性能问题

  • 读写性能受网络、硬件、工作负载等多种因素影响,需根据具体情况进行优化。
  • Redis 基于事件循环,完全局限于一个核心,在高并发场景下可能存在性能瓶颈。而 Memcached 是多线程的,在某些场景下性能表现更优。

扩展性问题

  • Redis 虽提供了扩展工具,但在集群部署和管理上相对复杂。
  • Memcached 缺乏内置的扩展工具,在需要大规模扩展时可能面临挑战。