

的版本 4 (V4) 适用于 .NET 的 AWS SDK 已经发布！

有关重大更改和迁移应用程序的信息，请参阅[迁移主题](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/net-dg-v4.html)。

 [https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/net-dg-v4.html](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/net-dg-v4.html)

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# Amazon EC2 竞价型实例教程
<a name="how-to-spot-instances"></a>

本教程向您展示如何使用管理 Amazon EC2 竞价型实例。 适用于 .NET 的 AWS SDK 

## 概述
<a name="tutor-spot-net-overview"></a>

竞价型实例允许您以低于按需价格请求未使用的 Amazon EC2 容量。这可以显著降低可能被中断的应用程序的 EC2 成本。

下面简要概述了如何请求和使用竞价型实例。

1. 创建竞价型实例请求，指定您愿意支付的最高价格。

1. 请求完成后，请像运行任何其它 Amazon EC2 实例一样运行该实例。

1. 根据需要运行该实例，然后将其终止，除非 *Spot 价格*发生变化导致实例终止。

1. 在不再需要竞价型实例请求时对其进行清理，这样就不会再创建竞价型实例。

这是对竞价型实例整体层面的概述。要更好地了解竞价型实例，请参阅 [Amazon EC2 用户指南](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)中的[竞价型实例](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html)。

## 关于本教程
<a name="about-spot-instances-tutorial"></a>

在学习本教程时，您可以使用 适用于 .NET 的 AWS SDK 来执行以下操作：
+ 创建竞价型实例请求
+ 确定何时执行该竞价型实例请求
+ 取消竞价型实例请求
+ 终止相关实例

