面试题答案
一键面试架构设计层面
- 引入消息队列:
- 在下单成功后,不直接扣减库存,而是发送一条包含订单信息的消息到消息队列(如 RocketMQ)。库存服务从消息队列中消费消息来扣减库存。这样可以将下单和扣减库存的操作异步化,避免因为库存服务的性能问题或网络抖动影响下单操作的高并发性能。
- 例如,订单服务在订单创建成功后,使用 RocketMQ 的生产者发送消息:
@Autowired private RocketMQTemplate rocketMQTemplate; // 假设 order 是订单对象 rocketMQTemplate.send("stock - topic", MessageBuilder.withPayload(order).build());
- 库存服务作为消费者监听该主题:
@RocketMQMessageListener(topic = "stock - topic", consumerGroup = "stock - consumer - group") @Component public class StockConsumer implements RocketMQListener<Order> { @Override public void onMessage(Order order) { // 执行扣减库存逻辑 } }
- 使用分布式事务框架增强:
- 对于分布式事务,可以考虑使用 Seata 等框架来增强事务的可靠性。Seata 提供了 AT、TCC、SAGA 等模式。在电商场景中,AT 模式相对简单易用。
- 配置 Seata Server,在各个服务(订单服务、库存服务等)中引入 Seata 的依赖,并配置相关参数。例如,在订单服务的
application.yml
中:
seata: tx - service - group: my_test_tx_group service: vgroup - mapping: my_test_tx_group: default grouplist: default: 127.0.0.1:8091
- 在业务代码中,通过
@GlobalTransactional
注解来标识一个全局事务。例如:
@Service public class OrderService { @GlobalTransactional public void createOrder(Order order) { // 下单逻辑 // 调用库存服务扣减库存逻辑 } }
配置优化层面
- 消息队列配置优化:
- 调整消息队列的参数,如增加队列数量、调整消费者线程数等。以 RocketMQ 为例,在
broker.conf
中可以配置defaultTopicQueueNums
来增加默认主题的队列数量,提高消息的并行处理能力。 - 在消费者端,可以通过
@RocketMQMessageListener
注解的consumeThreadMin
和consumeThreadMax
参数来调整消费者线程数。例如:
@RocketMQMessageListener(topic = "stock - topic", consumerGroup = "stock - consumer - group", consumeThreadMin = 10, consumeThreadMax = 20) @Component public class StockConsumer implements RocketMQListener<Order> { @Override public void onMessage(Order order) { // 执行扣减库存逻辑 } }
- 调整消息队列的参数,如增加队列数量、调整消费者线程数等。以 RocketMQ 为例,在
- Seata 配置优化:
- 调整 Seata Server 的配置参数,如事务日志的存储方式、存储位置等。可以将事务日志存储在高性能的存储介质上,提高事务日志的写入和读取效率。
- 在 Seata Server 的
file.conf
中,可以配置事务日志存储:
store { mode = "file" file { dir = "sessionStore" max - commit - log - size = 16384 max - rollback - log - size = 1024 } }
- 也可以考虑将存储模式切换为
db
,使用数据库存储事务日志,以提高可靠性和性能。
代码层面
- 幂等性处理:
- 在库存扣减服务中实现幂等性。可以通过数据库唯一索引或业务单号等方式来保证。例如,在扣减库存的 SQL 语句中使用
INSERT... ON DUPLICATE KEY UPDATE
语句。假设库存表结构为stock(id, product_id, quantity)
,扣减库存的 SQL 可以如下:
INSERT INTO stock_log(product_id, quantity, operation_type, create_time) VALUES(?,?, 'decrease', NOW()) ON DUPLICATE KEY UPDATE quantity = quantity - VALUES(quantity);
- 在代码中,对于库存扣减接口,每次调用时先根据订单号或相关业务标识查询是否已经执行过扣减操作,如果已经执行过则直接返回成功。
@Service public class StockService { @Autowired private StockMapper stockMapper; @Autowired private StockLogMapper stockLogMapper; public boolean decreaseStock(Order order) { StockLog stockLog = stockLogMapper.findByOrderId(order.getOrderId()); if (stockLog!= null) { return true; } boolean result = stockMapper.decreaseStock(order.getProductId(), order.getQuantity()); if (result) { stockLogMapper.insert(new StockLog(order.getOrderId(), order.getProductId(), order.getQuantity(), "decrease")); } return result; } }
- 在库存扣减服务中实现幂等性。可以通过数据库唯一索引或业务单号等方式来保证。例如,在扣减库存的 SQL 语句中使用
- 重试机制:
- 在消息队列消费失败或库存扣减失败时,引入重试机制。对于消息队列消费,可以利用 RocketMQ 的重试策略,默认情况下,消息消费失败后会进行 16 次重试,每次重试间隔逐渐增加。
- 也可以在代码中手动实现重试逻辑。例如,在库存扣减服务中:
@Service public class StockService { private static final int MAX_RETRIES = 3; @Autowired private StockMapper stockMapper; public boolean decreaseStock(Order order) { int retryCount = 0; while (retryCount < MAX_RETRIES) { try { return stockMapper.decreaseStock(order.getProductId(), order.getQuantity()); } catch (Exception e) { retryCount++; if (retryCount >= MAX_RETRIES) { throw e; } try { Thread.sleep(1000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } return false; } }
- 补偿机制:
- 建立补偿服务,定期扫描订单状态和库存状态不一致的数据。例如,可以通过定时任务查询订单表中已下单但库存扣减状态为失败的数据,重新发起库存扣减请求。
@Component @Scheduled(cron = "0 0/5 * * *?") public class CompensationTask { @Autowired private OrderMapper orderMapper; @Autowired private StockService stockService; public void compensate() { List<Order> orders = orderMapper.findOrdersWithFailedStockDecrease(); for (Order order : orders) { stockService.decreaseStock(order); } } }