高并发下扣减库存及DB数据库更新数据到redis

高并发下扣减库存及DB数据库更新数据到redis

直接上代码,里面有详细注释哦~
1.pom.xml中引入redis和redission相关依赖

<!--redis-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson分布式锁-->
<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.11.5</version>
</dependency>

2.application.yml中redis配置参数

spring:
  redis:
    #数据库索引
    host: 127.0.0.1
    port: 6379
    #  password: 123456
    # 连接超时时间(毫秒)
    timeout: 10000
    jedis:
      pool:
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 10
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 100
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1

3.加入Redission的实例Bean

package com.example.demo.study.HighConcurrency.config;

import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description: Redission分布式锁配置文件
 * @ClassName: RedissonConfig
 * @Author zhaomx
 * @Version 1.0
 * @Date 2021-06-29 17:29
 */
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

	//@Value("${spring.redis.password}")
    private String password = "";

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer().setAddress("redis://" + host + ":" + port);
        if (StringUtils.isNotBlank(password)) {
            singleServerConfig.setPassword(password);
        }
        System.out.println("------------- redisson -----------------------");
        System.out.println(config.getTransportMode());
        ;
        //适用于linux操作系统下
		//config.setTransportMode(TransportMode.EPOLL);
        return Redisson.create(config);
    }
}

4.测试类

package com.example.demo.redis;

import com.example.demo.study.HighConcurrency.service.CreateOrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 创建订单测试
 * @ClassName: CreateOrderTest
 * @Author zhaomx
 * @Version 1.0
 * @Date 2021-06-29 22:12
 */
@SpringBootTest
public class CreateOrderTest {

    @Autowired
    CreateOrderService createOrderService;

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void createOrder() throws IOException {

        String productId = "12003";
        redisTemplate.delete(productId);

        Object stock = redisTemplate.opsForHash().get(productId, "stock");
//        redisTemplate.opsForHash().put(productId, "stock", 10000);
        //核心线程数16,最大线程20,使用无界阻塞队列(注意内存溢出)的有界阻塞队列
        ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(20000));
        for (int i = 1; i < 10002; i++) {
            int finalI = i;
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    createOrderService.createOrder(String.valueOf(finalI), 1, productId);
                }
            });
        }
        executor.shutdown();

        //阻塞主线程
        System.in.read();
    }

}

5.具体实现类方法

package com.example.demo.study.HighConcurrency.service;

import com.example.demo.study.HighConcurrency.center.BadCreateOrderException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
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 org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Description: 模拟下单方法
 * @ClassName: CreateOrderService
 * @Author zhaomx
 * @Version 1.0
 * @Date 2021-06-29 18:40
 */
@Slf4j
@Service
public class CreateOrderService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(String orderSn, int number, String productId) {

        //log.info("订单编号:{}", orderSn);
        //创建订单
        //扣减库存
        deductionStock(productId, number);

    }

    /**
     * 扣减库存
     * 使用redis的原子自增方法保证扣减库存正确
     * 使用redisson分布式锁保证更新redis时 只有一个线程先去从数据库取数据更新到redis中
     * Rlock中再次判断当前商品是否已加入redis缓存
     *
     * @param productId
     * @param number
     */
    public void deductionStock(String productId, int number) {

        if (redisTemplate.opsForHash().get(productId, "stock") != null) {
            Long stock = redisTemplate.opsForHash().increment(productId, "stock", ~(number - 1));
            if (stock < 0) {
                redisTemplate.opsForHash().increment(productId, "stock", number);
                log.info("库存不足 stock:{}", stock);
                throw new BadCreateOrderException("库存不足");
            }
            if (stock == 0) {
                log.info("扣减库存成功 扣减数量:{} , 剩余数量:{}", number, stock);
            }

        } else {
            RLock rLock = redissonClient.getLock("stock_key_" + productId);
            try {
                rLock.lock();
                log.info("商品加锁 商品id:{}", productId);
                // 这里使用类似双重检测机制,比如同一时刻有5个线程要获取锁,4个等待,一个拿到了
                // 取到锁的线程执行完之后释放锁,其余等待线程会依次取到锁,为了避免重复从DB数据库查询更新缓存,这里就再判断一次
                if (redisTemplate.opsForHash().get(productId, "stock") == null) {
                    redisTemplate.opsForHash().put(productId, "stock", 10000);
                    log.info("更新redis 商品id:{} 更新后内容:{}", productId, redisTemplate.opsForHash().get(productId, "stock"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rLock.unlock();
                log.info("释放锁");
            }
            deductionStock(productId, number);
        }
    }
}

### 高并发场景下库存管理的实现与优化方案 #### 实现方案 在高并发场景下,库存管理系统需要具备高效性和一致性。通过消息队列(MQ)来处理库存更新是一种常见的方式[^1]。每当有新的订单请求到达时,系统会发送一条消息到 MQ 中,该消息包含了商品 ID 和数量等信息。随后,消费者线程从 MQ 获取这些消息并执行具体的业务逻辑,比如更新实际库存量以及调整预占库存和已扣减库存的状态。 为了保证数据的一致性,在利用 MQ 进行异步处理的同时还需要考虑事务的支持。这意味着当某条消息被消费成功之后才能确认其提交;反之,则需重新投递此消息直至完成整个流程为止。 另一种常见的做法是在进行扣减操作前先锁定资源或者采用乐观锁机制防止竞争条件的发生。具体而言就是先读取当前数据库中的记录版本号或者其他唯一标识符作为参照依据之一,在后续写入新值的时候再次校验它是否发生变化。如果没有变化就正常保存更改后的数值;如果有差异则表明期间发生了其他用户的修改行为从而触发重试逻辑直到满足预期结果位置结束循环过程[^2]。 此外还可以引入缓存层减少直接访问数据库的压力,例如 Redis 可用于存储临时性的热点商品可用数目的副本,并配合定时刷新策略保持两者之间同步关系良好运行状态下能够显著提升响应速度同时也降低了后端持久化组件负载程度达到双赢效果。 #### 优化方法 对于进一步提高性能可以采取以下几种措施: - **分布式锁**:使用像 Zookeeper 或者 etcd 提供的服务构建全局唯一的互斥信号灯用来协调多个实例间的协作避免重复计算相同的结果造成浪费现象发生。 - **批量处理**:尽可能多地积累一定时间范围内的多笔交易再统一提交给后台服务端做最终核算动作这样既可以降低网络传输成本又能增强吞吐能力表现优异度明显优于单次逐条方式。 - **分库分表设计**:按照特定规则划分成若干个小规模的数据集合分别部署于不同的物理节点之上进而分散查询压力使得整体架构更加健壮稳定不易崩溃瘫痪风险大大下降。 ```python import redis def update_stock_with_cache(product_id, quantity_to_deduct): r = redis.StrictRedis(host='localhost', port=6379, db=0) with r.pipeline() as pipe: while True: try: # Watch the key to ensure no other process modifies it during this operation. pipe.watch(f'stock:{product_id}') current_stock = int(pipe.get(f'stock:{product_id}') or 0) if current_stock >= quantity_to_deduct: new_stock = current_stock - quantity_to_deduct pipe.multi() pipe.set(f'stock:{product_id}', new_stock) pipe.execute() break return False except redis.WatchError: continue return True ``` 上述代码片段展示了如何借助 Redis 的事务特性安全地扣除指定产品的库存数量,同时确保即使面对频繁的操作也能维持较高的成功率和服务质量水平。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值