目录
- 什么是 Memcached?
- Java 客户端选择
- 环境准备
- 安装并启动 Memcached 服务
- 准备 Java 项目 (Maven)
- 核心功能使用 (Xmemcached 示例)
- 连接 Memcached
- 基本 CRUD 操作 (增删改查)
- 设置过期时间
- 操作模式 (Get, Gets, CAS)
- 高级特性
- 分布式与一致性哈希
- 连接池配置
- 序列化
- 最佳实践与注意事项
什么是 Memcached?
Memcached 是一个高性能、分布式的内存对象缓存系统,它通过在内存中缓存数据和对象来减少读取数据库的次数,从而加快动态网站、Web 应用程序的速度。

- 工作原理: 将数据以
key-value(键-值对) 的形式存储在内存中。 - 特点:
- 简单: 协议简单,API 易用。
- 快速: 所有数据都在内存中,读写速度极快。
- 多线程: 使用 I/O 多线程模型,能处理高并发连接。
- 分布式: 可以通过客户端将数据分布到多台 Memcached 服务器上,实现水平扩展。
Java 客户端选择
Java 程序不能直接与 Memcached 通信,需要一个客户端库,目前最主流的 Java 客户端有两个:
| 客户端 | 特点 | 推荐场景 |
|---|---|---|
| Xmemcached | - 高性能,基于 Java NIO,是异步非阻塞的。 - 功能全面,支持一致性哈希、连接池、序列化等。 - 社区活跃,维护良好。 |
首选推荐,适用于绝大多数新项目,尤其是对性能要求高的场景。 |
| SpyMemcached | - 老牌客户端,基于 Java NIO。 - 相对稳定,但更新频率和社区活跃度不如 Xmemcached。 |
一些遗留项目或特定偏好,新项目不推荐作为首选。 |
本教程将重点介绍 Xmemcached,因为它更现代、性能更好。
环境准备
a) 安装并启动 Memcached 服务
以 Linux (Ubuntu/Debian) 为例:
# 1. 安装 sudo apt-get update sudo apt-get install memcached # 2. 启动服务 (默认监听 11211 端口) sudo systemctl start memcached # 3. 检查服务状态 sudo systemctl status memcached # 4. 测试连接 (使用 telnet) telnet localhost 11211 # 输入 stats 查看状态信息,quit 退出
b) 准备 Java 项目 (Maven)
在你的 pom.xml 文件中添加 Xmemcached 的依赖:

