Práticas recomendadas para implantações de dispositivos IoT em grande escala

Dimensionar uma solução de IoT para milhões de dispositivos pode ser um desafio. Muitas vezes, as soluções de grande escala têm de ser concebidas de acordo com os limites de serviço e de subscrição. Quando os clientes usam o Serviço de Provisionamento de Dispositivo do Azure IoT, eles o usam em combinação com outros serviços e componentes da plataforma IoT do Azure, como o Hub IoT e os SDKs de dispositivo IoT do Azure. Este artigo descreve práticas recomendadas, padrões e código de exemplo que você pode incorporar em seu design para aproveitar esses serviços e permitir que suas implantações sejam dimensionadas. Seguindo esses padrões e práticas a partir da fase de design do projeto, você pode maximizar o desempenho de seus dispositivos IoT.

Provisionar novos dispositivos

O provisionamento pela primeira vez é o processo de integração de um dispositivo pela primeira vez como parte de uma solução de IoT. Ao trabalhar com implantações em grande escala, é importante agendar o processo de provisionamento para evitar situações de sobrecarga causadas por todos os dispositivos que tentam se conectar ao mesmo tempo.

Usar uma agenda de provisionamento escalonada

Para a implantação de dispositivos na escala de milhões, registrar todos os dispositivos de uma só vez pode resultar na sobrecarga da instância DPS devido à limitação (código 429, Too Many Requestsde resposta HTTP) e uma falha no registro de seus dispositivos. Para evitar essa limitação, use um cronograma de registro escalonado para os dispositivos. Configure os tamanhos dos lotes de registro do dispositivo de acordo com as cotas e limites do DPS. Por exemplo, se a taxa de registo for de 200 dispositivos por minuto, o tamanho do lote para integração será de 200 dispositivos por lote.

Operações de repetição

Se ocorrerem falhas transitórias devido a um serviço estar ocupado, a lógica de repetição permitirá que os dispositivos se conectem com êxito à nuvem IoT. No entanto, um grande número de novas tentativas pode degradar ainda mais um serviço ocupado que está funcionando perto ou em sua capacidade. Como em qualquer serviço do Azure, você deve implementar um mecanismo de repetição inteligente com backoff exponencial. Mais informações sobre diferentes padrões de repetição podem ser encontradas no padrão de design de repetição e no tratamento de falhas transitórias.

Em vez de tentar novamente imediatamente uma implantação quando limitada, aguarde até a hora especificada no retry-after cabeçalho. Se não houver um cabeçalho de repetição disponível no serviço, esse algoritmo pode ajudar a obter uma experiência de integração de dispositivo mais suave:

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

Com essa lógica, os dispositivos atrasam a reconexão por um período aleatório de tempo, entre min_retry_delay_msec e max_retry_delay_msec. O atraso máximo de repetição é calculado com as seguintes variáveis:

  • <load> é um fator configurável com valores > 0, que indica que a carga será executada a uma média de tempo de carregamento multiplicada pelo número de conexões por segundo
  • <T> é o tempo mínimo absoluto para inicializar a frio os dispositivos (calculado como T = N / cps onde N é o número total de dispositivos e cps é o limite de serviço para o número de conexões por segundo).

Para obter mais informações sobre o tempo das operações de repetição, consulte Tempo de repetição.

Reaprovisionar dispositivos

O reprovisionamento é o processo em que um dispositivo precisa ser provisionado para um Hub IoT depois de ter sido conectado com êxito anteriormente. Pode haver muitos motivos que resultam na necessidade de um dispositivo se reconectar a um Hub IoT, como:

  • Um dispositivo pode ser reinicializado devido a falta de energia, perda de conectividade de rede, realocação geográfica, atualizações de firmware, redefinição de fábrica ou rotação de chave de certificado.
  • A instância do Hub IoT pode estar indisponível devido a uma interrupção não planejada do Hub IoT.

Você não deve precisar passar pelo processo de provisionamento toda vez que um dispositivo for reinicializado. A maioria dos dispositivos que são reprovisionados acabam conectados ao mesmo hub IoT. Em vez disso, um dispositivo deve tentar se conectar ao seu hub IoT diretamente usando as informações que foram armazenadas em cache de uma conexão anterior bem-sucedida.

Dispositivos que podem armazenar uma cadeia de conexão

