Redis分散ロック
4995 ワード
前にプロジェクトを引き継いで、ピットを踏んだ経験を記録して、一緒に勉強します.(急いでいる学生は直接底のコードを持ってもいいです)
まず先輩の元のコードを見てみましょう:分かりやすくて、私はここで論理を簡略化しました:
コードは簡単ですが、2つの問題が隠されています.
1、ロックが固定されている、つまりredisに保存されている値が死んでいる.これにより、スレッドを持つロックが他のスレッドによって誤って削除され、新しいスレッドがロックを再取得することに成功する.
2、ネットワークの極端な条件の下で、業務コードの実行時間が10秒を超えた場合、つまりロックのタイムアウト時間であれば、ロックはredisによって消去され、新しいスレッドがロックを再取得することに成功する(ここでは、現在のスレッドをしばらく眠らせ、極端な条件をシミュレートする場合を強制することができる).
次は私が変更したコードです.
解決策はこうです.
1、各スレッドが取得するロックを設定するのは唯一(唯一のアルゴリズムについては、自分で発揮することができますが、ここではプレゼンテーションのみ)、こちらのclientIdです.ロックを削除するときに削除したロックを判断するのが、前に設定したロックです.
2、1つのデーモンスレッドを開いてロックのタイムアウト時間を判断し、ロックが生存し、タイムアウト時間が多くなければ、ロックのタイムアウト時間を再設定し、ロックがタイムアウトで期限切れにならないことを保証する.
総括して、このロジックは実は既成のオープンソースの実現があって、Redisson、コードの簡単な暴力、実は原理は第2部分で、興味のある学生はソースコードをめくることができます:
まず先輩の元のコードを見てみましょう:分かりやすくて、私はここで論理を簡略化しました:
public String redisLock() {
String lockKey = "ke";
String clintId = UUID.randomUUID().toString();
try {
// , true, , false, , redis setNx
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 10, TimeUnit.SECONDS);
if (!flag) {
return " ";
}
Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
// lock , ,
// try {
// Thread.sleep(11000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
if (stock > 0) {
stock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", stock.toString());
log.info(" , :{}", stock);
} else {
log.info(" !!!");
}
} catch (Exception e) {
log.info(" ", e);
} finally {
//
stringRedisTemplate.delete(lockKey);
}
return "end";
}
コードは簡単ですが、2つの問題が隠されています.
1、ロックが固定されている、つまりredisに保存されている値が死んでいる.これにより、スレッドを持つロックが他のスレッドによって誤って削除され、新しいスレッドがロックを再取得することに成功する.
2、ネットワークの極端な条件の下で、業務コードの実行時間が10秒を超えた場合、つまりロックのタイムアウト時間であれば、ロックはredisによって消去され、新しいスレッドがロックを再取得することに成功する(ここでは、現在のスレッドをしばらく眠らせ、極端な条件をシミュレートする場合を強制することができる).
次は私が変更したコードです.
//
public static final Integer EXPIRETIME = 10;
// , , redis
@Autowired
private DaemonThread daemonThread;
private static Thread thread;
@RequestMapping("lock")
public String redisLock() {
String lockKey = "ke";
String clintId = UUID.randomUUID().toString();
try {
Boolean flag =
stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clintId, EXPIRETIME, TimeUnit.SECONDS);
// , ,
if (thread != null) {
boolean isAlive = thread.isAlive();
if (!isAlive) {
thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
}
} else {
thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
}
if (!flag) {
return " ";
}
Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
// lock , ,
// try {
// Thread.sleep(11000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
if (stock > 0) {
stock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", stock.toString());
log.info(" , :{}", stock);
} else {
log.info(" !!!");
}
} catch (Exception e) {
log.info(" ", e);
} finally {
//
if (clintId.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
解決策はこうです.
1、各スレッドが取得するロックを設定するのは唯一(唯一のアルゴリズムについては、自分で発揮することができますが、ここではプレゼンテーションのみ)、こちらのclientIdです.ロックを削除するときに削除したロックを判断するのが、前に設定したロックです.
2、1つのデーモンスレッドを開いてロックのタイムアウト時間を判断し、ロックが生存し、タイムアウト時間が多くなければ、ロックのタイムアウト時間を再設定し、ロックがタイムアウトで期限切れにならないことを保証する.
総括して、このロジックは実は既成のオープンソースの実現があって、Redisson、コードの簡単な暴力、実は原理は第2部分で、興味のある学生はソースコードをめくることができます:
@Autowired
private RedissonClient redissonClient;
@RequestMapping("lock")
public String redisLock() {
String lockKey = "ke";
String clintId = UUID.randomUUID().toString();
RLock lock = redissonClient.getLock(lockKey);
try {
lock.tryLock(10, TimeUnit.SECONDS);
Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
stock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", stock.toString());
log.info(" , :{}", stock);
} else {
log.info(" !!!");
}
} catch (Exception e) {
log.info(" ", e);
} finally {
lock.unlock();
}
return "end";
}