You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring/Jedis环境下如何配置避免Redis写入键时读取失败,返回旧数据?

问题分析与解决方案

首先,你遇到的问题不是Jedis或Spring连接配置的问题,根源在于Spring Data Redis对@RedisHash实体的默认save操作不具备原子性:当调用repository.save()时,底层会先执行DEL DataBean:{id}删除整个Hash键,再执行HSET DataBean:{id} data {value}写入新字段。这两个命令之间存在微小的时间窗口,此时如果有读取操作,就会发现键不存在,导致日志中的“Not found”错误。

因为你可以接受读取旧数据,我们可以从保证操作原子性入手,提供几个简洁的解决方案:


方案1:改用String类型存储(最推荐,最简洁)

你的DataBean只有一个字段,完全没必要用Redis Hash结构。改用String类型存储的话,SET命令本身是原子的,写入时读取会直接返回旧值,完美符合你的需求。

修改步骤:

  1. 调整实体类(或直接用String作为值):
public class DataBean {
    private String data;

    public DataBean(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }
}
  1. 自定义Repository的String类型操作方法:
@Repository
public interface DataBeanRepository extends CrudRepository<DataBean, String> {
    @Modifying
    @Query("SET DataBean:#id :#{#entity.data}")
    void saveAsString(DataBean entity);

    @Query("GET DataBean:#id")
    String findAsStringById(String id);
}
  1. 生产者线程调用saveAsString,消费者线程调用findAsStringById即可。

方案2:自定义原子更新Hash的方法(保留Hash结构)

如果必须使用Hash结构,不要用默认的save方法,而是自定义一个直接更新Hash字段的方法,跳过删除整个键的步骤。HSET命令是原子的,不会删除键,读取时始终能拿到旧值或新值。

修改Repository:

@Repository
public interface DataBeanRepository extends CrudRepository<DataBean, String> {
    @Modifying
    @Query("HSET DataBean:#id data :#{#entity.data}")
    void update(DataBean entity);
}

修改生产者线程:

把repository.save(new DataBean(data))替换为repository.update(new DataBean(data)),这样就不会删除整个Hash键,而是直接更新字段,彻底消除键不存在的时间窗口。


为什么没有“连接配置”能解决这个问题?

Redis本身的读写是单线程串行执行的,但你的问题是客户端执行了两个独立的非原子命令(DEL + HSET),中间可能被读取命令插入。Jedis或Spring的连接配置(比如连接池、超时等)无法改变Redis命令的执行逻辑,所以必须从操作本身的原子性入手解决,这才是最根本的方案。

内容的提问来源于stack exchange,提问作者edtrcd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 09:43:00