面试题答案
一键面试选择 Saga 分布式事务解决方案
-
选择原因:Saga 模式适合长流程的事务,在微服务架构中,复杂业务流程可能涉及多个服务依次调用,Saga 通过将长事务分解为多个本地事务,每个本地事务对应一个 Saga 步骤,按顺序执行,若某一步骤失败,则执行相应的补偿操作,恢复到事务开始前的状态。相比 TCC,Saga 对业务侵入性相对较小,更适合复杂微服务架构。
-
Kotlin 代码实现示例: 假设我们有两个微服务,订单服务和库存服务,创建订单时需要扣减库存。
订单服务:
// 创建订单本地事务
fun createOrder(order: Order): Boolean {
// 这里是实际创建订单的数据库操作,简化示例假设成功返回true
return true
}
// 取消订单补偿操作
fun cancelOrder(orderId: String): Boolean {
// 这里是实际取消订单的数据库操作,简化示例假设成功返回true
return true
}
库存服务:
// 扣减库存本地事务
fun deductStock(productId: String, quantity: Int): Boolean {
// 这里是实际扣减库存的数据库操作,简化示例假设成功返回true
return true
}
// 恢复库存补偿操作
fun restoreStock(productId: String, quantity: Int): Boolean {
// 这里是实际恢复库存的数据库操作,简化示例假设成功返回true
return true
}
Saga 协调器:
class OrderSagaCoordinator {
fun processOrder(order: Order) {
try {
if (!createOrder(order)) {
throw RuntimeException("Create order failed")
}
if (!deductStock(order.productId, order.quantity)) {
cancelOrder(order.id)
throw RuntimeException("Deduct stock failed")
}
} catch (e: Exception) {
// 异常处理,记录日志等
e.printStackTrace()
}
}
}
高并发方面的挑战及应对策略
- 挑战:高并发情况下,多个 Saga 实例同时操作共享资源可能导致数据竞争,例如多个订单同时扣减同一库存,可能出现超卖现象。
- 应对策略:
- 使用分布式锁:可以借助 Redis 等分布式缓存实现分布式锁。在扣减库存等关键操作前获取锁,操作完成后释放锁。
import redis.clients.jedis.Jedis fun withDistributedLock(lockKey: String, action: () -> Unit) { val jedis = Jedis("localhost", 6379) try { val lock = jedis.set(lockKey, "locked", "NX", "EX", 10) if ("OK" == lock) { action() } } finally { jedis.del(lockKey) jedis.close() } }
- 乐观锁:在数据库层面,对库存表等资源表增加版本号字段。每次更新操作时,先读取版本号,更新时带上版本号并检查版本号是否匹配,若匹配则更新成功并递增版本号,否则重试。
系统可用性方面的挑战及应对策略
- 挑战:在 Saga 执行过程中,如果某个微服务出现故障,可能导致整个 Saga 流程中断,影响系统可用性。另外,补偿操作也可能因为服务故障而失败。
- 应对策略:
- 服务熔断与降级:使用 Hystrix 等工具实现服务熔断。当某个微服务调用失败次数达到一定阈值时,熔断器打开,后续请求直接返回默认值或错误提示,避免长时间等待故障服务响应。
- 重试机制:对于微服务调用失败的情况,设置合理的重试策略。例如使用 Spring Retry 库,在 Kotlin 中可以这样配置:
@Retryable(value = [Exception::class], maxAttempts = 3, backoff = Backoff(delay = 1000)) fun deductStock(productId: String, quantity: Int): Boolean { // 实际扣减库存操作 }
- 异步补偿:将补偿操作异步化,通过消息队列等方式发送补偿任务,即使某个服务暂时不可用,补偿任务也不会丢失,待服务恢复后继续处理。