chronicle-map 是 EasyFK 框架中基于 Chronicle Map 的高性能堆外键值存储组件。Chronicle Map 是一个开源的嵌入式键值存储引擎,数据存储在堆外内存(Off-Heap),不受 JVM GC 影响,同时支持文件持久化,进程重启后数据可自动恢复。
该模块提供了完整的自动配置、类型安全的 Map 管理器和便捷的操作模板,开发者只需通过 YAML 配置即可创建和使用高性能键值存储,适用于本地缓存、会话存储、配置中心、计数器等场景。
<dependency>
<groupId>com.mcst</groupId>
<artifactId>chronicle-map</artifactId>
</dependency>dependencies {
implementation 'com.mcst:chronicle-map'
}该模块会自动传递引入以下依赖:
net.openhft:chronicle-map — Chronicle Map 核心库Chronicle Map 基于内存映射文件(Memory-Mapped File)技术,将数据存储在堆外内存中:
┌─────────────────────────────────────────────────┐
│ 应用业务层 │
│ ChronicleMapTemplate.put / get / remove │
└──────────────────────┬──────────────────────────┘
│
┌──────────────────────▼──────────────────────────┐
│ ChronicleMapManager(Map 管理器) │
│ · 类型安全校验 · 读写锁优化 · 懒加载创建 │
│ · 类型验证缓存 · 类型化包装器缓存 │
└──────────────────────┬──────────────────────────┘
│
┌──────────────────────▼──────────────────────────┐
│ Chronicle Map(堆外键值存储引擎) │
│ · 堆外内存(Off-Heap)· 零 GC 影响 │
│ · 内存映射文件 · 持久化 / 恢复 │
│ · 多线程并发安全 · 亚微秒级读写 │
└──────────────────────┬──────────────────────────┘
│
┌──────────────────────▼──────────────────────────┐
│ 文件系统(可选持久化) │
│ ./chronicle-maps/mapName.dat │
└─────────────────────────────────────────────────┘mmap 将文件映射到内存,读写性能接近内存操作recoverPersistedTo 自动恢复模块通过 Spring Boot 自动配置机制注册以下 Bean:
| Bean | 类型 | 条件 | 说明 |
|---|---|---|---|
| ChronicleMapManager | Manager | @ConditionalOnMissingBean + @Lazy | Map 管理器,懒加载初始化 |
| ChronicleMapTemplate | Template | @ConditionalOnMissingBean | 操作模板,依赖 Manager |
ChronicleMapManager 使用 @Lazy 注解,只在首次使用时初始化,避免启动时不必要的资源消耗。easyfk.config.chronicle.map 前缀的配置属性ChronicleMapManager 实例maps 配置列表,为每个预定义的 Map 创建 Chronicle Map 实例并注册ChronicleMapTemplate 实例配置前缀:easyfk.config.chronicle.map
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| default-path | String | ./chronicle-maps | 默认存储目录 |
| default-max-entries | long | 100000 | 默认最大条目数 |
| default-average-key-size | double | 64.0 | 默认平均键大小(字节) |
| default-average-value-size | double | 1024.0 | 默认平均值大小(字节) |
| default-file-extension | String | .dat | 默认持久化文件扩展名 |
| persistence-enabled | boolean | true | 是否启用持久化 |
| recover-on-startup | boolean | true | 启动时是否恢复数据 |
| compression-enabled | boolean | false | 是否启用压缩 |
| apply-average-sizes | boolean | true | 是否应用平均大小参数 |
| fixed-size-optimization-enabled | boolean | true | 是否启用固定尺寸类型优化 |
通过 maps 列表预定义需要在启动时创建的 Chronicle Map:
easyfk:
config:
chronicle:
map:
default-path: ./chronicle-maps
default-max-entries: 100000
default-average-key-size: 64
default-average-value-size: 1024
persistence-enabled: true
maps:
- map-name: userCache
max-entries: 50000
average-key-size: 32
average-value-size: 512
persistence-enabled: true
key-type: java.lang.String
value-type: java.lang.Object
- map-name: sessionStore
max-entries: 10000
average-key-size: 64
average-value-size: 2048
persistence-enabled: true
- map-name: counterMap
max-entries: 1000
key-type: java.lang.String
value-type: java.lang.Long
persistence-enabled: false| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| map-name | String | 必填 | Map 名称,全局唯一标识 |
| file-path | String | 自动生成 | 持久化文件路径,未配置时按 defaultPath/mapName.ext 生成 |
| max-entries | Long | 使用全局默认 | 最大条目数 |
| average-key-size | Double | 使用全局默认 | 平均键大小(字节) |
| average-value-size | Double | 使用全局默认 | 平均值大小(字节) |
| persistence-enabled | boolean | true | 是否启用持久化 |
| compression-enabled | boolean | false | 是否启用压缩 |
| key-type | Class | String.class | 键的 Java 类型 |
| value-type | Class | Object.class | 值的 Java 类型 |
| file-extension | String | 使用全局默认 | 文件扩展名 |
| apply-average-sizes | Boolean | 使用全局默认 | 是否应用平均大小参数 |
| fixed-size-optimization-enabled | Boolean | 使用全局默认 | 是否启用固定尺寸类型优化 |
Long、Integer、Double 等基本类型包装类时,Chronicle Map 已知其精确大小,无需设置 averageKeySize / averageValueSize,启用该优化可自动跳过。ChronicleMapTemplate 提供简洁的 API,适合大部分使用场景:
@Service
public class UserCacheService {
@Autowired
private ChronicleMapTemplate chronicleMapTemplate;
private static final String MAP_NAME = "userCache";
// 存储
public void cacheUser(String userId, UserDTO user) {
chronicleMapTemplate.put(MAP_NAME, userId, user);
}
// 获取
public UserDTO getUser(String userId) {
return (UserDTO) chronicleMapTemplate.get(MAP_NAME, userId);
}
// 获取(带默认值)
public UserDTO getUserOrDefault(String userId, UserDTO defaultUser) {
return (UserDTO) chronicleMapTemplate.getOrDefault(MAP_NAME, userId, defaultUser);
}
// 不存在时才存储
public void cacheIfAbsent(String userId, UserDTO user) {
chronicleMapTemplate.putIfAbsent(MAP_NAME, userId, user);
}
// 删除
public void removeUser(String userId) {
chronicleMapTemplate.remove(MAP_NAME, userId);
}
// 检查是否存在
public boolean exists(String userId) {
return chronicleMapTemplate.containsKey(MAP_NAME, userId);
}
// 获取缓存大小
public long cacheSize() {
return chronicleMapTemplate.size(MAP_NAME);
}
}// 批量存储
Map<String, Object> batch = new HashMap<>();
batch.put("user:1001", user1);
batch.put("user:1002", user2);
batch.put("user:1003", user3);
chronicleMapTemplate.putAll("userCache", batch);
// 获取所有键
Set<String> keys = chronicleMapTemplate.keySet("userCache");
// 获取所有值
Collection<Object> values = chronicleMapTemplate.values("userCache");
// 获取所有键值对
Set<Map.Entry<String, Object>> entries = chronicleMapTemplate.entrySet("userCache");
// 清空
chronicleMapTemplate.clear("userCache");// 如果键不存在,计算并存储
Object value = chronicleMapTemplate.computeIfAbsent("configCache", "db.url",
key -> loadConfigFromDB(key));
// 如果键存在,重新计算
chronicleMapTemplate.computeIfPresent("counterMap", "loginCount",
(key, oldValue) -> (Long) oldValue + 1);对于已知类型的 Map,使用强类型 API 避免类型转换:
@Service
public class CounterService {
@Autowired
private ChronicleMapTemplate chronicleMapTemplate;
private static final String MAP_NAME = "counterMap";
// 强类型存储
public void setCounter(String name, Long value) {
chronicleMapTemplate.put(MAP_NAME, String.class, Long.class, name, value);
}
// 强类型获取
public Long getCounter(String name) {
return chronicleMapTemplate.get(MAP_NAME, String.class, Long.class, name);
}
// 强类型批量存储
public void setCounters(Map<String, Long> counters) {
chronicleMapTemplate.putAll(MAP_NAME, String.class, Long.class, counters);
}
// 强类型原子计算
public Long incrementCounter(String name) {
return chronicleMapTemplate.computeIfPresent(MAP_NAME, String.class, Long.class,
name, (k, v) -> v + 1);
}
}需要更精细控制时,可直接使用 ChronicleMapManager:
@Service
public class DynamicMapService {
@Autowired
private ChronicleMapManager chronicleMapManager;
// 动态创建 Map
public void createMap(String mapName) {
chronicleMapManager.getOrCreateMap(mapName, String.class, String.class);
}
// 带自定义配置创建
public void createCustomMap(String mapName) {
chronicleMapManager.getOrCreateMap(mapName,
50000, // maxEntries
32.0, // avgKeySize
256.0, // avgValueSize
"./data/" + mapName + ".dat" // filePath
);
}
// 检查 Map 是否存在
public boolean mapExists(String mapName) {
return chronicleMapManager.containsMap(mapName);
}
// 获取所有 Map 名称
public Set<String> listMaps() {
return chronicleMapManager.getMapNames();
}
// 获取统计信息
public String getStats(String mapName) {
return chronicleMapManager.getMapStats(mapName);
}
// 移除并关闭 Map
public void removeMap(String mapName) {
chronicleMapManager.removeMap(mapName);
}
// 关闭所有 Map(应用关闭时)
public void shutdown() {
chronicleMapManager.closeAll();
}
}// 检查 Map 是否存在
boolean exists = chronicleMapTemplate.mapExists("userCache");
// 获取所有 Map 名称
Set<String> mapNames = chronicleMapTemplate.getAllMapNames();
// 获取统计信息
String stats = chronicleMapTemplate.getStats("userCache");Chronicle Map 需要在创建时预估数据规模,合理的容量规划对性能至关重要:
String 类型的 key,按平均字符串长度估算字节数| 场景 | maxEntries | avgKeySize | avgValueSize |
|---|---|---|---|
| 用户信息缓存 | 100,000 | 32 | 512 |
| 会话存储 | 10,000 | 64 | 2048 |
| 配置中心 | 1,000 | 64 | 256 |
| 计数器 | 10,000 | 32 | 8(Long) |
| 限流令牌桶 | 50,000 | 64 | 128 |
easyfk:
config:
chronicle:
map:
persistence-enabled: true # 全局启用持久化
default-path: ./chronicle-mapsrecoverPersistedTo 自动恢复数据./chronicle-maps/ 目录下easyfk:
config:
chronicle:
map:
persistence-enabled: false # 纯内存模式可以为不同的 Map 分别设置持久化策略:
easyfk:
config:
chronicle:
map:
persistence-enabled: true
maps:
- map-name: importantData
persistence-enabled: true # 重要数据持久化
- map-name: tempCache
persistence-enabled: false # 临时缓存不持久化maxEntries、averageKeySize、averageValueSize,避免频繁 resize 或内存浪费。keyType / valueType),避免运行时类型转换错误。Serializable 接口,确保可序列化。ChronicleMapManager.closeAll() 关闭所有 Map,释放堆外内存。Long、Integer 等基本类型,启用 fixed-size-optimization-enabled 可跳过不必要的平均大小设置。getStats() 定期监控 Map 的大小和健康状态。ChronicleMapManager.getOrCreateMap() 动态创建 Map,自动处理持久化文件和类型注册。easyfk-chronicle-map — 堆外高性能键值存储,突破 JVM 内存限制。