分布式锁
1. 分布式锁作用
解决缓存击穿问题
2. 分布式锁思想
加锁:就是去存储一个数据,如果一个线程可以把数据存储成功,就说明当前线程获取到了锁;存储不成功,就说明当前线程没有获取到锁。
解锁:删除数据
3. 常用技术
mysql,redis,zookeeper是常用的分布式锁技术
加锁对性能有影响,最常用redis,读写效率高,对性能影响最小
4. 使用redis作为分布式锁的思想
获取锁的时候向Redis中插入数据,释放锁的时候从redis中删除数据
使用的命令:setnx
5. 代码示例
// 加锁
redisTemplate.opsForValue().setIfAbsent("lock", "1");
//解锁
redisTemplate.delete("lock");
6. 使用分布式锁,需要考虑的问题
问题一:执行业务的过程中产生异常,锁没有释放
解决方案:将释放锁的代码放到finally代码块中
问题二:释放锁前服务器宕机导致锁没有释放
解决方案:给锁设置过期时间,锁过期后会自动从redis中删除
代码示例:redisTemplate.expire("lock", 60, TimeUnit.SECONDS);
问题三:在给锁设置过期时间之前服务器宕机了
解决方案:保证加锁和设置过期时间的原子性操作
代码示例:redisTemplate.opsForValue().setIfAbsent("lock", "1", 300, TimeUnit.MILLISECONDS)
问题四:高并发情况下,释放别的线程的线程锁
解决方案:每一个线程在加锁的时候生成一个唯一的标识,把这个唯一的标识作为锁的值存储到redis中,在解锁的时候判断这个线程的标识和redis中存储的值是否一致,如果一致进行解锁操作。
问题五:判断锁之后,释放锁之前,锁过期了,导致释放了别的线程的锁
解决方案:保证判断和释放锁的原子性,使用lua脚本
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
// 使用lua脚本保证释放锁的原子性
String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end\n" ;
// 执行lua脚本
Long result = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Arrays.asList("lock"), uuid);
if(result == 0) {
log.error("锁是别人的,删除锁失败...");
}else {
log.info("锁是自己的,删除锁成功...");
}
问题六:业务执行时长大于锁的过期时间,导致锁失效
解决方案:开启一个守护线程或创建定时任务,每隔一段时间将锁续期到初始过期时间(看门狗机制)
开启守护线程代码示例:
Thread thread = new Thread(() -> {
while(true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
redisTemplate.expire("lock", 30, TimeUnit.SECONDS)
}
});
thread.setDaemon(true); //设置该线程为守护线程
thread.start();
作者:摆烂ing
出处:http://www.cnblogs.com/insilently/
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必追究法律责任
出处:http://www.cnblogs.com/insilently/
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必追究法律责任

浙公网安备 33010602011771号