🎈 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 重新测试
活动报名并发问题已解决。

本文详细描述了一次针对1000个用户并发报名的活动并发测试,涉及用户数据导入、JMeter配置、数据库事务处理与乐观锁解决并发问题的方法,以及如何通过增加Version字段来确保数据一致性。
1617

被折叠的 条评论
为什么被折叠?



