此文章涉及的技术点
·SpringBoot
·Redis
·nginx https://blog.****.net/weixin_44012722/article/details/105466791
·docker (如果不会不影响此文章的学习) https://blog.****.net/weixin_44012722/article/details/105468637
先让我们来思考一个问题 当我是小白的时候老师让我写一个商品抢购的时候我的想法是
当一个请求过来 就直接在数据库库存减一 这时候就会发生并发问题 结果过程如下
pom文件的依赖我先说我用了什么
spring-boot-starter-web
spring-boot-starter-data-redis
commons-lang3
普通的抢购写法
Controller
Service
Service实现类
结果
让我们来看看结果 这里我使用一个jmeter的工具 它是用来测试软件并发的,我们可以用它来模拟同一时间访问抢购的场景
具体jmeter怎么下载使用 请看我另一篇博客介绍
接下来我们用它模拟抢购的场景
这时候会发现97出现多次,这是什么问题导致,就是并发问题所导致的,因为redis是单线程是线程安全,但是你的逻辑代码不是线程安全的,所以当
同一时间出现多个请求拿到相同的库存数据,减一再放入redis数据库,这时候就是出现多个相同的数据的重复覆盖 这就会造成一个商品多卖的问题
那我们怎么解决呢? 很简单加上一个synchronize关键字的锁 一次只能一个线程执行 这样就可以解决 这样的线程并发问题
synchronize关键字的抢购写法
Controller
Service
Service实现类
结果
再让我们用jmeter测试一下结果
这时候我们可以看到并发问题就没有出现了 因为我们用synchronize关键字锁住我们的逻辑代码 一次只能一个线程拿到对象锁
进入逻辑代码中执行 执行解释后归还钥匙到锁池中 让其他线程争夺
这时候问题又来了 我们解决了并发问题但是这样写代码 给分布式的项目埋下一个坑 理由是当我们发布单个springboot应用时不会发生并发问题,但
当我们发布分布式springboot应用时并发问题又会出现了 接下来我将演示分布式带来的并发问题
首先 我们要有一台安装Nginx的linux服务器(推荐阿里云学生服务器 每月 9 块钱 白给) nginx的了解和安装详细讲解在这 https://blog.****.net/weixin_44012722/article/details/105466791
可通过docker容器技术去安装nginx,如果对docker技术不了解 可以先看看我这个入入门 https://blog.****.net/weixin_44012722/article/details/105468637
如果以上两种都不具备条件的 我们采用安装nginx到window 这次我就使用这一种方式
到nginx官网下载 http://nginx.org/en/download.html 解压修改配置文件nginx.conf即可使用
下面是我的配置文件的添加修改的内容
在nginx的文件目录下的终端命令窗口输入nginx.exe打开nginx
然后我们把我们的springboot项目再复制一份到我们父工程中改application.yml中的服务器端口 同时启动两个springboot项目
接下来我们开始用jmeter压力测试一下
让我们看看两个springboot的控制台 很容易看到有重复的抢购,分布式下synchronize的并发问题就出现了 原因是synchronize关键字只能锁住本项目的逻辑代码 并
锁不住另一个项目的逻辑代码 这个还是很容易理解的 所以有了下面的解决方案
redis简单锁的抢购写法
redis简单锁的实现
这里try catch finally的原因是当拿到锁后面的逻辑代码出现错误时 如果没有trycatch就会发生死锁
而这里的给锁设置过期时间是防止 程序在拿到锁后还没有释放锁时就服务器宕机或者被杀死进程 发生的死锁情况
修改添加到两个项目中我们再进行Jmeter测试一下
这时候这个分布式下的并发问题就解决了 但随之又来了一个问题就在超并发的场景下 数据库压力服务器压力导致 执行时间边长
假设我购买个商品或者其他操作 这个过程在拿到锁之后要执行15s 这时候当第10s时锁因为到达过期时间而被释放掉锁 当到11s第二个线程就可以拿到锁开始执行
当到15s时第一个线程操作完毕释放掉锁 这时候释放的锁是第二个线程的锁 以此类推第三个线程 … 就会发生 锁永久失效
在这里又有一个解决此类事情的方案
给锁的value设置一个uuid 这样子就不会出现释放掉其他线程的锁 但是一旦执行时间超过10s 锁还是会自动释放 所以启动一个守护线程每10秒去检测锁还在不在,再的话续约
锁的过期时间,一旦主线程结束 守护线程也会跟着结束
这里面用到的守护进程 用的是commons-lang3依赖的BasicThreadFactory创建的线程池
根据阿里巴巴的开发手册 Timer不推荐使用具体原因是因为Timer不支持并发编程,而阿里巴巴创建ScheduledExecutorService推荐手动创建线程池具体原因参考以下网址,总共三种方法
https://blog.****.net/qq_26869339/article/details/90141656
总结
总的来说解决了大部分的并发问题,可能有人担心内存开销,注意锁只有一把所以守护线程只有一个主线程消亡守护线程也会随着死掉所以不必担心内存开销
但是又有个问题就是我每一个并发编程我都要这么去编写的话 花费的精力会很大而且容易出现意想不到的bug
所以redisson这个框架出现就是帮我们简便解决这类问题
下面我们进入redisson的学习