共用方式為


大規模 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 執行個體的註冊率。 這種方法僅提供近似數字,並且還限制在一段時間內。

下一步