Conditional Update (Optimistic Concurrency)

 Windows Azure provides a number of mechanisms to help developers deal with concurrency around storage:

  1. Blobs and tables use optimistic concurrency (via ETags) to ensure that two concurrent changes won’t clobber each other.
  2. Queues help manage concurrency by distributing messages such that (under normal circumstances), each message is processed by only one consumer.
  3. Blob leases allow a process to gain exclusive write access to a blob on a renewable basis. Windows Azure Drives use blob leases to ensure that only a single VM has a VHD mounted read/write.


#1.1 Blobs optimistic concurrency (ETag)

The ETag value is an identifier assigned to the blob by the Blob service.It is updated onwrite operations to the blob. It may be used to perform operations conditionally, providing concurrency control and improved efficiency.

The IfMatch and IfNoneMatch methods take an ETag value and return an AccessCondition that may be specified on the request.

Here is code which handles only download blob when it is updated

using System;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace AzureStorageTest
{
    class Program
    {
        static void Main(string[] args)
        {

            CloudStorageAccount account = CloudStorageAccount.
            Parse(System.Configuration.ConfigurationManager.AppSettings["StorageAccountConnectionString"]);

            CloudBlobClient BlobClient = account.CreateCloudBlobClient();

            CloudBlobContainer ContainerReference = BlobClient.GetContainerReference("maincontainer");
            ContainerReference.CreateIfNotExist();

            CloudBlob BlobReference = ContainerReference.GetBlobReference("blobfile.txt");

            BlobReference.UploadFile("blobfile.txt");

            byte[] DownloadAsByteArray = BlobReference.DownloadByteArray();

            var LastReadETag = BlobReference.Properties.ETag;

            BlobRequestOptions RequestOption = new BlobRequestOptions
            {
                AccessCondition = AccessCondition.IfNoneMatch
                    (LastReadETag)
            };

            try
            {
                byte[] DownloadByteArraySecondTimeOnward = BlobReference.
                DownloadByteArray(RequestOption);
                Console.WriteLine("Blob is Updated, Download Success");
                Console.WriteLine(DownloadByteArraySecondTimeOnward);
            }
            catch (StorageClientException ex)
            {
                if (ex.StatusCode == System.Net.HttpStatusCode.NotModified)
                {

                }
            }

            Console.ReadKey();

        }
    }

}

#1.2 Table optimistic concurrency (ETag = by default, Timestamp )

Timestamp – Every entity has a version maintained by the system which is used for optimistic concurrency. Update and Delete requests by default send an ETag using the If-Match condition and the operation will fail if thetimestampsent in the If-Match header differs from the Timestamp property value on the server.

Entity Tracking

http://blogs.msdn.com/b/windowsazurestorage/archive/2010/11/06/how-to-get-most-out-of-windows-azure-tables.aspx

The above example shows an update of entity which was first retrieved via a query. Entities need to be tracked by the context for issuing updates or deletes and hence we retrieved it to allow the context to track the entity (it does this by default). The purpose of tracking is to send the Etag condition using If-Match header when an update or delete is issued. The operation succeeds only if the Etag that the context tracks for an entity matches that on the server. The Etag for an entity is its Timestamp as explained above and the service changes it with every update.

But what if I want to skip the Etag check i.e. unconditionally update/delete the entity? Or what if I know what the Etag is and I want to avoid issuing a query before I update or delete the entity?WCF Data Service API provides an AttachTo method for this purpose. This method takes the table name, entity and Etag to track it with and the context starts tracking the entity which will then allow update and delete to be issued on the entity.If“*” is used for the Etag value during AttachTo, an unconditional update request is sent to the service. A common mistake to be aware of here is that “AttachTo” can throw an exception if the entity is already being tracked by the context. Here is a snippet of code that shows an unconditional delete:


The error will happen when below code happen concurrency update

Error:"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n<error xmlns=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\">\r\n  <code>UpdateConditionNotSatisfied</code>\r\n  <message xml:lang=\"en-US\">0:The update condition specified in the request was not satisfied.\nRequestId:43d6bd0d-384b-4227-9739-7b77cd9325e6\nTime:2012-01-10T09:19:03.9047544Z</message>\r\n</error>"

using System;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System.Data.Services.Common;
using System.Data.Services.Client;

namespace AzureStorageTest
{
    class Program
    {
        static void Main(string[] args)
        {

            CloudStorageAccount account = CloudStorageAccount.
            Parse(System.Configuration.ConfigurationManager.AppSettings["StorageAccountConnectionString"]);

            CloudTableClient tableClient = account.CreateCloudTableClient();

            //Create Movie Table 
            string tableName = "Movies";
            tableClient.CreateTableIfNotExist(tableName);
            TableServiceContext context = tableClient.GetDataServiceContext();
            context.AddObject(tableName, new Movie("Action", "White Water Rapids Survival"));
            context.AddObject(tableName, new Movie("Action", "You are the one"));
            context.SaveChangesWithRetries();

            //Update
            var q = (from movie in context.CreateQuery<Movie>(tableName)
                        where movie.PartitionKey == "Action" && movie.Rating > 4.0
                        select movie).AsTableServiceQuery<Movie>();

            foreach (Movie movieToUpdate in q)
            {
                movieToUpdate.Favorite = true;
                string time = movieToUpdate.Timestamp.ToString();
                context.UpdateObject(movieToUpdate);
            }

            //WHEN HERE HAPPEN CONCURRENCY UPDATE ! ! !

            context.SaveChangesWithRetries(SaveChangesOptions.Batch);
            Console.ReadKey();

        }
    }

