Verrous distribués dans l’environnement de microservice Spring-boot

Supposons que nous soyons dans un environnement où une seule instance s’exécute en production. Nous voulons faire une mise à jour du compte et synchroniser la transaction où nous faisons la mise à jour. Cela peut facilement être réalisé à l’aide des API Reentrant Locks de Java, comme indiqué ci-dessous.
@Service
@RequiredArgsConstructor
@Slf4j
public class AccountService {
private final AccountRepository accountRepository;
private ReentrantLock lock = new ReentrantLock();
public void updateAccount(Long id) throws InterruptedException {
boolean lockAquired = lock.tryLock();
if(lockAquired){
try{
log.info("lock taken");
Account account = accountRepository.findById(id).get();
account.setBalance(account.getBalance() + 100L);
Thread.sleep(20_000);
accountRepository.save(account);
}
finally {
lock.unlock();
}
}
}
}
Considérons maintenant le scénario dans lequel plusieurs instances de ce service s’exécutent en production, ce qui est assez courant dans l’environnement de microservice d’aujourd’hui. La méthode ci-dessus échoue car le verrouillage est applicable à chaque instance et nous souhaitons qu’un mécanisme quelconque verrouille quelque chose de commun à toutes les instances de ce microservice. Quoi de mieux qu’une table dans notre base de données commune à toutes les instances. En tenant compte de cela, Spring a fourni des verrous distribués. Voyons comment nous pouvons configurer nos verrous distribués dans Spring Boot.
Nous aurions besoin des dépendances ci-dessous dans notre pom.xml.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
Dans notre fichier de configuration, nous avons besoin des beans suivants pour être configurés
@Bean
public DefaultLockRepository DefaultLockRepository(DataSource dataSource){
return new DefaultLockRepository(dataSource);
}
@Bean
public JdbcLockRegistry jdbcLockRegistry(LockRepository lockRepository){
return new JdbcLockRegistry(lockRepository);
}
Now, coming back to our service, we need to inject first LockRegistry
private final LockRegistry lockRegistry;
private final JdbcTemplate jdbcTemplate;
public void updateAccountViaDistributedLocks(Long id) throws InterruptedException {
var lock = lockRegistry.obtain(String.valueOf(id));
boolean lockAquired = lock.tryLock();
if(lockAquired){
try{
log.info("lock taken");
Account account = accountRepository.findById(id).get();
account.setBalance(account.getBalance() + 100L);
Thread.sleep(20_000);
accountRepository.save(account);
}
finally {
lock.unlock();
}
}
}
Nous avons d’abord obtenu le verrou sur id, et c’est un verrou distribué qui est pris sur la table INT_LOCK.
Nous devons créer une table dans la base de données sql avec le script suivant.
CREATE TABLE `INT_LOCK` (
`LOCK_KEY` char(36) NOT NULL,
`REGION` varchar(100) NOT NULL,
`CLIENT_ID` char(36) DEFAULT NULL,
`CREATED_DATE` timestamp NOT NULL,
PRIMARY KEY (`LOCK_KEY`,`REGION`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Créons maintenant l’API à des fins de démonstration. Nous avons créé deux apis l’une pour mettre à jour le compte via des verrous simples et l’autre avec des verrous distribués
@PostMapping("/updateAccount/{id}")
public void updateAccount(@PathVariable("id") Long id) throws InterruptedException {
accountService.updateAccount(id);
}
@PostMapping("/updateAccountViaDistributedLocks/{id}")
public void updateAccountViaDistributedLocks(@PathVariable("id") Long id) throws InterruptedException {
accountService.updateAccountViaDistributedLocks(id);
}
Voyons notre application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
maxIdle: 1
url: jdbc:mysql://localhost:3306/distributed_locks
testWhileIdle: true
username: root
password: root1234
jpa:
database: mysql
generate-ddl: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL55Dialect
jdbc:
time_zone: UTC
hibernate:
ddl-auto: update
show-sql: true
server:
port: 0
Nous avons server.port comme 0 afin que nous puissions avoir plusieurs instances de notre service s’exécutant sur des ports aléatoires.
Essai
Exécutez les boucles ci-dessous qui utilisent des verrous simples.
curl --location --request POST 'localhost:57841/monolith/updateAccount/1'
curl --location --request POST 'localhost:57896/monolith/updateAccount/1'
Si nous vérifions les données dans notre table de compte, nous verrions que nos données ne seront mises à jour qu’une seule fois, même si nous avons atteint l’API deux fois. Notre base de données est dans un état que nous ne connaissons pas (état incohérent).
Maintenant, appuyez sur les deux boucles ci-dessous pour lesquelles vous utilisez des verrous distribués
curl --location --request POST 'localhost:57841/monolith/updateAccountViaDistributedLocks/1'
curl --location --request POST 'localhost:57896/monolith/updateAccountViaDistributedLocks/1'
Nous verrions que notre compte a été mis à jour deux fois correctement. La base de données sera dans un état cohérent.
C’est tout de mon côté. Veuillez trouver le lien de ce projet sur Github :
https://github.com/anilgola90/distributedlock
Ce blog est initialement publié sur Moyen.
TROUVÉ CELA UTILE ? PARTAGEZ-LE
Source link