共用方式為


大規模 IoT 裝置部署的最佳做法

將 IoT 解決方案擴展至數百萬部裝置可能相當困難。 大規模解決方案通常需要配合服務和訂用帳戶限制加以設計。 客戶使用 Azure IoT 裝置佈建服務時,會搭配使用其他 Azure IoT 平台服務和元件,例如 IoT 中樞和 Azure IoT 裝置 SDK。 本文說明您可納入設計中的最佳做法、模式和範例程式碼,以利用這些服務並讓您的部署擴增。從專案設計階段開始遵循這些模式和做法,您可以將 IoT 裝置的效能最大化。

佈建新裝置

首次佈建是首次讓裝置上線成為 IoT 解決方案一部分的程序。 使用大規模部署時,請務必排程佈建程序,以避免所有裝置同時嘗試連線而造成多載情況。

使用交錯佈建排程

以數百萬部裝置的規模部署時,一次註冊所有裝置可能會導致 DPS 執行個體因為節流 (HTTP 回應碼 429, Too Many Requests) 而無法註冊您的裝置。 若要防止這種節流情況,針對裝置使用交錯註冊排程。 根據 DPS 配額和限制來設定裝置註冊批次大小,。 例如,如果註冊速率為每分鐘 200 部裝置,則上線的批次大小為每個批次 200 部裝置。

重試作業

如果因服務忙碌而發生暫時性錯誤,重試邏輯可讓裝置成功連線到 IoT 雲端。 不過如果發生大量重試,可能會進一步讓容量將用盡或已用盡的忙碌伺服器效能下降。 跟所有 Azure 服務一樣,您應該使用指數輪詢來實作智慧型重試機制。 如需不同重試模式的詳細資訊,請參閱重試設計模式暫時性錯誤處理

等到標頭中指定的 retry-after 時間經過,而不是在節流時立即重試部署。 如果服務沒有可用的重試標頭,此演算法有助於讓裝置上線體驗更加順暢:

min_retry_delay_msec = 1000
max_retry_delay_msec = (1.0 / <load>) * <T> * 1000
max_random_jitter_msec = max_retry_delay_msec

在此邏輯下,裝置會延遲 min_retry_delay_msecmax_retry_delay_msec 之間的一段隨機時間再重新連線。 重試延遲上限會以下列變數計算:

  • <load> 是一個可設定的因素,其值為 > 0,表示負載將以平均負載時間乘以每秒連線數執行
  • <T> 是裝置冷開機的絕對最短時間 (計算為 T = N / cps,其中 N 是裝置總數和 cps 是每秒連線數的服務限制)。

如需重試作業計時方式的詳細資訊,請參閱重試計時

重新佈建裝置

重新佈建是先前已成功連線之後,需將裝置佈建至 IoT 中樞的程序。 有許多原因會導致裝置需重新連線至 IoT 中樞,例如:

  • 裝置可能因為電源中斷、網路連線中斷、異地重新配置、韌體更新、重設為原廠設定或憑證金鑰輪替而重新開機。
  • IoT 中樞執行個體可能因為非計劃性IoT 中樞服務中斷而無法使用。

您不應該在每次裝置重新啟動時完成佈建程序。 大多數重新佈建的裝置,最終都會連線到相同的 IoT 中樞。 相反地,裝置應嘗試使用從先前成功連線快取的資訊,直接連線到其 IoT 中樞。

可儲存連接字串的裝置

在初始佈建之後能夠儲存其連接字串的裝置應該這麼做,並在重新啟動後嘗試直接重新連線到 IoT 中樞。 這個模式可減少成功連線到適當 IoT 中樞的延遲。 可能的情況有兩個:

  • 裝置重新開機時要連線的 IoT 中樞與先前連接的 IoT 中樞相同。

    從快取擷取的連接字串應正常運作,且裝置可以重新連線到相同的端點。 不需要全新開始佈建程序。

  • 裝置重新開機時要連線的 IoT 中樞與先前連接的 IoT 中樞不同。

    儲存在記憶體中的連接字串不正確。 嘗試連線到相同的端點不會成功,因此會觸發 IoT 中樞連線的重試機制。 達到 IoT 中樞連線失敗閾值後,重試機制會自動觸發佈建程序全新開始。

無法儲存連接字串的裝置

有些裝置的使用量或記憶體不足,無法容納過去成功連線 IoT 中樞連接字串的快取。 重新啟動之後,這些裝置必須透過 DPS 重新佈建。 使用 DPS 註冊 API 來重新註冊。 請記住,根據 DPS 裝置註冊限制,每分鐘重新註冊的數目會受到限制。

重新佈建範例

此區段的程式碼範例顯示一個類別,用於讀取和寫入裝置快取,後面接續的程式碼會嘗試將裝置重新連線到 IoT 中樞,如果找不到連接字串,就會透過 DPS 重新佈建。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ProvisioningCache
{
  public class ProvisioningDetailsFileStorage : IProvisioningDetailCache
  {
    private string dataDirectory = null;

    public ProvisioningDetailsFileStorage()
    {
      dataDirectory = Environment.GetEnvironmentVariable("ProvisioningDetailsDataDirectory");
    }

    public ProvisioningResponse GetProvisioningDetailResponseFromCache(string registrationId)
    {
      try
        {
          var provisioningResponseFile = File.ReadAllText(Path.Combine(dataDirectory, registrationId));

          ProvisioningResponse response = JsonConvert.DeserializeObject<ProvisioningResponse>(provisioningResponseFile);

          return response;
        }
      catch (Exception ex)
      {
        return null;
      }
    }

    public void SetProvisioningDetailResponse(string registrationId, ProvisioningResponse provisioningDetails)
    {
      var provisioningDetailsJson = JsonConvert.SerializeObject(provisioningDetails);

      File.WriteAllText(Path.Combine(dataDirectory, registrationId), provisioningDetailsJson);
    }
  }
}

