通过改变业务模型的预留模式避免分布式事务 - CodeOpinion


长时间运行的业务流程可能会持续几秒钟到几天,您无法使用分布式事务锁定服务中的资源。那么有什么选择呢?现实世界有一个解决方案,它是一种预订保留。
预订模式允许您获得有时限的有限保证,允许您与其他服务进行协调。

预订模式一直在现实世界中使用。我们可以利用代码中的模式为长期运行的业务流程在服务之间提供有时限的有限保证。这是一个可以过期的有限锁。

例如我在网上下了一个取货订单。该物品是为我保留的,这可以防止我去商店浪费时间并且该物品不可用。当员工从货架上拿下物品时,该预订有 7 天时间让我取货,否则该预订将过期,其他人可以购买。
有时您只需要在现实世界(业务领域)中寻找解决方案。

预订的一个重要方面是有时间限制。您会在电子邮件中注意到,如果我没有在 7 天内取货,他们将退还我的信用卡并将商品放回货架上。
他们正在为我的物品提供锁定/保留,该锁定将在 7 天内到期。
当我去当地商店取货时,这是确认我的预订。

预订模式
我们可以在代码中实现预订模式来解决类似类型的问题。在用户注册的示例中,我们可以在注册过程开始时在用户名上创建预订。一旦用户完成用户注册过程,我们就可以确认预订。
如果用户从未完成注册过程,则用户名将在我们定义的时间后过期,让其他人尝试注册相同的用户名。
预订模式有 3 个重要方面。保留、确认、到期。
为了首先在代码中说明这一点,我将首先展示同步版本,然后是异步版本,这通常更适用。
用户注册首先检查用户名的保留是否存在。如果没有,它将创建一个新帐户,将其保存到我们的数据库中,然后告诉预订它已完成。

public class UserRegistration
{
private static int _testCount = 0;
private readonly UsernameReservationSync _reservation;
private readonly IUserRepository _repository;

public UserRegistration(UsernameReservationSync reservation, IUserRepository repository)
{
    _reservation = reservation;
    _repository = repository;
}

public bool Register(string username)
{
    if (_reservation.Reserve(username) == false)
    {
        return false;
    }

    // For testing to show the expiry
    if (username ==
"test" && _testCount == 0)
    {
        _testCount++;
        return false;
    }

    var account = new Account(username);
    _repository.Add(account);
    _repository.Save();

    if (_reservation.Complete(username) == false)
    {
        _repository.Remove(account);
        return false;
    }

    return true;
}

预留本身具有预留、完成的能力。在内部 5 秒后,如果仍然保留,它将使用户名过期。这是一个示例,没有使用真实的数据库,也没有像在真实的生产环境中那样处理并发。

public class UsernameReservationSync
{
    private TimeSpan Timeout => TimeSpan.FromSeconds(5);
    private readonly FakeDatabase _db;

    public UsernameReservationSync(FakeDatabase db)
    {
        _db = db;
    }

    public bool Reserve(string username)
    {
        if (_db.RegisteredUsernames.Any(x => x == username))
        {
            return false;
        }
        if (_db.ReservedUsernames.Any(x => x == username))
        {
            return false;
        }

        _db.ReservedUsernames.Add(username);

        Task.Run(async () =>
        {
            await Task.Delay(Timeout);
            Expire(username);
        });

        return true;
    }

    private void Expire(string username)
    {
        _db.ReservedUsernames.Remove(username);
    }

    public bool Complete(string username)
    {
        if (_db.ReservedUsernames.Any(x => x == username) == false)
        {
            return false;
        }
        if (_db.RegisteredUsernames.Any(x => x == username))
        {
            return false;
        }

        _db.ReservedUsernames.Remove(username);
        _db.RegisteredUsernames.Add(username);

        return true;
    }
}

详细点击标题