一、仓库介绍
背景:常见高并发场景 -- 抢购商品
技术:SpringBoot、MyBatis、Redis
从 0 开始认识高并发,介绍悲观锁、乐观锁及其解决方案,最终使用 redis 应对高并发场景。
二、仓库使用与学习
开发过程,每一个版本都打上了对应标签,下方即为各版本介绍。
可以直接查看指定版本学习,或者从 1.0 开始逐层推进。
注意:由于环境的原因,某些版本可能没有达到预期效果,学习时可根据实际情况理解不同版本之间的区别。
三、克隆运行步骤
数据库名称:second_kill
开发使用的 SQL 语句,位于 src/resources/sql 下
安装 MySQL、Redis
创建数据库、表,初始化表数据
修改项目配置文件中 MySQL 连接密码
Maven 安装项目依赖
启动项目
版本 1.0
条件:
请求量:50000
初始库存:20000
使用浏览器模拟高并发,点击抢购按钮时发送 50000 个请求,同时更新当前请求数
问题:
请求可以发送,但 50000 个请求时间过长,按钮按下后距离弹起的时间过长,浏览器直接卡死
在连续发送请求的同时,浏览器无法更新 dom 元素(可能是我电脑太差......),请求数卡死在 0
版本 1.1
点击按钮发送请求 改为 进入页面自动请求,去掉更新请求数的行为
在控制台打印请求数
问题:
进入页面请求即开始,浏览器卡到根本打不开控制台(放弃查看当前请求数......)
版本 1.2
50000 的请求量太大,电脑总是崩溃,无法请求完成
修改:
请求量:10000 商品初始库存:5000
结果:
请求顺利完成
出现超发现象,最后库存为 -1
请求总时长大约为 40 秒
结果图:
库存:
时间:
版本 2.0
为避免超发现象,使用悲观锁
结果:
顺利解决超发现象,最后库存为 0
请求时间延长
使用悲观锁的代价就是请求时间延长,因为悲观锁会锁定每次查询到的数据,直到当前事务完成后其他事务才能访问
结果图:
库存:
时间:
版本 3.0
为提高性能,使用乐观锁
同时,为了避免 ABA 问题,修改操作加上版本号
结果:
请求时间与不加锁时相近,性能损失很小
从库存图片可以看出,请求结束时库存不为 0
乐观锁中,由于版本号的原因,会出现大量更新失败的请求
结果图:
库存:
时间:
版本 3.1
为降低使用乐观锁时请求的失败率,加入重入机制
使用时间戳限制重入,规定 100 ms 内可重试更新操作
结果:
请求结束时,库存为 0
但是使用时间戳重入的方法也有弊端,就是系统会随着自身的忙碌而大大减少重入的次数
结果图:
库存:
时间:
版本 3.2
为解决时间戳重入的弊端,使用限定次数的重入机制,即一定的次数内可重入
具体的限定次数可以根据实际情况修改,这里限定重入次数为 3 次
结果:
具体使用时,限定次数的重入也应当大大降低失败次数,可能是不同环境的原因,这里的失败次数反而更多了......
具体生产环境中,使用乐观锁时,重入方法需要视情况选择,但是更推荐大家使用 redis 来应对高并发场景
结果图:
库存:
时间:
版本 4.0
使用 redis 应对高并发场景
redis 保存商品信息,购买记录直接保存在 redis 中,抢购结束后定时将数据写入到数据库即可
特点:
redis 直接保存在内存中,读写速度非常快
使用 lua 语言编写脚本完成业务,业务执行具有原子性,不会出现超发现象
抢购数据直接从 redis 上读取,减小数据库压力
结果:
请求完成后,无超发现象
请求时间缩短至 3 秒
结果图:
库存:
时间: