初始化(可选)
零配置启动(默认行为)
// 无需任何初始化——直接使用即可。
// 首次操作时自动以内存缓存(LRU, maxEntries=100000, TTL=1h)启动。
Vostok.Cache.set("key", "value");
String val = Vostok.Cache.get("key");
显式配置内存缓存
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.MEMORY)
.maxEntries(50000)
.evictionPolicy(VKEvictionPolicy.LRU)
.defaultTtlMs(3600_000L) // 1 小时
);
Redis 缓存(单机)
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.endpoints("127.0.0.1:6379")
.password("yourpass")
.database(0)
.maxActive(20)
.defaultTtlMs(86400_000L)
);
Redis 哨兵模式
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.redisMode(VKRedisMode.SENTINEL)
.endpoints("sentinel1:26379", "sentinel2:26379", "sentinel3:26379")
.sentinelMaster("mymaster")
.password("yourpass")
.defaultTtlMs(3600_000L)
);
Redis 集群模式
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.redisMode(VKRedisMode.CLUSTER)
.endpoints("node1:7000", "node2:7001", "node3:7002")
.clusterVirtualNodes(128)
.defaultTtlMs(3600_000L)
);
自定义 Redis 连接池(如 Jedis)
// 1. 业务项目自行引入 Jedis/Lettuce 等依赖
// 2. 实现 VKRedisClientPoolFactory / VKRedisClientPool
// 3. 把第三方 client 适配成 VKCacheClient 并注入配置
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.codec("string")
.option("jedis.maxTotal", "32")
.redisClientPoolFactory(cfg -> new MyJedisPoolAdapter(cfg))
);
// MyJedisPoolAdapter 由业务项目实现,示意:
public final class MyJedisPoolAdapter implements VKRedisClientPool {
private final JedisPool pool;
public MyJedisPoolAdapter(VKCacheConfig cfg) {
this.pool = new JedisPool(cfg.getEndpoints()[0]);
}
public VKCacheClient borrow() {
return new MyJedisClientAdapter(pool.getResource());
}
}
外部池 SPI 说明
默认仍使用 Vostok 内建 Redis 连接池;仅当显式配置
redisClientPoolFactory(...) 时,才切换到外部池。
该 SPI 只支持
代码注入,不会由
VKCacheConfigFactory.fromMap/fromProperties 自动实例化。
TIERED 模式下,如果
l2Config 是 Redis,也同样支持这一扩展点。
poolMetrics() 在外部池下优先返回第三方池快照;若第三方池拿不到原生统计,
total/active/idle 会返回
-1 表示未知。
分层缓存(L1 内存 + L2 Redis)
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.TIERED)
.l1Config(new VKCacheConfig()
.providerType(VKCacheProviderType.MEMORY)
.maxEntries(1000)
.evictionPolicy(VKEvictionPolicy.LRU)
.defaultTtlMs(60_000L)) // L1 短 TTL,快速过期
.l2Config(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.endpoints("127.0.0.1:6379")
.defaultTtlMs(3600_000L)) // L2 权威数据源
);
零配置启动
Cache 模块无需显式初始化。首次调用任意缓存操作时,若尚未初始化,将自动以内存缓存启动:
maxEntries=100000、
evictionPolicy=LRU、
defaultTtlMs=3600000(1小时)。
如需使用 Redis 或自定义参数,在首次调用前调用
init(VKCacheConfig) 即可。
providerType 字段类型为
VKCacheProviderType 枚举,不是字符串。
基本操作
写入 / 读取
// 写入(使用默认 TTL)
Vostok.Cache.set("user:1", user);
// 写入(自定义 TTL,单位 ms)
Vostok.Cache.set("token:abc", session, 1800_000L);
// 读取(返回 String)
String raw = Vostok.Cache.get("key");
// 读取(指定类型,由 codec 反序列化)
User user = Vostok.Cache.get("user:1", User.class);
// 懒加载:未命中时自动调用 loader 并缓存结果(含 Single-Flight 去重)
User user = Vostok.Cache.getOrLoad("user:1", User.class, 3600_000L, () ->
Vostok.Data.findById(User.class, 1L)
);
删除 / 检查 / 过期
// 删除一个或多个 key,返回实际删除的 key 数量
long deleted = Vostok.Cache.delete("user:1", "user:2");
boolean exists = Vostok.Cache.exists("user:1");
// 重置 TTL,返回是否成功
boolean ok = Vostok.Cache.expire("user:1", 600_000L);
计数器
long v1 = Vostok.Cache.incr("pv:today"); // 原子加 1
long v2 = Vostok.Cache.incrBy("pv:today", 5L); // 原子加 N
long v3 = Vostok.Cache.decr("stock:100"); // 原子减 1
long v4 = Vostok.Cache.decrBy("stock:100", 3L); // 原子减 N
批量操作
多键读写(mget / mset)
// 批量读取(按参数顺序返回,未命中的位置为 null)
List<User> users = Vostok.Cache.mget(User.class, "user:1", "user:2", "user:3");
// 批量写入(使用默认 TTL)
Vostok.Cache.mset(Map.of("user:1", user1, "user:2", user2));
// 批量写入(指定 TTL)
Vostok.Cache.mset(Map.of("user:1", user1, "user:2", user2), 3600_000L);
Pipeline 批量命令
注意
Pipeline 仅支持写操作(
set、
del、
incrBy、
expire),不支持读取返回值。
set 方法接收
已序列化的 byte[],需手动调用 codec 编码;或用
pipelineWithResult 查看操作计数。
// 批量写命令(不关心返回值)
Vostok.Cache.pipeline(pipe -> pipe
.set("k1", codec.encode("hello"), 60_000L)
.incrBy("counter", 1L)
.expire("k2", 30_000L)
.del("old:key")
);
// 批量写命令并获取执行结果
VKCachePipelineResult result = Vostok.Cache.pipelineWithResult(pipe -> pipe
.incrBy("counter:a", 1L) // 索引 0
.incrBy("counter:b", 5L) // 索引 1
.del("tmp:key") // 索引 2(SET/DEL/EXPIRE 结果为 null)
);
long counterA = result.getCount(0); // INCRBY 结果(Long)
long counterB = result.getCount(1);
Object raw = result.get(2); // DEL 结果(null)
int total = result.size(); // 命令总数
Hash(哈希表)操作
// 设置 hash 字段,返回新增字段数(0=更新,1=新增)
long added = Vostok.Cache.hset("user:1:profile", "name", "Alice");
// 获取 hash 字段
String name = Vostok.Cache.hget("user:1:profile", "name", String.class);
// 获取 hash 所有字段
Map<String, String> profile = Vostok.Cache.hgetAll("user:1:profile", String.class);
// 删除 hash 字段,返回删除数量
long removed = Vostok.Cache.hdel("user:1:profile", "name", "email");
List(列表)操作
// 从左端插入,返回插入后列表长度
long len = Vostok.Cache.lpush("feed:user:1", msg1, msg2, msg3);
// 获取列表范围(start=0, stop=-1 表示全部)
List<Message> feed = Vostok.Cache.lrange("feed:user:1", 0, 49, Message.class);
Set(集合)操作
// 添加成员,返回新增成员数
long added = Vostok.Cache.sadd("online:users", "uid:1", "uid:2", "uid:3");
// 获取所有成员
Set<String> onlineUsers = Vostok.Cache.smembers("online:users", String.class);
ZSet(有序集合)操作
// 添加成员(含分数),返回新增成员数
long added = Vostok.Cache.zadd("rank:score", 9500.0, "player:1");
// 按排名范围获取成员(升序,start=0 start from lowest score)
List<String> top10 = Vostok.Cache.zrange("rank:score", 0, 9, String.class);
Key 扫描
// 按模式扫描 key(类似 Redis SCAN),返回匹配的 key 列表
List<String> keys = Vostok.Cache.scan("user:*", 100);
命名缓存(多缓存分区)
// 注册额外命名缓存分区
Vostok.Cache.registerCache("session", new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.endpoints("session-redis:6379")
.defaultTtlMs(1800_000L));
// 切换到命名缓存分区执行(Runnable)
Vostok.Cache.withCache("session", () -> {
Vostok.Cache.set("sid:abc", sessionObj);
});
// 切换到命名缓存分区执行(Supplier,有返回值)
User u = Vostok.Cache.withCache("session", () ->
Vostok.Cache.get("sid:abc", User.class)
);
// 获取当前线程所在的分区名
String name = Vostok.Cache.currentCacheName();
// 获取所有已注册的分区名
Set<String> names = Vostok.Cache.cacheNames();
Key 级锁
// 对同一 key 的并发操作串行化(本地 JVM 级别互斥)
String result = Vostok.Cache.withKeyLock("lock:order:42", () -> {
Order order = Vostok.Cache.get("order:42", Order.class);
order.setStatus("paid");
Vostok.Cache.set("order:42", order);
return order.getId();
});
命中率统计
// 获取当前分区统计
VKCacheStats stats = Vostok.Cache.stats();
System.out.printf("hits=%d, misses=%d, nullHits=%d, hitRate=%.2f%%%n",
stats.getHits(),
stats.getMisses(),
stats.getNullHits(),
stats.hitRate() * 100
);
System.out.printf("loads=%d, loadTimeNs=%d%n",
stats.getLoads(),
stats.getLoadTimeNs()
);
// 获取指定命名分区统计
VKCacheStats sessionStats = Vostok.Cache.stats("session");
// 重置当前分区统计归零
Vostok.Cache.resetStats();
VKCacheStats 方法说明
| 方法 | 返回值 | 说明 |
getHits() | long | 命中次数(含 NULL_HIT) |
getMisses() | long | 未命中次数 |
getNullHits() | long | null 占位命中次数(防穿透) |
getLoads() | long | 触发 loader 回源次数 |
getLoadTimeNs() | long | 所有 loader 执行总耗时(纳秒) |
hitRate() | double | 命中率(0.0~1.0),无请求时返回 0.0 |
reset() | void | 重置所有计数归零 |
事件监听
VKCacheConfig cfg = new VKCacheConfig()
.providerType(VKCacheProviderType.MEMORY)
.maxEntries(10000)
.evictionPolicy(VKEvictionPolicy.LRU)
.eventListener(event -> {
switch (event.type()) {
case EVICTED ->
System.out.println("驱逐: " + event.key());
case MISS ->
System.out.println("未命中: " + event.key());
case LOAD ->
System.out.printf("回源加载: %s 耗时 %dms%n",
event.key(), event.extraMs());
default -> {}
}
});
Vostok.Cache.init(cfg);
VKCacheEventType 枚举
| 枚举值 | 触发时机 |
SET | key 写入(set / mset / getOrLoad 写回) |
HIT | 缓存命中(get / getOrLoad 直接读到有效值) |
MISS | 缓存未命中(get / getOrLoad 未找到值) |
DELETE | key 被删除(delete 操作) |
LOAD | getOrLoad 触发 loader 回源加载,event.extraMs() 为 loader 耗时(ms) |
EVICTED | 条目因容量限制或 TTL 过期被后台驱逐 |
NULL_HIT | 命中 null 占位标记(防穿透缓存有效) |
布隆过滤器(防缓存穿透)
// 创建布隆过滤器:预期 100 万 key,误判率 1%
VKBloomFilter bloom = VKBloomFilter.create(1_000_000L, 0.01);
// 预热:将已知存在的 key 写入过滤器
Vostok.Data.findAll(User.class)
.forEach(u -> bloom.put("user:" + u.getId()));
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.endpoints("127.0.0.1:6379")
.bloomFilter(bloom) // 注入过滤器,get/getOrLoad 先过滤
.nullCacheEnabled(true) // 同时开启 null 缓存防二次穿透
.nullCacheTtlMs(30_000L)
);
// 手动检查
boolean maybe = bloom.mightContain("user:999"); // false = 绝对不存在
bloom.put("user:999"); // 新增 key 时同步写入
自定义编解码器
// 实现 VKCacheCodec 接口(name 用于 config.codec() 配置项引用)
Vostok.Cache.registerCodec(new VKCacheCodec() {
@Override
public String name() { return "protobuf"; }
@Override
public byte[] encode(Object value) {
return ((Message) value).toByteArray();
}
@Override
public <T> T decode(byte[] data, Class<T> type) {
/* 反序列化逻辑 */
return ...;
}
});
// 配置使用自定义 codec
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.endpoints("127.0.0.1:6379")
.codec("protobuf") // 内置:json(默认)/ string / bytes
);
连接池监控
// 获取所有分区的连接池指标
List<VKCachePoolMetrics> metrics = Vostok.Cache.poolMetrics();
for (VKCachePoolMetrics m : metrics) {
System.out.printf(
"[%s] total=%d active=%d idle=%d timeouts=%d leaks=%d evicted=%d rateLimited=%d%n",
m.cacheName(), m.total(), m.active(), m.idle(),
m.borrowTimeouts(), m.leakedConnections(),
m.evictedConnections(), m.rejectedByRateLimit()
);
}
限流与降级
Vostok.Cache.init(new VKCacheConfig()
.providerType(VKCacheProviderType.REDIS)
.endpoints("127.0.0.1:6379")
.rateLimitQps(5000) // 0 = 不限流
.degradePolicy(VKCacheDegradePolicy.RETURN_NULL) // 超限时读操作返回 null
);
VKCacheDegradePolicy 枚举
| 枚举值 | 说明 |
FAIL_FAST | 超出限流时抛出异常(默认) |
RETURN_NULL | 超出限流时读操作返回 null,写操作被跳过 |
SKIP_WRITE | 超出限流时跳过写操作,读操作仍尝试执行 |
配置参数
| 参数 | 类型 | 默认值 | 说明 |
| 连接 |
| providerType | VKCacheProviderType | MEMORY | 后端类型:MEMORY / REDIS / TIERED |
| endpoints | String... | ["127.0.0.1:6379"] | Redis 地址,格式 "host:port",支持多个 |
| redisMode | VKRedisMode | SINGLE | 单机 / 哨兵(SENTINEL)/ 集群(CLUSTER) |
| sentinelMaster | String | "mymaster" | 哨兵模式主节点名 |
| clusterVirtualNodes | int | 128 | 集群模式一致性哈希虚拟节点数 |
| username | String | — | Redis 6+ ACL 用户名 |
| password | String | — | Redis 密码 |
| database | int | 0 | Redis 数据库编号 |
| ssl | boolean | false | 启用 TLS/SSL 连接 |
| connectTimeoutMs | int | 2000 | 连接超时(ms) |
| readTimeoutMs | int | 2000 | 读取超时(ms) |
| heartbeatIntervalMs | int | 15000 | 心跳检测间隔(ms) |
| reconnectMaxAttempts | int | 2 | 断线重连最大次数 |
| 连接池 |
| minIdle | int | 1 | 最小空闲连接数 |
| maxActive | int | 8 | 最大活跃连接数 |
| maxWaitMs | long | 3000 | 获取连接最大等待时间(ms) |
| testOnBorrow | boolean | true | 借出时检测连接有效性 |
| testOnReturn | boolean | false | 归还时检测连接有效性 |
| idleValidationIntervalMs | long | 30000 | 空闲连接定期校验间隔(ms) |
| idleTimeoutMs | long | 120000 | 空闲连接超时回收(ms) |
| leakDetectMs | long | 60000 | 连接泄漏检测阈值(ms) |
| 重试 |
| retryEnabled | boolean | true | 是否启用自动重试 |
| maxRetries | int | 2 | 最大重试次数 |
| retryBackoffBaseMs | long | 30 | 指数退避基数(ms) |
| retryBackoffMaxMs | long | 500 | 指数退避上限(ms) |
| retryJitterEnabled | boolean | true | 是否在退避时间上加随机抖动 |
| 缓存行为 |
| defaultTtlMs | long | 0 | 默认 TTL(ms),0 = 永不过期 |
| ttlJitterMs | long | 0 | TTL 随机抖动范围(ms),防雪崩 |
| keyPrefix | String | "" | Key 全局前缀,用于命名空间隔离 |
| codec | String | "json" | 序列化器名称:json / string / bytes 或自定义 |
| metricsEnabled | boolean | true | 是否启用命中率统计 |
| nullCacheEnabled | boolean | true | 是否缓存 null 值(防缓存穿透) |
| nullCacheTtlMs | long | 30000 | null 占位符 TTL(ms) |
| singleFlightEnabled | boolean | true | 是否启用 Single-Flight(同 key 并发回源合并) |
| keyMutexEnabled | boolean | true | 是否启用 key 级本地互斥锁 |
| keyMutexMaxSize | int | 10000 | key 互斥锁表最大大小 |
| 限流与降级 |
| rateLimitQps | int | 0 | Redis 操作 QPS 限制(0=不限流) |
| degradePolicy | VKCacheDegradePolicy | FAIL_FAST | 限流触发时的降级策略 |
| 内存 Provider |
| maxEntries | int | 0 | 内存缓存最大条目数(0=不限) |
| evictionPolicy | VKEvictionPolicy | NONE | 淘汰策略:LRU / LFU / FIFO / NONE |
| memoryEvictionIntervalMs | long | 5000 | 后台驱逐线程扫描周期(ms) |
| 分层缓存 |
| l1Config | VKCacheConfig | — | TIERED 模式 L1(内存)配置 |
| l2Config | VKCacheConfig | — | TIERED 模式 L2(Redis 等)配置 |
| 其他 |
| bloomFilter | VKBloomFilter | noOp() | 布隆过滤器,用于 get/getOrLoad 的前置过滤 |
| eventListener | VKCacheEventListener | — | 缓存事件监听器(函数式接口) |
API 速查
初始化与生命周期
| 方法 | 返回值 | 说明 |
init(VKCacheConfig) | void | 显式初始化(可选;不调用则首次操作自动以默认内存配置初始化) |
init(VKCacheConfigLoader) | void | 通过自定义 ConfigLoader 初始化 |
reinit(VKCacheConfig) | void | 重新初始化(替换配置,重建连接池) |
started() | boolean | 是否已初始化 |
config() | VKCacheConfig | 获取当前配置 |
close() | void | 关闭缓存模块,释放连接 |
基础操作
| 方法 | 返回值 | 说明 |
set(key, value) | void | 写入(默认 TTL) |
set(key, value, ttlMs) | void | 写入(自定义 TTL) |
get(key) | String | 读取为 String |
get(key, Class<T>) | T | 读取并反序列化为指定类型 |
getOrLoad(key, Class<T>, ttlMs, Supplier<T>) | T | 读取或懒加载(含 Single-Flight) |
delete(keys...) | long | 删除一或多个 key,返回删除数量 |
exists(key) | boolean | 判断 key 是否存在 |
expire(key, ttlMs) | boolean | 重置过期时间 |
incr(key) | long | 原子自增 1 |
incrBy(key, delta) | long | 原子自增 N |
decr(key) | long | 原子自减 1 |
decrBy(key, delta) | long | 原子自减 N |
mget(Class<T>, keys...) | List<T> | 批量读取,顺序对应,未命中为 null |
mset(Map) | void | 批量写入(默认 TTL) |
mset(Map, ttlMs) | void | 批量写入(自定义 TTL) |
scan(pattern, count) | List<String> | 按模式扫描 key |
Hash / List / Set / ZSet
| 方法 | 返回值 | 说明 |
hset(key, field, value) | long | 设置 hash 字段,返回新增字段数 |
hget(key, field, Class<T>) | T | 获取 hash 字段值 |
hgetAll(key, Class<T>) | Map<String,T> | 获取 hash 所有字段 |
hdel(key, fields...) | long | 删除 hash 字段,返回删除数 |
lpush(key, values...) | long | 从左端插入列表,返回列表长度 |
lrange(key, start, stop, Class<T>) | List<T> | 获取列表范围(-1 表示末尾) |
sadd(key, members...) | long | 添加集合成员,返回新增数 |
smembers(key, Class<T>) | Set<T> | 获取集合所有成员 |
zadd(key, score, member) | long | 添加有序集合成员,返回新增数 |
zrange(key, start, stop, Class<T>) | List<T> | 按分数升序范围获取有序集合成员 |
分区、Pipeline 与其他
| 方法 | 返回值 | 说明 |
registerCache(name, VKCacheConfig) | void | 注册命名缓存分区 |
withCache(name, Runnable) | void | 切换分区执行(无返回值) |
withCache(name, Supplier<T>) | T | 切换分区执行(有返回值) |
withKeyLock(key, Supplier<T>) | T | key 级别本地互斥锁 |
currentCacheName() | String | 当前线程所在分区名 |
cacheNames() | Set<String> | 所有已注册分区名 |
pipeline(Consumer<VKCachePipeline>) | void | 批量写命令(不关心结果) |
pipelineWithResult(Consumer<VKCachePipeline>) | VKCachePipelineResult | 批量写命令(获取每条结果) |
stats() | VKCacheStats | 获取当前分区命中率统计 |
stats(name) | VKCacheStats | 获取指定分区命中率统计 |
resetStats() | void | 重置当前分区统计 |
poolMetrics() | List<VKCachePoolMetrics> | 获取所有分区连接池指标 |
registerCodec(VKCacheCodec) | void | 注册自定义编解码器 |