在 Azure 存储中管理并发Managing Concurrency in Azure Storage

通常情况下,基于 Internet 的新型应用程序允许多名用户同时查看和更新数据。Modern Internet-based applications typically have multiple users viewing and updating data simultaneously. 应用程序开发人员需要认真考虑如何提供可预测的用户体验,尤其是在多个用户可以更新相同数据的情况下。Application developers need to think carefully about providing a predictable user experience, particularly where multiple users can update the same data. 开发人员通常考虑下面三个主要数据并发策略:There are three main data concurrency strategies that developers typically consider:

  1. 乐观并发 - 执行更新的应用程序在更新过程中会验证数据是否自该应用程序上次读取该数据起已发生更改。Optimistic concurrency - An application doing an update will, as part of its update, verify if the data has changed since the application last read that data. 例如,如果两名查看 wiki 页的用户对同一页进行更新,则 wiki 平台必须确保第二次更新不会覆盖第一次更新,并且两名用户都了解其更新成功与否。For example, if two users viewing a wiki page make an update to the same page then the wiki platform must ensure that the second update doesn't overwrite the first update - and that both users understand whether their update was successful or not. 此策略最常用于 Web 应用程序中。This strategy is most often used in web applications.
  2. 悲观并发 - 要执行更新的应用程序会锁定某个对象,以防其他用户在该锁释放前更新数据。Pessimistic concurrency - An application looking to do an update will take a lock on an object, preventing other users from updating the data until the lock is released.
  3. 以最后一次写入为准 - 一种方法,即允许任何更新操作继续进行,不验证任何其他应用程序是否自应用程序首次读取数据起就已更新该数据。Last writer wins - An approach that allows any update operations to continue without verifying if any other application has updated the data since the application first read the data. 当数据分区时,通常会使用此策略,这样就不可能有多个用户访问相同的数据。This strategy is often used when data is partitioned so there's no likelihood that multiple users will access the same data. 该策略可能还适用于正在处理短期数据流的情况。It can also be useful where short-lived data streams are being processed.

本文概述了 Azure 存储如何通过支持上述所有三个并发策略来简化开发。This article provides an overview of how Azure Storage simplifies development by supporting all three of these concurrency strategies.

Azure 存储可简化云开发Azure Storage simplifies cloud development

Azure 存储支持所有三个策略,但是它在为乐观和悲观并发提供全面支持的能力方面与众不同。Azure Storage supports all three strategies, although it's distinctive in its ability to provide full support for optimistic and pessimistic concurrency. Azure 存储旨在采用强大的一致性模型,确保在提交数据插入或更新操作后,对该数据进行的进一步访问都会看到最新更新。Azure Storage was designed to embrace a strong consistency model, guaranteeing that when it commits a data insert or update operation all further accesses to that data will see the latest update. 如果存储平台使用最终一致性模型,则在一名用户执行写入后,需要延迟一段时间其他用户才可以看到更新的数据。Storage platforms that use an eventual consistency model have a lag between when a write is performed by one user and when the updated data can be seen by other users.

开发人员还应了解存储平台如何隔离更改,尤其是跨事务对同一对象进行的更改。Developers should also be aware of how a storage platform isolate change - particularly changes to the same object across transactions. Azure 存储服务使用快照隔离,因此可以在单个分区中并发执行读取操作与写入操作。The Azure Storage service uses snapshot isolation to allow read operations to happen concurrently with write operations within a single partition. 与其他隔离级别不同,快照隔离保证,所有读取操作看到的数据快照是一致的,即使在执行更新时也是如此。事实上,这是通过在处理更新事务时返回上次提交的值实现的。Unlike other isolation levels, snapshot isolation guarantees that all reads see a consistent snapshot of the data even while updates are occurring - essentially by returning the last committed values while an update transaction is being processed.

在 Blob 存储中管理并发Managing concurrency in Blob storage

可选择使用乐观并发模型或悲观并发模型,来管理对 Blob 服务中的 Blob 和容器的访问。You can opt to use either optimistic or pessimistic concurrency models to manage access to blobs and containers in the Blob service. 如果你未显式指定策略,则默认设置是“以最后一次写入为准”。If you don't explicitly specify a strategy, last write wins is the default.