您可以使用類似下列的程式碼,在判斷快取中是否有連線資訊之後,決定如何繼續重新連接裝置:

IProvisioningDetailCache provisioningDetailCache = new ProvisioningDetailsFileStorage();

var provisioningDetails = provisioningDetailCache.GetProvisioningDetailResponseFromCache(registrationId);

// If no info is available in cache, go through DPS for provisioning
if(provisioningDetails == null)
{
  logger.LogInformation($"Initializing the device provisioning client...");
  using var transport = new ProvisioningTransportHandlerAmqp();
  ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create(dpsEndpoint, dpsScopeId, security, transport);
  logger.LogInformation($"Initialized for registration Id {security.GetRegistrationID()}.");
  logger.LogInformation("Registering with the device provisioning service... ");

  // This method will attempt to retry in case of a transient fault
  DeviceRegistrationResult result = await registerDevice(provClient);
  provisioningDetails = new ProvisioningResponse() { iotHubHostName = result.AssignedHub, deviceId = result.DeviceId };
  provisioningDetailCache.SetProvisioningDetailResponse(registrationId, provisioningDetails);
}

// If there was IoT Hub info from previous provisioning in the cache, try connecting to the IoT Hub directly
// If trying to connect to the IoT Hub returns status 429, make sure to retry operation honoring
//   the retry-after header
// If trying to connect to the IoT Hub returns a 500-series server error, have an exponential backoff with
//   at least 5 seconds of wait-time
// For all response codes 429 and 5xx, reprovision through DPS
// Ideally, you should also support a method to manually trigger provisioning on demand
if (provisioningDetails != null)
{
  logger.LogInformation($"Device {provisioningDetails.deviceId} registered to {provisioningDetails.iotHubHostName}.");
  logger.LogInformation("Creating TPM authentication for IoT Hub...");
  IAuthenticationMethod auth = new DeviceAuthenticationWithTpm(provisioningDetails.deviceId, security);
  logger.LogInformation($"Testing the provisioned device with IoT Hub...");
  DeviceClient iotClient = DeviceClient.Create(provisioningDetails.iotHubHostName, auth, TransportType.Amqp);
  logger.LogInformation($"Registering the Method Call back for Reprovisioning...");
  await iotClient.SetMethodHandlerAsync("Reprovision",reprovisionDirectMethodCallback, iotClient);

  // Now you should start a thread into this method and do your business while the DeviceClient is still connected
  await startBackgroundWork(iotClient);
  logger.LogInformation("Wait until closed...");

  // Wait until the app unloads or is cancelled
  var cts = new CancellationTokenSource();
  AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel();
  Console.CancelKeyPress += (sender, cpe) => cts.Cancel();

  await WhenCancelled(cts.Token);
  await iotClient.CloseAsync();
  Console.WriteLine("Finished.");
}

IoT 中樞連線能力考量

任何單一 IoT 中樞的裝置加上模組數上限均為一百萬。 如果您預計會擁有超過 100 萬部裝置,請將每個中樞的裝置數上限設為一百萬,並在擴增部署規模時視需要新增中樞。 如需詳細資訊,請參閱 IoT 中樞配額。 如果您預計會擁有超過一百萬部裝置,而且需要在特定區域 (例如歐盟地區以符合資料落地規範) 支援這些裝置,您可以與我們連絡,確保您要部署的區域容量可支援您目前和未來的規模。

透過 DPS 連線到 IoT 中樞時,裝置應該使用下列邏輯來回應連線時的錯誤碼:

  • 收到任何 500 系列伺服器錯誤回應時,使用快取的認證或裝置註冊狀態查閱 API 呼叫的結果來重試連線。
  • 收到 401, Unauthorized403, Forbidden404, Not Found 時,呼叫 DPS 註冊 API 以執行完整重新註冊。

裝置應該隨時都能回應使用者起始的重新佈建命令。

如果裝置與 IoT 中樞中斷連線,裝置應該嘗試直接重新連線到相同的 IoT 中樞 15-30 分鐘,再嘗試回到 DPS。

使用 DPS 的其他 IoT 中樞案例:

  • IoT 中樞容錯移轉:裝置應該持續運作,因為連線資訊不應變更,且設好的邏輯會在中樞恢復可用狀態後重試連線。
  • IoT 中樞有所變更:應使用自訂配置原則,將裝置指派給不同的 IoT 中樞。
  • 重試 IoT 中樞連線:您不應該使用積極的重試策略。 相反地,允許重試前至少一分鐘的間距。
  • IoT 中樞分割區:如果您的裝置策略高度依賴遙測,則應增加裝置到雲端的分割區數目。

監視裝置

整體部署的一個重要部分是端對端監視解決方案,可確保系統正常執行。 有多種方式可以針對大規模部署 IoT 裝置監視服務的健康情況。 經過實證,下列模式在監視服務時有效:

  • 建立應用程式以查詢 DPS 執行個體上的每個註冊群組、取得向該群組註冊的裝置總數,然後彙總來自各個註冊群組的數量。 此數提供目前透過 DPS 註冊且可用來監視服務狀態的確切裝置計數。
  • 監視特定期間內裝置註冊數。 例如監視過去五天內 DPS 執行個體的註冊率。 請注意,此方法只會提供近似數字,而且也會受限於特定一段時間。

下一步