Os dispositivos que têm a capacidade de armazenar sua cadeia de conexão após o provisionamento inicial devem fazê-lo e tentar se reconectar diretamente ao Hub IoT após a reinicialização. Esse padrão reduz a latência na conexão bem-sucedida ao Hub IoT apropriado. Há dois casos possíveis aqui:

  • O Hub IoT a ser conectado após a reinicialização do dispositivo é o mesmo que o Hub IoT conectado anteriormente.

    A cadeia de conexão recuperada do cache deve funcionar bem e o dispositivo pode se reconectar ao mesmo ponto de extremidade. Não há necessidade de um novo começo para o processo de provisionamento.

  • O Hub IoT para se conectar após a reinicialização do dispositivo é diferente do Hub IoT conectado anteriormente.

    A cadeia de conexão armazenada na memória é imprecisa. A tentativa de conexão com o mesmo ponto de extremidade não será bem-sucedida e, portanto, o mecanismo de repetição para a conexão do Hub IoT será acionado. Quando o limite para a falha de conexão do Hub IoT é atingido, o mecanismo de repetição aciona automaticamente um novo início para o processo de provisionamento.

Dispositivos que não conseguem armazenar uma cadeia de ligação

Alguns dispositivos não têm um espaço ou memória grande o suficiente para acomodar o cache da cadeia de conexão de uma conexão bem-sucedida passada do Hub IoT. Esses dispositivos precisam ser reprovisionados através do DPS após a reinicialização. Use a API de registro do DPS para se registrar novamente. Lembre-se de que o número de novos registros por minuto é limitado com base no limite de registro do dispositivo DPS.

Exemplo de reprovisionamento

Os exemplos de código nesta seção mostram uma classe para leitura e gravação no cache do dispositivo, seguida por um código que tenta reconectar um dispositivo ao Hub IoT se uma cadeia de conexão for encontrada e reprovisionar por meio do DPS se não estiver.

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);
    }
  }
}

Você pode usar um código semelhante ao seguinte para determinar como proceder com a reconexão de um dispositivo depois de determinar se há informações de conexão no cache:

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.");
}

Considerações sobre conectividade do Hub IoT

Qualquer hub IoT é limitado a 1 milhão de dispositivos mais módulos. Se você planeja ter mais de um milhão de dispositivos, limite o número de dispositivos a 1 milhão por hub e adicione hubs conforme necessário ao aumentar a escala de sua implantação. Para obter mais informações, consulte Cotas do Hub IoT. Se você tiver planos para mais de um milhão de dispositivos e precisar oferecer suporte a eles em uma região específica (como em uma região da UE para requisitos de residência de dados), entre em contato conosco para garantir que a região em que está implantando tenha a capacidade de suportar sua escala atual e futura.

Ao se conectar ao Hub IoT via DPS, os dispositivos devem usar a seguinte lógica em resposta aos códigos de erro ao se conectarem:

  • Ao receber qualquer uma das 500 séries de respostas de erro do servidor, tente novamente a conexão usando credenciais armazenadas em cache ou os resultados de uma chamada de API de Pesquisa de Status de Registro de Dispositivo.
  • Ao receber 401, Unauthorized ou 403, Forbidden 404, Not Found, execute um novo registro completo chamando a API de registro do DPS.

A qualquer momento, os dispositivos devem ser capazes de responder a um comando de reprovisionamento iniciado pelo usuário.

Se os dispositivos forem desconectados do Hub IoT, os dispositivos devem tentar se reconectar diretamente ao mesmo Hub IoT por 15 a 30 minutos antes de tentar voltar ao DPS.

Outros cenários do Hub IoT ao usar o DPS:

  • Failover do Hub IoT: os dispositivos devem continuar a funcionar, pois as informações de conexão não devem ser alteradas e a lógica está em vigor para tentar novamente a conexão assim que o hub estiver disponível novamente.
  • Alteração do Hub IoT: a atribuição de dispositivos a um Hub IoT diferente deve ser feita usando uma política de alocação personalizada.
  • Repetir a conexão do Hub IoT: você não deve usar uma estratégia agressiva de repetição. Em vez disso, permita um intervalo de pelo menos um minuto antes de uma nova tentativa.
  • Partições do Hub IoT: se a sua estratégia de dispositivo depender muito da telemetria, o número de partições de dispositivo para nuvem deve ser aumentado.

Dispositivos de monitorização

Uma parte importante da implantação geral é monitorar a solução de ponta a ponta para garantir que o sistema esteja funcionando adequadamente. Há várias maneiras de monitorar a integridade de um serviço para implantação em larga escala de dispositivos IoT. Os seguintes padrões provaram ser eficazes no monitoramento do serviço:

  • Crie um aplicativo para consultar cada grupo de registro em uma instância do DPS, obtenha o total de dispositivos registrados nesse grupo e, em seguida, agregue os números de vários grupos de registro. Este número fornece uma contagem exata dos dispositivos que estão atualmente registrados via DPS e pode ser usado para monitorar o estado do serviço.
  • Monitorize os registos de dispositivos durante um período específico. Por exemplo, monitore as taxas de registro para uma instância DPS nos cinco dias anteriores. Observe que essa abordagem fornece apenas um valor aproximado e também é limitada a um período de tempo.

Próximos passos