redisベースの分散ロック(SETNXおよびRedisson)の実装(在庫の削減を事例とする)
5226 ワード
一:実現原理:
redisのsetコマンドを使用して分散ロックを実現します.
Redis 2.6.12以降、setでは次のパラメータを使用できます.
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
EX second:キーの有効期限をsecond秒に設定します.SET key value EX second効果はSETEX key second valueと同等です.PX millisecond:キーの有効期限をmillisecondミリ秒に設定します.SET key value PX millisecond効果はPSETEX key millisecond valueと同等です.NX:キーが存在しない場合のみ、キーの設定操作を行います.SET key value NX効果はSETNX key valueに等しい.XX:キーが既に存在する場合のみ、キーを設定します.
二:SETNX
Redisは単一プロセスの単一スレッドモードであり、キューモードを使用すると同時アクセスがシリアルアクセスになり、マルチクライアントによるRedisへの接続に競合はありません.SETNXコマンドを使用してsetnx name xiaowangなどの分散ロックを実現できます.
キーが存在しない場合にのみ、キーの値をvalueに設定します.与えられたキーが既に存在する場合、SETNXは何もしない
SETNXは『SET if Not eXists』(存在しなければSET)の略である.
戻り値:設定に成功し、1を返します.設定に失敗し、0を返します.
3:次に、redisを使用して分散ロックを実装する方法を例に挙げます.
これは古典的な在庫控除の例で、実現するたびに在庫が1つ減少します.
ここではspringboot統合redisを用いて実現し,springboot操作redistributionのテンプレートを注入した.もちろん分からなくても大丈夫です.私はすべての肝心なコードの後ろでjedisで説明しました.
例えばこれはkey、valueの操作を設定します.jedis.set(key,value);
1:次に、このコードを具体的に見てみましょう.まず、このコードに何か問題があるかどうかを見ることができますか.
コンカレントの問題であることがわかりますが、このコードを実行するスレッドが複数ある場合、多くのリクエストがあり、3人のユーザーが同時にこのコードにアクセスしている場合、50件の商品がある場合、同時に発生した場合、最後に戻る可能性があるのは49で、理屈では47で、コンカレントの問題が発生した場合、どのように解決しますか?
2:ソリューション:synchronizedロックを追加して問題を解決できますか?
単体アーキテクチャであればsynchronizedの追加は問題ありません.しかし、クラスタアーキテクチャでは、このwarパッケージを複数のtomcatの上に置くと、jdkはsynchronizedロックやlockロックなど、多くのロックを提供してくれます.例えば、synchronizedロックやlockロックはjvmプロセス上であり、1つのtomcatの上でしか役に立ちません.nginxを通じて複数のtomcatの上に配布されると、実際にはロックできません.
3:redisのSETNXで分散ロックを実現
多くのリクエストが送信されると、redisは単一スレッドアーキテクチャであるため、リクエストが実行されます.
質問1:stringRedisTemplate.delete(lockKey);//このロックを解除すると、このコードに異常が発生した場合、このロックは解除されず、他のスレッドが入ってくるとロックが取れず、デッドロック状態に陥ります.
解決方法:このコードに異常を加えて処理し、最後にこのロックを解放し、デッドロックに陥らないようにします.
質問2:まだロックが解除されていない場合、システムが突然ダウンし、finallyの後ろのコードが実行できなくなりました.どうすればいいですか.
解決策:このkeyに10秒の期限切れを設定することができ、時間が来たとき、redisは自動的にこのkeyを削除します.
問題3:超高同時シーンではkey'の有効期限を10秒に設定し、最初のスレッドが来て15秒実行されると、高同時の場合、最初のスレッドが2番目のスレッドのロックを解放し、ロックが無効になる可能性が高い.
解決策:各スレッドに重複しない文字列を生成し、ロックを解除するときに、このスレッド自体のロックかどうかを検証します.
問題4:もし私たちのコードが10秒実行されたら、このkeyはもう期限切れで、コードがまだ実行されていないのに、この時どうやって解決しますか?
解決策:私たちはこのkeyが期限切れになった後にこのkeyの生命の時間を歌って彼に命を続けることができて、私たちはredissonで実現することができます.
四:Redissonによる分散ロック
Redissonはredisのオープンソースフレームワークであり、このフレームワークは上記の問題をすべてカプセル化し、直接使用すればよい.
Redissonの下部は基本的に上記の問題であり、コードをより簡潔にするためにパッケージ化されています.
redisのsetコマンドを使用して分散ロックを実現します.
Redis 2.6.12以降、setでは次のパラメータを使用できます.
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
EX second:キーの有効期限をsecond秒に設定します.SET key value EX second効果はSETEX key second valueと同等です.PX millisecond:キーの有効期限をmillisecondミリ秒に設定します.SET key value PX millisecond効果はPSETEX key millisecond valueと同等です.NX:キーが存在しない場合のみ、キーの設定操作を行います.SET key value NX効果はSETNX key valueに等しい.XX:キーが既に存在する場合のみ、キーを設定します.
二:SETNX
Redisは単一プロセスの単一スレッドモードであり、キューモードを使用すると同時アクセスがシリアルアクセスになり、マルチクライアントによるRedisへの接続に競合はありません.SETNXコマンドを使用してsetnx name xiaowangなどの分散ロックを実現できます.
キーが存在しない場合にのみ、キーの値をvalueに設定します.与えられたキーが既に存在する場合、SETNXは何もしない
SETNXは『SET if Not eXists』(存在しなければSET)の略である.
戻り値:設定に成功し、1を返します.設定に失敗し、0を返します.
3:次に、redisを使用して分散ロックを実装する方法を例に挙げます.
@RestController
public class IndexController {
// private static final Logger LOGGER= LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public String deductStock(){
int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
if(stock>0){
int realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
}else{
System.out.println(" , ");
}
return "end";
}
}
これは古典的な在庫控除の例で、実現するたびに在庫が1つ減少します.
ここではspringboot統合redisを用いて実現し,springboot操作redistributionのテンプレートを注入した.もちろん分からなくても大丈夫です.私はすべての肝心なコードの後ろでjedisで説明しました.
例えばこれはkey、valueの操作を設定します.jedis.set(key,value);
stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
1:次に、このコードを具体的に見てみましょう.まず、このコードに何か問題があるかどうかを見ることができますか.
コンカレントの問題であることがわかりますが、このコードを実行するスレッドが複数ある場合、多くのリクエストがあり、3人のユーザーが同時にこのコードにアクセスしている場合、50件の商品がある場合、同時に発生した場合、最後に戻る可能性があるのは49で、理屈では47で、コンカレントの問題が発生した場合、どのように解決しますか?
2:ソリューション:synchronizedロックを追加して問題を解決できますか?
単体アーキテクチャであればsynchronizedの追加は問題ありません.しかし、クラスタアーキテクチャでは、このwarパッケージを複数のtomcatの上に置くと、jdkはsynchronizedロックやlockロックなど、多くのロックを提供してくれます.例えば、synchronizedロックやlockロックはjvmプロセス上であり、1つのtomcatの上でしか役に立ちません.nginxを通じて複数のtomcatの上に配布されると、実際にはロックできません.
3:redisのSETNXで分散ロックを実現
多くのリクエストが送信されると、redisは単一スレッドアーキテクチャであるため、リクエストが実行されます.
質問1:stringRedisTemplate.delete(lockKey);//このロックを解除すると、このコードに異常が発生した場合、このロックは解除されず、他のスレッドが入ってくるとロックが取れず、デッドロック状態に陥ります.
解決方法:このコードに異常を加えて処理し、最後にこのロックを解放し、デッドロックに陥らないようにします.
質問2:まだロックが解除されていない場合、システムが突然ダウンし、finallyの後ろのコードが実行できなくなりました.どうすればいいですか.
解決策:このkeyに10秒の期限切れを設定することができ、時間が来たとき、redisは自動的にこのkeyを削除します.
問題3:超高同時シーンではkey'の有効期限を10秒に設定し、最初のスレッドが来て15秒実行されると、高同時の場合、最初のスレッドが2番目のスレッドのロックを解放し、ロックが無効になる可能性が高い.
解決策:各スレッドに重複しない文字列を生成し、ロックを解除するときに、このスレッド自体のロックかどうかを検証します.
問題4:もし私たちのコードが10秒実行されたら、このkeyはもう期限切れで、コードがまだ実行されていないのに、この時どうやって解決しますか?
解決策:私たちはこのkeyが期限切れになった後にこのkeyの生命の時間を歌って彼に命を続けることができて、私たちはredissonで実現することができます.
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public String deductStock(){
String lockKey="lockKey";
String redisId= UUID.randomUUID().toString();
try { //
Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"lockkey",10, TimeUnit.SECONDS);//
if(!result){
return "err";
}
int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
if(stock>0){
int realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
}else{
System.out.println(" , ");
}
}finally {
if(stringRedisTemplate.opsForValue().get(lockKey).equals(redisId)){ //
stringRedisTemplate.delete(lockKey);//
}
}
return "end";
}
四:Redissonによる分散ロック
Redissonはredisのオープンソースフレームワークであり、このフレームワークは上記の問題をすべてカプセル化し、直接使用すればよい.
@RestController
public class IndexController {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public String deductStock(){
String lockKey="lockKey";
RLock redisId=redisson.getLock(lockKey);//
try { //
redisId.lock();//j
int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
if(stock>0){
int realStock=stock-1;
stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
}else{
System.out.println(" , ");
}
}finally {
redisId.unlock();
}
return "end";
}
}
Redissonの下部は基本的に上記の問題であり、コードをより簡潔にするためにパッケージ化されています.