MST

星途 面试题库

面试题:分布式系统中原子性在并发操作下如何保障数据一致性

在分布式系统后端开发场景下,多个节点同时对共享数据进行并发操作。假设我们有一个银行转账的操作,从账户A向账户B转账100元,在并发环境中可能出现多个转账请求同时执行。请阐述如何利用原子性来保障该转账操作的数据一致性,避免出现A账户少了钱但B账户未收到,或者重复转账等问题,说出至少两种常见的实现方式及原理。
10.3万 热度难度
后端开发分布式系统

知识考点

AI 面试

面试题答案

一键面试

锁机制

  1. 原理:通过对共享数据(这里是账户A和账户B对应的余额数据)加锁,当一个转账请求执行时,先获取锁,其他转账请求只能等待锁释放后才能获取锁并执行。这样就保证了同一时间只有一个转账操作能对账户数据进行修改,从而避免并发问题。例如,在Java中可以使用synchronized关键字或ReentrantLock来实现锁机制。
  2. 示例
class BankAccount {
    private int balance;
    public BankAccount(int initialBalance) {
        this.balance = initialBalance;
    }
    public synchronized void transfer(BankAccount to, int amount) {
        if (this.balance >= amount) {
            this.balance -= amount;
            to.balance += amount;
        }
    }
}

数据库事务

  1. 原理:数据库事务具有原子性、一致性、隔离性和持久性(ACID特性)。在银行转账场景中,将从账户A扣钱和向账户B加钱的操作放在一个事务中。数据库会确保这个事务中的所有操作要么全部成功执行,要么全部回滚。例如,在MySQL数据库中,可以使用START TRANSACTIONCOMMITROLLBACK语句来管理事务。
  2. 示例
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
COMMIT;

分布式一致性协议(如两阶段提交协议 2PC)

  1. 原理:在分布式系统中,有一个协调者和多个参与者(这里的参与者可以是存储账户数据的不同节点)。第一阶段,协调者向所有参与者发送准备消息,参与者检查自身是否能够完成任务(如账户A余额是否足够),如果可以则回复准备就绪。第二阶段,如果所有参与者都准备就绪,协调者发送提交消息,参与者执行实际的转账操作;如果有任何一个参与者准备失败,协调者发送回滚消息,所有参与者回滚操作。
  2. 局限性:两阶段提交协议存在单点故障问题(协调者故障可能导致整个系统阻塞)、性能问题(涉及多次网络交互)等。

分布式锁(基于Redis等分布式缓存)

  1. 原理:利用Redis的单线程特性来实现分布式锁。当一个转账请求到达时,尝试在Redis中设置一个特定的键值对(例如以转账操作为标识的键),如果设置成功则表示获取到锁,该请求可以执行转账操作,操作完成后删除该键值对释放锁。如果设置失败则表示锁已被其他请求获取,该请求等待或重试。
  2. 示例(使用Jedis库在Java中实现)
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
    private Jedis jedis;
    private String lockKey;
    private String lockValue;
    public RedisDistributedLock(Jedis jedis, String lockKey) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.lockValue = java.util.UUID.randomUUID().toString();
    }
    public boolean acquireLock() {
        return "OK".equals(jedis.set(lockKey, lockValue, "NX", "EX", 10));
    }
    public void releaseLock() {
        if (lockValue.equals(jedis.get(lockKey))) {
            jedis.del(lockKey);
        }
    }
}

在转账操作时使用:

Jedis jedis = new Jedis("localhost", 6379);
RedisDistributedLock lock = new RedisDistributedLock(jedis, "transfer_lock");
if (lock.acquireLock()) {
    // 执行转账操作
    lock.releaseLock();
}