RedisTemplate 的泛型描述 Java 侧类型,Redis 实际收到的仍是序列化后的字节。key 匹配不到、Integer 读不回来和计数失败,应该先查编码契约,而不是只看模板名称。

两个模板,不是天然两座独立仓库

StringRedisTemplate 使用字符串序列化;RedisTemplate 可以分别配置 key、value 和 hash 的序列化器。Spring Data Redis 模板说明 解释了这层转换。

相同 Java 字符串经不同序列化器可能变成不同 key 字节;如果编码与数据契约相同,两种模板也可以互操作。“用这个模板写入、另一个模板看不到”是线索,不是二者必然不互通的规则。

一个星号能匹配多长,不由模板泛型决定

Redis glob 中 * 匹配任意长度,? 匹配单个字符。对于字符串 key test1,test* 与 tes* 都应符合对应前缀模式;并不是剩几位就需要写几个星号。KEYS 模式文档

若两个模式结果不同,检查模式和 key 的实际序列化字节、库编号、实例、集群范围与编码,再判断是否真是服务端匹配规则。不要把二进制对象编码的 key 当作普通文本猜测。

KEYS 会遍历键空间,不应成为生产请求的常规模糊搜索。大集合遍历需要评估游标扫描或业务索引;SCAN 也不等于无成本、无重复或强一致快照。

存一个 Java Integer,与 Redis INCR 是两回事

对象模板 set(key, 3) 的结果可能是对象序列化数据,并非文本 "3"。get 后能转换成 Integer,不意味着服务端整数命令也能解释那份字节。

如果需要共享计数,可明确使用文本整数契约。以下方法片段假定 strings 是注入的 StringRedisTemplate:

public long incrementCounter(String key) {
    Long value = strings.opsForValue().increment(key);
    if (value == null) {
        throw new IllegalStateException("未取得即时计数结果");
    }
    return value;
}

public java.util.OptionalLong readCounter(String key) {
    String raw = strings.opsForValue().get(key);
    if (raw == null) {
        return java.util.OptionalLong.empty();
    }
    return java.util.OptionalLong.of(Long.parseLong(raw));
}

普通同步调用可取得结果;事务或管道中的即时返回行为要另外处理,不能直接套这个服务方法。

INCR 对不存在的 key 以零为初始值后递增,要求现有值是可解释的整数,范围受有符号 64 位整数约束。不要在 Java 中先读、加一、再写回来替代原子递增。

不存在、类型错与真正的零,要保留区别

上面的读取用 OptionalLong 表达不存在;不能把非法文本、对象字节或超范围数字都默默当成零。错误应暴露为可定位的数据契约问题。

若业务保存 Double 或金额,也要明确使用对象还是文本、精度与舍入方式;Double 不是精确十进制金额方案。改变序列化器不会自动迁移已有数据,应先在受控样本验证读写和兼容性,不能直接更改生产模板后期待旧键全部自动可读。