Blob 和容器的乐观并发Optimistic concurrency for blobs and containers

存储服务会为每个存储对象分配一个标识符。The Storage service assigns an identifier to every object stored. 每次在对象上执行更新操作时,都会更新此标识符。This identifier is updated every time an update operation is done on an object. 在使用 HTTP 协议中定义的 ETag(实体标记)标头的情况下,该标识符作为 HTTP GET 响应的一部分返回到客户端。The identifier is returned to the client as part of an HTTP GET response using the ETag (entity tag) header that is defined within the HTTP protocol. 对此类对象执行更新的用户可以在原始 ETag 中将内容连同条件标头一起发送,以确保仅当满足特定条件时才会进行更新 - 在这种情况下,该条件是“If-Match”标头,该标头要求存储服务确保在更新请求中指定的 ETag 的值与存储服务中存储的值相同。A user doing an update on such an object can send in the original ETag along with a conditional header to ensure that an update will only occur if a certain condition has been met - in this case the condition is an "If-Match" header, which requires the Storage Service to ensure the value of the ETag specified in the update request is the same as that stored in the Storage Service.

此进程的概述如下:The outline of this process is as follows:

  1. 从存储服务检索 Blob,响应包括用于在存储服务中标识对象当前版本的 HTTP ETag 标头值。Retrieve a blob from the storage service, the response includes an HTTP ETag Header value that identifies the current version of the object in the storage service.
  2. 在更新 Blob 时,应将在步骤 1 中获得的 ETag 值包括在发送到服务的请求的 If-Match 条件标头中。When you update the blob, include the ETag value you received in step 1 in the If-Match conditional header of the request you send to the service.
  3. 该服务会将请求中的 ETag 值与 Blob 的当前 ETag 值进行比较。The service compares the ETag value in the request with the current ETag value of the blob.
  4. 如果 Blob 的当前 ETag 值与请求的 If-Match 条件标头中的 ETag 的版本不同,则该服务将 412 错误返回到客户端。If the current ETag value of the blob is a different version than the ETag in the If-Match conditional header in the request, the service returns a 412 error to the client. 此错误向客户端表明,另一进程在客户端检索 Blob 后已更新该 Blob。This error indicates to the client that another process has updated the blob since the client retrieved it.
  5. 如果 Blob 的当前 ETag 值与请求的 If-Match 条件标头中的 ETag 的版本相同,则该服务将执行请求的操作,并更新该 Blob 的当前 ETag 值,以说明它已创建新版本。If the current ETag value of the blob is the same version as the ETag in the If-Match conditional header in the request, the service performs the requested operation and updates the current ETag value of the blob to show that it has created a new version.

以下 C# 代码段(使用客户端存储库 4.2.0)显示一个简单示例,说明如何根据从以前检索到或插入的 Blob 属性访问的 ETag 值构造 If-Match AccessConditionThe following C# snippet (using the Client Storage Library 4.2.0) shows a simple example of how to construct an If-Match AccessCondition based on the ETag value that is accessed from the properties of a blob that was previously either retrieved or inserted. 然后,在更新该 Blob 时使用 AccessCondition 对象:AccessCondition 对象将 If-Match 标头添加到请求中。It then uses the AccessCondition object when it updates the blob: the AccessCondition object adds the If-Match header to the request. 如果其他进程已更新该 Blob,则 Blob 服务返回 “HTTP 412 (不满足前提条件)”状态消息。If another process has updated the blob, the Blob service returns an HTTP 412 (Precondition Failed) status message. 可以在此处下载完整示例:Managing Concurrency using Azure Storage(使用 Azure 存储管理并发)。You can download the full sample here: Managing Concurrency using Azure Storage.

// Retrieve the ETag from the newly created blob
// Etag is already populated as UploadText should cause a PUT Blob call
// to storage Blob service which returns the ETag in response.
string originalETag = blockBlob.Properties.ETag;

// This code simulates an update by a third party.
string helloText = "Blob updated by a third party.";

// No ETag provided so original blob is overwritten (thus generating a new ETag)
blockBlob.UploadText(helloText);
Console.WriteLine("Blob updated. Updated ETag = {0}",
blockBlob.Properties.ETag);

