【问题标题】:Spring data redis concurrency issueSpring数据redis并发问题
【发布时间】:2023-04-08 20:20:01
【问题描述】:

在 spring-redis-data 中使用多个线程时我遇到了一个大问题,而且它很容易重现,我认为我错过了一些琐碎的事情。

直入主题

如果我在执行保存操作时查询 CrudRepository,有时(高达 60%)在 Redis 上找不到记录。

环境

代码

尽管可以在上面的链接中找到完整的代码,但这是主要组件:

CrudRepository

​​>
@Repository
public interface MyEntityRepository extends CrudRepository<MyEntity, Integer> {

}

实体

@RedisHash("my-entity")
public class MyEntity implements Serializable {

    @Id
    private int id1;

    private double attribute1;
    private String attribute2;
    private String attribute3;

控制器

    @GetMapping( "/my-endpoint")
    public ResponseEntity<?> myEndpoint () {

        MyEntity myEntity = new MyEntity();
        myEntity.setAttribute1(0.7);
        myEntity.setAttribute2("attr2");
        myEntity.setAttribute3("attr3");
        myEntity.setId1(1);

        myEntityRepository.save(myEntity);//create it in redis

        logger.info("STARTED");

        for (int i = 0; i < 100; i++){
            new Thread(){
                @Override
                public void run() {
                    super.run();

                    myEntity.setAttribute1(Math.random());

                    myEntityRepository.save(myEntity); //updating the entity

                    Optional<MyEntity> optionalMyEntity = myEntityRepository.findById(1);
                    if (optionalMyEntity.isPresent()) {
                        logger.info("found");
                    }else{
                        logger.warning("NOT FOUND");
                    }
                }
            }.start();

        }

        return ResponseEntity.noContent().build();
    }

结果

2020-05-26 07:52:53.769  INFO 30655 --- [nio-8080-exec-2] my-controller-logger                     : STARTED
2020-05-26 07:52:53.795  INFO 30655 --- [     Thread-168] my-controller-logger                     : found
2020-05-26 07:52:53.798  WARN 30655 --- [     Thread-174] my-controller-logger                     : NOT FOUND
2020-05-26 07:52:53.798  WARN 30655 --- [     Thread-173] my-controller-logger                     : NOT FOUND
2020-05-26 07:52:53.806  INFO 30655 --- [     Thread-170] my-controller-logger                     : found
2020-05-26 07:52:53.806  WARN 30655 --- [     Thread-172] my-controller-logger                     : NOT FOUND
2020-05-26 07:52:53.812  WARN 30655 --- [     Thread-175] my-controller-logger                     : NOT FOUND
2020-05-26 07:52:53.814  WARN 30655 --- [     Thread-176] my-controller-logger                     : NOT FOUND
2020-05-26 07:52:53.819  WARN 30655 --- [     Thread-169] my-controller-logger                     : NOT FOUND
2020-05-26 07:52:53.826  INFO 30655 --- [     Thread-171] my-controller-logger                     : found
2020-05-26 07:52:53.829  INFO 30655 --- [     Thread-177] my-controller-logger                     : found

所以简单地用 10 个线程,其中 6 个在 db 中找不到结果。

用spring数据redis替换

here提到的,用spring数据替换redis中的redis至少包含9个操作。

第一个结论

所以,要替换redis中的值,它必须删除哈希,索引,然后再次添加新的哈希和新索引,可能一个线程正在执行此操作而其他线程尝试按索引查找值,该索引尚未添加。

第二个结论

我认为带有data-redis的spring数据几乎不可能有这样的错误,所以我想知道我对data-redis或redis的不了解。由于 redis 具有并发性,我认为可能会发生一些不同的事情,但是对于提供的示例,它似乎是这样的......

提前感谢大家

【问题讨论】:

    标签: java spring multithreading concurrency spring-data-redis


    【解决方案1】:

    This ticket 提出了同样的问题。

    故意选择该行为以避免挥之不去的哈希条目。删除散列可确保一致的状态并避免不应再成为散列一部分的其他条目。
    Redis 存储库操作不是原子的。

    所以它不是原子的
    并在工单中建议,解决方案将是using PartialUpdate

    下面是一个sn-p的例子

        @Autowired
        private RedisKeyValueTemplate redisKVTemplate;
        ...
        // id is the @Id value of the entity
        private void update(Integer id) {
            PartialUpdate update = new PartialUpdate<MyEntity>(id, MyEntity.class)
                    .set("attribute1", Math.random());
            redisKVTemplate.update(update);
        }
    

    参考:
    Update entity in redis with spring-data-redis

    【讨论】:

      【解决方案2】:

      您有一个MyEntity 实例:

      MyEntity myEntity = createEntity();
      

      那么你已经启动了 10 个线程,所有这些线程都在更新那个对象 myEntity.set...

      那么当你将它保存为myEntityRepository.save(myEntity); 时,无法判断保存的是什么值,因为所有线程都在竞争插入自己的值。

      当您调用myEntityRepository.save 时,它可能正在保存(再次)另一个线程写入myEntity 的值。所以这个线程永远没有机会将它的值写入 repo,因此你不会找到它!

      我不是@RedisHash,所以我可能错了,但我认为每次要保存记录时都需要创建一个新的实体对象。

      另一个与您的代码无关的问题是无限线程创建(除非您不打算在生产中使用它)。

      【讨论】:

      • 对不起,我已经更新了我的代码以简化它。但结构和问题保持不变。当存储库尝试保存实例时,它会检查 @Id 属性,以便知道是插入还是更新,以及在这种情况下应该更新哪一行。所以是的,线程正在竞争更新值,但如果每个操作都是原子的,它应该总是找到数据。事实上,线程 7 可以找到通过线程 9 完成的修改的数据,但它们应该总是找到数据。
      • @HéctorBerlanga 尝试同步 run 方法的内容以使操作原子化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多