分布式锁
1. 分布式锁要解决的问题
随着业务的发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程,多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的javaAPI并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源访问,这就是分布式锁要解决的问题
2. 解决方案
- 基于数据库实现分布式锁
- 基于缓存(redis等)
- 基于zookeeper
本章我们基于性能最高redis来学习
3. 使用redis实现分布式锁
- 使用setnx上锁,通过del可以释放锁
- 锁一直没有释放,可以设置key的过期时间,自动释放
- 如果在上锁的时候,服务器突然断电,就设置不了过期时间了。其实我们可以边上锁边设置过期时间
set user 10 nx ex 20
nx 表示上锁
ex 表示设置过期时间 20
UUID防止误删锁
问题的出现
解决的思路
我们给锁一个标识(UUID),当Redis需要删除锁时,需要先和uuid对比,相同才能删
java代码实现
package com.google.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("/redisTest")
public void testLock() {
//获取uuid作为锁的标识符
String uuid = UUID.randomUUID().toString();
//1.获取锁,setnx
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
if (lock) {
//2.如果获取到了锁,将num加1
Object value = redisTemplate.opsForValue().get("num");
if(value.equals(null)){
return;
}
int num = Integer.parseInt(value + "");
//3.将num值加1
redisTemplate.opsForValue().set("num", ++num);
String lockUuid = (String)redisTemplate.opsForValue().get("lock");
if(lockUuid.equals(uuid)){ //如果锁的标识相同,则释放锁
//4.释放锁
redisTemplate.delete("lock");
}
System.out.println("不为空");
} else {
try {
//如果没有获取到锁,就等0.1秒再尝试获取锁
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
lua脚本保证锁的原子性
锁的实现需要同时满足一下四个条件:
- 互斥性。在任意时刻,只有一个客户能持有锁
- 不会发生死锁,即使有一个客户端在持有锁的期间崩溃,而没有主动释放锁,也能保证后续其他客户能加锁。
- 加锁和解锁必须是同一个客户端,客户端自己不能把别人的锁给解了
- 加锁解锁必须具有原子性


浙公网安备 33010602011771号