// Now try to update the blob using the original ETag provided when the blob was created
try
{
    Console.WriteLine("Trying to update blob using original ETag to generate if-match access condition");
    blockBlob.UploadText(helloText,accessCondition:
    AccessCondition.GenerateIfMatchCondition(originalETag));
}
catch (StorageException ex)
{
    if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
    {
        Console.WriteLine("Precondition failure as expected. Blob's original ETag no longer matches");
        // TODO: client can decide on how it wants to handle the 3rd party updated content.
    }
    else
        throw;
}

Azure 存储还支持条件性标头,例如 If-Modified-Since、If-Unmodified-Since、If-None-Match,以及这些标头的组合 。Azure Storage also includes support for conditional headers such as If-Modified-Since, If-Unmodified-Since, If-None-Match, and combinations of those headers. 有关详细信息,请参阅为 Blob 服务操作指定条件标头For more information, see Specifying Conditional Headers for Blob Service Operations.

下表概述接受条件标头(例如请求中的 If-Match )的容器操作,以及在响应中返回 ETag 值的容器操作。The following table summarizes the container operations that accept conditional headers such as If-Match in the request and that return an ETag value in the response.

操作Operation 返回容器的 ETag 值Returns Container ETag value 接受条件标头Accepts conditional headers
创建容器Create Container Yes No
获取容器属性Get Container Properties Yes No
获取容器元数据Get Container Metadata Yes No
设置容器元数据Set Container Metadata Yes Yes
获取容器 ACLGet Container ACL Yes No
设置容器 ACLSet Container ACL Yes 是 (*)Yes (*)
删除容器Delete Container No Yes
租赁容器Lease Container Yes Yes
列出 BlobList Blobs No No

(*) 由 SetContainerACL 定义的权限会进行缓存,传播这些权限的更新需要 30 秒的时间,在此期间,不保证更新是一致的。(*) The permissions defined by SetContainerACL are cached and updates to these permissions take 30 seconds to propagate during which period updates aren't guaranteed to be consistent.

下表概述接受条件标头(例如请求中的 If-Match)的 Blob 操作,以及在响应中返回 ETag 值的 Blob 操作。The following table summarizes the blob operations that accept conditional headers such as If-Match in the request and that return an ETag value in the response.

操作Operation 返回 ETag 值Returns ETag value 接受条件标头Accepts conditional headers
放置 BlobPut Blob Yes Yes
获取 BlobGet Blob Yes Yes
获取 Blob 属性Get Blob Properties Yes Yes
设置 Blob 属性Set Blob Properties Yes Yes
获取 Blob 元数据Get Blob Metadata Yes Yes
设置 Blob 元数据Set Blob Metadata Yes Yes
租赁 Blob (*)Lease Blob (*) Yes Yes
快照 BlobSnapshot Blob Yes Yes
复制 BlobCopy Blob Yes 是(对于源 Blob 和目标 Blob)Yes (for source and destination blob)
中止复制 BlobAbort Copy Blob No No
删除 BlobDelete Blob No Yes
放置块Put Block No No
放置块列表Put Block List Yes Yes
获取阻止列表Get Block List Yes No
放置页面Put Page Yes Yes
获取页面范围Get Page Ranges Yes Yes

(*) 租赁 Blob 不更改 Blob 上的 ETag。(*) Lease Blob doesn't change the ETag on a blob.

Blob 的悲观并发Pessimistic concurrency for blobs

若要锁定 Blob 进行独占使用,请获取针对它的租约To lock a blob for exclusive use, acquire a lease on it. 获取租约时,可以指定租约的时间段。When you acquire a lease, you specify a time period for the lease. 时间段的范围为 15 到 60 秒或无限,无限相当于一个排他锁。The time period ranges from 15 to 60 seconds or infinite, which amounts to an exclusive lock. 续订有限期租约以对其进行扩展。Renew a finite lease to extend it. 完成租约后,请释放该租约。Release a lease when you're finished with it. Blob 存储在有限期租约到期时会自动释放这些租约。Blob Storage automatically releases finite leases when they expire.

