对于 Java 工程师而言,从 CRUD 开发者进阶为架构师,关键在于能否驾驭“企业级分布式”场景。高并发、高可用、数据一致性是面试中的必考题,也是决定薪资分水岭的核心技能。
本文将基于一个典型的 “秒杀抢购 + 订单处理” 业务场景,抽丝剥茧,通过实战代码演示三大核心方案:分布式锁解决超卖、MQ 异步解耦削峰、以及分布式事务保证数据一致性。
一、 高并发库存扣减:Redis 分布式锁方案
在秒杀场景下,大量用户同时抢购同一商品。如果不加锁,会导致库存超卖(卖出的数量 > 实际库存);如果直接用数据库行锁,数据库会瞬间崩溃。
方案核心:利用 Redis 的 SETNX(Set if Not Exists)特性实现互斥锁,并设置合理的过期时间防止死锁。为了解决业务执行时间超过锁过期时间的问题,我们引入**“看门狗”机制**自动续期。
1. Redis 分布式锁核心实现(Lua 脚本 + 看门狗)
java
复制
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class RedisLockService {
private final StringRedisTemplate redisTemplate;
// 锁续期线程
private Thread renewThread;
private volatile boolean isRenew = false;
public RedisLockService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 加锁 Lua 脚本:保证原子性
* KEYS[1]: 锁的 key
* ARGV[1]: 锁的 value (UUID)
* ARGV[2]: 过期时间 (毫秒)
*/
private static final String LOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('pexpire', KEYS[1], ARGV[2]); " +
"else " +
" return redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2], 'NX'); " +
"end";
/**
* 尝试加锁(带看门狗自动续期)
*/
public boolean tryLock(String key, long expireTime, long waitTime) {
String value = UUID.randomUUID().toString();
long start = System.currentTimeMillis();
while (true) {
// 执行加锁
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expireTime));
if (result != null && result == 1) {
// 加锁成功,启动后台线程自动续期
startRenewThread(key, value, expireTime);
return true;
}
// 超时判断
if (System.currentTimeMillis() - start > waitTime) {
return false;
}
try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
/**
* 启动看门狗线程:每隔 expireTime/3 时间续期一次
*/
private void startRenewThread(String key, String value, long expireTime) {
renewThread = new Thread(() -> {
while (isRenew) {
try {
Thread.sleep(expireTime / 3);
// 简单的续期逻辑:如果锁还是自己的,就重置时间
Boolean isSuccess = redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS);
if (!isSuccess) break;
} catch (InterruptedException e) {
break;
}
}
});
isRenew = true;
renewThread.setDaemon(true);
renewThread.start();
}
/**
* 释放锁 Lua 脚本:保证只有锁的持有者才能释放
*/
private static final String UNLOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]); " +
"else " +
" return 0; " +
"end";
/**
* 释放锁
*/
public void unlock(String key, String value) {
isRenew = false; // 停止续期
if (renewThread != null) renewThread.interrupt();
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
redisTemplate.execute(redisScript, Collections.singletonList(key), value);
}
}
二、 流量削峰与解耦:RabbitMQ 延时队列实战
用户下单后,系统不仅要扣减库存,还要发送短信、增加积分、更新物流等。如果同步执行,响应时间极长且系统脆弱。
方案核心:使用 RabbitMQ 将“下单”与“后续处理”解耦。特别演示 “订单超时取消” 功能,利用死信队列(DLX)+ TTL 实现延时任务。
2. 消息配置与生产者
java
复制
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OrderMqConfig {
public static final String ORDER_EXCHANGE = "order.exchange";
public static final String ORDER_QUEUE = "order.queue";
public static final String ORDER_DELAY_EXCHANGE = "order.delay.exchange";
public static final String ORDER_DELAY_QUEUE = "order.delay.queue";
@Autowired
private RabbitTemplate rabbitTemplate;
// 1. 定义普通业务交换机与队列
@Bean
public DirectExchange orderExchange() {
return new DirectExchange(ORDER_EXCHANGE);
}
@Bean
public Queue orderQueue() {
return QueueBuilder.durable(ORDER_QUEUE).build();
}
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue()).to(orderExchange()).with("order.success");
}
// 2. 定义延时队列 (基于 Dead Letter Exchange)
@Bean
public DirectExchange delayExchange() {
return new DirectExchange(ORDER_DELAY_EXCHANGE);
}
@Bean
public Queue delayQueue() {
return QueueBuilder.durable(ORDER_DELAY_QUEUE)
.withArgument("x-message-ttl", 30000) // 消息存活30秒
.withArgument("x-dead-letter-exchange", ORDER_EXCHANGE) // 过期后转发到业务交换机
.withArgument("x-dead-letter-routing-key", "order.timeout") // 路由键
.build();
}
@Bean
public Binding delayBinding() {
return BindingBuilder.bind(delayQueue()).to(delayExchange()).with("order.create");
}
// 发送延时消息
public void sendDelayOrder(String orderId) {
rabbitTemplate.convertAndSend(ORDER_DELAY_EXCHANGE, "order.create", orderId);
System.out.println("订单 " + orderId + " 已发送,30分钟后检查支付状态");
}
}
三、 分布式事务:Seata TCC 模式实战
在微服务架构中,订单服务和库存服务属于不同的数据库。本地事务无法解决跨库一致性问题。
方案核心:使用 Seata 的 TCC(Try-Confirm-Cancel)模式。
- Try:预留资源(库存冻结,余额冻结)。
- Confirm:确认执行(库存扣减,余额扣款)。
- Cancel:取消执行(库存回滚,余额解冻)。
3. 库存服务 TCC 接口实现
java
复制
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
@LocalTCC
public interface StockTccService {
/**
* Try 阶段:冻结库存
* @TwoPhaseBusinessAction: 定义 TCC 的名称与提交/回滚方法名
*/
@TwoPhaseBusinessAction(name = "stockTccAction", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepareDeductStock(
@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "count") Integer count,
@BusinessActionContextParameter(paramName = "orderId") String orderId);
/**
* Confirm 阶段:真正扣减库存(删除冻结记录)
*/
boolean commit(BusinessActionContext context);
/**
* Cancel 阶段:释放冻结库存(恢复可用库存)
*/
boolean rollback(BusinessActionContext context);
}
// --- 实现类 ---
@Service
public class StockTccServiceImpl implements StockTccService {
@Autowired
private StockMapper stockMapper;
@Autowired
private StockFreezeMapper stockFreezeMapper;
@Override
@Transactional
public boolean prepareDeductStock(Long productId, Integer count, String orderId) {
// 1. 检查剩余库存是否足够
Stock stock = stockMapper.selectById(productId);
if (stock.getTotalStock() - stock.getFrozenStock() < count) {
throw new RuntimeException("库存不足");
}
// 2. 增加冻结库存
stockMapper.increaseFrozenStock(productId, count);
// 3. 记录冻结日志(用于 Cancel 阶段幂等校验)
StockFreeze freeze = new StockFreeze(orderId, productId, count);
stockFreezeMapper.insert(freeze);
return true;
}
@Override
@Transactional
public boolean commit(BusinessActionContext context) {
String orderId = context.getActionContext("orderId").toString();
Long productId = Long.parseLong(context.getActionContext("productId").toString());
Integer count = Integer.parseInt(context.getActionContext("count").toString());
// 幂等性检查:如果冻结记录不存在,说明已经 Commit 过了
StockFreeze freeze = stockFreezeMapper.selectByOrderId(orderId);
if (freeze == null) return true;
// 1. 扣减真实库存
stockMapper.deductStock(productId, count);
// 2. 减少冻结库存
stockMapper.decreaseFrozenStock(productId, count);
// 3. 删除冻结记录
stockFreezeMapper.deleteByOrderId(orderId);
return true;
}
@Override
@Transactional
public boolean rollback(BusinessActionContext context) {
String orderId = context.getActionContext("orderId").toString();
Long productId = Long.parseLong(context.getActionContext("productId").toString());
Integer count = Integer.parseInt(context.getActionContext("count").toString());
// 幂等性检查
StockFreeze freeze = stockFreezeMapper.selectByOrderId(orderId);
if (freeze == null) return true; // 已回滚或未冻结
// 1. 减少冻结库存(恢复)
stockMapper.decreaseFrozenStock(productId, count);
// 2. 删除冻结记录
stockFreezeMapper.deleteByOrderId(orderId);
return true;
}
}
四、 总结:涨薪的关键在于体系化思维
上述代码不仅仅是 API 的调用,背后蕴含的是企业级开发的体系化思维:
- 资源隔离与互斥:Redis 分布式锁解决了并发竞争问题,看门狗机制体现了对边界情况的考量。
- 异步与解耦:RabbitMQ 延时队列将复杂的业务逻辑在时间与空间上拆分,提升了系统的吞吐量与容错性。
- 一致性保证:TCC 模式虽然开发复杂,但它提供了最强的数据一致性保证,适用于资金、库存等核心敏感业务。
掌握这些方案,不仅能在面试中对答如流,更能让你在实际架构设计中游刃有余。这才是 Java 工程师突破薪资瓶颈的必由之路。