Windows Azure Storage - 公有云存储独占访问(lease)与数据一致性

上一篇介绍了如何使用签名授权方式(SAS)来实现客户端程序操作Blob Storage,SAS方式较适用于授权临时读操作,如果是多个客户端需要写入或者更新同一个目标blob文件时,SAS授权方式将会带来很大的数据一致性问题。因此,在数据一致性检查以及写操作同步控制中,开发者可以采用Azure Storage中提供的独占锁(Lease)来保证独占访问和数据一致性。

当众多客户端访问同一个WAS中的数据时,数据一致性需要慎重处理,最简单的一种处理方式是查看每一个Blob对应的ETag、LastModifiedUtc、ContentMD5等属性。在应用程序代码中,开发者可以通过Azure SDK提供的CloudBlockBlob.Properties.ETag、CloudBlockBlob. Properties. LastModified和blockBlob.Properties.ContentMD5属性来确认目标Blob文件的最后更新状态,以此来检查Blob文件的更改操作是否和预期的一致。

如图所示,通过Azure Storage Explorer也可以查看Blob文件对应的ETag、LastModifiedUtc、ContentMD5等属性。

 

 

另外,和文件系统一样,开发者可以针对每个Container或Blob设置独占式访问权限,通过对某个Container或者Blob进行加锁(Lease)从而控制访问同步问题(Concurrency),客户端在访问Container或者Blob前将目标容器或Blob文件加锁并得到相应的标识符(Lease ID),紧接着,客户端可以凭借Lease ID进行目标文件的读写操作,如更新Blob文件内容。代码实现如下:

 

var Account = CloudStorageAccount.DevelopmentStorageAccount; // 或者使用开发者自己的Global Azure Storage帐号或者China Azure Storage帐号,如上一篇中最后的部分。

 

const int timeout = 90;

BlobClient = Account.CreateCloudBlobClient();

try

{

    CloudBlobContainer container = BlobClient.GetContainerReference("testcontainer");

    CloudBlob blob = container.GetBlobReference("test.txt");

 

    //acquire lease to access the blob file exclussively

    var lease = "";

    var creds = blob.ServiceClient.Credentials;

    var transformedUri = new Uri(creds.TransformUri(blob.Uri.ToString()));

    var request = BlobRequest.Lease(transformedUri, timeout, LeaseAction. Acquire, null);

    blob.ServiceClient.Credentials.SignRequest(request);

    using (var response = request.GetResponse())

    {

        lease = response.Headers["x-ms-lease-id"];

        Console.WriteLine("true " + lease);

    }

 

    //start a new thread to renew lease before the past one expires

    System.Threading.Thread renewalThread = new Thread(() =>

    {

    while (true)

       {

            Thread.Sleep(TimeSpan.FromSeconds(40));

            request = BlobRequest.Lease(transformedUri, timeout, LeaseAction. Renew, lease);

            creds.SignRequest(request);

            request.GetResponse().Close();

            Console.WriteLine("Successfully renew lease.");

        }

    });

    renewalThread.Start();

 

    //create a put request based on the leasing and get response

    var updateText = BlobRequest.Put(blob.Uri, 60, new BlobProperties(), BlobType.BlockBlob, lease, 0);

    using (var stream = new StreamWriter(updateText.GetRequestStream()))

    {

        stream.Write("after");

    }

    BlobClient.Credentials.SignRequest(updateText);

    updateText.GetResponse().Close();

    Console.Read();

}

catch (StorageClientException ex)

{

    if ((int)ex.StatusCode == 404)

    {

        Console.WriteLine("false : lease is already existing.");

    }

    throw;

}

 

由于申请到的锁(Lease)在一段时间后会自动过期,如上示例代码,常用的做法是在客户端开启一个独立的线程,保证在每个锁过期之前自动更新同步锁。在客户端不需要再独占目标Blob资源时,客户端可以选择停止独立线程,停止同步锁的更新,很快目标Blob资源就可以被其他客户端访问了。上述示例代码是基于Azure Storage Library 1.7实现的,除了可以申请同步锁之外,还可以使用类似的方法来实现解除同步锁、更新同步锁等。

//release a lease

LeaseOperation(blob, leaseId, LeaseAction.Release, timeout);

 

//renew existing lease

LeaseOperation(blob, leaseId, LeaseAction.Renew, timeout);

 

//break existing lease

LeaseOperation(blob, null, LeaseAction.Break, timeout);

 

    public static void LeaseOperation(CloudBlob blob, string leaseId, LeaseAction action, int timeout)

    {

        var creds = blob.ServiceClient.Credentials;

        var transformedUri = new Uri(creds.TransformUri(blob.Uri. ToString()));

            var request = BlobRequest.Lease(transformedUri, timeout, action, leaseId);

            creds.SignRequest(request);

            request.GetResponse().Close();

    }

 

若使用最新的Azure Storage Library 2.0或者更高版本,则稍作修正即可,使用起来更为高效。

 

CloudStorageAccount storageAccount = CloudStorageAccount. DevelopmentStorageAccount;

CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();

CloudBlobContainer container = cloudBlobClient. GetContainerReference(containerName);

string blobName = "<Blob Name e.g. myblob.txt>";

CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

TimeSpan? leaseTime = TimeSpan.FromSeconds(15);//Acquire a 15 second lease on the blob Leave it null for infinite lease. Otherwise it should be between 15 and 60 seconds

string proposedLeaseId = null;//proposed lease id (leave it null for storage service to return you one)

string leaseId = blob.AcquireLease(leaseTime, proposedLeaseId);

如果Blob资源被某一个客户端加锁(Lease)独占访问,那么所有不基于Lease ID的Blob请求都会被拒绝,并得到以下错误信息:

There is currently a lease on the blob and no lease ID was specified in the request.

小结:

  • 公开的数据文件,可以存放在公开的(Public)容器(Container)中,对外提供基于容器和文件名的公开访问URL。如:https://teststorage.blob.core.windows.net/testcontainer/Logs222.rar
  • 公开的数据文件,其为静态文件,不常改变,且访问者来自不同地域,可以存放在公开的(Public)容器(Container)中,且使用CDN(Content Delivery Network)来缓存目标容器(文件),对外提供基于CDN的公开URL。(关于China Azure中提供的CDN,请参考https://www.windowsazure.cn/zh-cn/manage/services/cdn/,Global Azure中的CDN功能同China Azure中的CDN一致,可参考:https://msdn.microsoft.com/library/azure/ff919703.aspx
  • 私密的数据文件,需要临时授权给客户端进行读(写)操作,尽量存放在私密的(Private)容器(Container)中,使用签名授权(SAS)的方式临时授予读写权限,避免直接暴露WAS访问密钥。
  • 私密的数据文件,且多用户同时写入(更改)时,尽量存放在私密的(Private)容器(Container)中,充分利用同步锁(Lease)、ETag验证等方式保证存储资源的安全访问和有序读写。