Spring数据redis并发问题
Spring data redis concurrency issue
在 spring-redis-data 中使用多个线程时我遇到了一个大问题,它很容易重现,我想我遗漏了一些微不足道的东西。
开门见山
如果我在执行保存操作时查询 CrudRepository,有时(高达 60%)在 Redis 上找不到记录。
环境
代码
尽管可以在上面的 link 中找到完整的代码,但这是主要组件:
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在redis中替换为spring数据redis至少包含9个操作。
第一个结论
因此,要替换redis中的值,它必须删除散列和索引,然后再次添加新散列和新索引,也许一个线程正在执行此操作,而其他线程尝试按索引查找值,但尚未添加该索引。
第二个结论
我认为spring data with data-redis 几乎不可能有这样的错误,所以我想知道我对data-redis 或redis 有什么不了解。由于 redis 具有并发性,我认为可能会发生一些不同的事情,但是对于提供的示例,它似乎是这样的...
提前谢谢大家
您有一个 MyEntity
个实例:
MyEntity myEntity = createEntity();
然后你启动了 10 个线程,所有线程都在更新那个对象 myEntity.set...
。
然后当您将其保存为 myEntityRepository.save(myEntity);
时,无法判断保存的是什么值,因为所有线程都在竞争插入自己的值。
当您调用 myEntityRepository.save
时,它可能会(再次)保存另一个线程写入 myEntity
的值。所以这个线程从来没有机会将它的值写入 repo,因此你找不到它!
我没说完 @RedisHash
所以我可能是错的,但我认为你每次想要保存记录时都需要创建一个新的实体对象。
另一个与您的代码无关的问题是无限线程创建(除非您不打算在生产中使用它)。
This ticket 提出了同样的问题。
The behavior was chosen deliberately to avoid lingering hash entries. Deleting the hash ensures a consistent state and avoids additional entries that should no longer be part of the hash.
Redis Repository operations are not atomic.
所以它不是原子。
并在工单中建议,解决方案将是 using 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);
}
参考文献:
Update entity in redis with spring-data-redis
在 spring-redis-data 中使用多个线程时我遇到了一个大问题,它很容易重现,我想我遗漏了一些微不足道的东西。
开门见山
如果我在执行保存操作时查询 CrudRepository,有时(高达 60%)在 Redis 上找不到记录。
环境
代码
尽管可以在上面的 link 中找到完整的代码,但这是主要组件:
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在redis中替换为spring数据redis至少包含9个操作。
第一个结论
因此,要替换redis中的值,它必须删除散列和索引,然后再次添加新散列和新索引,也许一个线程正在执行此操作,而其他线程尝试按索引查找值,但尚未添加该索引。
第二个结论
我认为spring data with data-redis 几乎不可能有这样的错误,所以我想知道我对data-redis 或redis 有什么不了解。由于 redis 具有并发性,我认为可能会发生一些不同的事情,但是对于提供的示例,它似乎是这样的...
提前谢谢大家
您有一个 MyEntity
个实例:
MyEntity myEntity = createEntity();
然后你启动了 10 个线程,所有线程都在更新那个对象 myEntity.set...
。
然后当您将其保存为 myEntityRepository.save(myEntity);
时,无法判断保存的是什么值,因为所有线程都在竞争插入自己的值。
当您调用 myEntityRepository.save
时,它可能会(再次)保存另一个线程写入 myEntity
的值。所以这个线程从来没有机会将它的值写入 repo,因此你找不到它!
我没说完 @RedisHash
所以我可能是错的,但我认为你每次想要保存记录时都需要创建一个新的实体对象。
另一个与您的代码无关的问题是无限线程创建(除非您不打算在生产中使用它)。
This ticket 提出了同样的问题。
The behavior was chosen deliberately to avoid lingering hash entries. Deleting the hash ensures a consistent state and avoids additional entries that should no longer be part of the hash.
Redis Repository operations are not atomic.
所以它不是原子。
并在工单中建议,解决方案将是 using 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);
}
参考文献:
Update entity in redis with spring-data-redis