<dependencies>
<!-- Xmemcached 客户端 -->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version> <!-- 建议使用最新稳定版 -->
</dependency>
<!-- 如果需要使用 JSON 序列化,可以添加 fastjson 或 jackson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
核心功能使用 (Xmemcached 示例)
a) 连接 Memcached
最简单的方式是创建一个 MemcachedClient 实例,并提供服务器地址和端口。
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.exception.MemcachedException;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class MemcachedExample {
public static void main(String[] args) {
// Memcached 服务器地址和端口
String ip = "localhost";
int port = 11211;
try {
// 创建 MemcachedClient 实例
// 构造函数参数: 服务器地址:端口
MemcachedClient memcachedClient = new XMemcachedClient(ip, port);
// --- 后续操作 ---
// 关闭客户端
memcachedClient.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
b) 基本 CRUD 操作
Xmemcached 的核心 API 是 set, get, delete。
-
set(key, expire, value): 存储数据。key: 键,必须是字符串。expire: 过期时间(秒)。0表示永不过期(但内存不足时仍可能被淘汰)。value: 值,可以是任意Serializable对象。
-
get(key): 获取数据。
(图片来源网络,侵删) -
delete(key): 删除数据。
// ... (接上面的 main 方法)
MemcachedClient memcachedClient = new XMemcachedClient("localhost", 11211);
try {
// 1. Set (添加/更新)
// 设置一个键为 "user:1",值为 "Alice",10秒后过期
boolean setSuccess = memcachedClient.set("user:1", 10, "Alice");
System.out.println("Set 'user:1': " + setSuccess);
// 2. Get (查询)
// 获取键 "user:1" 的值
String userName = memcachedClient.get("user:1");
System.out.println("Get 'user:1': " + userName);
// 3. Delete (删除)
// 删除键 "user:1"
boolean deleteSuccess = memcachedClient.delete("user:1");
System.out.println("Delete 'user:1': " + deleteSuccess);
// 再次获取,应该返回 null
userName = memcachedClient.get("user:1");
System.out.println("Get 'user:1' after delete: " + userName);
} catch (MemcachedException | InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
// 确保在程序结束时关闭客户端
if (memcachedClient != null) {
try {
memcachedClient.shutdown();
} catch (IOException e) {
e.printStackTrace();
}
}
}
c) 设置过期时间
如上所示,set 方法的第二个参数就是用于设置过期时间的(单位:秒)。
// 设置一个键值对,5秒后自动过期
memcachedClient.set("temp:data", 5, "This will expire in 5 seconds");
// 等待6秒
Thread.sleep(6000);
// 再次获取,应该为 null
String tempData = memcachedClient.get("temp:data");
System.out.println("Get 'temp:data' after 6s: " + tempData); // 输出 null
d) 操作模式 (Get, Gets, CAS)
Memcached 提供了 CAS (Check-And-Set) 机制,用于乐观锁,防止并发写入时的数据覆盖问题。
gets(key): 获取数据的同时,获取该数据的CAS值(一个唯一的数字)。cas(key, casValue, expire, value): 只有当key的当前CAS值与传入的casValue相同时,才会执行更新操作。
// ... (接上面的 main 方法)
// 1. 设置初始值
memcachedClient.set("product:1001", 0, "Stock: 100");
// 2. 模拟两个客户端同时读取
// 客户端 A
GetsResponse<Object> responseA = memcachedClient.gets("product:1001");
String stockA = (String) responseA.get();
long casValueA = responseA.getCAS();
System.out.println("Client A gets Stock: " + stockA + ", CAS: " + casValueA);
// 客户端 B (同时读取,会得到相同的 CAS 值)
GetsResponse<Object> responseB = memcachedClient.gets("product:1001");
String stockB = (String) responseB.get();
long casValueB = responseB.getCAS();
System.out.println("Client B gets Stock: " + stockB + ", CAS: " + casValueB);
// 3. 客户端 A 先进行更新 (成功)
// 假设 A 卖出了一件,库存减一
String newStockA = "Stock: 99";
boolean casSuccessA = memcachedClient.cas("product:1001", casValueA, 0, newStockA);
System.out.println("Client A CAS update success: " + casSuccessA);
// 4. 客户端 B 尝试更新 (失败,因为 CAS 值已变)
// B 的操作基于旧的 CAS 值,所以会失败
String newStockB = "Stock: 98"; // B 也想卖出一件
boolean casSuccessB = memcachedClient.cas("product:1001", casValueB, 0, newStockB);
System.out.println("Client B CAS update success: " + casSuccessB); // 输出 false
// 5. 检查最终结果
String finalStock = memcachedClient.get("product:1001");
System.out.println("Final stock: " + finalStock); // 输出 Stock: 99
高级特性
a) 分布式与一致性哈希
当有多个 Memcached 服务器时,客户端需要决定将数据存放在哪台服务器上,默认情况下,Xmemcached 使用一致性哈希算法,这能保证在增加或减少服务器时,只会影响少量键的重新分布,而不会造成大规模的缓存失效(“雪崩”)。
配置方式:
// 服务器列表 String servers = "localhost:11211 localhost:11212 localhost:11213"; // 创建客户端,传入服务器列表 MemcachedClient memcachedClient = new XMemcachedClient(servers);
b) 连接池配置
Xmemcached 内置了连接池功能,在高并发场景下能有效管理连接资源,避免频繁创建和销毁连接。
import net.rubyeye.xmemcached.MemcachedClient;
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.TimeUnit;
public class MemcachedWithPool {
public static void main(String[] args) throws IOException {
// 使用 XMemcachedClientBuilder 来构建客户端
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(
AddrUtil.getAddresses("localhost:11211")
);
// 1. 设置连接池大小
builder.setConnectionPoolSize(10); // 设置连接池的最大连接数
// 2. 设置连接超时
builder.setOpTimeout(1000); // 设置操作超时时间,单位毫秒
// 3. 设置连接空闲超时
builder.setIdleConnTimeout(60000); // 设置连接在池中的最大空闲时间
// 4. (可选) 设置连接的 TCP 参数
builder.setSocketOption(StandardSocketOptions.SO_RCVBUF, 32 * 1024);
builder.setSocketOption(StandardSocketOptions.SO_SNDBUF, 32 * 1024);
MemcachedClient memcachedClient = builder.build();
try {
// ... 使用客户端进行操作 ...
memcachedClient.set("pool:test", 0, "Hello, Connection Pool!");
String value = memcachedClient.get("pool:test");
System.out.println(value);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭客户端,会自动释放连接池
memcachedClient.shutdown();
}
}
}
c) 序列化
Xmemcached 默认使用 Java 序列化,Java 序列化存在性能较差、安全性问题等缺点,在实际项目中,推荐使用更高效的序列化方式,如 Kryo、Protostuff 或 JSON。
Xmemcached 允许你自定义序列化器,这里以 fastjson 为例,将对象以 JSON 字符串的形式存储。
import com.alibaba.fastjson.JSON;
import net.rubyeye.xmemcached.transcoders.Transcoder;
// 自定义 JSON 序列化器
public class JsonTranscoder<T> implements Transcoder<T> {
private final Class<T> type;
public JsonTranscoder(Class<T> type) {
this.type = type;
}
@Override
public byte[] encode(T o) {
return JSON.toJSONString(o).getBytes(); // 对象转 JSON 字节数组
}
@Override
public T decode(byte[] d) {
if (d == null) {
return null;
}
return JSON.parseObject(new String(d), type); // JSON 字节数组转对象
}
// ... 其他方法可以简单实现或抛出 UnsupportedOperationException ...
@Override public int getMaxSize() { return 0; }
@Override public T decode(byte[] d, int offset, int length) { return decode(d); }
@Override public boolean isCompression() { return false; }
@Override public void setCompressionThreshold(int threshold) {}
}
// 使用自定义序列化器
public class User {
private String name;
private int age;
// getters, setters, constructor...
}
// 在客户端中使用
MemcachedClient memcachedClient = new XMemcachedClient("localhost", 11211);
JsonTranscoder<User> userTranscoder = new JsonTranscoder<>(User.class);
User user = new User("Bob", 30);
memcachedClient.set("user:2", 0, user, userTranscoder);
User retrievedUser = memcachedClient.get("user:2", userTranscoder);
System.out.println("Retrieved User: " + retrievedUser.getName() + ", " + retrievedUser.getAge());
最佳实践与注意事项
-
Key 的设计:
- 清晰: 使用有意义的键名,如
user:1001:profile。 - 长度: 键名不宜过长,会浪费内存和网络带宽。
- 避免空格: 使用冒号 、下划线
_等分隔符。
- 清晰: 使用有意义的键名,如
-
Value 的大小:
- Memcached 适合存储小块数据(通常小于 1MB)。
- 过大的 Value 会降低缓存效率,并增加网络开销。
-
缓存穿透、击穿、雪崩:
- 穿透: 查询一个不存在的数据,解决方案:对查询结果为 null 的数据也进行缓存,并设置较短的过期时间。
- 击穿: 一个热点 Key 在过期瞬间,大量并发请求直接打到数据库,解决方案:使用互斥锁(如
CAS或synchronized)来重建缓存。 - 雪崩: 大量 Key 在同一时间过期,导致所有请求瞬间涌向数据库,解决方案:给 Key 的过期时间加上一个随机值,避免集体失效。
-
客户端生命周期:
MemcachedClient是重量级对象,不要频繁创建和销毁,应该在应用启动时初始化,在应用关闭时销毁,最好是单例模式。
-
监控:
- 监控 Memcached 的内存使用情况 (
stats slabs)、连接数、命中率 (stats cachedump) 等指标,及时发现潜在问题。
- 监控 Memcached 的内存使用情况 (
在 Java 中使用 Memcached 主要分为以下几步:
- 选择客户端: 推荐使用高性能的
Xmemcached。 - 引入依赖: 在 Maven 或 Gradle 中添加客户端库。
- 创建连接: 初始化
MemcachedClient,配置服务器地址和连接池。 - 执行操作: 使用
set,get,delete,cas等方法进行数据交互。 - 优化配置: 根据业务需求配置序列化方式、连接池参数等。
- 遵循最佳实践: 设计好 Key,注意 Value 大小,防范缓存问题。
通过以上步骤,你就可以在你的 Java 应用中轻松地集成 Memcached,显著提升数据访问性能。
