Redisson分布式锁解决库存超卖

利用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;

}

<think>好的,用户想了解如何使用Redisson分布式锁实现高并发环境下的库存管理系统,确保多库并发控制的正确性和效率。我需要结合他们提供的引用内容来构建一个全面的回答。 首先,根据引用[1]和[2],Redisson分布式锁的推荐方案,因为它解决了数据库和缓存不一致的问题,并且符合CAP原理。用户可能关心Redisson的基本原理和如何集成到库存系统中。我需要解释Redisson的锁机制,比如可重入锁、锁续期等。 然后,库存管理通常涉及扣减库存的操作,必须保证原子性。这里需要提到使用Redis的原子操作,比如Lua脚本,或者结合Redisson的RLock。引用[3]提到了锁等待时间,这可能和锁的时设置有关,需要说明如何合理配置锁的过期时间,避免死锁和长时间阻塞。 接下来是多库并发控制的问题。库存系统可能有多个仓库或数据库,分布式锁需要跨所有节点协调。根据引用[1]中的RedLock算法,可能需要多个独立的Redis实例来实现更可靠的锁。不过Redisson已经支持RedLock,可以介绍如何配置和使用。 另外,引用[4]提到锁的粒度问题,使用userid作为锁的key,这里库存管理可能需要以商品ID或库存ID作为锁的键,确保同一商品的库存操作串行化,避免。需要强调锁的粒度要合适,太粗会影响并发性能,太细会增加复杂度。 还需要考虑高并发下的性能优化,比如分段锁。引用[3]提到了Go的分段加锁Demo,可能可以借鉴到Java中,将库存分成多个段,每个段单独加锁,提高并发度。例如,将库存拆分为多个子库存,减少锁竞争。 此外,异常处理也很重要,比如锁的自动续期、避免锁泄漏。Redisson的看门狗机制可以自动延长锁的持有时间,防止业务未完成时锁过期。需要提醒用户正确处理锁的获取和释放,确保在异常情况下也能释放锁,比如在finally块中释放。 最后,分布式事务和幂等性可能也是需要考虑的,引用[2]提到了接口的幂等性,库存操作需要保证即使重复请求也不会多次扣减。可以通过唯一事务ID或版本号机制来实现。 总结回答结构:先介绍Redisson分布式锁的核心机制,然后详细说明库存扣减流程,包括锁的获取、原子操作、多库处理,接着讨论锁的优化如分段锁和RedLock,最后强调异常处理和监控。这样用户能一步步了解如何正确高效地实现分布式库存管理。</think>### Redisson分布式锁库存管理系统中的实现实践 #### 一、核心机制设计 1. **可重入锁机制** Redisson的`RLock`支持线程级别的重入特性,同一线程可重复获取锁,防止死锁。适用于需要嵌套调用的库存操作场景: ```java RLock lock = redissonClient.getLock("product_stock:1001"); lock.lock(10, TimeUnit.SECONDS); // 设置自动过期时间[^1] ``` 2. **看门狗自动续期** 当未显式设置`leaseTime`参数时,Redisson会启动后台线程每10秒检测锁状态并自动续期(默认30秒有效期),避免业务未完成时锁过期[^1]。 #### 二、库存扣减流程实现 ```java public boolean deductStock(String productId, int quantity) { String lockKey = "stock_lock:" + productId; RLock lock = redissonClient.getLock(lockKey); try { // 尝试获取锁(等待5秒,锁持有30秒) if (lock.tryLock(5, 30, TimeUnit.SECONDS)) { int currentStock = redisTemplate.opsForValue().get(productId); if (currentStock >= quantity) { redisTemplate.opsForValue().decrement(productId, quantity); return true; } return false; } } finally { lock.unlock(); } } ``` #### 三、多库并发控制方案 1. **库存分片策略** 采用分段锁提升并发能力,将总库存拆分为N个分片: ```java int shard = productId.hashCode() % 16; String lockKey = "stock_shard_lock:" + shard; ``` 2. **跨库事务协调** 使用RedLock算法实现跨多Redis节点的强一致性: ```java RLock lock1 = redissonClient1.getLock("lockKey"); RLock lock2 = redissonClient2.getLock("lockKey"); RedissonRedLock redLock = new RedissonRedLock(lock1, lock2); ``` #### 四、性能优化实践 | 优化维度 | 实施方法 | 效果提升 | |----------------|-----------------------------------|---------------------------| | 锁粒度控制 | 按商品ID+仓库ID组合锁定 | 降低锁竞争概率 | | 热点库存拆分 | 将1000件库存拆分为10个100件的分片 | 并发能力提升8-10倍 | | 预扣库存策略 | 使用Redis原子操作提前扣减虚拟库存 | 减少数据库压力 | #### 五、异常处理关键点 1. **网络分区防护** 设置合理的`lockWatchdogTimeout`(默认30秒),配合`innodb_lock_wait_timeout`(数据库行锁时)形成双层保护[^3] 2. **幂等性保证** 为每个操作生成唯一事务ID: ```sql UPDATE inventory SET stock=stock-? WHERE product_id=? AND version=? ``` #### 六、监控指标建议 1. 锁等待时间百分位(P99 ≤ 50ms) 2. 库存操作TPS与锁获取成功率关联分析 3. 分片锁的竞争热度分布
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值