外观
数据操作
本页示例放入已注入 StoreTemplate<String> store 的方法中,使用 java.time.Duration、java.time.Instant 和 java.util 下的集合类型。模板装配见快速开始。
逻辑键与有效期
通过 store.value()、map()、set()、list()、atomic() 选择数据类型,再传入非空、非纯空白的逻辑键,例如 lookup:region:cn。同名键在五种类型中独立,删除 value() 的键不影响 map() 的同名键。值和集合元素应为非 null,Map 字段名也不能空白。
五类数据接口都支持:
| 操作 | 行为 |
|---|---|
exists(key) | 当前类型空间中是否存在该键 |
delete(key) | 删除整个键;实际删除返回 true,不存在返回 false |
expire(key, ttl) | 为已有键设置相对有效期;不存在返回 false |
expireAt(key, deadline) | 设置 Instant 截止时间;时间已到则删除键 |
低层接口的 TTL 必须为正 Duration,不能传 null、零或负数。Map、Set、List 的 TTL 作用于整个集合,不能单独设置某个字段或元素的过期时间。先写入再 expire 才能为新键设定有效期;这两个调用不构成一个原子操作。
java
store.map().put("lookup:region", "cn", "中国");
store.map().expire("lookup:region", Duration.ofMinutes(10));
store.map().expireAt("lookup:region", Instant.now().plusSeconds(300));普通读取不续期。集合内部添加、覆盖或删除元素不会主动刷新已有 TTL;集合键删除或过期后重新创建时,需要重新设置 TTL。
单值读写与条件删除
java
store.value().set("lookup:message", "ready", Duration.ofMinutes(5));
String value = store.value().get("lookup:message");
boolean removed = store.value().compareAndDelete("lookup:message", "ready");
store.value().set("lookup:once", "payload");
String taken = store.value().getAndDelete("lookup:once");get 在不存在时返回 null;getAndDelete 原子读取并删除,适合一次性消费临时值,但不提供消费失败后的重试或确认机制。compareAndDelete 只在当前编码字节与期望值相同时删除,可避免读出旧值后误删其他请求刚写入的新值。
无 TTL 的 set(key, value) 覆盖已有值时会清除原有有效期;需要保留有限生命周期时使用带 TTL 的重载。自定义 Codec 的一致性要求见命名空间与编码协议。
Map:按字段读取
java
store.map().putAll("lookup:labels", Map.of("on", "启用", "off", "停用"));
store.map().put("lookup:labels", "pending", "处理中");
String label = store.map().get("lookup:labels", "on");
Map<String, String> labels = store.map().get("lookup:labels");
boolean removed = store.map().remove("lookup:labels", "pending");putAll 合并并覆盖同名字段,不替换整个 Map。contains(key, field) 查询字段是否存在,fields 返回字段集合,values 返回值集合,size 返回字段数。键或字段不存在时,单字段读取返回 null、移除返回 false;整表读取返回空 Map,大小为零。
Set:去重与成员判断
java
boolean first = store.set().add("lookup:features", "export");
long added = store.set().addAll("lookup:features", List.of("export", "preview"));
boolean enabled = store.set().contains("lookup:features", "preview");
Set<String> features = store.set().members("lookup:features");
long removed = store.set().removeAll("lookup:features", List.of("export", "preview"));add 返回是否新增,addAll 返回实际新增数量;同一个编码值不会重复加入。remove 返回是否移除,removeAll 返回实际移除数量。不存在时 members 返回空 Set,size 为零,成员判断和单值移除为 false。
List:顺序读取与两端操作
java
store.list().delete("lookup:steps");
store.list().addAllLast("lookup:steps", List.of("validate", "save"));
store.list().addAllFirst("lookup:steps", List.of("receive", "parse"));
List<String> firstTwo = store.list().range("lookup:steps", 0, 2);
// firstTwo 为 [receive, parse]
String first = store.list().pollFirst("lookup:steps");
store.list().set("lookup:steps", 0, "parsed");两种批量插入都保留输入集合的迭代顺序,addAllFirst([a, b]) 在头部得到 a, b,不是逐项压栈后的逆序。addFirst、addLast 添加单个元素;pollFirst、pollLast 移除并返回两端元素,空列表返回 null。这些是非阻塞操作,不包含可靠队列的确认或重投递机制。
get(key) 返回整表,get(key, index) 按零起始下标读取。range 使用左闭右开区间,要求 0 <= from <= to <= size,越界抛出 IndexOutOfBoundsException,不会自动截断;缺失键仅 [0, 0) 合法。Redis 实现先检查长度再读区间,并发修改时不能视为稳定快照。
remove(key, index) 删除并返回指定元素;remove(key, value) 只删除首个编码匹配的元素并返回 boolean。对于 StoreTemplate<Integer>,按值删除时传 Integer.valueOf(...),避免整数实参被解释为下标。contains 判断值是否存在,size 返回长度。
集合读取的共同边界
返回的 Map、Set、List 和字段/值集合均为不可变结果,不是可写回的存储视图;修改元素应调用对应 Store 方法。Map 与 Set 不承诺返回顺序,整集合读取会一次性加载结果,应控制键内数据规模。
本地实现删除最后一个元素后可能保留空集合键,Redis 集合移除最后一个元素后通常不再保留该键。因此不要依赖空集合时的 exists 或 delete 返回值来表达业务状态,应使用内容或 size 判断。需要彻底清理时显式 delete。
本地集合容量限制见本地后端。多次 API 调用不形成事务;即使单次操作内部有同步,也不能把“先查询再写入”的组合当成原子流程,需要时使用作用域锁。
原子计数
java
store.atomic().delete("lookup:revision");
long before = store.atomic().getAndIncrement("lookup:revision"); // 0
long after = store.atomic().incrementAndGet("lookup:revision"); // 2
boolean changed = store.atomic().compareAndSet(
"lookup:revision", 2L, 10L, Duration.ofMinutes(5));
long current = store.atomic().get("lookup:revision"); // 10Atomic 保存 long,不使用模板的值 Codec。缺失键的 get 返回零,首次递增从零开始;compareAndSet 将缺失值按零比较,仅比较成功时更新并刷新 TTL。不应依赖比较失败后缺失键是否被创建:本地实现可能留下值为零的键。
getAndIncrement(key, ttl) 和 incrementAndGet(key, ttl) 每次递增都刷新有效期,适合“最后一次操作后过期”,不是固定窗口计数。计数更新和 TTL 设置分两步执行,无法保证二者同时成功;当前递增重载在更新后才校验/设置 TTL,非法 TTL 或设置失败也可能已经改变计数。调用前应保证有效期合法,不能将此重载作为带过期的全有或全无事务。
键扫描
小规模检查使用 keys,返回逻辑键集合:
java
Set<String> sample = store.value().keys("lookup:*", 100);两端都支持 Redis 风格 glob,例如 *、?、[ab] 和转义字符。keys(pattern) 默认请求 1000 条;实际结果受 own.store.scan-max-results 和显式 limit 限制,顺序不保证。Redis 使用扫描接口,不直接执行全库 KEYS。
需要遍历全部匹配键时使用惰性迭代,不受上述结果上限限制:
java
for (String key : store.value().scanKeys("lookup:*")) {
System.out.println(key);
}
for (var item : store.scanKeys("lookup:*")) {
System.out.println(item.type() + ":" + item.key());
}无参数 scanKeys() 等价于匹配 *。数据类型接口只返回自己的键;模板级扫描返回带 type() 和 key() 的 StoreKey,覆盖五类数据,不包括锁和限流器。同名逻辑键可能因类型不同出现多次。
扫描不是一致性快照:并发增删、过期及 Redis 扫描过程可能影响结果,也不承诺全程无重复。遍历时应流式处理、让处理逻辑可重复执行,不把扫描数量当成精确统计,也不将全量结果直接收集进内存。