可以使用租约支持不同的同步策略。Leases enable different synchronization strategies to be supported. 策略包括“独占写入/共享读取”、“独占写入/独占读取”和“共享写入/独占读取”。Strategies include exclusive write/shared read, exclusive write/exclusive read, and shared write/exclusive read. 如果租约存在,则 Azure 存储会强制执行独占写入(放置、设置和删除操作),但是,若要确保读取操作的独占性,开发人员需要确保所有客户端应用程序都使用一个租约 ID,并且一次只有一个客户端具有有效的租约 ID。Where a lease exists, the Azure Storage enforces exclusive writes (put, set and delete operations) however ensuring exclusivity for read operations requires the developer to ensure that all client applications use a lease ID and that only one client at a time has a valid lease ID. 不包括租约 ID 的读取操作会导致共享读取。Read operations that don't include a lease ID result in shared reads.

以下 C# 代码段显示一个示例,说明如何在 30 秒内对 Blob 获取独占租约,更新 Blob 的内容,并释放该租约。The following C# snippet shows an example of acquiring an exclusive lease for 30 seconds on a blob, updating the content of the blob, and then releasing the lease. 尝试获取新租约时,如果 Blob 中已经存在有效租约,则 Blob 服务会返回“HTTP (409)冲突”状态结果。If there's already a valid lease on the blob when you try to acquire a new lease, the Blob service returns an "HTTP (409) Conflict" status result. 在发出请求以在存储服务中更新 Blob 时,以下代码段使用 AccessCondition 对象封装租约信息。The following snippet uses an AccessCondition object to encapsulate the lease information when it makes a request to update the blob in the storage service. 可以在此处下载完整示例:Managing Concurrency using Azure Storage(使用 Azure 存储管理并发)。You can download the full sample here: Managing Concurrency using Azure Storage.

// Acquire lease for 15 seconds
string lease = blockBlob.AcquireLease(TimeSpan.FromSeconds(15), null);
Console.WriteLine("Blob lease acquired. Lease = {0}", lease);

// Update blob using lease. This operation will succeed
const string helloText = "Blob updated";
var accessCondition = AccessCondition.GenerateLeaseCondition(lease);
blockBlob.UploadText(helloText, accessCondition: accessCondition);
Console.WriteLine("Blob updated using an exclusive lease");

//Simulate third party update to blob without lease
try
{
    // Below operation will fail as no valid lease provided
    Console.WriteLine("Trying to update blob without valid lease");
    blockBlob.UploadText("Update without lease, will fail");
}
catch (StorageException ex)
{
    if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
        Console.WriteLine("Precondition failure as expected. Blob's lease does not match");
    else
        throw;
}

如果尝试对租赁的 Blob 执行写入操作,而不传递租约 ID,则请求会失败,显示 412 错误。If you attempt a write operation on a leased blob without passing the lease ID, the request fails with a 412 error. 如果该租约在调用 UploadText 方法前到期,但你仍传递租约 ID,则请求也会失败,显示 412 错误。If the lease expires before calling the UploadText method but you still pass the lease ID, the request also fails with a 412 error. 有关如何管理租约到期时间和租约 ID 的详细信息,请参阅 Lease Blob(租用 Blob)REST 文档。For more information about managing lease expiry times and lease IDs, see the Lease Blob REST documentation.

以下 Blob 操作可以使用租约来管理悲观并发:The following blob operations can use leases to manage pessimistic concurrency:

  • 放置 BlobPut Blob
  • 获取 BlobGet Blob
  • 获取 Blob 属性Get Blob Properties
  • 设置 Blob 属性Set Blob Properties
  • 获取 Blob 元数据Get Blob Metadata
  • 设置 Blob 元数据Set Blob Metadata
  • 删除 BlobDelete Blob
  • 放置块Put Block
  • 放置块列表Put Block List
  • 获取阻止列表Get Block List
  • 放置页面Put Page
  • 获取页面范围Get Page Ranges
  • 制作 Blob 快照 - 可选租约 ID(如果租约存在)Snapshot Blob - lease ID optional if a lease exists
  • 复制 Blob - 如果目标 Blob 中存在租约,则需要提供租约 IDCopy Blob - lease ID required if a lease exists on the destination blob
  • 中止复制 Blob - 如果目标 Blob 中存在无限期租约,则需要提供租约 IDAbort Copy Blob - lease ID required if an infinite lease exists on the destination blob
  • 租赁 BlobLease Blob

容器的悲观并发Pessimistic concurrency for containers

