Spring Data Redis 并发问题

7

我在使用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个线程在数据库中找不到结果。

使用Spring Data Redis进行替换

此处所述,使用Spring Data Redis进行Redis的替换至少包含9个操作。

第一个结论

因此,要在Redis中替换一个值,必须先删除哈希表和索引,然后再添加新哈希表和新索引,也许一个线程正在执行这些操作的中间阶段,而另一个线程尝试按索引查找该值,但该索引尚未被添加。

第二个结论

我认为Spring Data Redis不太可能有这样的bug,所以我想知道我没有理解Data Redis或Redis的哪些方面。由于Redis具有并发性,我认为可能会发生一些不同的事情,但根据提供的示例似乎不是这种情况...

提前感谢大家。

2个回答

2

这个问题 提出了相同的问题。

该行为是有意选择的,以避免挂起的哈希条目。删除哈希可确保一致的状态并避免不再是哈希的一部分的额外条目。
Redis存储库操作不是原子性的。

因此,它旨在不是 原子性的
如建议中所述,在票证中,解决方案将是使用PartialUpdate

以下是示例片段

    @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);
    }

参考资料:
使用spring-data-redis更新redis中的实体


0

你有一个 MyEntity 实例:

MyEntity myEntity = createEntity();

然后你启动了10个线程,它们都在更新那个对象myEntity.set...

然后当你像这样保存它myEntityRepository.save(myEntity);时,不可能知道正在保存什么值,因为所有线程都在竞争插入自己的值。

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

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

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


1
抱歉,我已经更新了我的代码以简化它。但结构和问题仍然存在。当存储库尝试保存实例时,它会检查@Id属性,以便知道是插入还是更新以及在这种情况下应更新哪一行。因此,是的,线程正在竞争更新值,但如果每个操作都是原子的,它应该始终能够找到数据。实际上,例如,线程7可以找到使用线程9进行的修改的数据,但它们应该始终能够找到数据。 - Héctor Berlanga
@HéctorBerlanga 请尝试同步您的run方法的内容,以使操作原子化。 - Kartik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接