Memcachedは、高速な分散型インメモリキャッシュシステムであり、Javaアプリケーションとの統合にはspymemcachedライブラリが広く採用されています。以下に、接続・操作・最適化を含む実装パターンを再構成した実践的な解説を示します。
依存関係の設定
最新安定版(v2.12.3)を用いる場合、Mavenではpom.xmlに次のように記述します:
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
Gradleではbuild.gradleに以下を追加します:
implementation 'net.spy:spymemcached:2.12.3'
基本的なキャッシュ操作(再設計版)
以下のクラスは、接続管理とライフサイクルを明示的に分離し、例外処理も強化しています:
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.AddrUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class CacheManager {
private final MemcachedClient client;
public CacheManager(String serverAddress) throws IOException {
this.client = new MemcachedClient(AddrUtil.getAddresses(serverAddress));
// タイムアウトを明示的に設定
client.setOpTimeout(5, TimeUnit.SECONDS);
}
public boolean store(String key, Object value, int ttlSeconds) {
return client.set(key, ttlSeconds, value).isDone();
}
public <T> T retrieve(String key, Class<T> type) {
Object raw = client.get(key);
return type.isInstance(raw) ? type.cast(raw) : null;
}
public boolean evict(String key) {
return client.delete(key).isDone();
}
public void close() {
if (client != null) {
client.shutdown();
}
}
// 使用例
public static void main(String[] args) {
try (CacheManager cache = new CacheManager("127.0.0.1:11211")) {
cache.store("user:1001", "Alice", 1800); // 30分有効
cache.store("user:1002", "Bob", 1800);
String user1 = cache.retrieve("user:1001", String.class);
System.out.println("Fetched: " + user1); // Alice
cache.evict("user:1001");
} catch (IOException e) {
System.err.println("Failed to initialize cache client: " + e.getMessage());
}
}
}
一括操作と楽観的ロックの活用
高負荷環境では、getBulk()による複数キーの一括取得や、gets()/cas()による競合回避が重要です。以下はその応用例です:
import java.util.HashMap;
import java.util.Map;
import net.spy.memcached.CASResponse;
import net.spy.memcached.CASValue;
public class AdvancedCacheOps {
private final MemcachedClient client;
public AdvancedCacheOps(String endpoint) throws IOException {
this.client = new MemcachedClient(AddrUtil.getAddresses(endpoint));
}
// 複数キーの一括取得(効率的なI/O)
public Map<String, String> fetchProfiles(String... userIds) {
Map<String, Object> bulkResult = client.getBulk(userIds);
Map<String, String> result = new HashMap<>();
for (Map.Entry<String, Object> entry : bulkResult.entrySet()) {
if (entry.getValue() instanceof String) {
result.put(entry.getKey(), (String) entry.getValue());
}
}
return result;
}
// CASによるアトミックなカウンタ更新
public boolean incrementCounter(String key) {
for (int attempt = 0; attempt < 3; attempt++) {
CASValue<Object> current = client.gets(key);
if (current == null) {
return client.set(key, 0, "1").isDone();
}
long newValue = Long.parseLong(current.getValue().toString()) + 1;
CASResponse response = client.cas(key, current.getCas(), String.valueOf(newValue));
if (response == CASResponse.OK) {
return true;
}
}
return false;
}
}
この実装では、CAS失敗時のリトライロジックを組み込み、データ整合性を保証しています。また、getBulk()はネットワーク往復を最小化し、スループット向上に寄与します。
運用上の注意点
- 接続プーリングはspymemcachedでは非対応のため、シングルトンまたはDIコンテナで
MemcachedClientを共有することを推奨 - 値のシリアライズはデフォルトでJavaの
Serializableを使用するが、パフォーマンスを重視する場合はJSONやProtobufへの置き換えを検討 - タイムアウト値はネットワーク遅延とサービスSLAに応じて調整し、過度なブロッキングを回避