1.什么是分布式锁
系统部署在不同的机器上就是分布式(运行在不同的jvm中),在分布式环境中,对不同的应用程序共享资源加锁就是分布式锁
2.为什么会有分布式锁
传统的锁机制,如lock锁,其底层原理是通过jdk的一些指令进行加锁操作,而分布式环境中,应用运行在不用的jvm中,因此传统的同步锁机制已经不能够满足
3.什么时候分布式锁,什么时候分布式事务
应用之间的共享资源发生竞争时,需要使用分布式锁,而数据库中需要保持数据一致性的时候,则需要使用分布式事务
4.常用的分布式锁有哪些
数据库,乐观锁;redis分布式锁;zookeeper分布式锁
开始实战
1.创建一个maven项目,引入依赖
2.创建jedispoll的配置类
private static JedisPool pool;//jedis连接池
private static int maxLink = 20;//最大连接数
private static int minDleLink = 10;//最小空闲连接数
private static int maxDleLink = 20;//最大空闲连接数
private static boolean testOnBorrow = true;//测试连接可用性
static {
//初始化连接池
initPool();
}
public static Jedis getJedis() {
return pool.getResource();
}
public static void close(Jedis jedis) {
jedis.close();
}
private static void initPool() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxLink);
config.setMaxIdle(maxDleLink);
config.setMinIdle(minDleLink);
config.setTestOnBorrow(testOnBorrow);
config.setBlockWhenExhausted(true);
pool = new JedisPool(config, "10.10.10.10", 6379, 5000, null);
}
3.创建锁的工具类,方便锁的操作(注意,jedis依赖需要 3.1版本以下,set方法支持多参数传入)
public class DistributedLockUtil {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 1. 通过setnx()方法尝试加锁,如果当前锁不存在,返回加锁成功。
* 2. 如果锁已经存在则获取锁的过期时间,和当前时间比较,如果锁已经过期,则设置新的过期时间,返回加锁成功
*
* @param lockName
* @return 存在问题
* 1. 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。
* 2. 当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。
* 3. 锁不具备拥有者标识,即任何客户端都可以解锁
*/
public static boolean errorLock(String lockName) {
System.out.println(Thread.currentThread() + "开始尝试加锁");
//设置key获取锁
Long result = RedisPoolUtil.setnx(lockName, String.valueOf(System.currentTimeMillis() + 5000));
//判断加锁是否成功
if (result != null && result.intValue() == 1) {
System.out.println("线程" + Thread.currentThread() + "加锁成功");
//给锁一个过期时间,避免死锁
RedisPoolUtil.expire(lockName, 5);
//释放锁
RedisPoolUtil.del(lockName);
return true;
} else {
//取锁失败()
String lockValueA = RedisPoolUtil.get(lockName);
//比较是否过期
if (lockValueA != null && Long.parseLong(lockValueA) >= System.currentTimeMillis()) {
String lockValueB = RedisPoolUtil.getSet(lockName, String.valueOf(System.currentTimeMillis() + 5000));
if (lockValueB == null || lockValueB.equals(lockValueA)) {
System.out.println(Thread.currentThread() + "加锁成功!");
RedisPoolUtil.expire(lockName, 5);
RedisPoolUtil.del(lockName);
return true;
} else {
return false;
}
} else {
return false;
}
}
}
/**
* 1.set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性
* 2.对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁
* 3.value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端
*
* @return
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放锁
*
* @return
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//使用lua命令
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
System.out.println(Thread.currentThread() + "释放锁成功");
return true;
}
return false;
}
/**
* 不能区分锁的拥有者
*
* @param jedis
* @param lockKey
*/
public static void errorReleaseLock(Jedis jedis, String lockKey) {
jedis.del(lockKey);
}
}
4.编写业务测试类,模拟火车票购票业务
public class TrainTicketService {
@Autowired
private RedisPool redisPool;
private final String LOCK_KEY = "LOCK_KEY";
public void bugTicket() {
//获取锁
Jedis jedis = RedisPool.getJedis();
String requestId = UUID.randomUUID().toString();
System.out.println(Thread.currentThread() + "尝试加锁");
boolean result = DistributedLockUtil.tryGetDistributedLock(jedis, LOCK_KEY, requestId, 30);
if (!result) {
System.out.println(Thread.currentThread() + "获取锁失败");
try {
Thread.sleep(1);
bugTicket();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread() + "获取锁成功,开始业务代码");
//释放锁
DistributedLockUtil.releaseDistributedLock(jedis, LOCK_KEY, requestId);
}
public class TrainThread extends Thread {
@Autowired
private TrainTicketService service;
public TrainThread(TrainTicketService service) {
this.service = service;
}
@Override
public void run() {
service.bugTicket();
}
}
6.测试
TrainTicketService service = new TrainTicketService();
for (int i = 0; i < 20; i++) {
TrainThread thread = new TrainThread(service);
thread.start();
}
结果显示