Memcached 是一个高性能的分布式内存对象缓存系统,用于加速动态 Web 应用程序,减轻数据库负载,在 Java 中,我们通常使用客户端库来与 Memcached 服务器进行交互。

这篇指南将涵盖以下内容:
- 环境准备:安装 Memcached 服务器。
- Java 客户端选择:选择合适的 Java 客户端库(最推荐 Spymemcached)。
- 项目集成:在 Maven/Gradle 项目中添加依赖。
- 核心 API 使用:连接、增删改查、操作等。
- 高级特性:序列化、连接池、分布式等。
- 完整示例代码。
- 最佳实践与注意事项。
环境准备:安装 Memcached 服务器
你的机器上需要有一个正在运行的 Memcached 服务器。
在 macOS 上 (使用 Homebrew):
brew install memcached # 启动服务 brew services start memcached
在 Linux (Ubuntu/Debian) 上:

sudo apt-get update sudo apt-get install memcached # 启动服务 sudo systemctl start memcached # 或直接运行 memcached -d -m 512 -l 127.0.0.1 -p 11211
在 Windows 上: 可以从 Memcached for Windows 等第三方站点下载安装包或可执行文件,然后手动启动。
默认情况下,Memcached 监听 0.0.1:11211。
Java 客户端选择
有几个流行的 Java 客户端库,但 Spymemcached 是目前最推荐的选择,因为它稳定、轻量级且功能齐全。
- Spymemcached: GitHub - spymemcached
- 优点: 老牌项目,非常稳定,基于
Netty,性能好,支持异步操作。 - 缺点: 相较于一些新库,API 可能略显“陈旧”。
- 优点: 老牌项目,非常稳定,基于
- Xmemcached: GitHub - xmemcached
- 优点: API 设计更现代化,支持 NIO,性能优异。
- 缺点: 相对 Spymemcached,社区和生态稍小。
- (不推荐) Whalin (Memcached-Java-Client): 一个比较老的客户端,已停止维护,不建议在新项目中使用。
本教程将使用 Spymemcached 作为示例。