容器中的租约支持的同步策略(独占写入/共享读取、独占写入/独占读取和共享写入/独占读取)与 Blob 中的租约一样,但与 Blob 不同的是,存储服务仅对删除操作强制实施独占性。 Leases on containers enable the same synchronization strategies to be supported as on blobs (exclusive write/shared read, exclusive write/exclusive read, and shared write/exclusive read) however unlike blobs the storage service only enforces exclusivity on delete operations. 要删除具有活动租约的容器,客户端必须将活动租约 ID 包括在删除请求中。To delete a container with an active lease, a client must include the active lease ID with the delete request. 在不包括租约 ID 的情况下,对租赁容器的其他所有容器操作都会成功,在这种情况下,这些操作是共享操作。All other container operations succeed on a leased container without including the lease ID in which case they are shared operations. 如果需要更新(放置或设置)或读取操作的独占性,则开发人员应确保所有客户端都使用一个租约 ID,而且一次只有一个客户端具有有效的租约 ID。If exclusivity of update (put or set) or read operations is required then developers should ensure all clients use a lease ID and that only one client at a time has a valid lease ID.

以下容器操作可以使用租约来管理悲观并发:The following container operations can use leases to manage pessimistic concurrency:

  • 删除容器Delete Container
  • 获取容器属性Get Container Properties
  • 获取容器元数据Get Container Metadata
  • 设置容器元数据Set Container Metadata
  • 获取容器 ACLGet Container ACL
  • 设置容器 ACLSet Container ACL
  • 租赁容器Lease Container

有关详细信息,请参阅:For more information, see:

在表存储中管理并发Managing concurrency in Table storage

当你处理实体时,表服务使用乐观并发检查作为默认行为,这与 Blob 服务不同,在该服务中,你必须明确选择执行乐观并发检查。The Table service uses optimistic concurrency checks as the default behavior when you're working with entities, unlike the Blob service where you must explicitly choose to do optimistic concurrency checks. 表服务与 Blob 服务的另一个区别在于,使用表服务,只能管理实体的并发行为,而使用 Blob 服务,则既可以管理容器的并发,又可以管理 Blob 的并发。The other difference between the table and Blob services is that you can only manage the concurrency behavior of entities but with the Blob service you can manage the concurrency of both containers and blobs.

若要使用乐观并发,并检查其他进程是否自从表存储服务检索到实体起修改了该实体,可使用在表服务返回实体时获得的 ETag 值。To use optimistic concurrency and to check if another process modified an entity since you retrieved it from the table storage service, you can use the ETag value you receive when the table service returns an entity. 此进程的概述如下:The outline of this process is as follows:

  1. 从表存储服务检索实体,响应包括用于标识与存储服务中的该实体关联的当前标识符的 ETag 值。Retrieve an entity from the table storage service, the response includes an ETag value that identifies the current identifier associated with that entity in the storage service.
  2. 更新实体时,应将在步骤 1 中获得的 ETag 值包括在发送到服务的请求的必需 If-Match 标头中。When you update the entity, include the ETag value you received in step 1 in the mandatory If-Match header of the request you send to the service.
  3. 该服务会将请求中的 ETag 值与实体的当前 ETag 值进行比较。The service compares the ETag value in the request with the current ETag value of the entity.
  4. 如果实体的当前 ETag 值与请求的必需 If-Match 标头中的 Etag 不同,则该服务将 412 错误返回到客户端。If the current ETag value of the entity is different than the ETag in the mandatory If-Match header in the request, the service returns a 412 error to the client. 这向客户端表明,其他进程自该客户端检索到实体起已更新该实体。This indicates to the client that another process has updated the entity since the client retrieved it.
  5. 如果实体的当前 ETag 值与请求中的必需 If-Match 标头内的 ETag 相同,或者 If-Match 标头包含通配符 (*),则服务会执行请求的操作并更新实体的当前 ETag 值,表明该值已更新。If the current ETag value of the entity is the same as the ETag in the mandatory If-Match header in the request or the If-Match header contains the wildcard character (*), the service does the requested operation and updates the current ETag value of the entity to show that it has been updated.

