使用redis读写锁实现抢券功能

思路:100个线程抢3张券。多线程同时操作共享资源,也就是库存,不能超卖。

查询库存(缓存中有从缓存取,缓存没有就查询DB,再缓存起来)(在读锁中实现)

库存>0,更新DB,并删除缓存(放在写锁中实现)。

java 复制代码
package com.niuniu.order.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.niuniu.common.vo.Response;
import com.niuniu.order.model.Order;

public interface CouponService extends IService<Order> {

    /**
     * 100个人抢三张券,每人限购一张
     * @return
     */
    Response dealCoupon(Long productId, Integer num);
}
java 复制代码
package com.niuniu.order.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.niuniu.common.vo.Response;
import com.niuniu.order.feignclient.ProductStoreClient;
import com.niuniu.order.mapper.OrderMapper;
import com.niuniu.order.model.Order;
import com.niuniu.order.service.CouponService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Objects;

@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<OrderMapper, Order> implements CouponService {

    @Autowired
    private ProductStoreClient productStoreClient;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 抢券逻辑
     * @return
     */
    @Override
    public Response dealCoupon(Long productId, Integer num) {
        // 1、查询库存
        Integer stock = this.getStockById(productId);
        System.out.println(Thread.currentThread().getName()+"读到的剩余量是"+stock);
        if (stock > 0){
            // 减少库存
            this.updateStock(productId, num);
        }
        return Response.ok();
    }

    /**
     * 根据商品id查询库存
     * @param productId
     * @return
     */
    private Integer getStockById(Long productId){
        RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock("READ_WRITE_STOCK_" + productId);
        RLock rLock = rReadWriteLock.readLock();
        Integer stock = 0;
        try{
            rLock.lock();
            String stockObj = redisTemplate.opsForValue().get("STOCK_" + productId);
            // 从缓存中取
            if (Objects.nonNull(stockObj)) {
                stock = Integer.parseInt(stockObj);
            } else { // 从数据库查询并放入缓存
                Response<Integer> response = productStoreClient.getStockById(productId);
                if (Objects.isNull(response)){
                    throw new RuntimeException("查询商品库存失败!");
                }
                stock = response.getBody();
                redisTemplate.opsForValue().set("STOCK_" + productId, String.valueOf(stock));
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return stock;
    }

    /**
     * 更新DB,删除缓存
     * @param productId
     * @param num
     */
    private void updateStock(Long productId, Integer num){
        RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock("READ_WRITE_STOCK_" + productId);
        RLock wLock = rReadWriteLock.writeLock();
        try {
            wLock.lock();
            Integer stock = this.getStockById(productId);
            if (stock > 0) {
                // 更新DB
                productStoreClient.updateStockById(productId, num);
                // 删除缓存
                redisTemplate.delete("STOCK_" + productId);
                System.out.println(Thread.currentThread().getName() + ",抢到券了");
            }
        } finally {
            wLock.unlock();
        }
    }
}
java 复制代码
@GetMapping("/dealCoupon")
    public Response dealCoupon(){
        // 100个人抢三张券,每人限购一张
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                couponService.dealCoupon(1L, 1);
            }).start();
        }
        return Response.ok();
    }