目录
- Memcached 简介
- 环境准备
- 安装 Memcached 服务器
- Java 开发环境
- Java 客户端选择
- XMemcached (推荐)
- Spymemcached
- 比较
- 实战:使用 XMemcached
- Maven 依赖
- 基本配置与连接
- 核心操作 (增删改查)
- 高级特性 (数据类型、CAS、分布式)
- 代码示例
- Spymemcached 简要介绍
- 最佳实践与注意事项
- Key 设计
- Value 设计
- 过期时间
- 连接池
- 缓存穿透、击穿、雪崩
- 监控与调优
Memcached 简介
Memcached 是一个高性能、分布式的内存对象缓存系统,它主要用于通过在内存中缓存数据和对象来加速动态 Web 应用程序,以减轻数据库的负载。

核心特点:
- 简单协议:基于文本的简单协议,易于实现。
- 基于内存:所有数据都存储在内存中,读写速度极快。
- 多线程:使用 I/O 多线程模型,可以高效处理并发连接。
- 分布式:通过客户端实现分片,可以将数据分布到多台 Memcached 服务器上,形成一个逻辑上的统一缓存池。
- 功能简单:只支持最核心的
set,get,delete,add,replace,incr/decr等操作,没有持久化功能。
环境准备
1 安装 Memcached 服务器
在开始之前,你需要有一台安装了 Memcached 的服务器。
在 Linux (Ubuntu/Debian) 上安装:
# 安装 sudo apt-get update sudo apt-get install memcached # 启动 (默认监听 11211 端口) sudo systemctl start memcached # 设置开机自启 sudo systemctl enable memcached # 检查状态 sudo systemctl status memcached
在 macOS 上安装 (使用 Homebrew):

brew install memcached # 启动 brew services start memcached
2 Java 开发环境
确保你已经安装了 JDK (建议 JDK 8 或更高版本) 和 Maven。
Java 客户端选择
Java 程序不能直接与 Memcached 通信,需要一个客户端库,目前主流的客户端有两个:
1 XMemcached (推荐)
- 特点:
- 高性能:基于
NIO(非阻塞 I/O),性能优异,尤其在高并发场景下。 - 功能丰富:支持连接池、CAS (Check-And-Set)、分布式、数据压缩等。
- 活跃维护:社区活跃,更新频繁。
- API 设计:API 设计相对现代化,支持同步和异步操作。
- 高性能:基于
2 Spymemcached
- 特点:
- 历史悠久:是 Memcached 官方推荐的 Java 客户端之一,非常稳定。
- 基于 Netty:底层使用 Netty 框架,性能也很好。
- API 设计:API 设计相对传统,一些开发者认为不如 XMemcached 直观。
3 比较
| 特性 | XMemcached | Spymemcached |
|---|---|---|
| 底层技术 | NIO | Netty |
| 性能 | 非常高 | 高 |
| API 风格 | 相对现代,易于使用 | 相对传统 |
| 功能 | 连接池、CAS、异步等 | 连接池、CAS、异步等 |
| 社区活跃度 | 较高 | 相对稳定,更新较慢 |
| 推荐度 | 强烈推荐 | 适用于维护旧项目 |
对于新项目,强烈推荐使用 XMemcached。
实战:使用 XMemcached
下面我们以 XMemcached 为例,进行详细的代码演示。

1 Maven 依赖
在你的 pom.xml 文件中添加 XMemcached 的依赖:
<dependencies>
<!-- XMemcached 客户端 -->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version> <!-- 请使用最新版本 -->
</dependency>
<!-- 日志依赖,XMemcached 使用 slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
2 基本配置与连接
你需要创建一个 MemcachedClient 实例,推荐使用单例模式,因为创建和销毁连接开销较大。
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.auth.AuthInfo;
import net.rubyeye.xmemcached.utils.AddrUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeoutException;
public class MemcachedUtil {
private static MemcachedClient memcachedClient;
static {
try {
// 1. 创建客户端构建器
// Memcached 服务器地址,格式为 "host1:port1 host2:port2"
String serverAddresses = "127.0.0.1:11211";
MemcachedClientBuilder builder = new XMemcachedClientBuilder(
AddrUtil.getAddresses(serverAddresses)
);
// 2. (可选) 设置连接池大小
builder.setConnectionPoolSize(10); // 设置连接池为10个连接
// 3. (可选) 设置连接超时
builder.setOpTimeout(3000); // 设置操作超时为3秒
// 4. (可选) 如果有认证,设置认证信息
// builder.setAuthInfo("user", "password");
// 5. 构建并获取客户端实例
memcachedClient = builder.build();
} catch (IOException e) {
e.printStackTrace();
// 初始化失败,需要处理
throw new RuntimeException("初始化 Memcached 客户端失败", e);
}
}
public static MemcachedClient getClient() {
return memcachedClient;
}
public static void shutdown() {
if (memcachedClient != null) {
try {
memcachedClient.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3 核心操作 (增删改查)
XMemcached 的 API 非常直观,方法名基本对应操作。
set (新增/覆盖)
public class Main {
public static void main(String[] args) {
MemcachedClient client = MemcachedUtil.getClient();
try {
// set(key, 超时时间(秒), value)
// 超时时间为0表示永不过期(但 Memcached 会在32天后自动删除)
client.set("user:1001", 3600, "张三");
System.out.println("Set 'user:1001' 成功");
// 也可以设置一个对象,但对象必须实现 Serializable 接口
User user = new User(1002, "李四", "lisi@example.com");
client.set("user:1002", 3600, user);
System.out.println("Set 'user:1002' 对象成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
// MemcachedUtil.shutdown(); // 在应用关闭时调用
}
}
}
// 一个简单的 Java Bean
class User implements java.io.Serializable {
private int id;
private String name;
private String email;
// ... 构造方法、getter 和 setter
}
get (查询)
try {
// get(key)
String name = client.get("user:1001");
System.out.println("Get 'user:1001': " + name);
// 获取对象
User user = client.get("user:1002");
if (user != null) {
System.out.println("Get 'user:1002': " + user.getName());
}
// 获取一个不存在的 key,会返回 null
String nonExistent = client.get("nonexistent:key");
System.out.println("Get 'nonexistent:key': " + nonExistent);
} catch (Exception e) {
e.printStackTrace();
}
delete (删除)
try {
// delete(key)
boolean deleted = client.delete("user:1001");
if (deleted) {
System.out.println("Delete 'user:1001' 成功");
} else {
System.out.println("Delete 'user:1001' 失败,key 可能不存在");
}
} catch (Exception e) {
e.printStackTrace();
}
add (仅当 key 不存在时新增)
try {
// key 不存在,则添加成功
boolean added = client.add("user:1003", 3600, "王五");
System.out.println("Add 'user:1003': " + added);
// key 已存在,则添加失败
boolean addedAgain = client.add("user:1003", 3600, "赵六");
System.out.println("Add 'user:1003' again: " + addedAgain); // 输出 false
} catch (Exception e) {
e.printStackTrace();
}
replace (仅当 key 存在时覆盖)
try {
// key 不存在,则替换失败
boolean replaced = client.replace("nonexistent:key", 3600, "value");
System.out.println("Replace 'nonexistent:key': " + replaced); // 输出 false
// 先添加一个 key
client.add("temp:key", 3600, "old_value");
// key 存在,则替换成功
boolean replacedAgain = client.replace("temp:key", 3600, "new_value");
System.out.println("Replace 'temp:key' again: " + replacedAgain); // 输出 true
String value = client.get("temp:key");
System.out.println("Get 'temp:key' after replace: " + value); // 输出 new_value
} catch (Exception e) {
e.printStackTrace();
}
incr/decr (数值增减)
try {
// 先设置一个数值类型的值
client.set("counter:pageview", 0, "100");
// incr(key, 增量)
long newCount = client.incr("counter:pageview", 1);
System.out.println("Pageview after incr: " + newCount); // 输出 101
// decr(key, 减量)
long newCount2 = client.decr("counter:pageview", 5);
System.out.println("Pageview after decr: " + newCount2); // 输出 96
} catch (Exception e) {
e.printStackTrace();
}
4 高级特性
-
CAS (Check-And-Set):乐观锁机制,用于防止并发更新导致的数据覆盖。
gets(key)获取值的同时,会返回一个CAS值(一个唯一的数字)。cas(key, casValue, expiry, value)在更新时,会检查当前的CAS值是否与你提供的一致,一致才执行更新。
try { // 1. 使用 gets 获取值和 CAS token CASValue<Object> casValue = client.gets("user:1002"); User user = (User) casValue.getValue(); long cas = casValue.getCas(); System.out.println("Original user: " + user.getName() + ", CAS: " + cas); // 2. 模拟另一个线程修改了这个值 (在实际应用中,这发生在别处) // user.setEmail("new_email@example.com"); // client.set("user:1002", 3600, user); // 3. 在当前线程尝试使用 CAS 更新 user.setName("李四-updated-by-cas"); boolean success = client.cas("user:1002", cas, 3600, user); System.out.println("CAS update success: " + success); // 如果另一个线程已经修改,CAS token 会变,导致本次更新失败 // 如果成功,gets 会得到新的 CAS token } catch (Exception e) { e.printStackTrace(); } -
分布式 (多服务器):XMemcached 客户端会自动根据 key 的哈希值将数据分布到不同的 Memcached 服务器上,你只需要在初始化时传入多个地址即可。
// 在 MemcachedUtil 中修改 serverAddresses String serverAddresses = "127.0.0.1:11211 127.0.0.1:11212 192.168.1.100:11211"; MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(serverAddresses)); // ...
Spymemcached 简要介绍
如果你需要使用 Spymemcached,流程类似。
Maven 依赖:
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version> <!-- 请使用最新版本 -->
</dependency>
基本操作示例:
import net.spy.memcached.MemcachedClient;
import java.net.InetSocketAddress;
import java.util.concurrent.Future;
public class SpyMemcachedExample {
public static void main(String[] args) {
try {
// 创建客户端
MemcachedClient mcc = new MemcachedClient(
new InetSocketAddress("127.0.0.1", 11211)
);
// set (异步)
Future<Boolean> setFuture = mcc.set("spy_key", 3600, "Hello SpyMemcached");
setFuture.get(); // 阻塞直到操作完成
System.out.println("Set 'spy_key' success: " + setFuture.get());
// get (同步)
Object value = mcc.get("spy_key");
System.out.println("Get 'spy_key': " + value);
// delete (异步)
Future<Boolean> deleteFuture = mcc.delete("spy_key");
System.out.println("Delete 'spy_key' success: " + deleteFuture.get());
// 关闭客户端
mcc.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
API 最大的区别是 Spymemcached 的很多操作返回 Future 对象,需要调用 get() 方法来获取最终结果,这既是优点(可以异步处理)也是缺点(容易忘记 get() 导致问题)。
最佳实践与注意事项
-
Key 设计
- 清晰可读:使用有意义的命名,如
user:1001:profile,product:567:details。 - 避免过长:过长的 key 会消耗更多内存和网络带宽。
- 避免特殊字符:最好使用字母、数字、下划线和冒号。
- 保持一致性:在整个应用中遵循统一的 Key 命名规范。
- 清晰可读:使用有意义的命名,如
-
Value 设计
- 序列化:存入的对象必须实现
java.io.Serializable接口。 - 体积小:Memcached 是内存数据库,单个 value 最好不要超过 1MB,大 value 会影响性能。
- 考虑压缩:对于大的文本或 JSON 数据,可以在客户端进行压缩后再存入,节省内存。
- 序列化:存入的对象必须实现
-
过期时间
- 永远设置:为所有缓存项设置合理的过期时间,避免数据无限增长。
- 动态调整:根据业务场景动态调整过期时间,热点数据可以设置长一点,冷门数据短一点。
- 利用 "缓存穿透":对于不存在的数据,缓存一个空值或特殊标记,并设置一个较短的过期时间。
-
连接池
- 必须使用:不要频繁创建和销毁
MemcachedClient,务必使用连接池(XMemcached 默认内置)。 - 合理配置大小:根据服务器的负载和应用的并发量,配置合适的连接池大小,通常设置为服务器核心数的 1-2 倍。
- 必须使用:不要频繁创建和销毁
-
缓存问题
- 缓存穿透:查询一个根本不存在的数据。解决方案:缓存一个空对象,并设置较短的过期时间。
- 缓存击穿:一个 key 在某个瞬间过期,大量并发请求直接打到数据库。解决方案:
- 使用互斥锁(如
synchronized或 Redis 的SETNX),只允许一个线程查询数据库并回填缓存。 - 永不过期(逻辑上),后台线程定时更新。
- 使用互斥锁(如
- 缓存雪崩:大量 key 在同一时间集体过期,导致所有请求瞬间涌向数据库。解决方案:
- 在过期时间上加上一个随机值,避免集体失效,基础时间 3600 秒,加上 0-300 秒的随机数。
- 高可用集群,避免整个缓存服务宕机。
-
监控与调优
- 监控指标:监控 Memcached 的
get/miss比率、内存使用量、连接数等。 - 调优参数:根据监控情况,调整
-m(内存大小),-c(最大并发连接数),-t(线程数) 等启动参数。 - 客户端调优:调整客户端的连接池大小、超时时间等。
- 监控指标:监控 Memcached 的
希望这份详细的指南能帮助你顺利地在 Java 项目中使用 Memcached!