项目集成 (以 Maven 为例)
在你的 pom.xml 文件中添加 Spymemcached 的依赖。
<dependencies>
<!-- Spymemcached 客户端 -->
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version> <!-- 请检查最新版本 -->
</dependency>
<!-- 为了演示 POJO 缓存,我们需要一个 JSON 库 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <!-- 请检查最新版本 -->
</dependency>
</dependencies>
核心 API 使用
1 连接到 Memcached
使用 MemcachedClient 类来建立连接。
import net.spy.memcached.MemcachedClient;
import java.net.InetSocketAddress;
public class MemcachedExample {
public static void main(String[] args) {
try {
// 创建一个 MemcachedClient 实例
// 参数是一个 InetSocketAddress 列表,可以连接多个 Memcached 服务器实现分布式
MemcachedClient mcc = new MemcachedClient(
new InetSocketAddress("127.0.0.1", 11211));
System.out.println("Successfully connected to Memcached server.");
// ... 在这里进行缓存操作 ...
// 关闭连接
mcc.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3 基本操作 (CRUD)
Memcached 的基本操作是 get, set, delete, add, replace。
set: 设置一个键值对,如果键已存在,则覆盖。add: 添加一个键值对,如果键已存在,则操作失败。replace: 替换一个键值对,如果键不存在,则操作失败。get: 获取一个键对应的值。delete: 删除一个键。
注意: Memcached 的值只能是 java.lang.ByteBuffer 类型,所以你需要自己处理对象的序列化和反序列化。
示例:缓存字符串
// 设置一个字符串值,并设置过期时间为 10 秒
Future<Boolean> setFuture = mcc.set("user:1001:name", 10, "Alice");
// setFuture.get() 会阻塞直到操作完成
System.out.println("Set user:1001:name: " + setFuture.get());
// 获取值
Object myObject = mcc.get("user:1001:name");
System.out.println("Get user:1001:name: " + myObject);
// 删除值
Future<Boolean> deleteFuture = mcc.delete("user:1001:name");
System.out.println("Delete user:1001:name: " + deleteFuture.get());
// 再次获取,应为 null
myObject = mcc.get("user:1001:name");
System.out.println("Get user:1001:name after delete: " + myObject);
高级特性
1 缓存 Java 对象 (POJO)
直接缓存 Java 对象会报错,因为 Memcached 只能存字节数组,你需要将对象序列化(Serialization)为 JSON 或二进制格式。
使用 Jackson 进行 JSON 序列化
import com.fasterxml.jackson.databind.ObjectMapper;
// 1. 创建 ObjectMapper
ObjectMapper mapper = new ObjectMapper();
// 2. 定义一个要缓存的对象
class User {
private int id;
private String name;
private String email;
// 构造函数、Getters 和 Setters
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}';
}
// Getters and Setters ...
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// 3. 缓存 User 对象
User user = new User(1002, "Bob", "bob@example.com");
// 序列化为 JSON 字符串
String userJson = mapper.writeValueAsString(user);
mcc.set("user:1002", 60, userJson); // 缓存 60 秒
System.out.println("Cached user:1002 as JSON: " + userJson);
// 4. 获取并反序列化 User 对象
Object cachedUserJson = mcc.get("user:1002");
if (cachedUserJson != null) {
User cachedUser = mapper.readValue((String) cachedUserJson, User.class);
System.out.println("Retrieved and deserialized user: " + cachedUser);
}
2 连接池
Spymemcached 内置了连接池功能,在创建 MemcachedClient 时配置即可。
// 创建连接池配置
ConnectionFactoryBuilder connectionFactoryBuilder = new ConnectionFactoryBuilder()
.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY) // 使用二进制协议,效率更高
.setOpTimeout(1000) // 操作超时时间 (ms)
.setInitialConnections(5) // 初始连接数
.setDaemon(true); // 设置为守护线程
// 使用连接池配置创建客户端
MemcachedClient mcc = new MemcachedClient(
connectionFactoryBuilder,
new InetSocketAddress("127.0.0.1", 11211)
);
3 分布式与哈希算法
当你连接多个 Memcached 服务器时,客户端会根据哈希算法将数据分布到不同的服务器上,Spymemcached 默认使用一致性哈希算法,这在服务器增减时能最大限度地减少缓存失效(即 "雪崩" 效应)。
// 连接两个 Memcached 服务器
List<InetSocketAddress> addresses = new ArrayList<>();
addresses.add(new InetSocketAddress("127.0.0.1", 11211));
addresses.add(new InetSocketAddress("127.0.0.1", 11212)); // 假设你有第二个实例
MemcachedClient mcc = new MemcachedClient(addresses);
完整示例代码
这是一个结合了对象序列化和连接池的完整示例。
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.ConnectionFactoryBuilder.Protocol;
import net.spy.memcached.AddrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.Future;
public class FullMemcachedExample {
public static void main(String[] args) {
MemcachedClient mcc = null;
ObjectMapper mapper = new ObjectMapper();
try {
// 1. 初始化客户端(带连接池)
mcc = new MemcachedClient(
new ConnectionFactoryBuilder()
.setProtocol(Protocol.BINARY)
.setOpTimeout(1000)
.build(),
AddrUtil.getAddresses("127.0.0.1:11211")
);
System.out.println("Connected to Memcached.");
// 2. 准备要缓存的对象
User user = new User(1003, "Charlie", "charlie@example.com");
// 3. 序列化并缓存对象
String userJson = mapper.writeValueAsString(user);
Future<Boolean> setFuture = mcc.set("user:1003", 30, userJson);
if (setFuture.get()) { // 阻塞等待设置完成
System.out.println("Successfully cached user:1003.");
}
// 4. 获取并反序列化对象
Object cachedJson = mcc.get("user:1003");
if (cachedJson != null) {
User cachedUser = mapper.readValue((String) cachedJson, User.class);
System.out.println("Retrieved user from cache: " + cachedUser);
} else {
System.out.println("User not found in cache.");
}
// 5. 演示 CAS (Check-And-Set) 操作
// 获取带有 CAS ID 的值
Future<Object> getsFuture = mcc.asyncGet("user:1003");
// Spymemcached 的 gets 返回一个 net.spy.memcached.CASValue 对象
// 注意:Spymemcached 的 CAS API 不如 Xmemcached 直观,这里仅作概念演示
// CASValue<Object> casValue = (CASValue<Object>) getsFuture.get();
// long casId = casValue.getCas();
// System.out.println("CAS ID for user:1003 is " + casId);
// // 尝试用新的 CAS ID 更新
// Future<Boolean> casFuture = mcc.cas("user:1003", casId, userJson);
// System.out.println("CAS update result: " + casFuture.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭连接
if (mcc != null) {
mcc.shutdown();
}
}
}
}
// User 类定义
class User {
private int id;
private String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
最佳实践与注意事项
-
选择合适的序列化方式:
- JSON: 可读性好,调试方便,但性能略逊于二进制序列化。
- Kryo / FST: 高性能的二进制序列化库,适合对性能要求极高的场景。
- Java原生序列化: 不推荐,效率低且版本不兼容问题多。
-
设置合理的过期时间:
- 避免将所有数据设置为永不过期,这会导致内存耗尽和缓存无法更新。
- 根据业务场景设置不同的 TTL(Time To Live),热点数据可以设置长一点,冷门数据设置短一点。
-
处理缓存穿透:
- 问题: 查询一个根本不存在的数据,缓存中没有,就去查询数据库,数据库也没有,但应用会频繁请求,导致数据库压力。
- 解决方案: 如果查询的数据在缓存和数据库中都不存在,可以在缓存中存储一个空对象(如 或
null)并设置一个较短的过期时间,防止大量请求直接打到数据库。
-
处理缓存雪崩:
- 问题: 大量缓存在同一时间失效,导致所有请求瞬间涌入数据库,造成数据库宕机。
- 解决方案:
- 在设置缓存时,给不同的 Key 的过期时间加上一个随机值,避免集体失效。
- 使用高可用的缓存集群,即使一台挂了,还有其他可以顶上。
-
处理缓存击穿:
- 问题: 某个 Key 非常热点,在某一刻突然失效,大量并发请求直接打到数据库。
- 解决方案:
- 互斥锁/分布式锁: 当缓存失效时,只允许一个线程去查询数据库并重建缓存,其他线程等待。
- 热点数据永不过期: 在逻辑上设置一个过期时间,但实际不删除,由后台线程定时更新。
-
监控与维护:
- 监控 Memcached 的内存使用情况、连接数、命中率等关键指标。
- 定期清理过期的 Key,但 Memcached 本身有 LRU(最近最少使用)机制,会自动淘汰不常用的数据。
希望这份详细的指南能帮助你顺利地在 Java 项目中使用 Memcached!
