使用 Azure Cosmos DB for NoSQL SDK 对应用程序进行故障排除

Azure Cosmos DB 提供了一组广泛的响应代码,可帮助我们轻松解决不同操作类型产生的问题。 捕获是为了确保在为 Azure Cosmos DB 创建应用时,我们创建了正确的错误处理程序。

在此实验中,将创建一个菜单驱动程序,用于支持插入或删除两个文档中的一个。 此实验的主要目的是介绍如何使用一些最常见的响应代码以及如何在应用的错误处理代码中使用它们。 虽然我们将为多个响应代码的错误处理进行编码,但只会在两种不同类型的条件下触发。 此外,错误处理中不执行任何复杂的操作,可能在屏幕上显示一条消息或等待 10 秒然后重试操作一次,具体取决于响应代码。

准备开发环境

如果你还没有将 DP-420 的实验室代码存储库克隆到使用此实验室的环境,请按照以下步骤操作。 否则,请在 Visual Studio Code 中打开以前克隆的文件夹。

  1. 启动 Visual Studio Code。

    📝 如果你还不熟悉 Visual Studio Code 界面,请参阅 Visual Studio Code 入门指南

  2. 打开命令面板并运行 Git: Clone,将 https://github.com/microsoftlearning/dp-420-cosmos-db-dev GitHub 存储库克隆到你选择的本地文件夹中。

    💡 你可以使用 Ctrl+Shift+P 键盘快捷方式打开命令面板。

  3. 克隆存储库后,打开在 Visual Studio Code 中选择的本地文件夹。

创建 Azure Cosmos DB for NoSQL 帐户

Azure Cosmos DB 是一项基于云的 NoSQL 数据库服务,它支持多个 API。 在首次预配 Azure Cosmos DB 帐户时,可以选择希望该帐户支持的 API(例如 Mongo API 或 NoSQL API) 。 Azure Cosmos DB for NoSQL 帐户完成预配后,你可以检索终结点和密钥。 使用终结点和密钥以编程方式连接到 Azure Cosmos DB for NoSQL 帐户。 在 Azure SDK for .NET 或任何其他 SDK 的连接字符串上使用终结点和密钥。

  1. 在新的 Web 浏览器窗口或选项卡中,导航到 Azure 门户 (portal.azure.com)。

  2. 使用与你的订阅关联的 Microsoft 凭证登录到门户。

  3. 选择“+ 创建资源”,搜索“Cosmos DB”,然后使用以下设置创建新的“Azure Cosmos DB for NoSQL”帐户资源,并将所有其余设置保留为默认值:

    设置
    订阅 你的现有 Azure 订阅
    资源组 选择现有资源组,或创建新资源组
    帐户名 输入全局唯一名称
    位置 选择任何可用区域
    容量模式 预配的吞吐量
    应用免费分级折扣 不应用

    📝 你的实验室环境可能存在阻止你创建新资源组的限制。 如果是这种情况,请使用现有的预先创建的资源组。

  4. 等待部署任务完成,然后继续执行此任务。

  5. 转到新创建的 Azure Cosmos DB 帐户资源,并导航到“键”窗格。

  6. 此窗格包含从 SDK 连接到帐户所需的连接详细信息和凭据。 具体而言:

    1. 请注意“URI”** 字段。 稍后在本练习中将用到此终结点值。

    2. 请注意“主键”** 字段。 稍后在本练习中将用到此键值。

  7. 最小化浏览器窗口,但不要关闭它。 在接下来的步骤中,我们将在启动后台工作负载几分钟后返回 Azure 门户。

将 Microsoft.Azure.Cosmos 库导入 .NET 脚本

.NET CLI 包含 add package 命令,用于从预配置的包源导入包。 .NET 安装使用 NuGet 作为默认包源。

  1. 在 Visual Studio Code 的“资源管理器”窗格中,浏览到 26-sdk-troubleshoot****

  2. 打开 26-sdk-troubleshoot 文件夹的上下文菜单,然后选择“在集成终端中打开”以打开新的终端实例****。

    📝 此命令将打开起始目录已设置为 26-sdk-troubleshoot 文件夹的终端**

  3. 使用以下命令从 NuGet 添加 Microsoft.Azure.Cosmos 包:

     dotnet add package Microsoft.Azure.Cosmos --version 3.22.1
    

运行脚本以创建菜单驱动的选项,用于插入和删除文档。

