Azure编程笔记(2):重复CloudTable的修改操作

内容提要

对MicrosoftAzure的CloudTable进行操作,有很多种操作失败的可能,比如网络连接异常,比如短时间内发送的请求数太多。很多时候我们在失败之后过一段时间再重试,就能操作成功。但是有些失败不是简单重试就解决的。本文讲述的在修改操作时候发生的PreconditionFailed错误就是一个例子。

问题描述

在上一篇博客里,我们定义一个类型Account,用来模拟社交网站的账户基本信息。这里我们继续用这个类型为例子来讲述多个线程同时发送更新请求时可能发生的问题。

我们首先用如下代码创建10个账户,并且在多个线程里同时把他们互相加为好友

public async Task AddFriend(string email1, string email2)
{
    Account account1 = await GetAccount(email1);
    Account account2 = await GetAccount(email2);

    if (account1 == null || account2 == null)
    {
        return;
    }

    account1.Friends.Add(email2);
    account2.Friends.Add(email1);

    await UpdateAccount(account1);
    await UpdateAccount(account2);
}

下面代码实现了把两个账号互相加为好友的功能:

public async Task<Account> GetAccount(string email)
{
    string partition = Account.AccountsPartitionKey;
    var retrieve = TableOperation.Retrieve<Account>(partition, email);
    TableResult result = await this.accountsTable.ExecuteAsync(retrieve);
    Account account = (Account)result.Result;
    return account;
}

public async Task UpdateAccount(Account account)
{
    var update = TableOperation.Replace(account);
    await this.accountsTable.ExecuteAsync(update);
}

运行上述代码,将会出现如下错误:

问题根源

从错误信息中,我们可以看到错误代码是412,错误类型是PreconditionFailed。出现这个错误的原因是修改操作所需要的前提条件不满足。

存入CloudTable的每个TableEntity都有一个字段叫ETag,它标识了每个数据的版本。每当一个数据被更新时,ETag字段也会被更新。

在跟新一个数据时,先要把该数据从CloudTable中读出来(函数GetAccount的功能),接着对数据进行修改,最后在把修改之后存入到CloudTable中去(函数UpdateAccount的功能)。在把数据存入到CloudTable之前,Azure会比较数据的ETag和CloudTable里对应的数据的ETag是不是一致。如果不一致,表明CloudTable里的数据已经被其他人修改了。这时Update操作不能再继续,而是抛出代码为412的错误并中断操作。

在这个例子中,实现添加好友的AddFriend函数实际上是一个修改操作。当我们向一个账号添加好友时,他的Friends字段会被修改。

在这个例子中,实现添加好友的AddFriend函数实际上是一个修改操作。当我们向一个账号添加好友时,他的Friends字段会被修改。

出现这个问题是有可能多个线程同时往一个账号里添加好友,也就有可能同时修改一个账号。在一个线程读出一个账号的数据之后并在再次存入CloudTable之前,有可能其他线程已经往该线程里添加了好友,从而导致出错。

解决问题

如前面分析的那样,出现代码为412的错误的原因是在把一个账号的数据存回CloudTable的时候该数据已经被另外一个线程修改了。此时简单地重复执行TableOperation.Replace并不能解决问题,因为此时数据的ETag已经和CloudTable里数据的ETag不一致,再重试也枉然。

正确的方法时我们重新从CloudTable里读出账号数据,再执行修改,然后再存入到CloudTable里去。

如果有两个线程同时往同一个账号里添加好友。一个线程添加好友A,一个线程添加B。假设添加好友A的线程先结束。添加B的线程准备把数据存回CloudTable的时候,就会抛出代码为412的错误。此时我们应该重新从CloudTable里读出账号的数据(包含好友A),然后再加入好友B,接着再存回CloudTable。当操作结束的时候,CloudTable里的数据同时包含了好友A和B。这个结果和我们预期的一致。

修改之后的代码如下所示:

public async Task AddFriendWithRetry(string email1, string email2)
{
    int maxRetry = 10;
    int retry = 0;
    int maxDelay = 2000;
    int minDelay = 10;

    StorageException storageException = null;
    while (retry < maxRetry)
    {
        try
        {
            await AddFriend(email1, email2);
            break;
        }
        catch (StorageException ex)
        {
            storageException = ex;
        }

        if (storageException != null)
        {
            if (storageException.Message.Contains("412"))
            {
                retry++;
                await Task.Delay(new Random().Next(minDelay, maxDelay));
            }
            else
            {
                throw storageException;
            }
        }
    }

    if(retry == maxRetry && storageException != null)
    {
        throw storageException;
    }
}

在上述代码中,我们当捕捉到代码为412的异常,就完整里重复执行AddFriend函数。AddFriend函数会重新从CloudTable里读出账号,添加好友,然后再把修改过的账号存回到CloudTable。

Azure编程笔记(2):重复CloudTable的修改操作

时间: 2024-09-28 16:21:33

Azure编程笔记(2):重复CloudTable的修改操作的相关文章

