面试题答案
一键面试分布式事务解决方案选择 - Saga
- 选择原因:XA 方案对资源管理器要求高,性能开销大;TCC 实现复杂,业务侵入性强。Saga 模式适合长事务场景,将长事务分解为多个本地事务,通过补偿机制保证最终一致性,对业务侵入相对较小,符合微服务架构特点。
具体实现步骤
- 定义Saga事务步骤:在 Kotlin 中,为每个业务操作定义 Saga 步骤。例如,假设有订单服务和库存服务,订单创建和库存扣减为两个步骤。
data class OrderSagaStep( val orderId: String, val amount: Int )
- 创建Saga协调器:使用 Spring Cloud 的编排或Choreography方式管理 Saga 事务。以编排方式为例,创建一个 Saga 协调器服务。
@Service class OrderSagaCoordinator { @Autowired private lateinit var orderService: OrderService @Autowired private lateinit var inventoryService: InventoryService fun createOrderWithInventory(orderSagaStep: OrderSagaStep) { try { // 执行订单创建 orderService.createOrder(orderSagaStep.orderId, orderSagaStep.amount) // 执行库存扣减 inventoryService.decreaseInventory(orderSagaStep.orderId, orderSagaStep.amount) } catch (e: Exception) { // 出现异常,进行补偿 inventoryService.increaseInventory(orderSagaStep.orderId, orderSagaStep.amount) orderService.cancelOrder(orderSagaStep.orderId) } } }
- 集成到Spring Cloud:将 Saga 协调器和相关服务注册到 Eureka 等服务发现组件。
在每个服务的启动类加上eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
@EnableEurekaClient
注解。
面临的挑战及解决方法
- 并发控制:
- 挑战:多个 Saga 实例可能同时操作相同资源,导致数据不一致。
- 解决方法:使用分布式锁(如 Redis 分布式锁)。在执行 Saga 步骤前获取锁,执行完释放锁。
@Autowired private lateinit var stringRedisTemplate: StringRedisTemplate fun createOrderWithInventory(orderSagaStep: OrderSagaStep) { val lockKey = "order:${orderSagaStep.orderId}:lock" val lockValue = UUID.randomUUID().toString() try { // 获取锁 val isLockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue) if (isLockAcquired) { // 执行事务步骤 } else { // 未获取到锁,等待或重试 } } finally { // 释放锁 if (lockValue == stringRedisTemplate.opsForValue().get(lockKey)) { stringRedisTemplate.delete(lockKey) } } }
- 网络故障:
- 挑战:在 Saga 执行过程中,网络故障可能导致部分步骤未执行或补偿不完整。
- 解决方法:引入事件驱动机制(如 RabbitMQ),记录 Saga 步骤执行状态。当网络恢复,根据状态继续执行或补偿。
@Autowired private lateinit var rabbitTemplate: RabbitTemplate fun createOrderWithInventory(orderSagaStep: OrderSagaStep) { try { // 执行订单创建 orderService.createOrder(orderSagaStep.orderId, orderSagaStep.amount) // 发送库存扣减消息 rabbitTemplate.convertAndSend("inventoryExchange", "decreaseInventory", orderSagaStep) } catch (e: Exception) { // 发送补偿消息 rabbitTemplate.convertAndSend("orderExchange", "cancelOrder", orderSagaStep.orderId) } }
- 数据一致性验证:
- 挑战:如何确保最终所有相关数据是一致的。
- 解决方法:定期进行数据对账。例如,通过定时任务查询订单和库存数据,比对数量是否匹配,若不匹配则进行修复。
@Scheduled(fixedRate = 60000) fun reconcileData() { val allOrders = orderService.getAllOrders() allOrders.forEach { order -> val inventoryAmount = inventoryService.getInventoryAmount(order.orderId) if (order.amount != inventoryAmount) { // 进行数据修复 } } }