在应用程序能够运行之前,需要将其连接到 Azure Cosmos DB 帐户。

  1. 在 Visual Studio Code 的“资源管理器”窗格中,浏览到 26-sdk-troubleshoot****

  2. 打开 Program.cs 代码文件。

  3. 更新名为 endpoint 的现有变量,将其值设置为前面创建的 Azure Cosmos DB 帐户的终结点 。

     private static readonly string endpoint = "<cosmos-endpoint>";
    

    📝 例如,如果终结点为:https­://dp420.documents.azure.com:443/,则 C# 语句将为:private static readonly string endpoint = “https­://dp420.documents.azure.com:443/”;。

  4. 更新名为 key 的现有变量,将其值设置为前面创建的 Azure Cosmos DB 帐户的键 。

     private static readonly string key = "<cosmos-key>";
    

    📝 例如,如果你的键为:fDR2ci9QgkdkvERTQ==,则 C# 语句应为:private static readonly string key = “fDR2ci9QgkdkvERTQ==”;。

  5. 保存文件。

  6. 使用 dotnet run 命令生成并运行项目:

     dotnet run
    

    📝 这是一个非常简单的程序。 它将显示一个菜单,其中包含五个选项,如下所示。 两个选项用于插入预定义的文档,两个用于删除预定义的文档,还有一个选项用于退出程序。

    1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'
    2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'
    3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'
    4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'
    5) Exit
    Select an option:
    

