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验证等方式保证存储资源的安全访问和有序读写。