表服务要求客户端将 If-Match 标头包括在更新请求中。The table service requires the client to include an If-Match header in update requests. 但是,如果客户端在请求中将 If-Match 标头设置为通配符 (*),则可以强制执行非条件更新(“以最后写入者为准”策略)并绕过并发检查。However, it is possible to force an unconditional update (last writer wins strategy) and bypass concurrency checks if the client sets the If-Match header to the wildcard character (*) in the request.

以下 C# 代码段显示以前创建或检索到的客户实体是如何更新其电子邮件地址的。The following C# snippet shows a customer entity that was previously either created or retrieved having their email address updated. 初始插入或检索操作将 ETag 值存储在客户对象中,因为示例在执行替换操作时使用相同的对象实例,所以将 ETag 值自动发送回表服务,从而使该服务可以检查是否存在并发违规情况。The initial insert or retrieve operation stores the ETag value in the customer object, and because the sample uses the same object instance when it executes the replace operation, it automatically sends the ETag value back to the table service, enabling the service to check for concurrency violations. 如果其他进程已更新表存储中的实体,则该服务返回 HTTP 412 (不满足前提条件) 状态消息。If another process has updated the entity in table storage, the service returns an HTTP 412 (Precondition Failed) status message. 可以在此处下载完整示例:Managing Concurrency using Azure Storage(使用 Azure 存储管理并发)。You can download the full sample here: Managing Concurrency using Azure Storage.

try
{
    customer.Email = "updatedEmail@contoso.org";
    TableOperation replaceCustomer = TableOperation.Replace(customer);
    customerTable.Execute(replaceCustomer);
    Console.WriteLine("Replace operation succeeded.");
}
catch (StorageException ex)
{
    if (ex.RequestInformation.HttpStatusCode == 412)
        Console.WriteLine("Optimistic concurrency violation - entity has changed since it was retrieved.");
    else
        throw;
}

若要明确禁用并发检查,应该在执行替换操作前,将 employee 对象的 ETag 属性设置为“*”。To explicitly disable the concurrency check, you should set the ETag property of the employee object to "*" before you execute the replace operation.

customer.ETag = "*";

下表概述表实体操作是如何使用 ETag 值的:The following table summarizes how the table entity operations use ETag values:

操作Operation 返回 ETag 值Returns ETag value 需要 If-match 请求标头Requires If-Match request header
查询实体Query Entities Yes No
插入实体Insert Entity Yes No
更新实体Update Entity Yes Yes
合并实体Merge Entity Yes Yes
删除实体Delete Entity No Yes
插入或替换实体Insert or Replace Entity Yes No
插入或合并实体Insert or Merge Entity Yes No

请注意,“插入或替换实体”和“插入或合并实体”操作不可以执行任何并发检查,因为这些操作不会将 ETag 值发送到表服务。Note that Insert or Replace Entity and Insert or Merge Entity operations don't do any concurrency checks because they don't send an ETag value to the table service.

一般情况下,使用表的开发人员应当依靠乐观并发。In general, developers using tables should rely on optimistic concurrency. 如果你在访问表时需要悲观锁定,请为每个表分配所选的 Blob,并尝试在对该表执行操作前获取 Blob 的租约。If you need pessimistic locking when accessing Tables, assign a chosen blob for each table and try to take a lease on the blob before operating on the table. 此方法要求应用程序确保在对表执行操作前,所有数据访问路径都获得租约。This approach requires the application to ensure all data access paths obtain the lease before operating on the table. 还应注意,最短租赁时间为 15 秒,这要求慎重考虑可伸缩性。You should also note that the minimum lease time is 15 seconds, which requires careful consideration for scalability.

有关详细信息,请参阅:For more information, see:

在队列服务中管理并发Managing Concurrency in the Queue Service

