一、Redis 分布式锁(集群环境使用)
依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.20.0</version>
</dependency>
1)配置 Redisson
spring:
redis:
host: localhost
port: 6379
2)Service 完整代码
@Service
public class OrderService {
@Autowired
private ProductMapper productMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private RedissonClient redissonClient;
private static final int MAX_RETRY = 3;
@Transactional
public void createOrderWithRedisLock(Long userId, Long productId) {
// 锁 key:每个商品一把锁
String lockKey = "lock:product:" + productId;
RLock lock = redissonClient.getLock(lockKey);
boolean lockSuccess = false;
try {
// 尝试加锁,最多等待 3s,锁 10s 自动释放
lockSuccess = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!lockSuccess) {
throw new RuntimeException("系统繁忙,请稍后再试");
}
// 查库存
Product product = productMapper.selectById(productId);
if (product.getStock() <= 0) {
throw new RuntimeException("库存不足");
}
// 扣库存(乐观锁也可以一起用)
product.setStock(product.getStock() - 1);
int rows = productMapper.updateById(product);
if (rows == 0) {
throw new RuntimeException("并发更新失败");
}
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
orderMapper.insert(order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("系统异常");
} finally {
if (lockSuccess && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
二、秒杀高并发:Redis 预扣库存
思路:
- 库存放 Redis
- 用
decr原子扣减 - 扣成功再异步写入数据库
1)初始化库存到 Redis
// 项目启动或后台上架时调用
public void initStockToRedis(Long productId, int stock) {
redisTemplate.opsForValue().set("stock:" + productId, stock);
}
2)秒杀核心代码(极快)
@Service
public class SeckillService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate; // 或 MQ
public void secKill(Long userId, Long productId) {
String stockKey = "stock:" + productId;
// 原子减 1
Long remain = redisTemplate.opsForValue().decrement(stockKey);
if (remain < 0) {
// 恢复回去,避免负数
redisTemplate.opsForValue().increment(stockKey);
throw new RuntimeException("已抢完");
}
// 发送 MQ 异步创建订单 + 扣数据库库存
SecKillMessage msg = new SecKillMessage();
msg.setUserId(userId);
msg.setProductId(productId);
rabbitTemplate.convertAndSend("seckill.exchange", "seckill.key", msg);
}
}
3)MQ 消费者异步落库
@Component
@RabbitListener(queues = "seckill.queue")
public class SecKillConsumer {
@Autowired
private ProductMapper productMapper;
@Autowired
private OrderMapper orderMapper;
@Transactional
public void consume(SecKillMessage msg) {
Long productId = msg.getProductId();
Long userId = msg.getUserId();
Product product = productMapper.selectById(productId);
if (product.getStock() <= 0) {
return;
}
// 数据库扣库存
product.setStock(product.getStock() - 1);
productMapper.updateById(product);
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
orderMapper.insert(order);
}
}
三、事务失效高频坑(必看)
1)同一个类内调用方法,事务失效 ❌
@Service
public class OrderService {
// Controller 调用这个
public void create() {
doCreateOrder(); // 同类内直接调用 → 不经过代理 → 事务失效
}
@Transactional
public void doCreateOrder() {
// 扣库存
// 下单
}
}
2)正确写法 ✅
方式 A:注入自己
@Autowired
private OrderService self;
public void create() {
self.doCreateOrder(); // 走代理
}
方式 B:方法直接加 @Transactional
3)try-catch 吞异常,事务不回滚 ❌
@Transactional
public void test() {
try {
// 异常
} catch (Exception e) {
// 事务不会回滚
}
}
4)正确 ✅
@Transactional
public void test() {
try {
} catch (Exception e) {
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw e;
}
}
四、四种方案怎么选?
- 普通并发、单服务:乐观锁
- 集群、多实例:Redisson 分布式锁
- 秒杀、高并发:Redis 预扣 + MQ 异步
- 最稳妥生产:Redis 分布式锁 + 乐观锁 + 唯一索引
&spm=1001.2101.3001.5002&articleId=160025933&d=1&t=3&u=a1d4639d84a9453ba0d432f5d3c590e0)
146

被折叠的 条评论
为什么被折叠?



