SpringBoot实战实现分布式锁一之重现多线程高并发场景
实战前言:从本篇文章开始,我将开始介绍"SpringBoot实战实现分布式锁"系列博文涉及到的相关知识要点,感兴趣的小伙伴可以关注关注学习学习!!工欲善其事,必先利其器,介绍分布式锁使用的前因后果之前,得先想办法说清楚为啥需要分布式锁以及如何才需要将分布式锁搬上用场!!
实战概要:故而此文将介绍一下分布式锁出现的背景以及如何才能将分布式锁搬上用场(即如何重新多线程高并发的场景)。
实战内容:
1、“同一时刻多个线程高并发下访问共享资源”的场景在当前互联网产品或者项目下并不少见,这一场景随之带来的问题便显而易见:这一共享资源在并发访问的前后出现了数据不一致或者并非预期出现的结果的现象!!简而言之,这种现象其实就是大伙熟悉的 “高并发多线程访问共享资源时需要加同步代码块”的口头语(甚至可以说是面试时常见的对白了!)
2、单体应用时代加“同步锁”常见的方式是利用jdk天然提供的类/组件:ReentrantLock或者Synchronized,但在分布式系统架构下项目一般以微服务的方式开发、独立部署甚至集群部署,当不同的服务或者集群环境同一服务不同实例发生对共享资源的高并发访问时,ReentrantLock或者Synchronized 的方式将很难解决 “高并发导致数据不一致或者并发预期出现的结果”的问题!!
3、于是乎,“分布式锁”便出现了,“分布式锁”其实只是一解决方案,并非一专有组件或者类,实现这一解决方案仍旧需要借助额外的组件或者中间件来实现,甚至某些情况下,需要借助数据库级别的方式来实现。总体来说,目前较为流行的解决方式还是有很多种,在我的视频课程或者文章中,我将介绍一下几种方式来实战实现 “分布式锁”
(1)数据库级别锁-乐观悲观锁
(2)基于Redis的原子操作实现分布式锁
(3)基于Zookeeper实战实现分布式锁
(4)基于Redisson实战实现分布式锁
4、既然我们知道分布式锁出现的背景以及其相应的实战实现方式,那我们回到本篇文章的核心内容:重现多线程高并发访问共享资源的场景
5、下面我们以“商城系统/秒杀系统抢单”场景为例,借助Jmeter测试工具,基于SpringBoot微服务项目重现高并发多线程访问共享资源的场景!即:重现1秒内100线程、1000线程、10000线程等充当抢单请求对一商品进行抢单!!!
6、这一场景其实很像“抢微信红包”、“某一商城如小米商城饥饿营销时抢手机”等业务场景。下面我们大概模拟重现其中的核心逻辑-即抢单的过程:建库-spring_boot_distribute,建一商品信息表语句如下(mysql5.6版本):
CREATE TABLE `product_lock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `product_no` varchar(255) DEFAULT NULL COMMENT '产品编号', `stock` int(11) DEFAULT NULL COMMENT '库存量', `version` int(11) DEFAULT NULL COMMENT '版本号', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_unique` (`id`) USING BTREE ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='产品信息表';
并在其中录入一个商品的信息(主要是库存 stock 的设置)!
7、接着采用IDEA的SpringBoot Initializr组件构建多模块的SpringBoot微服务项目,并开发一Controller跟一Service,采用Mybatis逆向工程生成上面那张数据库表对应的entity、mapper、mapper.xml,相关代码以及截图如下:
@Service public class DataLockService { private static final Logger log= LoggerFactory.getLogger(DataLockService.class); @Autowired private ProductLockMapper lockMapper; /** * 正常更新商品库存 - 重现了高并发的场景 * @param dto * @return * @throws Exception */ @Transactional(rollbackFor = Exception.class) public int updateStock(ProductLockDto dto) throws Exception{ int res=0; ProductLock entity=lockMapper.selectByPrimaryKey(dto.getId()); if (entity!=null && entity.getStock().compareTo(dto.getStock())>=0){ entity.setStock(dto.getStock()); return lockMapper.updateStock(entity); } return res; }}
DataLockController代码如下:
@RestController public class DataLockController { private static final Logger log= LoggerFactory.getLogger(DataLockController.class); private static final String prefix="lock"; @Autowired private DataLockService dataLockService; /** * 更新商品库存-1 * @param dto * @param bindingResult * @return */ @RequestMapping(value = prefix+"/data/base/positive/update",method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public BaseResponse dataBasePositive(@RequestBody @Validated ProductLockDto dto, BindingResult bindingResult){ if (bindingResult.hasErrors()){ return new BaseResponse(StatusCode.InvalidParam); } BaseResponse response=new BaseResponse(StatusCode.Ok); try { log.debug("当前请求数据:{} ",dto); int res=dataLockService.updateStock(dto); if (res<=0) { return new BaseResponse(StatusCode.Fail); } }catch (Exception e){ log.error("发生异常:",e.fillInStackTrace()); response=new BaseResponse(StatusCode.Fail); } return response; }}
ProductLockDto 代码如下:
@Data @ToString public class ProductLockDto { @NotNull private Integer id; @NotNull private Integer stock=1;}
Mybatis逆向工程生成的那三个组件就不贴了,在这里贴一下 DataLockService调用的ProductLockMapper更新库存的方法以及动态sql的写法:
ProductLockMapper类的方法: int updateStock(ProductLock lock);
ProductLockMapper.xml的动态sql:
<update id="updateStock" parameterType="com.debug.steadyjack.entity.ProductLock"> update product_lock set stock = stock - #{stock,jdbcType=INTEGER} where id = #{id,jdbcType=INTEGER} update>
8、至此简单的抢单系统/商城秒杀系统的抢单场景就大致模拟好了,下面我们采用Jmeter测试工具来模拟这一高并发场景,Jmeter的相关设置如下:
(1)首先我们设置1s并发100个线程,后面你可以在这里设置1000、10000甚至更多个线程!
(2)接着我们设置 “HTTP信息头管理器” ,因为我们的抢单接口接收的媒体类型是 json格式的post请求!
(3)接着我们创建 “HTTP请求” ,设置我们的项目上下文、端口以及我们的请求接口路径跟方法体(ProductLockDto的字段:商品的id跟需要抢的量stock)
(4)最后我们设置stock字段来源于我们配置的CSV数据文件设置中读取的变量stock 的值,即代表我们的用户可以任意随机的下单一定的量!!
(5)其中的csv文件是长这样的:
9、最后,我们点击这一按钮,即开启了 1s 内启动100个并发线程对设定的产品进行 “抢” 的请求。
10、这个时候,我们先对这一产品的库存量在数据库进行设置,我们设置为 100,即现有的库存量为100。理论情况下,不管发生多少次的“哄抢”,“最终的库存应当是被抢完而且应当是恰好被抢完,而且需要发送相应的短信/通知告知用户抢到了!!”,然后,现实是很残酷的(当你按下那一个start run的按钮时,数据库最终出现的结果却不是我们预期的那样!!)
11、下面是抢单接口的打印日志以及数据库最终对这一商品更新的结果:
15、你会惊讶的看到,100个库存在随机产生的100个线程(每个线程库存2或者5-csv文件读取的)更新之后竟然变成了负数(按道理来说,我们写的数据库更新逻辑以及代码判断逻辑没有多大问题啊!!!)
实战分析:“按道理来说,我们写的数据库更新逻辑以及代码判断逻辑没有多大问题啊!!!”,实则不然,其实问题正是出在这两点:数据库更新逻辑 跟 代码判断逻辑 。 欲知问题何在,请听下篇分解!!
实战总结:本篇文章主要基于SpringBoot微服务项目重现了高并发多线程并发访问同一共享资源时出现的问题
(附注:“SpringBoot实战实现分布式锁”系列博文是 SpringBoot实战实现分布式锁” 视频教程的文字配合讲解,目的在于巩固加深大伙对于分布式锁实现的理解,感兴趣的伙伴可以前往学习:https://www.roncoo.com/course/view/4737639130da4d6892e11190412433a6 )
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Spring MVC入门
Spring mvc架构 1.1架构图 1.2.架构流程 1.用户发送请求至前端控制器DispatcherServlet 2.DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3.处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 4.DispatcherServlet通过HandlerAdapter处理器适配器调用处理器 5.执行处理器(Controller,也叫后端控制器)。 6.Controller执行完成返回ModelAndView 7.HandlerAdapter将controller执行结果ModelAndView返回 8.DispatcherServletDispatcherServlet将ModelAndView传给ViewReslover视图解析器 9.ViewReslover解析后返回具体View 10.DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。 11.DispatcherServlet响应用户 一....
- 下一篇
springboot--邮件服务
springboot仍然在狂速发展,才五个多月没有关注,现在看官网已经到1.5.3.RELEASE版本了。准备慢慢在写写springboot相关的文章,本篇文章使用springboot最新版本1.5.3进行开发。 发送邮件应该是网站的必备功能之一,什么注册验证,忘记密码或者是给用户发送营销信息。最早期的时候我们会使用JavaMail相关api来写发送邮件的相关代码,后来spring退出了JavaMailSender更加简化了邮件发送的过程,在之后springboot对此进行了封装就有了现在的spring-boot-starter-mail,本章文章的介绍主要来自于此包。 简单使用 1、pom包配置 pom包里面添加spring-boot-starter-mail包引用 1.<dependencies> 2.<dependency> 3.<groupId>org.springframework.boot</groupId> 4.<artifactId>spring-boot-starter-mail</artifactId...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- CentOS8编译安装MySQL8.0.19
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装