Redisson分布式锁解决库存超卖

一、为何出现库存超卖

库存超卖通常出现在高并发的销售场景中,如团购、秒杀或特价活动期间。这些活动往往会导致访问量激增,成千上万的用户同时抢购有限的商品,这就极易造成库存超卖现象。

具体来说,库存超卖的主要原因包括:

  • 并发操作:在高并发场景下,多个用户可能会同时读取到相同的库存数量,并在此基础上进行扣减操作,这可能导致实际销售的商品数量超过库存量。
  • 系统延迟:在分布式系统中,由于网络延迟或者数据库锁等待等原因,可能会导致订单处理的延迟,从而在库存扣减之前产生更多的订单请求。
  • 缓存不一致性:如果使用缓存来提高性能,缓存数据与数据库数据之间的不一致性也可能导致超卖问题。
  • 缺乏适当的并发控制策略:在没有适当并发控制的情况下,多个请求几乎同时到达服务器,对库存资源进行竞争,可能导致超卖问题。

二、如何解决库存超卖

为了防止库存超卖,可以采取以下措施:

  • 使用乐观锁或悲观锁:通过锁定机制确保在更新库存时不会被其他事务干扰。
  • 减少系统延迟:优化系统架构和数据库设计,减少处理订单的时间。
  • 缓存预加载:在活动开始前预先加载缓存,减少对数据库的直接访问。
  • 限流和排队:对请求进行限流,确保系统能够平稳处理请求,避免因瞬间流量过大而导致的问题。
  • 分布式锁:在分布式环境下使用分布式锁,确保同一时间只有一个节点能够执行特定的代码块。

总的来说,库存超卖是一个复杂的问题,需要综合考虑系统的并发处理能力、数据库的设计以及缓存策略等多个方面,以确保在高并发的销售活动中能够准确控制库存,防止超卖现象的发生

三、使用Redisson分布式锁解决库存超卖

导入 Redisson 和 AOP 的依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.24.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

新建redisson.yml配置文件,配置以下内容

singleServerConfig:
  address: "redis://43.139.81.26:6379"
  database: 9
  password: null
  idleConnectionTimeout: 10000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  subscriptionsPerConnection: 5
  clientName: null
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  connectionMinimumIdleSize: 24
  connectionPoolSize: 64
  dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.Kryo5Codec> { }
transportMode: "NIO"

application.yml文件配置:

spring:
  redis:
    redisson:
      file: "classpath:redisson.yml"

在控制层的创建订单的方法中添加 @DistributedLock 注解,注解配置一些参数

package com.qf.cloud.mq.lock.controller;

import com.qf.cloud.mq.lock.distributed.annotation.DistributedLock;
import com.qf.cloud.mq.lock.distributed.enmus.LockType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("lock")
public class DistributedLockController {

    private int num = 100;

    @PostMapping("/create")
    @DistributedLock(value = "order",lockType = LockType.DEFAULT_LOCK,lockName = "update",moduleName = "order",waitTime = 30,leaseTime = 30)
    public int createOrder(){
        num--;
        return num;
    }
}

新建AOP类

package com.qf.cloud.mq.lock.distributed.aop;

import com.qf.cloud.mq.lock.distributed.annotation.DistributedLock;
import com.qf.cloud.mq.lock.distributed.enmus.LockType;
import com.qf.cloud.mq.lock.distributed.enmus.ReadWriteLockType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
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.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;

@Component
@Aspect
public class DistributedLockAspect {

    /**
     * 固定锁的名称前缀
     */
    public static final String DEFAULT_PREFIX_LOCK_NAME = "lock:distributed:";

    /**
     * 常量定义,表示默认的锁名称为"default"
     */
    public static final String DEFAULT_LOCK_NAME = "default";

    /**
     * RedissonClient是一个用于操作Redis数据库的客户端类,
     * 它提供了丰富的API来执行各种Redis操作,如连接管理、数据操作、分布式锁等
     */
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 注解切入点
     */
    @Pointcut("@annotation(com.qf.cloud.mq.lock.distributed.annotation.DistributedLock)")
    public void pointCut() {
    }

    /**
     * Around 环绕注解 (切入点)
     *
     * @param pjp Spring AOP中的一个接口,提供了一些方法
     * @return 返回核心方法的结果
     */
    @Around("pointCut()")
    public Object locked(ProceedingJoinPoint pjp) {
        Object proceed = null;
        // 获取切面中的签名信息,获取目标方法的签名信息,包括方法名、参数类型等
        Signature signature = pjp.getSignature();
        // 判断切面中的签名信息是否为方法签名
        if (signature instanceof MethodSignature) {
            // 强转
            MethodSignature methodSignature = (MethodSignature) signature;
            // 获取切面中的签名信息所对应的方法对象
            Method method = methodSignature.getMethod();
            // 用于获取方法上的 @DistributedLock 注解对象
            DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
            // 获取锁对象,可以是重入锁,公平锁,读写锁
            RLock lock = getLock(distributedLock);
            /**
             *  用于尝试获取锁,如果锁可用(即未被其他线程持有),返回true并获取锁
             *  如果锁不可用,则立即返回false,不会等待锁的释放
             *  三个参数 1、等待时间的长度,底层通过while循环不断获取锁,超过时间未获取到就报错
             *  2、等待时间的单位,比如 30,如果是秒的话,就是30秒后就会释放锁,给其他线程上锁
             *  3、等待时间的类型,比如 秒,分,时
             */
            try {
                boolean isLock = lock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), distributedLock.unit());
                // 判断锁可用?
                if (isLock) {
                    // 继续执行核心代码
                    proceed = pjp.proceed();
                }
            } catch (Throwable e) {
                throw new RuntimeException(e);
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
        // 返回核心方法的结果
        return proceed;
    }