    [DataServiceKey("PartitionKey", "RowKey")]
    public class Movie
    {
        /// Movie Category is the partition key 
        public string PartitionKey { get; set; }
        /// Movie Title is the row key 
        public string RowKey { get; set; }
        public DateTime Timestamp { get; set; }
        public int ReleaseYear { get; set; }
        public double Rating { get; set; }
        public string Language { get; set; }
        public bool Favorite { get; set; }

        public Movie() { }
        public Movie(string partitionKey, string rowkey)
        {
            PartitionKey = partitionKey;
            RowKey=rowkey;
            Rating=5;
        }
    }

}


Unconditionally update/delete the entity (AttachTo)

using System;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System.Data.Services.Common;
using System.Data.Services.Client;

namespace AzureStorageTest
{
    class Program
    {
        static void Main(string[] args)
        {

            CloudStorageAccount account = CloudStorageAccount.
            Parse(System.Configuration.ConfigurationManager.AppSettings["StorageAccountConnectionString"]);

            CloudTableClient tableClient = account.CreateCloudTableClient();

            //Create Movie Table 
            string tableName = "Movies";
            tableClient.CreateTableIfNotExist(tableName);
            TableServiceContext context = tableClient.GetDataServiceContext();
            context.AddObject(tableName, new Movie("Action", "White Water Rapids Survival"));
            context.AddObject(tableName, new Movie("Action", "You are the one"));
            context.SaveChangesWithRetries();
            //Update
            context.MergeOption = MergeOption.NoTracking;
            var q = (from movie in context.CreateQuery<Movie>(tableName)
                        where movie.PartitionKey == "Action" && movie.Rating > 4.0
                        select movie).AsTableServiceQuery<Movie>();

            foreach (Movie movieToUpdate in q)
            {
                movieToUpdate.Favorite = true;
                string time = movieToUpdate.Timestamp.ToString();
                context.AttachTo(tableName, movieToUpdate, "*");
                context.UpdateObject(movieToUpdate);
            }

            //WHEN HERE HAPPEN CONCURRENCY UPDATE ! ! !

            context.SaveChangesWithRetries(SaveChangesOptions.Batch);
            Console.ReadKey();

        }
    }

    [DataServiceKey("PartitionKey", "RowKey")]
    public class Movie
    {
        /// Movie Category is the partition key 
        public string PartitionKey { get; set; }
        /// Movie Title is the row key 
        public string RowKey { get; set; }
        public DateTime Timestamp { get; set; }
        public int ReleaseYear { get; set; }
        public double Rating { get; set; }
        public string Language { get; set; }
        public bool Favorite { get; set; }

        public Movie() { }
        public Movie(string partitionKey, string rowkey)
        {
            PartitionKey = partitionKey;
            RowKey=rowkey;
            Rating=5;
        }
    }

}

The Lease Blob operation establishes and manages aone-minute lock on a blob for write operations.

When a lease is active, the lease ID must be included in the request for any of the following operations:

using System;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.StorageClient.Protocol;
using System.IO;

namespace AzureStorageTest
{
    class Program
    {
        static void Main(string[] args)
        {

            CloudStorageAccount account = CloudStorageAccount.
            Parse(System.Configuration.ConfigurationManager.AppSettings["StorageAccountConnectionString"]);

            CloudBlobClient BlobClient = account.CreateCloudBlobClient();

            CloudBlobContainer ContainerReference = BlobClient.GetContainerReference("maincontainer");
            ContainerReference.CreateIfNotExist();

            CloudBlob BlobReference = ContainerReference.GetBlobReference("blobfile.txt");

            var leaseId = BlobReference.AcquireLease();

            // BlobReference.UploadText("new content", "123");
            BlobReference.UploadText("new content", leaseId);

            byte[] DownloadAsByteArray = BlobReference.DownloadByteArray();

            Console.WriteLine(DownloadAsByteArray);
            Console.ReadKey();

        }

       
    }

    public static class LeaseBlobExtensions
    {
        public static string AcquireLease(this CloudBlob blob)
        {
            var creds = blob.ServiceClient.Credentials;
            var transformedUri = new Uri(creds.TransformUri(blob.Uri.ToString()));
            var req = BlobRequest.Lease(transformedUri,
                90, // timeout (in seconds)
                LeaseAction.Acquire, // as opposed to "break" "release" or "renew"
                null); // name of the existing lease, if any
            blob.ServiceClient.Credentials.SignRequest(req);
            using (var response = req.GetResponse())
            {
                return response.Headers["x-ms-lease-id"];
            }
        }

        public static void UploadText(this CloudBlob blob, string text, string leaseId)
        {
            string url = blob.Uri.ToString();
            if (blob.ServiceClient.Credentials.NeedsTransformUri)
            {
                url = blob.ServiceClient.Credentials.TransformUri(url);
            }
            var req = BlobRequest.Put(new Uri(url), 90, new BlobProperties(), BlobType.BlockBlob, leaseId, 0);
            using (var writer = new StreamWriter(req.GetRequestStream()))
            {
                writer.Write(text);
            }
            blob.ServiceClient.Credentials.SignRequest(req);
            req.GetResponse().Close();
        }
    }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章