package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服务实现类 * </p> * * @author 虎哥 * @since 2021-12-22 */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.获取优惠券信息 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判断是否已经开始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒杀尚未开始!"); } //3.判断是否已经结束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒杀已经结束了!"); } //4.判断库存是否充足 if (voucher.getStock() < 1) { Result.fail("库存不充足!"); } //5.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId) .update(); if (!success){ Result.fail("库存不充足!"); } //6. 创建订单 VoucherOrder voucherOrder = new VoucherOrder(); //6.1添加订单id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //6.2添加用户id Long userId = UserHolder.getUser().getId(); voucherOrder.setUserId(userId); //6.3添加优惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //7.返回订单id return Result.ok(orderId); } }2 Oversold problem (key point)Let’s first Try running the above code with high concurrency. (Using jmx tool) The picture below shows that two hundred threads were created and a coupon request was issued in an instant But when we look at the aggregation report, we find that The outlier value is only 45.5%, which logically should be 50% (because there are 100 items in inventory, and 200 requests were sent here) Look at the inventory number, good guy , it is -9 . The order is also added to 109, which is obviously an oversold problem. So, why does this problem occur? Look at the picture and talk: Follow our normal process, that is, thread 1 has checked the inventory, and then deducted the inventory. At this time, thread 2 will query the inventory again and deduct the inventory, so It's no problem. The oversold problem lies in that after order 1 checks the inventory, it is found to be 1, but before the deduction is made, thread 2 also checks the inventory. It was found that it was also 1, and deductions were also made (in high concurrency scenarios) This led to the problem of oversold. For this kind of high concurrency problem, the most common solution is: lock~But locks include pessimistic locks and optimistic locks. Pessimistic locking simply means: I think the thread will definitely happen, and then everyone takes the lock before the operation. After you finish the execution, it is the next one's turn to execute (serial execution)Optimistic lock: It is optimistic (thinking that thread safety will never happen), as long as before each data modification, it is judged whether other threads have modified the data to ensure thread safety. Pessimistic locking is relatively simple, optimistic locking is implemented here. The key to optimistic locking is to determine whether the data obtained by the previous query has been modified. There are two common methodsWarm reminder: The data in the table on the left are all data after the execution of thread 1 Oh~1. The version number method is to add a version number to the step of querying the inventory. After each modification of the data, add the version number + 1 and add the where condition to judge. Is the version number the same as before the modification? This way you can achieve thread safety~2.CAS methodThis is There is no need for a version number. Just add the where condition after modifying the database to determine whether the inventory is the inventory before modification Code implementation to solve the oversold problem: In the final analysis, when we deduct inventory, we add a where condition to determine whether the inventory is greater than 0
//5.1扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id" , voucherId).gt("stock" ,0) .update();
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服务实现类 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.获取优惠券信息 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判断是否已经开始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒杀尚未开始!"); } //3.判断是否已经结束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒杀已经结束了!"); } //4.判断库存是否充足 if (voucher.getStock() < 1) { Result.fail("库存不充足!"); } //5.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("库存不充足!"); } //6. 创建订单 VoucherOrder voucherOrder = new VoucherOrder(); //6.1添加订单id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //6.2添加用户id Long userId = UserHolder.getUser().getId(); voucherOrder.setUserId(userId); //6.3添加优惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //7.返回订单id return Result.ok(orderId); } }
The above is the detailed content of How to solve the Redis coupon flash sale problem. For more information, please follow other related articles on the PHP Chinese website!