🎈 1 参考文档
🚀2 1000个不同用户同时并发请求报名测试
2.1 创建用户信息csv文件
2.2 MySQL存储过程批量添加1000位用户
DROP PROCEDURE if EXISTS BatchInsert;
delimiter $$
CREATE PROCEDURE BatchInsert(IN initId BIGINT, IN loop_counts BIGINT)
BEGIN
DECLARE Var INT;
DECLARE ID INT;
SET Var = 0;
SET ID = initId;
WHILE Var < loop_counts DO
INSERT INTO `yue_activity_user` (`UserName`,`Avatar`,`AddTime`)
VALUES (CONCAT('参与者', ID), CONCAT('参与者', ID, '头像') , now());
SET ID = ID + 1;
SET Var = Var + 1;
END WHILE;
COMMIT;
END$$;
delimiter ; -- 界定符复原为默认的分号
set autocommit = 0; -- 关闭自动提交事务,提高插入效率
CALL BatchInsert(1, 1000); -- 调用存储过程
set autocommit = 1; -- 开启自动提交事务
结果如下:
2.3 配置JMeter,使用版本为5.5
-
总体目录。
-
创建一个线程组。
- 设置线程数量和Ramp-Up
- 表示5秒内启动1000线程,不一定是每秒启动200个线程。
-
创建一个Http请求默认值。
-
添加相应的协议、IP、端口、HTTP请求和路径等。
-
创建
currentUserId
变量${id}
,这里括号里id
对应的是csv中的id
字段。
-
-
创建CSV数据文件设置,将带有1000个csv文件导入。
-
添加同步定时器,保证同时触发。
2.4 代码逻辑
当用户报名成功后,会增加实际报名人数,当实际报名人数到达最大报名人数时,无法继续报名。
但是会出现并发问题,实际报名人数会超过最大报名人数。
-
mapper
@Update("update yue_activity_info set JoinNum = JoinNum + 1, UpdateTime = now() where id = #{id}") int incrJoinNumById(int id);
-
controller
/** * @author Cauli */ @Slf4j @RestController @RequestMapping("/yueQiLai/activity/sign") public class YueQiLaiActivityJoinerController extends BaseController { @Resource private YueQiLaiInfoService yueQiLaiInfoService; @Resource private YueQiLaiActivityJoinerService yueQiLaiActivityJoinerService; /** * 改变用户的报名状态,增加活动的报名数量 * * @param activityId * @return */ @Transactional(rollbackFor = Exception.class) @PostMapping("/activitySignUp") public ResponseDO activitySignUp(int activityId, int currentUserId, HttpServletRequest request) { // 获取当前用户id // int currentUserId = getCurrentUserId(request); // 参数验证 if (currentUserId < 0) { return new ResponseDO(false, "请登录后再操作!", FAIL_CODE, currentUserId); } if (activityId < 0) { return new ResponseDO(false, "活动id错误!", FAIL_CODE, activityId); } log.info("signUp activity," + activityId); log.info("signUp user," + currentUserId); // 获得活动详情对象 YueQiLaiInfo activityInfo = yueQiLaiInfoService.loadById(activityId); if (activityInfo == null) { return new ResponseDO(true, "该活动不存在!", FAIL_CODE, activityId); } else if (activityInfo.getJoinNum() >= activityInfo.getMostJoinNum()) { // 活动报名已满,不能报名 return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "活动报名已满!"); } else if (activityInfo.getStatus() == YueQiLaiActivityStatusEnum.SignUpActivityClosed.getId()) { //不在活动报名时间,不能报名 return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "不在活动报名时间!"); } // 获得活动报名用户对象 YueQiLaiActivityJoiner activityJoiner = yueQiLaiActivityJoinerService.loadByUserIdAndActivityId(currentUserId, activityId); if (activityJoiner != null) { // 活动报名记录存在 if (activityJoiner.getStatus() == YueQiLaiActivityJoinerStatusEnum.WaitingToStart.getId()) { // 已报名该活动,不能报名 return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "你已报名该活动!"); } else { // 未报名该活动,可以报名 // ------------------------------修改之前-------------------------------- // 修改记录 yueQiLaiActivityJoinerService.updateJoinerStatusById(currentUserId, activityId, YueQiLaiActivityJoinerStatusEnum.WaitingToStart.getId()); } } else { // 活动报名记录不存在 activityJoiner = new YueQiLaiActivityJoiner(); activityJoiner.setActivityId(activityId); activityJoiner.setJoinerId(currentUserId); activityJoiner.setStatus(YueQiLaiActivityJoinerStatusEnum.WaitingToStart.getId()); // ------------------------------修改之前-------------------------------- // 新增记录 yueQiLaiActivityJoinerService.insert(activityJoiner); } // ------------------------------修改之前-------------------------------- // 报名人数增加 int i = yueQiLaiInfoService.incrJoinNumByIdAndVersion(activityId, activityInfo.getVersion()); return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "报名活动成功!"); } }
2.5 测试
-
数据库部分表字段。
-
执行测试。
-
查看结果。
🚀3 通过乐观锁解锁并发问题
3.1 活动表中增加Version字段
3.2 修改之后的代码逻辑
-
mapper
@Update("update yue_activity_info set JoinNum = JoinNum + 1, Version = Version + 1, UpdateTime = now() where id = #{id} and Version = #{version}") int incrJoinNumByIdAndVersion(int id,int version);
-
Controller
/** * @author Cauli */ @Slf4j @RestController @RequestMapping("/yueQiLai/activity/sign") public class YueQiLaiActivityJoinerController extends BaseController { @Resource private YueQiLaiInfoService yueQiLaiInfoService; @Resource private YueQiLaiActivityJoinerService yueQiLaiActivityJoinerService; /** * 改变用户的报名状态,增加活动的报名数量 * * @param activityId * @return */ @Transactional(rollbackFor = Exception.class) @PostMapping("/activitySignUp") public ResponseDO activitySignUp(int activityId, int currentUserId, HttpServletRequest request) { // 获取当前用户id // int currentUserId = getCurrentUserId(request); // 参数验证 if (currentUserId < 0) { return new ResponseDO(false, "请登录后再操作!", FAIL_CODE, currentUserId); } if (activityId < 0) { return new ResponseDO(false, "活动id错误!", FAIL_CODE, activityId); } log.info("signUp activity," + activityId); log.info("signUp user," + currentUserId); // 获得活动详情对象 YueQiLaiInfo activityInfo = yueQiLaiInfoService.loadById(activityId); if (activityInfo == null) { return new ResponseDO(true, "该活动不存在!", FAIL_CODE, activityId); } else if (activityInfo.getJoinNum() >= activityInfo.getMostJoinNum()) { // 活动报名已满,不能报名 return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "活动报名已满!"); } else if (activityInfo.getStatus() == YueQiLaiActivityStatusEnum.SignUpActivityClosed.getId()) { //不在活动报名时间,不能报名 return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "不在活动报名时间!"); } // 获得活动报名用户对象 YueQiLaiActivityJoiner activityJoiner = yueQiLaiActivityJoinerService.loadByUserIdAndActivityId(currentUserId, activityId); if (activityJoiner != null) { // 活动报名记录存在 if (activityJoiner.getStatus() == YueQiLaiActivityJoinerStatusEnum.WaitingToStart.getId()) { // 已报名该活动,不能报名 return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "你已报名该活动!"); } else { // 未报名该活动,可以报名 // ------------------------------修改之后-------------------------------- // 报名人数增加 int i = yueQiLaiInfoService.incrJoinNumByIdAndVersion(activityId, activityInfo.getVersion()); if (i > 0) { // 修改记录 yueQiLaiActivityJoinerService.updateJoinerStatusById(currentUserId, activityId, YueQiLaiActivityJoinerStatusEnum.WaitingToStart.getId()); } } } else { // 活动报名记录不存在 activityJoiner = new YueQiLaiActivityJoiner(); activityJoiner.setActivityId(activityId); activityJoiner.setJoinerId(currentUserId); activityJoiner.setStatus(YueQiLaiActivityJoinerStatusEnum.WaitingToStart.getId()); // ------------------------------修改之后-------------------------------- // 报名人数增加 int i = yueQiLaiInfoService.incrJoinNumByIdAndVersion(activityId, activityInfo.getVersion()); if (i > 0) { // 新增记录 yueQiLaiActivityJoinerService.insert(activityJoiner); } } return new ResponseDO(true, SUCCESS, SUCCESS_CODE, "报名活动成功!"); } }
3.3 重新测试
活动报名并发问题已解决。