以下各节提供了此示例的片段和其它信息。片段后显示了[该示例的完整代码](#tutor-spot-net-main)，并且可以按原样构建和运行。

**Topics**
+ [概述](#tutor-spot-net-overview)
+ [关于本教程](#about-spot-instances-tutorial)
+ [先决条件](#tutor-spot-net-prereq)
+ [收集所需内容](#tutor-spot-net-gather)
+ [创建竞价型实例请求](#tutor-spot-net-submit)
+ [确定您的竞价型实例请求的状态](#tutor-spot-net-request-state)
+ [清理您的竞价型实例请求](#tutor-spot-net-clean-up-request)
+ [清理您的竞价型实例](#tutor-spot-net-clean-up-instance)
+ [完整代码](#tutor-spot-net-main)
+ [其他注意事项](#tutor-spot-net-additional)

## 先决条件
<a name="tutor-spot-net-prereq"></a>

有关 APIs 和先决条件的信息，请参阅父部分 ([使用 Amazon EC2](ec2-apis-intro.md))。

## 收集所需内容
<a name="tutor-spot-net-gather"></a>

要创建竞价型实例请求，您需要一些东西。
+ 实例数量和其实例类型。有几个实例类型可供选择。有关更多信息，请参阅《Amazon EC2 用户指南》[https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)中的 [Amazon EC2 实例类型](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html)。另请参阅[实例类型详细信息](https://aws.amazon.com/ec2/instance-types/)和[实例类型浏览器](https://aws.amazon.com/ec2/instance-explorer/)。

  在本教程中，默认数量为 1。
+ 要用于创建实例的自定义亚马逊机器映像（AMI）的 ID。有关信息 AMIs，请参阅 [Amazon [EC2 用户指南中的亚马逊系统](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)映像 (AMIs)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html)。具体而言，请参阅[查找 AMI](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/finding-an-ami.html) 并[共享 AMIs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharing-amis.html)。
+ 您每小时愿意为每个实例支付的最高价格。您可以在 [Amazon EC2 定价页面](https://aws.amazon.com/ec2/pricing/)上查看所有实例类型（包括按需实例和竞价型实例）的价格。本教程的默认价格将在后面说明。
+ 如果您想远程连接到实例，则需要具有适当配置和资源的安全组。[使用 Amazon EC2 中的安全组](security-groups.md)中对此进行了描述，并在[启动 Amazon EC2 实例](run-instance.md)中提供了有关[收集所需内容](run-instance.md#run-instance-gather)和[连接到实例](run-instance.md#connect-to-instance)的信息。为简单起见，本教程使用了所有新 AWS 账户都拥有的名为 **default** 的安全组。

有很多方法可以请求 Spot 实例。以下是常见策略：
+ 提出请求以确保成本低于按需定价。
+ 基于最终的计算值提出请求。
+ 提出请求以便尽快获得计算容量。

以下解释参考了 [Amazon EC2 用户指南](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)中的[竞价型实例定价历史记录](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances-history.html)。

### 降低成本至低于按需价格
<a name="reduce-cost"></a>

您需要进行花费数小时或数天的批处理工作。然而，您可以灵活调整启动和结束时间。您希望看到是否以低于按需实例的成本完成。

您可以通过使用 Amazon EC2 控制台或 Amazon EC2 API 来检查各个类型实例的 Spot 价格历史记录。在您分析了给定可用区内所需实例类型的价格记录之后，您有两种可供选择的方法发出请求：
+ 指定在 Spot 价格范围（这仍然低于按需定价）的上限发出请求，预测您单次竞价型实例请求很有可能会达成，并运行足够的连续计算时间来完成此项工作。
+ 指定在 Spot 价格范围的下限发出请求，随着时间的推移，通过持久的请求，计划结合多种已启动实例。总计一下，该实例会以较低的总成本、花费很长时间来完成这项工作。

### 支付不超过该结果的值
<a name="value-of-result"></a>

您需要进行数据处理工作。您将会对该工作的结果有一个很好的了解，以便于能够让您知道在计算成本方面它们的价值。

当您分析了实例类型的 Spot 价格历史记录之后，选择一个计算时间成本不高于该工作结果成本的价格。由于 Spot 价格的波动，该价格可能会达到或低于您的请求，所以您要创建一个持久请求，并允许它间歇运行。

### 快速获取计算容量
<a name="acquire-quickly"></a>

您对附加容量有一个无法预料的短期需求，该容量不能通过按需实例获取。当您分析了实例类型的 Spot 价格历史记录之后，您选择高于历史最高价格的价格，以大幅提高完成您请求的可能性，并继续计算，直到完成实例。

收集所需内容并选择策略后，就可以请求竞价型实例了。对于本教程，默认的最高 Spot 实例价格设置为与按需价格相同（本教程为 0.003 美元）。以这种方式设置价格可最大限度地提高请求被执行的机会。

## 创建竞价型实例请求
<a name="tutor-spot-net-submit"></a>

以下代码片段向您展示了如何使用之前收集的元素创建竞价型实例请求。

[本主题末尾](#tutor-spot-net-main)的示例显示了此片段的使用情况。

```
    //
    // Method to create a Spot Instance request
    private static async Task<SpotInstanceRequest> CreateSpotInstanceRequest(
      IAmazonEC2 ec2Client, string amiId, string securityGroupName,
      InstanceType instanceType, string spotPrice, int instanceCount)
    {
      var launchSpecification = new LaunchSpecification{
        ImageId = amiId,
        InstanceType = instanceType
      };
      launchSpecification.SecurityGroups.Add(securityGroupName);
      var request = new RequestSpotInstancesRequest{
        SpotPrice = spotPrice,
        InstanceCount = instanceCount,
        LaunchSpecification = launchSpecification
      };

      RequestSpotInstancesResponse result =
        await ec2Client.RequestSpotInstancesAsync(request);
      return result.SpotInstanceRequests[0];
    }
```

此方法返回的重要值是竞价型实例请求 ID，它包含在返回[SpotInstanceRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TSpotInstanceRequest.html)对象的`SpotInstanceRequestId`成员中。

**注意**  
您需要为启动的任何竞价型实例付费。为避免不必要的开支，请务必[取消所有请求](#tutor-spot-net-clean-up-request)并[终止所有实例](#tutor-spot-net-clean-up-instance)。

## 确定您的竞价型实例请求的状态
<a name="tutor-spot-net-request-state"></a>

以下代码片段向您展示了如何获取有关您竞价型实例请求的信息。您可以使用这些信息在代码中做出某些决定，例如是否继续等待竞价型实例请求得到满足。

[本主题末尾](#tutor-spot-net-main)的示例显示了此片段的使用情况。

```
    //
    // Method to get information about a Spot Instance request, including the status,
    // instance ID, etc.
    // It gets the information for a specific request (as opposed to all requests).
    private static async Task<SpotInstanceRequest> GetSpotInstanceRequestInfo(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);
      return describeResponse.SpotInstanceRequests[0];
    }
```

该方法返回有关竞价型实例请求的信息，例如实例 ID、其状态和状态代码。有关竞价型实例请求状态代码的更多信息，请参阅 [Amazon EC2 用户指南](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)中的[竞价型实例请求状态](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-bid-status.html#spot-instance-bid-status-understand)。

## 清理您的竞价型实例请求
<a name="tutor-spot-net-clean-up-request"></a>

当您不再需要请求竞价型实例时，请务必取消任何未处理的请求，以防止这些请求被重新执行。以下代码片段演示了如何取消一个竞价型实例请求。

[本主题末尾](#tutor-spot-net-main)的示例显示了此片段的使用情况。

```
    //
    // Method to cancel a Spot Instance request
    private static async Task CancelSpotInstanceRequest(
      IAmazonEC2 ec2Client, string requestId)
    {
      var cancelRequest = new CancelSpotInstanceRequestsRequest();
      cancelRequest.SpotInstanceRequestIds.Add(requestId);

      await ec2Client.CancelSpotInstanceRequestsAsync(cancelRequest);
    }
```

## 清理您的竞价型实例
<a name="tutor-spot-net-clean-up-instance"></a>

为避免不必要的费用，终止任何从竞价型实例请求启动的实例非常重要；只是取消竞价型实例请求并不会终止您的实例，这意味着您需要继续为它们支付费用。以下代码片段演示了在获取活动竞价型实例的实例标识符后如何终止实例。

[本主题末尾](#tutor-spot-net-main)的示例显示了此片段的使用情况。

```
    //
    // Method to terminate a Spot Instance
    private static async Task TerminateSpotInstance(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      // Retrieve the Spot Instance request to check for running instances.
      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);

      // If there are any running instances, terminate them
      if(   (describeResponse.SpotInstanceRequests[0].Status.Code
              == "request-canceled-and-instance-running")
         || (describeResponse.SpotInstanceRequests[0].State == SpotInstanceState.Active))
      {
        TerminateInstancesResponse response =
          await ec2Client.TerminateInstancesAsync(new TerminateInstancesRequest{
            InstanceIds = new List<string>(){
              describeResponse.SpotInstanceRequests[0].InstanceId } });
        foreach (InstanceStateChange item in response.TerminatingInstances)
        {
          Console.WriteLine($"\n  Terminated instance: {item.InstanceId}");
          Console.WriteLine($"  Instance state: {item.CurrentState.Name}\n");
        }
      }
    }
```

## 完整代码
<a name="tutor-spot-net-main"></a>

以下代码示例调用前面描述的方法来创建和取消竞价型实例请求并终止竞价型实例。

### SDK 参考
<a name="w2aac19c15c21c21c43b5b1"></a>

NuGet 包裹：
+ [AWSSDK.EC2](https://www.nuget.org/packages/AWSSDK.EC2)

编程元素：
+ 命名空间 [Amazon.EC2](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/NEC2.html)

  [Amazon EC2 客户端](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TEC2Client.html)

  班级 [InstanceType](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TInstanceType.html)
+ 命名空间 [Amazon.EC2.Model](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/NEC2Model.html)

  班级 [CancelSpotInstanceRequestsRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TCancelSpotInstanceRequestsRequest.html)

  班级 [DescribeSpotInstanceRequestsRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TDescribeSpotInstanceRequestsRequest.html)

  班级 [DescribeSpotInstanceRequestsResponse](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TDescribeSpotInstanceRequestsResponse.html)

  班级 [InstanceStateChange](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TInstanceStateChange.html)

  班级 [LaunchSpecification](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TLaunchSpecification.html)

  班级 [RequestSpotInstancesRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TRequestSpotInstancesRequest.html)

  班级 [RequestSpotInstancesResponse](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TRequestSpotInstancesResponse.html)

  班级 [SpotInstanceRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TSpotInstanceRequest.html)

  班级 [TerminateInstancesRequest](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TTerminateInstancesRequest.html)

  班级 [TerminateInstancesResponse](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/EC2/TTerminateInstancesResponse.html)

### 代码
<a name="w2aac19c15c21c21c43b7b1"></a>

```
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Amazon.EC2;
using Amazon.EC2.Model;

namespace EC2SpotInstanceRequests
{
  class Program
  {
    static async Task Main(string[] args)
    {
      // Some default values.
      // These could be made into command-line arguments instead.
      var instanceType = InstanceType.T1Micro;
      string securityGroupName = "default";
      string spotPrice = "0.003";
      int instanceCount = 1;

      // Parse the command line arguments
      if((args.Length != 1) || (!args[0].StartsWith("ami-")))
      {
        Console.WriteLine("\nUsage: EC2SpotInstanceRequests ami");
        Console.WriteLine("  ami: the Amazon Machine Image to use for the Spot Instances.");
        return;
      }

      // Create the Amazon EC2 client.
      var ec2Client = new AmazonEC2Client();

      // Create the Spot Instance request and record its ID
      Console.WriteLine("\nCreating spot instance request...");
      var req = await CreateSpotInstanceRequest(
        ec2Client, args[0], securityGroupName, instanceType, spotPrice, instanceCount);
      string requestId = req.SpotInstanceRequestId;

      // Wait for an EC2 Spot Instance to become active
      Console.WriteLine(
        $"Waiting for Spot Instance request with ID {requestId} to become active...");
      int wait = 1;
      var start = DateTime.Now;
      while(true)
      {
        Console.Write(".");

        // Get and check the status to see if the request has been fulfilled.
        var requestInfo = await GetSpotInstanceRequestInfo(ec2Client, requestId);
        if(requestInfo.Status.Code == "fulfilled")
        {
          Console.WriteLine($"\nSpot Instance request {requestId} " +
            $"has been fulfilled by instance {requestInfo.InstanceId}.\n");
          break;
        }

        // Wait a bit and try again, longer each time (1, 2, 4, ...)
        Thread.Sleep(wait);
        wait = wait * 2;
      }

      // Show the user how long it took to fulfill the Spot Instance request.
      TimeSpan span = DateTime.Now.Subtract(start);
      Console.WriteLine($"That took {span.TotalMilliseconds} milliseconds");

      // Perform actions here as needed.
      // For this example, simply wait for the user to hit a key.
      // That gives them a chance to look at the EC2 console to see
      // the running instance if they want to.
      Console.WriteLine("Press any key to start the cleanup...");
      Console.ReadKey(true);

      // Cancel the request.
      // Do this first to make sure that the request can't be re-fulfilled
      // once the Spot Instance has been terminated.
      Console.WriteLine("Canceling Spot Instance request...");
      await CancelSpotInstanceRequest(ec2Client, requestId);

      // Terminate the Spot Instance that's running.
      Console.WriteLine("Terminating the running Spot Instance...");
      await TerminateSpotInstance(ec2Client, requestId);

      Console.WriteLine("Done. Press any key to exit...");
      Console.ReadKey(true);
    }


    //
    // Method to create a Spot Instance request
    private static async Task<SpotInstanceRequest> CreateSpotInstanceRequest(
      IAmazonEC2 ec2Client, string amiId, string securityGroupName,
      InstanceType instanceType, string spotPrice, int instanceCount)
    {
      var launchSpecification = new LaunchSpecification{
        ImageId = amiId,
        InstanceType = instanceType
      };
      launchSpecification.SecurityGroups.Add(securityGroupName);
      var request = new RequestSpotInstancesRequest{
        SpotPrice = spotPrice,
        InstanceCount = instanceCount,
        LaunchSpecification = launchSpecification
      };

      RequestSpotInstancesResponse result =
        await ec2Client.RequestSpotInstancesAsync(request);
      return result.SpotInstanceRequests[0];
    }


    //
    // Method to get information about a Spot Instance request, including the status,
    // instance ID, etc.
    // It gets the information for a specific request (as opposed to all requests).
    private static async Task<SpotInstanceRequest> GetSpotInstanceRequestInfo(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);
      return describeResponse.SpotInstanceRequests[0];
    }


    //
    // Method to cancel a Spot Instance request
    private static async Task CancelSpotInstanceRequest(
      IAmazonEC2 ec2Client, string requestId)
    {
      var cancelRequest = new CancelSpotInstanceRequestsRequest();
      cancelRequest.SpotInstanceRequestIds.Add(requestId);

      await ec2Client.CancelSpotInstanceRequestsAsync(cancelRequest);
    }


    //
    // Method to terminate a Spot Instance
    private static async Task TerminateSpotInstance(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      // Retrieve the Spot Instance request to check for running instances.
      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);

      // If there are any running instances, terminate them
      if(   (describeResponse.SpotInstanceRequests[0].Status.Code
              == "request-canceled-and-instance-running")
         || (describeResponse.SpotInstanceRequests[0].State == SpotInstanceState.Active))
      {
        TerminateInstancesResponse response =
          await ec2Client.TerminateInstancesAsync(new TerminateInstancesRequest{
            InstanceIds = new List<string>(){
              describeResponse.SpotInstanceRequests[0].InstanceId } });
        foreach (InstanceStateChange item in response.TerminatingInstances)
        {
          Console.WriteLine($"\n  Terminated instance: {item.InstanceId}");
          Console.WriteLine($"  Instance state: {item.CurrentState.Name}\n");
        }
      }
    }
  }
}
```

## 其他注意事项
<a name="tutor-spot-net-additional"></a>
+ 运行本教程后，最好登录 [Amazon EC2 控制台](https://console.aws.amazon.com/ec2/)，验证[竞价型实例请求](https://console.aws.amazon.com/ec2/home#SpotInstances:)是否已取消，[竞价型实例](https://console.aws.amazon.com/ec2/v2/home#Instances)是否已终止。