大家好,又见面了,我是你们的朋友全栈君。
要介绍 什么是分布式锁,那首先要提到 与之对应的 的两个锁:线程锁 和 进程锁
主要 用来 给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有一个线程可以执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码。但是,其余线程是可以访问对象中没有被加锁的代码。线程锁只在同一个JVM 中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如 Synchrogazeed、Lock 等。
为了控制同一个操作系统中多个进程访问同一个共享资源,知识因为程序的独立性,各个进程是无法控制其他进程对资源的访问的,但是可以使用本地系统的信号量控制。
当多个进程不在同一个系统中时,使用分布式锁控制多个进程对资源的访问。
实现分布式锁的方法有很多,比如:使用redis、ZK 等
本文就以 redis 为例,一步步来实现 一把分布式锁。
建一个 springboot 项目,写一个前端控制器,控制器中 就是 简单模拟一个 减库存的逻辑,库存 放在 redis中。每次请求 都减一次库存。
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@GetMapping("/cut")
public Object kc(){
redisTemplate.setKeySerializer(new StringRedisSerializer());
int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
if (num>0){
int lastNum = num -1;
redisTemplate.opsForValue().set("num",lastNum+"");
System.out.println("扣减库存成功,剩余库存为:"+lastNum);
}else{
System.out.println("扣减库存失败,库存不足");
}
return "ok";
}
这里 我们先不使用 锁,看看 请求过来,是否会产生问题
就先启动一个 实例,接下来进行测试
貌似 很和谐呀? 库存也 正常减少,每次访问,就减少一个,很不错。
是不是开始怀疑了,为什么要用锁呢?还 搞什么 线程锁 进程锁 分布式锁,这不用锁不也正常减库存,不也没问题?
你是不是这样想的的呢?
咱们继续往下走 这才刚刚开始呢。。。
按照我们的预想,每次请求 就减少一个库存,库存 总共 1000,发送了 1000 个请求,理论上 库存 应该为 0 啊,为什么剩余库存 还有 518 呢? 再看上图,好多都是重复,一条记录代表一次请求,那么多次请求 打印出来的剩余库存 都是 剩余 999,这就是问题
我们简单分析一下,看图 -请求1进来,获取到 库存 为:1000,然后往下执行,进行本地扣减库存,当1号 本地扣减号库存后,准备将扣减完的剩余库存(999)保存到redis中时,此时还没来得及保存,2 号 就进来了 获取 库存(1000)虽然1号本地减了库存,但是它没有保存同步到redis中,所以redis中的 num 值 还是为 1000,此时 2 号进来 获取到的库存 就为 1000,依次类推,在 1 号 2 号 还没有将剩余库存同步到redis 中时,3号 4 号 5号 也进来了,获取库存值,还是为 1000,这时 大家都拿着 num = 1000 往下执行,导致 多次请求库存也只减少了1,我们就看到 那么多打印 相同 剩余库存。
– 那之前 测试1 为什么 就没问题呢?
@GetMapping("/cut")
public Object kc() {
redisTemplate.setKeySerializer(new StringRedisSerializer());
synchronized (this) {
int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
if (num > 0) {
int lastNum = num - 1;
redisTemplate.opsForValue().set("num", lastNum + "");
System.out.println("扣减库存成功,剩余库存为:" + lastNum);
} else {
System.out.println("扣减库存失败,库存不足");
}
}
return "ok";
}
开头
结尾
一千条打印,太长了,我就 不全部贴出,就贴出 一头一尾,从头尾就可以很明显看出 与之前没有加锁的区别了吧。 每次请求减库存1,共请求了1000 次 刚好 剩余库存为:0 一次 不信,认为 巧合,可以多次试验一下,结果都是一样的(非分布式,仅此单体)
这下知道 锁 的作用了吧 hahahahah。。。。
要是 单体 架构,就一个项目 部署一个 Tomcat 那是没啥问题(其他性能什么的锁优化什么的这里忽略哈)
那 放在 分布式架构 多个 Tomcat 提供相同服务,那你这把 锁还能锁住吗? 开干,带你来测试一下就知道了
还是 上面的 项目,但我们启动两个 服务,同样 进行库存扣减
这里 使用到了 NGINX 做负载均衡,NGINX 的使用,下期给大家讲哟。
我这里测试环境 是 VM 虚拟机,centos7 ,在docker 中装了 一个 Nginx 为了方便开发测试。
其实 你用 本地 下载一个 NGINX ,然后开启动两次项目,提供两个服务,在NGINX上配置一下也是可以的。
两个服务 出现了相同的数据,剩余库存都是:63,说明说明呢?
说明,库存 64 同时 被 两个服务获取到,这个就像前面我们讲的,多个线程同时 读到相同的库存数1000 一样,上一个线程扣减库存还没保存,就被下一个库存进来读取错误的 库存数,而 分布式呢,就是服务1 读到 库存为 64 然后进行执行本地扣减,在准备将扣减后的库存保存到 redis中时,原来这个另一个服务 就进行读取 库存操作,结果也读到 64,两个服务各自执行,结果出现两条 相同的数据“ 扣减库存成功,剩余库存为:63”。
那 有小伙伴就会问了,这不是 加锁了吗?怎么还会出错?
我们这里 使用 锁,是线程锁,只对 同一个jvm 有效,而分布式 多服务里,会有多个服务 部署在不同的 Tomcat 或服务器 上,所以 这个 synchronized 锁 不住别人的。
**
**
注释:内容 纯属 个人 见解 学习记录。如有 错误 望 大神 指教。。。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/152019.html原文链接:https://javaforall.cn