Azure编程笔记(3):用Fiddler调试Azure的应用程序

 内容提要 Azure的服务是通过RESTfulAPI提供的.虽然Azure针对很多编程语言都提供了SDK,但这些SDK也只是RESTfulAPI的一层封装.在调用SDK或者RESTfulAPI出错时,我们需要使用调试工具来分析并解决问题.Fiddler是一款功能强大的免费工具,我们可以使用Fiddler来调试Azure的应用程序.本文展示如何用Fiddler调试一个常见的访问Storage的问题. 问题描述 在前面的两篇博客中,我们模拟社交网站定义了一个Account类型.本文我们继续以A

Azure编程笔记(5):长时间的异步操作带来的问题

?? 内容提要 Azure Storage里很多操作需要花费很长的时间.为了提高效率,这些耗时的操作是以异步的方式响应的.也就是说调用这些操作对应的函数虽然结束,但背后的操作可能还要持续一段时间.如果没有合理的对待从函数返回到操作真正结束这段时间,我们的程序就有可能出现问题.下面以删除CloudTable为例讨论这类问题. 问题描述 我们写一个简单的单元测试的Class来重现这个问题.首先我们定义两个函数,分别用来初始化测试函数和清理测试函数所留下的数据.函数InitializeTest初始化一

Azure编程笔记(4):管理Cloud Service的证书

?? 我们在Microsoft Azure中部署CloudService的时候,可能会用到证书.通常在两种情况下需要用到证书.一是把证书安装在服务器端.此时证书用来建立HTTPS/SSL连接,以便保护传输中的数据.二是把证书部署在客户端.此时客户端发起连接请求时,它会把证书信息添加到请求中.服务器端收到请求之后,会验证其中的证书是不是合法的证书.这种情况下证书是用来验证用户的.接下来我们分两种情况来讨论如何管理证书. 把证书安装在服务器端 假设我们用ASP .NET的Web API开发一个Web

Azure编程笔记(1):序列化复杂类型的TableEntity字段

内容提要 在使用MicrosoftAzure的CloudTable存储数据时,我们先要把数据定义成TableEntity的子类.如果TableEntity中包含复杂类型(比如容器类型如List等.或者自定义类型)的字段,这些字段需要自己做序列化才能保存到CloudTable中去. 问题描述 我们模拟社交网站定义一个类型Account,它包含一个账户的基本信息如果邮箱.姓名.生日等等,同时它还包含一个账户的好友列表: public class Account : TableEntity { pub

[Spring Data MongoDB]学习笔记--MongoTemplate插入修改操作

插入操作: 直接给个例子 import static org.springframework.data.mongodb.core.query.Criteria.where; import static org.springframework.data.mongodb.core.query.Criteria.query; … Person p = new Person("Bob", 33); mongoTemplate.insert(p);//还可以多加一个参数,来提供collectio

python核心编程--笔记

python核心编程--笔记 的解释器options: 1.1 –d   提供调试输出 1.2 –O   生成优化的字节码(生成.pyo文件) 1.3 –S   不导入site模块以在启动时查找python路径 1.4 –v   冗余输出(导入语句详细追踪) 1.5 –m mod 将一个模块以脚本形式运行 1.6 –Q opt 除法选项(参阅文档) 1.7 –c cmd 运行以命令行字符串心事提交的python脚本 1.8 file   以给定的文件运行python脚本 2 _在解释器中表示最后

C++MFC编程笔记day10 MF界面控件的使用2、属性页对话框、MFC线程

一 树型控件 1 相关类 CTreeCtrl-父类是CWnd,控件类. CTreeView-父类是CCtrlView,视图类.CTreeView=CView+CTreeCtrl CTreeView::GetTreeCtrl 2 CTreeCtrl的使用 对比CListCtrl:列表控件的每个数据项之间是平等关系,通过数据项 的索引值得到数据项的信息.树控件每个数据项称之为节点.节点之间 的关系包括父子关系和兄弟关系.通常通过节点句柄得到某个节点. 通常用来表示层次关系的数据. 2.1 设置控件的

C++MFC编程笔记day09 MF界面控件的使用1

一 MFC控件 1 控件介绍 1.1 静态控件包括图片.静态文本和分组框.生成的控件的ID统一是 IDC_STATIC.很少程序中访问和操作,只是使用静态控件显示信息 1.2 编辑框控件,控件类是CEdit,通常使用它接收用户的输入,显示 信息给用户. 1.3 按钮控件包括一般按钮.复选按钮和单元按钮,控件类都是CButton 单选按钮注意,同一组的按钮Tab键顺序的编号连续(Ctrl+d  显示tab顺序),而且编号小的 按钮设置group属性,才能在同一个对话框中,实现多个单选按钮的 多个分

C++windows内核编程笔记day07_day08,可视化建菜单、加速键使用、绘图等

可视化操作创建的菜单,加载到窗口. 方法1:注册时指定菜单 wce.lpszMenuName=MAKEINTRESOURCE(IDR_MENUMAIN);//数字形式的资源ID转换为字符串形式的资源 方法2: //创建窗口时加载菜单资源 HMENU menumain= LoadMenu(g_hinstance,MAKEINTRESOURCE(IDR_MENUMAIN)); menumain 传入 CreateWindowEx();//倒数第三个参数 窗口指定小图标: 1.注册时指定 wce.hI