    /**
     * 获取锁对象
     *
     * @param distributedLock 方法上的注解对象
     * @return 返回一个锁对象  可以是重入锁,公平锁,读写锁
     */
    private RLock getLock(DistributedLock distributedLock) {
        RLock rLock = null;
        // 获取注解对象里的lockTpe参数
        LockType lockType = distributedLock.lockType();
        switch (lockType) {
            case FAIR_LOCK:
                // getFairLock 获取公平锁,参数是锁的完整名称
                rLock = redissonClient.getFairLock(getLockName(distributedLock));
                break;
            case READ_WRITE_LOCK:
                // readWriteLock 获取读写锁,参数是锁的完整名称
                RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(getLockName(distributedLock));
                // 通过方法上的注解里的 获取 ReadWriteLockType 锁的类型
                ReadWriteLockType readWriteLockType = distributedLock.readWriteLockType();
                // 是读锁就用读锁,写锁就用写锁
                rLock = distributedLock.readWriteLockType().equals(ReadWriteLockType.READ_LOCK) ? readWriteLock.readLock() : readWriteLock.writeLock();
                break;
            default:
                // getLock 获取普通锁,参数是锁的完整名称
                rLock = redissonClient.getLock(getLockName(distributedLock));
                break;
        }
        return rLock;
    }

    /**
     * 获取锁的完整名称
     *
     * @param distributedLock 方法上的注解对象
     * @return 完整的锁名称
     */
    private String getLockName(DistributedLock distributedLock) {
        // 判断切入点参数有没有 lockName,没有就用默认的值,有就用有的
        String lockName = ObjectUtils.isEmpty(distributedLock.lockName()) ? DEFAULT_LOCK_NAME : distributedLock.lockName();
        // 判断跌入点的参数与没有 moduleName,没有就为空,有就用有的
        String moduleName = distributedLock.moduleName();
        // 固定锁的名称前缀 lock:distributed:
        StringBuffer stringBuffer = new StringBuffer(DEFAULT_PREFIX_LOCK_NAME);
        // 如果 moduleName 不为空
        if (!ObjectUtils.isEmpty(moduleName)) {
            // 锁名称前缀 + moduleName + :
            stringBuffer.append(moduleName).append(":");
        }
        // 锁名称前缀 + moduleName + : + lockName
        stringBuffer.append(lockName);
        // 例如: lock:distributed:moduleName:default
        return stringBuffer.toString();
    }
}

切入点的注解类

package com.qf.cloud.mq.lock.distributed.annotation;

import com.qf.cloud.mq.lock.distributed.enmus.LockType;
import com.qf.cloud.mq.lock.distributed.enmus.ReadWriteLockType;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;

// RetentionPolicy 是一个枚举类型,它定义了三种保留策略:
// SOURCE:注解仅在源代码中保留,编译时会被忽略。
// CLASS:注解在编译时被保留,但在运行时会被忽略。
// RUNTIME:注解在运行时被保留,可以通过反射机制读取
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {

    /**
     * 注解切入点里的参数
     * 例如 @DistributedLock(value = "order", lockType = LockType.FAIR_LOCK,
     * lockName = "create", moduleName = "test", waitTime = 30,
     * leaseTime = 10, unit = TimeUnit.SECONDS)
     */

    // value 默认值为空
    String value() default "";

    // waitTime() 等待时间的长度,底层通过while循环不断获取锁,超过时间未获取到就报错
    long waitTime() default 100;

    // 等待时间的单位,比如 30,如果是秒的话,就是30秒后就会释放锁,给其他线程上锁
    long leaseTime() default 30;

    // 等待时间的类型,比如 秒,分,时
    TimeUnit unit() default TimeUnit.SECONDS;

    // 锁的名称 默认值为空
    String lockName() default "";

    // 模块名称,组成锁的名称
    String moduleName() default "";

    // lockType()是一个方法,表示返回一个LockType锁的类型,默认使用 DEFAULT_LOCK 作为锁类型
    LockType lockType() default LockType.DEFAULT_LOCK;

    // readWriteLockType()是一个方法,表示返回一个ReadWriteLockType锁的类型,默认使用 READ_LOCK 作为锁类型
    ReadWriteLockType readWriteLockType() default ReadWriteLockType.READ_LOCK;
}

枚举类

package com.qf.cloud.mq.lock.distributed.enmus;

/**
 *  枚举项 锁的类型
 */
@SuppressWarnings("ALL")
public enum LockType {
    // 默认
    DEFAULT_LOCK,
    // 公平锁
    FAIR_LOCK,
    // 读写锁
    READ_WRITE_LOCK;
}
package com.qf.cloud.mq.lock.distributed.enmus;

@SuppressWarnings("ALL")
public enum ReadWriteLockType {
    // 读锁
    READ_LOCK,
    // 写锁
    WRITE_LOCK;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值