插入和删除文档的时间。

  1. 选择“1”并按 ENTER 以插入第一个文档****。 该程序将插入第一个文档并返回以下消息。

     Insert Successful.
     Document for customer with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828' Inserted.
     Press [ENTER] to continue
    
  2. 再次选择“1”并按 ENTER 以插入第一个文档****。 这次程序将崩溃,并引发异常。 通过查看错误堆栈,可以找到程序失败的原因。 通过从错误堆栈中提取的消息可以看出,遇到了一个未处理的异常“冲突 (409)”

     Unhandled exception. Microsoft.Azure.Cosmos.CosmosException : Response status code does not indicate success: Conflict (409);
    
  3. 由于要插入文档,因此需要查看创建文档时返回的常用的创建文档状态代码的列表。 此代码的说明是:为新文档提供的 ID 已被现有文档使用**。 这是显而易见的,因为刚刚才运行了菜单选项,创建了同一个文档。

  4. 进一步深入堆栈中,可以看到这个异常是从第 100 行调用的,而这又是从第 64 行调用的。

     at Program.CreateDocument1(Container Customer) in C:\Git\dp-420-cosmos-db-dev\26-sdk-troubleshoot\Program.cs:line 100   
    at Program.CompleteTaskOnCosmosDB(String consoleinputcharacter, Container container) in C:\Git\dp-420-cosmos-db-dev\26-sdk-troubleshoot\Program.cs:line 64
    
  5. 检查第 100 行,可以看到此错误是由 CreateItemAsync 操作引起的,与预期一致**。

         ItemResponse<customerInfo> response = await Customer.CreateItemAsync<customerInfo>(customer, new PartitionKey(customerID));
    
  6. 此外,通过查看 100 到 103 行,可以明显看到此代码没有相关的错误处理。 需要解决这个问题。

         ItemResponse<customerInfo> response = await Customer.CreateItemAsync<customerInfo>(customer, new PartitionKey(customerID));
         Console.WriteLine("Insert Successful.");
         Console.WriteLine("Document for customer with id = '" + customerID + "' Inserted.");
    
  7. 需要确定错误处理代码应执行的操作。 通过查看创建文档状态代码,可以选择为此操作的每个可能的状态代码创建错误处理代码。 在此实验中,将把此列表中的状态代码 403 视为 409。 返回的所有其他状态代码将直接显示系统错误消息。

    📝 请注意,虽然我们将为 403 异常编写一个错误处理任务,但在本实验中,我们不会生成 403 异常。

  8. 现在为名为 CompleteTaskOnCosmosDB 的函数添加错误处理**。 在第 45 行的函数 Main 中找到 while 循环,并使用错误处理代码结束对 CompleteTaskOnCosmosDB 的调用****。 我们将第 47 行的 CompleteTaskOnCosmosDB 语句替换为以下代码**。 在这段新代码中首先要注意的是,在 catch 上,将捕获 CosmosException 类型的类的异常**。 此类包含属性 StatusCode,该属性可从 Azure Cosmos DB 服务返回请求完成状态代码。 StatusCode 属性的类型为 System.Net.HttpStatusCode,我们可以使用此值并将其与 .NET HTTP 状态代码中的字段名称进行比较**

         try
         {
             await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
         }
         catch (CosmosException e)
         {
                     switch (e.StatusCode.ToString())
                     {
                         case ("Conflict"):
                             Console.WriteLine("Insert Failed. Response Code (409).");
                             Console.WriteLine("Can not insert a duplicate partition key, customer with the same ID already exists."); 
                             break;
                         case ("Forbidden"):
                             Console.WriteLine("Response Code (403).");
                             Console.WriteLine("The request was forbidden to complete. Some possible reasons for this exception are:");
                             Console.WriteLine("Firewall blocking requests.");
                             Console.WriteLine("Partition key exceeding storage.");
                             Console.WriteLine("Non-data operations are not allowed.");
                             break;
                         default:
                             Console.WriteLine(e.Message);
                             break;
                     }
    
         }
    
    
  9. 保存文件,由于崩溃了,需要再次运行 Menu 程序,因此运行命令:

     dotnet run
    
  10. 再次选择“1”并按 ENTER 以插入第一个文档****。 这次不会发生故障,而是会获得用户友好的消息。

     Insert Failed. 
     Response Code (409).
     Can not insert a duplicate partition key, customer with the same ID already exists.
    
  11. 此代码添加了对 403 和 409 异常的错误处理,现在,让我们为一些常见通信类型的异常添加代码**。 这些是三种常见的通信异常类型:429、503** 和 408,分别对应“请求过多”、“服务不可用”和“请求超时”。 在第 66 行附近现在应该有一个 default 语句,因此请在前面的 break; 语句之后和 default 语句之前添加下面的代码****。 此代码将验证我们是否发现任何这些通信异常,如果是,请等待 10 秒,然后再尝试插入文档一次。 现在在代码中添加更多内容:

                         case ("TooManyRequests"):
                         case ("ServiceUnavailable"):
                         case ("RequestTimeout"):
                             // Check if the issues are related to connectivity and if so, wait 10 seconds to retry.
                             await Task.Delay(10000); // Wait 10 seconds
                             try
                             {
                                 Console.WriteLine("Try one more time...");
                                 await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                             }
                             catch (CosmosException e2)
                             {
                                 Console.WriteLine("Insert Failed. " + e2.Message);
                                 Console.WriteLine("Can not insert a duplicate partition key, Connectivity issues encountered.");
                                 break;
                             }
                             break;
    

    📝 请注意,虽然要编写一个任务代码,在遇到 429、503 或 408 异常时执行相应的任务,但本实验中不会生成相应类型的异常错误。

  12. Main 函数现在应如下所示**

         public static async Task Main(string[] args)
         {
    
             CosmosClient client = new CosmosClient(connectionString,new CosmosClientOptions() { AllowBulkExecution = true, MaxRetryAttemptsOnRateLimitedRequests = 50, MaxRetryWaitTimeOnRateLimitedRequests = new TimeSpan(0,1,30)});
    
             Console.WriteLine("Creating Azure Cosmos DB Databases and containers");
    
             Database CustomersDB = await client.CreateDatabaseIfNotExistsAsync("CustomersDB");
             Container CustomersDB_Customer_container = await CustomersDB.CreateContainerIfNotExistsAsync(id: "Customer", partitionKeyPath: "/id", throughput: 400);
    
             Console.Clear();
             Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("5) Exit");
             Console.Write("\r\nSelect an option: ");
        
             string consoleinputcharacter;
            
             while((consoleinputcharacter = Console.ReadLine()) != "5") 
             {
                  try
                  {
                      await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                  }
                  catch (CosmosException e)
                  {
                      switch (e.StatusCode.ToString())
                      {
                         case ("Conflict"):
                             Console.WriteLine("Insert Failed. Response Code (409).");
                             Console.WriteLine("Can not insert a duplicate partition key, customer with the same ID already exists."); 
                             break;
                         case ("Forbidden"):
                             Console.WriteLine("Response Code (403).");
                             Console.WriteLine("The request was forbidden to complete. Some possible reasons for this exception are:");
                             Console.WriteLine("Firewall blocking requests.");
                             Console.WriteLine("Partition key exceeding storage.");
                             Console.WriteLine("Non-data operations are not allowed.");
                             break;
                         case ("TooManyRequests"):
                         case ("ServiceUnavailable"):
                         case ("RequestTimeout"):
                             // Check if the issues are related to connectivity and if so, wait 10 seconds to retry.
                             await Task.Delay(10000); // Wait 10 seconds
                             try
                             {
                                 Console.WriteLine("Try one more time...");
                                 await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                             }
                             catch (CosmosException e2)
                             {
                                 Console.WriteLine("Insert Failed. " + e2.Message);
                                 Console.WriteLine("Can not insert a duplicate partition key, Connectivity issues encountered.");
                                 break;
                             }
                             break;
                         default:
                             Console.WriteLine(e.Message);
                             break;
                      }
                 }
                    
    
                 Console.WriteLine("Choose an action:");
                 Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("5) Exit");
                 Console.Write("\r\nSelect an option: ");
             }
         }
    
  13. 请注意,CreateDocument2 函数的问题仍然通过上面的更改来修复。**

  14. 最后,函数 DeleteDocument1 和 DeleteDocument2 还需要将以下代码替换为类似于 CreateDocument1 函数的适当错误处理代码****。 除了使用 DeleteItemAsync 而不是 CreateItemAsync 以外,这些函数的唯一区别在于,“删除”状态代码与“插入”状态代码不同**。 对于删除,我们只用注意 404 状态代码,它表示未找到文档。 现在来了解一下其他情况的 CompleteTaskOnCosmosDB 函数调用错误处理。 在 Main 函数中,需要在默认情况下添加以下代码**

                     case ("NotFound"):
                         Console.WriteLine("Delete Failed. Response Code (404).");
                         Console.WriteLine("Can not delete customer, customer not found.");
                         break;         
    
  15. 保存文件。

  16. 完成所有函数的修复后,多次测试所有菜单选项,以确保应用程序在遇到异常时返回消息并不会崩溃。 如果应用崩溃,则解决错误,然后只重新运行以下命令:

     dotnet run
    
  17. 不要速览,完成后,Main 代码应与此类似。

         public static async Task Main(string[] args)
         {
             CosmosClient client = new CosmosClient(connectionString,new CosmosClientOptions() { AllowBulkExecution = true, MaxRetryAttemptsOnRateLimitedRequests = 50, MaxRetryWaitTimeOnRateLimitedRequests = new TimeSpan(0,1,30)});
    
             Console.WriteLine("Creating Azure Cosmos DB Databases and containers");
    
             Database CustomersDB = await client.CreateDatabaseIfNotExistsAsync("CustomersDB");
             Container CustomersDB_Customer_container = await CustomersDB.CreateContainerIfNotExistsAsync(id: "Customer", partitionKeyPath: "/id", throughput: 400);
    
             Console.Clear();
             Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("5) Exit");
             Console.Write("\r\nSelect an option: ");
        
             string consoleinputcharacter;
            
             while((consoleinputcharacter = Console.ReadLine()) != "5") 
             {
                     try
                     {
                         await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                     }
                     catch (CosmosException e)
                     {
                         switch (e.StatusCode.ToString())
                         {
                             case ("Conflict"):
                                 Console.WriteLine("Insert Failed. Response Code (409).");
                                 Console.WriteLine("Can not insert a duplicate partition key, customer with the same ID already exists."); 
                                 break;
                             case ("Forbidden"):
                                 Console.WriteLine("Response Code (403).");
                                 Console.WriteLine("The request was forbidden to complete. Some possible reasons for this exception are:");
                                 Console.WriteLine("Firewall blocking requests.");
                                 Console.WriteLine("Partition key exceeding storage.");
                                 Console.WriteLine("Non-data operations are not allowed.");
                                 break;
                             case ("TooManyRequests"):
                             case ("ServiceUnavailable"):
                             case ("RequestTimeout"):
                                 // Check if the issues are related to connectivity and if so, wait 10 seconds to retry.
                                 await Task.Delay(10000); // Wait 10 seconds
                                 try
                                 {
                                     Console.WriteLine("Try one more time...");
                                     await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                                 }
                                 catch (CosmosException e2)
                                 {
                                     Console.WriteLine("Insert Failed. " + e2.Message);
                                     Console.WriteLine("Can not insert a duplicate partition key, Connectivity issues encountered.");
                                     break;
                                 }
                                 break;    
                             case ("NotFound"):
                                 Console.WriteLine("Delete Failed. Response Code (404).");
                                 Console.WriteLine("Can not delete customer, customer not found.");
                                 break; 
                             default:
                                 Console.WriteLine(e.Message);
                                 break;
                         }
    
                     }
    
                 Console.WriteLine("Choose an action:");
                 Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("5) Exit");
                 Console.Write("\r\nSelect an option: ");
             }
         }
    

结论

即使是最初级的开发人员也知道需要向所有代码添加正确的错误处理。 这段代码中的错误处理很简单,但通过它你应该了解了有关 Azure Cosmos DB 异常组件的基础知识,通过这些组件可在代码中创建可靠的错误处理解决方案。