01.分布式锁
1.1 为什么使用分布式锁
一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题.
1.2 基于zookeeper的分布式锁原理
一个分布式锁对应ZooKeeper的一个节点,每个需要获取这个分布式锁的客户端线程在这个节点下创建一个临时有序节点,此时有两种情况:
-
创建的临时顺序节点是文件夹下的第一个节点,则认为是获取分布式锁成功。
-
创建的临时顺序节点不是文件夹下的第一个节点,则认为当前锁已经被另一个客户端线程获取,此时需要进入阻塞状态,等待节点顺序中的前一个节点释放锁的时候唤醒当前线程。
02 应用场景实战:基于ZK的分布式锁
2.1 核心类
2.2 实战场景
这里我们模拟修改用户积分(score)的操作,如果多个线程同时修改一个用户的积分就会有并发问题,因此需要加入锁来进行控制。
【详见1.1】
2.3 代码实现
- 创建package: com.itheima.study.zk.lock
- 创建UserDao,提供getScoreFromDb和updateScore两个方法
- 创建DistributedLockDemo类以及main方法
- 完善main方法
- 实例化zookeeper客户端CuratorFramework
- 实例化分布式锁InterProcessLock
- 实例化UserDao
- 并发执行100遍修改用户积分(score)操作
- 输出用户最终的积分(score)
- 执行查看结果
- 去除代码中锁的逻辑再次执行并查看结果
- com.itheima.study.zk.lock.UserService
/**
* 用户DAO层,模拟读写数据库
*/
public class UserDao {
private int score = 0;
/**
* 模拟从数据库获取用户积分
* @return
*/
public int getScoreFromDb() {
//模拟网络请求耗时1毫秒
try {
Thread.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查徐数据
return score;
}
/**
* 模拟更新数据库里的用户积分
* @param score
*/
public void updateScore(int score) {
//模拟网络请求耗时1毫秒
try {
Thread.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.score = score;
}
}
- com.itheima.study.zk.lock.DistributedLockDemo
/**
* curator实现的分布式锁使用实例
*/
public class DistributedLockDemo {
public static void main(String[] args) throws InterruptedException {
//构造超时重试策略,每1s重试一次,重试10次
RetryPolicy retryPolicy = new RetryNTimes(10,1000);
//构造客户端
CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181",retryPolicy);
// 启动客户端
client.start();
//构造锁
InterProcessLock lock = new InterProcessMutex(client,"/user/1/update");
//构造userDao
UserDao userDao = new UserDao();
//并发修改100遍
for (int i = 0; i < 100; i++) {
new Thread((new Runnable() {
@Override
public void run() {
try {
//lock.acquire();
// 查询当前积分
int score = userDao.getScoreFromDb();
// 积分加1
score++;
//更新数据库
userDao.updateScore(score);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
})).start();
}
//随眠5s等待任务执行完成
Thread.sleep(5000L);
//输出最终的结果
System.out.println("完成,结果:"+userDao.getScoreFromDb());
//关闭client
client.close();
}
}
- 输出结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yy1ZyM9H-1600261160235)(img/1573978931011.png)]
- 注释掉代码中获取锁和释放锁的逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTeIp2IG-1600261160237)(img/1573978812698.png)]
- 再次执行,执行的结果就不是100了,而且每次都不同,这就是并发问题导致的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TU2s28Na-1600261160239)(img/1574044589454.png)]