最佳实践
本文档总结了使用 RogueMap 的最佳实践和常见陷阱。
资源管理
1. 使用 try-with-resources
java
// 推荐 ✅
try (RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build()) {
// 使用 map
map.put("key", 100L);
} // 自动调用 close(),释放资源
// 避免 ❌
RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
// 忘记调用 close() 会导致内存泄漏2. 注册 Shutdown Hook
java
RogueMap<String, Long> map = RogueMap.<String, Long>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
// 注册 Shutdown Hook 确保优雅关闭
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
map.close();
System.out.println("RogueMap closed successfully");
} catch (Exception e) {
System.err.println("Error closing RogueMap: " + e.getMessage());
}
}));性能优化
1. 优先使用原始类型
java
// 好 ✅ - 零拷贝,高性能
RogueMap<Long, Long> map = RogueMap.<Long, Long>offHeap()
.keyCodec(PrimitiveCodecs.LONG)
.valueCodec(PrimitiveCodecs.LONG)
.build();
// 避免 ❌ - 序列化开销
RogueMap<String, String> map = RogueMap.<String, String>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(StringCodec.INSTANCE)
.build();
// 如果可以用 Long,就不要用 String2. 选择合适的索引
java
// 高并发场景:SegmentedHashIndex
RogueMap<String, User> cache = RogueMap.<String, User>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(KryoObjectCodec.create(User.class))
.segmentedIndex(128) // 高并发
.build();
// Long 键 + 内存敏感:LongPrimitiveIndex
RogueMap<Long, Long> idMap = RogueMap.<Long, Long>offHeap()
.keyCodec(PrimitiveCodecs.LONG)
.valueCodec(PrimitiveCodecs.LONG)
.primitiveIndex() // 节省 81% 内存
.build();3. 批量操作
java
// 批量写入
Map<String, Long> batch = new HashMap<>();
for (int i = 0; i < 10000; i++) {
batch.put("key" + i, (long) i);
}
// 一次性提交
for (Map.Entry<String, Long> entry : batch.entrySet()) {
map.put(entry.getKey(), entry.getValue());
}4. 合理设置内存大小
java
// 估算内存需求
long recordCount = 1_000_000;
int avgKeySize = 20;
int avgValueSize = 100;
double overhead = 1.2;
long requiredMemory = (long) (recordCount * (avgKeySize + avgValueSize) * overhead);
RogueMap<K, V> map = RogueMap.<K, V>offHeap()
.maxMemory(requiredMemory)
.build();存储模式选择
1. OffHeap - 内存敏感场景
java
// 适合:降低 GC 压力,中等数据量
RogueMap<String, User> cache = RogueMap.<String, User>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(KryoObjectCodec.create(User.class))
.maxMemory(2 * 1024 * 1024 * 1024) // 2GB
.build();适用场景:
- 需要降低 GC 压力
- 数据量在 GB 级别
- 不需要持久化
2. Mmap Temp - 大数据临时处理
java
// 适合:大数据量临时处理
RogueMap<Long, Record> tempData = RogueMap.<Long, Record>mmap()
.temporary()
.keyCodec(PrimitiveCodecs.LONG)
.valueCodec(KryoObjectCodec.create(Record.class))
.allocateSize(50L * 1024 * 1024 * 1024) // 50GB
.build();适用场景:
- 大数据量临时处理(10GB+)
- 批处理任务
- 自动清理
3. Mmap Persist - 持久化存储
java
// 适合:需要持久化
RogueMap<String, Document> db = RogueMap.<String, Document>mmap()
.persistent("data/documents.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(KryoObjectCodec.create(Document.class))
.allocateSize(20L * 1024 * 1024 * 1024) // 20GB
.build();适用场景:
- 需要数据持久化
- 嵌入式数据库
- 配置管理
并发使用
1. 高并发读写
java
// 使用 SegmentedHashIndex
RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.segmentedIndex(128) // 增加段数
.build();
// 多线程并发访问
ExecutorService executor = Executors.newFixedThreadPool(32);
for (int i = 0; i < 1000; i++) {
final int index = i;
executor.submit(() -> {
map.put("key" + index, (long) index);
Long value = map.get("key" + index);
});
}2. 避免复合操作
java
// 避免 ❌ - 非原子性
if (!map.containsKey("key")) {
map.put("key", 100L);
}
// 好 ✅ - 使用外部同步
synchronized (lock) {
if (!map.containsKey("key")) {
map.put("key", 100L);
}
}持久化最佳实践
1. 定期刷盘
java
RogueMap<String, Long> map = RogueMap.<String, Long>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
// 定期刷盘
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
map.flush();
} catch (Exception e) {
logger.error("Flush failed", e);
}
}, 1, 5, TimeUnit.MINUTES);2. 保持编解码器一致
java
// 创建时
RogueMap<String, Long> map1 = RogueMap.<String, Long>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
map1.close();
// 恢复时 - 必须使用相同的编解码器 ✅
RogueMap<String, Long> map2 = RogueMap.<String, Long>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE) // 相同
.valueCodec(PrimitiveCodecs.LONG) // 相同
.build();3. 异常处理
java
RogueMap<String, Long> map = null;
try {
map = RogueMap.<String, Long>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
// 使用 map
} catch (Exception e) {
logger.error("Error", e);
} finally {
if (map != null) {
try {
map.close();
} catch (Exception e) {
logger.error("Error closing", e);
}
}
}内存管理
1. 设置 JVM 参数
bash
# 同时设置堆内存和直接内存
java -Xmx4g -XX:MaxDirectMemorySize=4g -jar app.jar2. 监控内存使用
java
// 堆内存
Runtime runtime = Runtime.getRuntime();
long heapUsed = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Heap: " + heapUsed / 1024 / 1024 + " MB");
// Map 大小
int size = map.size();
System.out.println("Map size: " + size);3. 避免内存泄漏
java
// 确保关闭
try (RogueMap<String, Long> map = ...) {
// 使用 map
} // 自动关闭
// 或使用 Shutdown Hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
map.close();
}));常见陷阱
1. 忘记关闭
java
// 错误 ❌
RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
// 忘记调用 close()
// 正确 ✅
try (RogueMap<String, Long> map = ...) {
// 使用
}2. 超出内存限制
java
// 错误 ❌
RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
.maxMemory(10L * 1024 * 1024 * 1024) // 10GB
.build();
// 但 -XX:MaxDirectMemorySize=2g 只有 2GB
// 正确 ✅
// 确保 maxMemory <= MaxDirectMemorySize3. 编解码器不一致
java
// 错误 ❌
RogueMap<String, Long> map1 = RogueMap.<String, Long>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
map1.close();
RogueMap<String, String> map2 = RogueMap.<String, String>mmap()
.persistent("data.db")
.keyCodec(StringCodec.INSTANCE)
.valueCodec(StringCodec.INSTANCE) // 不同!
.build();
// 正确 ✅
// 使用相同的编解码器4. 磁盘空间不足
java
// 检查磁盘空间
File file = new File("data.db");
long freeSpace = file.getFreeSpace();
long allocateSize = 100L * 1024 * 1024 * 1024; // 100GB
if (freeSpace < allocateSize) {
throw new IllegalStateException("Not enough disk space");
}调试和监控
1. 启用日志
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(MyApp.class);
RogueMap<String, Long> map = RogueMap.<String, Long>offHeap()
.keyCodec(StringCodec.INSTANCE)
.valueCodec(PrimitiveCodecs.LONG)
.build();
logger.info("RogueMap created, size: {}", map.size());2. 性能监控
java
long startTime = System.nanoTime();
// 执行操作
for (int i = 0; i < 1_000_000; i++) {
map.put("key" + i, (long) i);
}
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // ms
System.out.println("Write 1M entries: " + duration + " ms");3. 定期检查
java
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
int size = map.size();
Runtime runtime = Runtime.getRuntime();
long heapUsed = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Map size: " + size);
System.out.println("Heap used: " + heapUsed / 1024 / 1024 + " MB");
}, 0, 1, TimeUnit.MINUTES);生产环境建议
1. 配置文件化
java
// application.properties
roguemap.maxMemory=2147483648
roguemap.dataDir=/var/data/roguemap
roguemap.segments=128
// 读取配置
long maxMemory = config.getLong("roguemap.maxMemory");
String dataDir = config.getString("roguemap.dataDir");
int segments = config.getInt("roguemap.segments");2. 监控告警
java
// 监控内存使用
if (heapUsed > maxMemory * 0.9) {
logger.warn("Memory usage high: {}%", heapUsed * 100 / maxMemory);
// 发送告警
}3. 优雅关闭
java
// Spring Boot 示例
@PreDestroy
public void destroy() {
logger.info("Closing RogueMap...");
try {
map.close();
logger.info("RogueMap closed successfully");
} catch (Exception e) {
logger.error("Error closing RogueMap", e);
}
}总结
DO:
- ✅ 使用 try-with-resources
- ✅ 优先使用原始类型
- ✅ 选择合适的索引
- ✅ 定期刷盘(持久化模式)
- ✅ 设置 JVM 参数
- ✅ 异常处理
DON'T:
- ❌ 忘记关闭
- ❌ 超出内存限制
- ❌ 编解码器不一致
- ❌ 忽略磁盘空间检查
- ❌ 复合操作不加锁