且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Cloud Firestore 文档锁定

更新时间:2023-02-14 09:55:24

悲观与乐观

Cloud Firestore 中没有本机独占锁定.由于该系统旨在运行在大型分布式系统(例如,数千名手机用户或 Kubernetes 集群)中,因此它基于乐观锁定模式.

这意味着当你做一个读写事务时,如果你读的文档在你提交事务之前被写入,事务就会失败,客户端可以回滚.

构建您自己的 - 移动 SDK 和安全规则

我将假设您一开始使用的是移动 SDK,并将在下一部分介绍服务器客户端.

您可以使用单独的文档在此之上构建排他锁.例如,假设您要对集合 critical_data 中的文档实施排他锁定.

为此,我们将使用一个名为 mutex_critical_data 的单独集合,其中的文档是集合 critical_data 中具有相同 id 的文档的互斥锁.>

在您可以访问 critical_data 中名为 doc_id 的文档之前,您需要执行写入事务以将互斥字段 owner 设置为你.我假设您正在使用 Firebase 身份验证,因此您 == 用户的身份验证 ID auth_id.这可以是唯一标识用户或进程的任何 ID.

完成文档后,删除互斥文档,以便其他人可以使用它.

为确保它是独占的并且其他人无法窃取它,您需要在安全规则定义中添加一些检查.

//在匹配部分将文档 id 设置为 'doc_id'函数 mutex_exists(){返回存在(/databases/$(database)/documents/mutex_critical_data/$(doc_id));}函数 mutex_owner(){返回 get(/databases/$(database)/documents/mutex_critical_data/$(doc_id)).data;}函数 user_owns_mutex() {返回 mutex_owner().owner == request.auth.uid;}允许写:如果不是(互斥量存在())||user_owns_mutex;

您也可以使用规则来强制执行互斥锁,方法是通过同时拥有互斥锁来预测写入资源.

构建自己的 - 服务器客户端

请记住,安全规则适用于移动/网络 SDK 访问,不适用于服务器客户端.由于服务器客户端被认为是受信任的环境,而不是通过规则强制执行排他性逻辑,您需要在互斥锁上的读写事务中进行检查.

租约不是锁

最后一点,如果你构建这个,我强烈建议你研究独占租约而不是独占锁.

租约就像一把锁,但如果承租人没有(或不允许)在设定的时间之前续订租约,它就会自动到期.这意味着如果客户端没有回来(例如,客户端崩溃),其他人最终将能够在没有管理员操作的情况下获得租用.

概念是一样的,但不仅仅是设置所有者,您还可以在字段中设置租用时间.如果租约大于x old,其中x为租约的时间长度,则认为不再由所有者持有.在租约到期之前,您可以选择通过设置新的租约时间来允许所有者续租.

To ensure exclusive use of a Cloud Firestore document in a shared setting, is it sufficient to read, then write to a field (eg 'lockedBy') within a transaction?

Pessimistic vs Optimistic

There is no native exclusive locking in Cloud Firestore. As the system is designed to run in large distributed systems (e.g, 1000's of mobile phone users or a Kubernetes cluster), it is based around optimistic locking patterns.

This means that when you do a read-write transaction, if the document you read is written to before you can commit the transaction, the transaction will fail so the client can rollback.

Building your own - Mobile SDKs & Security Rules

I'm going to assume you're coming in from a mobile SDK to start with, and will address server clients in the next section.

You can build an exclusive lock on top of this by using a separate document. For example, say you want to implement exclusive locking on documents in collection critical_data.

For this, we're going to use a separate collection called mutex_critical_data, with the documents inside being mutexes for documents with the same id in collection critical_data.

Before you can access a document called doc_id in critical_data you'll want to perform a write transaction to set the mutex field owner to you. I'll assume you're using Firebase Auth so you == the user's auth id auth_id. This can be any id that uniquely identifies the user or process though.

Once you are finished with the document, delete the mutex document so others can use it.

To ensure it's exclusive and other people cannot steal it, you'll want to add some checks in your security rules definition.

// In the match section that sets the document id to 'doc_id'
function mutex_exists ()
{
    return exists(/databases/$(database)/documents/mutex_critical_data/$(doc_id));
}

function mutex_owner ()
{
    return get(/databases/$(database)/documents/mutex_critical_data/$(doc_id)).data;
}

function user_owns_mutex () {
    return mutex_owner().owner == request.auth.uid;
}

allow write: if not(mutex_exists()) || user_owns_mutex;

You can also use rules to enforce the mutex as well, by predicating writing to the resource by also owning the mutex.

Building your own - Server Clients

Keep in mind that security rules are for Mobile/Web SDK access and aren't used for server clients. As server clients are considered a trusted environment, rather than having the exclusivity logic enforced by rules, you'll want to do the check in the read-write transaction on the mutex.

Leases not Locks

Last point if you build this, is I'd highly recommend looking into exclusive leases rather than exclusive locks.

A lease is like a lock, but it automatically expires if the leasee doesn't (or isn't allowed) to renew the lease before a set time. This means if the client doesn't come back (e.g, client crashes), someone else will eventually be able to obtain the lease without administrator action.

The concept is the same, but rather than setting just the owner, you also set the lease time in a field. If the lease is greater than x old, where x is the time length of the lease, it is consider to no longer be held by the owner. Before the lease expires, you can optionally allow the owner to renew the lease by setting a new lease time.