Spring Boot - コントローラーへの同時アクセスを回避する方法
これは古典的なロックの問題です。一度に 1 つのクライアントのみがデータを操作できるようにする (相互排除) 悲観的ロックを使用するか、複数の同時クライアントがデータを操作できるようにするが、最初のコミッターのみが成功することを許可する楽観的ロックを使用できます。
使用しているテクノロジーに応じて、さまざまな方法があります。たとえば、これを解決する別の方法は、適切なデータベース分離レベルを使用することです。あなたの場合、少なくとも「反復可能な読み取り」分離レベルが必要なようです。
反復可能な読み取りにより、2 つの同時トランザクションがほぼ同時に同じレコードの読み取りと変更を行った場合、そのうちの 1 つだけが成功することが保証されます。
あなたの場合、適切な分離レベルで Spring トランザクションをマークできます。
@Transacational(isolation=REPEATABLE_READ)
public void toggleSwitch() {
String status = readSwithStatus();
if(status.equals("on") {
updateStatus("off");
} else {
updateStatus("on");
}
}
2 つの同時クライアントがスイッチ ステータスを更新しようとすると、最初にコミットしたクライアントが優先され、2 番目のクライアントは常に失敗します。 2 番目のクライアントに、同時障害のためにトランザクションが成功しなかったことを伝える準備をしておく必要があります。この 2 番目のトランザクションは自動的にロールバックされます。あなたまたはあなたのクライアントは、再試行するかどうかを決定できます。
@Autowire
LightService lightService;
@GET
public ResponseEntity<String> toggleLight(){
try {
lightService.toggleSwitch();
//send a 200 OK
}catch(OptimisticLockingFailureException e) {
//send a Http status 409 Conflict!
}
}
しかし、私が言ったように、使用しているもの (JPA、Hibernate、プレーンな JDBC など) に応じて、悲観的または楽観的なロック戦略でこれを行う方法は複数あります。
スレッドの同期だけではないのはなぜですか?
これまでに提案された他の回答は、単一の JVM がある場合に機能する同期ブロックを使用して、スレッド レベルで Java の相互排除を使用することによる悲観的ロックに関するものです。 コードを実行しています。コードを実行する JVM が複数ある場合、または最終的に水平方向にスケーリングしてロード バランサーの背後に JVM ノードを追加する場合、この戦略は効果がないことが判明する可能性があります。この場合、スレッド ロックでは問題が解決されなくなります。
ただし、プロセスがデータベース レコードを変更する前に強制的にロックし、データベース レベルで相互排除ゾーンを作成することにより、データベース レベルでペシミスティック ロックを実装することもできます。
したがって、ここで重要なことは、ロックの原則を理解してから、特定のシナリオと技術スタックに有効な戦略を見つけることです。ほとんどの場合、あなたの場合、ある時点でデータベース レベルでなんらかの形式のロックが必要になります。