Home  >  Article  >  Database  >  How to solve the Redis coupon flash sale problem

How to solve the Redis coupon flash sale problem

王林
王林forward
2023-05-28 14:52:171209browse

1 Implement the coupon flash sale function

How to solve the Redis coupon flash sale problem

##When placing an order, you need to judge two points: 1. Whether the flash sale has started or ended 2. Whether the inventory is sufficient

Therefore, our business logic is as follows

1. Get coupon information through coupon ID

2. Determine whether the flash sale has started, and if no error message is returned

3. Determine Whether the flash sale is over, if it has ended, return an error message

4. If it is within the flash sale time, determine whether the inventory is sufficient

5. If it is sufficient, deduct the inventory

6. Create order information and save it to the coupon order table

6.1 Save order id

6.2 Save user id

6.3 Save coupon id

7 .Return order id

How to solve the Redis coupon flash sale problem

Code implementation: (Service layer implementation class)

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

How to solve the Redis coupon flash sale problem

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)

How to solve the Redis coupon flash sale problem

Look at the inventory number, good guy , it is -9

How to solve the Redis coupon flash sale problem

. 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.

How to solve the Redis coupon flash sale 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)

How to solve the Redis coupon flash sale problem

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.

How to solve the Redis coupon flash sale problem

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 methods

Warm 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?

How to solve the Redis coupon flash sale problem

This way you can achieve thread safety~

2.CAS method

This 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

How to solve the Redis coupon flash sale problem

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!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete