MST
星途 面试题库

面试题:微服务架构下Spring Cloud Alibaba分布式事务优化与实践难题

在实际项目中,使用Spring Cloud Alibaba的分布式事务解决方案时,可能会遇到性能瓶颈、网络波动导致事务回滚不一致等问题。假设你正在处理一个高并发的电商下单场景,已经出现了由于网络抖动导致部分订单事务提交成功但库存扣减失败的情况,你会如何从架构设计、配置优化以及代码层面去解决这些问题,确保数据的最终一致性?
41.8万 热度难度
后端开发微服务架构

知识考点

AI 面试

面试题答案

一键面试

架构设计层面

  1. 引入消息队列
    • 在下单成功后,不直接扣减库存,而是发送一条包含订单信息的消息到消息队列(如 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) {
            // 执行扣减库存逻辑
        }
    }
    
  2. 使用分布式事务框架增强
    • 对于分布式事务,可以考虑使用 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) {
            // 下单逻辑
            // 调用库存服务扣减库存逻辑
        }
    }
    

配置优化层面

  1. 消息队列配置优化
    • 调整消息队列的参数,如增加队列数量、调整消费者线程数等。以 RocketMQ 为例,在 broker.conf 中可以配置 defaultTopicQueueNums 来增加默认主题的队列数量,提高消息的并行处理能力。
    • 在消费者端,可以通过 @RocketMQMessageListener 注解的 consumeThreadMinconsumeThreadMax 参数来调整消费者线程数。例如:
    @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) {
            // 执行扣减库存逻辑
        }
    }
    
  2. Seata 配置优化
    • 调整 Seata Server 的配置参数,如事务日志的存储方式、存储位置等。可以将事务日志存储在高性能的存储介质上,提高事务日志的写入和读取效率。
    • 在 Seata Server 的 file.conf 中,可以配置事务日志存储:
    store {
      mode = "file"
      file {
        dir = "sessionStore"
        max - commit - log - size = 16384
        max - rollback - log - size = 1024
      }
    }
    
    • 也可以考虑将存储模式切换为 db,使用数据库存储事务日志,以提高可靠性和性能。

代码层面

  1. 幂等性处理
    • 在库存扣减服务中实现幂等性。可以通过数据库唯一索引或业务单号等方式来保证。例如,在扣减库存的 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;
        }
    }
    
  2. 重试机制
    • 在消息队列消费失败或库存扣减失败时,引入重试机制。对于消息队列消费,可以利用 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;
        }
    }
    
  3. 补偿机制
    • 建立补偿服务,定期扫描订单状态和库存状态不一致的数据。例如,可以通过定时任务查询订单表中已下单但库存扣减状态为失败的数据,重新发起库存扣减请求。
    @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);
            }
        }
    }