初始化(可选)

零配置启动(默认行为)

// 无需任何初始化——直接使用即可。
// 首次操作时自动以内存缓存(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=100000evictionPolicy=LRUdefaultTtlMs=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 仅支持写操作(setdelincrByexpire),不支持读取返回值。
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()longnull 占位命中次数(防穿透)
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 枚举

枚举值触发时机
SETkey 写入(set / mset / getOrLoad 写回)
HIT缓存命中(get / getOrLoad 直接读到有效值)
MISS缓存未命中(get / getOrLoad 未找到值)
DELETEkey 被删除(delete 操作)
LOADgetOrLoad 触发 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超出限流时跳过写操作,读操作仍尝试执行

配置参数

参数类型默认值说明
连接
providerTypeVKCacheProviderTypeMEMORY后端类型:MEMORY / REDIS / TIERED
endpointsString...["127.0.0.1:6379"]Redis 地址,格式 "host:port",支持多个
redisModeVKRedisModeSINGLE单机 / 哨兵(SENTINEL)/ 集群(CLUSTER)
sentinelMasterString"mymaster"哨兵模式主节点名
clusterVirtualNodesint128集群模式一致性哈希虚拟节点数
usernameStringRedis 6+ ACL 用户名
passwordStringRedis 密码
databaseint0Redis 数据库编号
sslbooleanfalse启用 TLS/SSL 连接
connectTimeoutMsint2000连接超时(ms)
readTimeoutMsint2000读取超时(ms)
heartbeatIntervalMsint15000心跳检测间隔(ms)
reconnectMaxAttemptsint2断线重连最大次数
连接池
minIdleint1最小空闲连接数
maxActiveint8最大活跃连接数
maxWaitMslong3000获取连接最大等待时间(ms)
testOnBorrowbooleantrue借出时检测连接有效性
testOnReturnbooleanfalse归还时检测连接有效性
idleValidationIntervalMslong30000空闲连接定期校验间隔(ms)
idleTimeoutMslong120000空闲连接超时回收(ms)
leakDetectMslong60000连接泄漏检测阈值(ms)
重试
retryEnabledbooleantrue是否启用自动重试
maxRetriesint2最大重试次数
retryBackoffBaseMslong30指数退避基数(ms)
retryBackoffMaxMslong500指数退避上限(ms)
retryJitterEnabledbooleantrue是否在退避时间上加随机抖动
缓存行为
defaultTtlMslong0默认 TTL(ms),0 = 永不过期
ttlJitterMslong0TTL 随机抖动范围(ms),防雪崩
keyPrefixString""Key 全局前缀,用于命名空间隔离
codecString"json"序列化器名称:json / string / bytes 或自定义
metricsEnabledbooleantrue是否启用命中率统计
nullCacheEnabledbooleantrue是否缓存 null 值(防缓存穿透)
nullCacheTtlMslong30000null 占位符 TTL(ms)
singleFlightEnabledbooleantrue是否启用 Single-Flight(同 key 并发回源合并)
keyMutexEnabledbooleantrue是否启用 key 级本地互斥锁
keyMutexMaxSizeint10000key 互斥锁表最大大小
限流与降级
rateLimitQpsint0Redis 操作 QPS 限制(0=不限流)
degradePolicyVKCacheDegradePolicyFAIL_FAST限流触发时的降级策略
内存 Provider
maxEntriesint0内存缓存最大条目数(0=不限)
evictionPolicyVKEvictionPolicyNONE淘汰策略:LRU / LFU / FIFO / NONE
memoryEvictionIntervalMslong5000后台驱逐线程扫描周期(ms)
分层缓存
l1ConfigVKCacheConfigTIERED 模式 L1(内存)配置
l2ConfigVKCacheConfigTIERED 模式 L2(Redis 等)配置
其他
bloomFilterVKBloomFilternoOp()布隆过滤器,用于 get/getOrLoad 的前置过滤
eventListenerVKCacheEventListener缓存事件监听器(函数式接口)

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>)Tkey 级别本地互斥锁
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注册自定义编解码器