1.场景描述
用户通过APP点击预约进行线上约课处理,预约课程的逻辑是先查询用户预约的课程是否已经存在,存在则提示课程已预约,停止预约处理;课程不存在再进行添加预约记录.实际测试过程中发现,用户短时间内重复点击时后台会发现录入多条重复数据.出现的原因是由于并发导致重复数据添加.
2.处理方案
1.java关键字:synchronize
课程预约方法上添加synchronize关键字
@Override
@Transactional
public synchronized void applyCourse(ApplyOrderCourseDto applyOrderCourseDto) throws Exception {
// 查询用户预约课程信息是否预约过
// 如果没有预约过则进行添加预约记录;如果预约过提示:课程已预约,不允许重复预约
}
2.数据库悲观锁
java业务逻辑中不需要做单独处理,mybatis中查询课程是否预约sql中添加for update.
<select id="findCourseRecord"
resultType="com.kawaxiaoyu.api.appointCourse.entity.CourseRecord">
SELECT id,login,data_id,card_no,start_time,type,status FROM manage_course_record
<where>
<if test="支持多条件查询">
</if>
</where>
FOR UPDATE
</select>
注意:查询的字段一定是主键或者唯一索引,不然可能造成锁表,处理起来会非常麻烦;乐观锁主要是处理更新类的业务,本场景中暂不适合!
3.数据库添加联合唯一索引(接口幂等处理方案)
接口幂等:简单理解就是同一个接口调用多次与调用一次返回的信息相同.对于本场景中,需要设置多个字段为联合唯一索引.以下为sqlyug设置方式(设置预约接口请求参数的多个字段为联合唯一索引)
设置唯一索引之后有两种方案:一种是按照现有的逻辑每次预约时需要查询指定类型的指定课程是否已经预约过,如果没有预约过则进行课程预约,已经预约过的提示:课程已预约,不允许重复预约(原逻辑);另一种是预约逻辑中直接进行添加预约记录信息,执行操作时会显示唯一约束重复异常提示,类似于(本场景中是三个对应设置的联合唯一主键):
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '861878837883138-1-123456' for key 'unquire'
出现异常,重复添加的预约记录问题即能避免!
4.redis处理
利用redis是单线程的特点,对于并发可以利用锁的方式将并行请求转化成串行请求(此处的加锁是指设置一个key value键值对),保证拿到锁的才能进行业务逻辑处理,处理完成之后主要释放锁.为避免死锁的出现,需要在加锁时给锁设置过期时间,避免释放失败导致其他请求无法进行业务逻辑处理.伪代码如下:
获取锁:
// 判断是否获取锁(注意设置过期时间,返回true表示获取锁,false表示没有获取锁):
Boolean isGetLock = redisTemplate.opsForValue().setIfAbsent(key值, value值,2,TimeUnit.SECONDS);
释放锁:
Boolean isDelete = redisTemplate.delete(key值);
3.测试
单个用户多个请求测试: jemeter一秒钟设置1000个请求,发送post请求,http请求头管理中设置token信息,请求均响应成功,只有一个请求显示预约成功,其余请求均显示课程已预约,不允许重复预约.数据库中指定课程类型信息只添加一条记录信息,符合预期!
以上为自己业务处理中的简单总结,如果描述不正确之处,欢迎评论区留言交流!