在队列服务中考虑并发的一种情况是,多个客户端正从一个队列检索消息。One scenario in which concurrency is a concern in the queueing service is where multiple clients are retrieving messages from a queue. 在从队列检索消息时,响应包括消息和 pop 接收方值,这是删除该消息所必需的。When a message is retrieved from the queue, the response includes the message and a pop receipt value, which is required to delete the message. 该消息不会从队列中自动删除,但是在检索到后,该消息在 visibilitytimeout 参数指定的时间间隔内不显示给其他客户端。The message isn't automatically deleted from the queue, but after it has been retrieved, it isn't visible to other clients for the time interval specified by the visibility timeout parameter. 检索消息的客户端应在消息处理后响应的 TimeNextVisible 元素指定的时间前删除该消息,该时间是根据 visibilitytimeout 参数的值计算的。The client that retrieves the message is expected to delete the message after it has been processed, and before the time specified by the TimeNextVisible element of the response, which is calculated based on the value of the visibility timeout parameter. 若要确定 TimeNextVisible 的值,可将 visibilitytimeout 的值添加到消息的检索时间。The value of visibility timeout is added to the time at which the message is retrieved to determine the value of TimeNextVisible.

队列服务不支持乐观并发或悲观并发,因此,处理从队列检索到的消息的客户端应确保以幂等方式处理消息。The queue service doesn't have support for either optimistic or pessimistic concurrency and for this reason clients processing messages retrieved from a queue should ensure messages are processed in an idempotent manner. “以最后一次写入为准”策略用于更新操作,例如 SetQueueServiceProperties、SetQueueMetaData、SetQueueACL 和 UpdateMessage。A last writer wins strategy is used for update operations such as SetQueueServiceProperties, SetQueueMetaData, SetQueueACL, and UpdateMessage.

有关详细信息,请参阅:For more information, see:

在 Azure 文件存储中管理并发Managing concurrency in Azure Files

文件服务可以使用下面两个不同的协议终结点访问:SMB 和 REST。The file service can be accessed using two different protocol endpoints - SMB and REST. REST 服务不支持乐观锁定或悲观锁定,所有更新将遵循“以最后一次写入为准”策略。The REST service doesn't have support for either optimistic locking or pessimistic locking and all updates will follow a last writer wins strategy. SMB 客户端如果装载文件共享,则可以使用文件系统锁定机制来管理对共享文件的访问,包括可以执行悲观锁定。SMB clients that mount file shares can use file system locking mechanisms to manage access to shared files - including the ability to perform pessimistic locking. 在打开文件后,SMB 客户端会同时指定文件访问权限和共享模式。When an SMB client opens a file, it specifies both the file access and share mode. 如果将“文件访问权限”选项设置为“读取”或“读取/写入”,同时将“文件共享”模式设置为“无”,则将导致文件在关闭前被 SMB 客户端锁定。Setting a File Access option of "Write" or "Read/Write" along with a File Share mode of "None" will result in the file being locked by an SMB client until the file is closed. 如果尝试对 SMB 客户端已锁定的文件执行 REST 操作,则 REST 服务返回状态代码 409 (冲突),以及错误代码 SharingViolation。If REST operation is attempted on a file where an SMB client has the file locked the REST service will return status code 409 (Conflict) with error code SharingViolation.

当 SMB 客户端打开文件以进行删除时,会将该文件标记为待删除,直到该文件上的其他所有 SMB 客户端打开句柄关闭为止。When an SMB client opens a file for delete, it marks the file as pending delete until all other SMB client open handles on that file are closed. 当文件标记为待删除时,对该文件执行的任何 REST 操作返回状态代码 409 (冲突),以及错误代码 SMBDeletePending。While a file is marked as pending delete, any REST operation on that file will return status code 409 (Conflict) with error code SMBDeletePending. 不返回状态代码“404 (找不到)”,因为 SMB 客户端可能会在关闭文件前删除待删除标志。Status code 404 (Not Found) isn't returned since it is possible for the SMB client to remove the pending deletion flag prior to closing the file. 换而言之,仅当删除文件后,才应返回状态代码 404 (找不到)。In other words, status code 404 (Not Found) is only expected when the file has been removed. 请注意,当文件处于 SMB 待删除状态时,它不会包含在“列出文件”结果中。Note that while a file is in an SMB pending delete state, it will not be included in the List Files results. 另请注意,“REST 删除文件”和“REST 删除目录”操作以原子方式提交,不会导致待删除状态。Also, note that the REST Delete File and REST Delete Directory operations are committed atomically and do not result in a pending delete state.

有关详细信息,请参阅:For more information, see:

后续步骤Next steps

对于本博客中引用的完整示例应用程序:For the complete sample application referenced in this blog:

有关 Azure 存储的详细信息,请参阅:For more information on Azure Storage, see: