高并发下扣减库存及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);
}
}
}