Exportar e importar registos dos Hubs de Notificação do Azure em massa

Existem cenários em que é necessário criar ou modificar um grande número de registos num hub de notificação. Alguns destes cenários são atualizações de etiquetas após cálculos em lotes ou migração de uma implementação push existente para utilizar os Hubs de Notificação do Azure.

Este artigo explica como realizar um grande número de operações num hub de notificação ou exportar todos os registos em massa.

NOTA: A importação/exportação em massa só está disponível para o escalão de preço "standard"

Fluxo de alto nível

O suporte do Batch foi concebido para suportar tarefas de execução prolongada que envolvem milhões de registos. Para alcançar este dimensionamento, o suporte em lotes utiliza o Armazenamento do Azure para armazenar os detalhes e a saída da tarefa. Para operações de atualização em massa, o utilizador tem de criar um ficheiro num contentor de blobs, cujo conteúdo é a lista de operações de atualização de registo. Ao iniciar a tarefa, o utilizador fornece um URL para o blob de entrada, juntamente com um URL para um diretório de saída (também num contentor de blobs). Após o início da tarefa, o utilizador pode verificar o estado ao consultar uma localização de URL fornecida no início da tarefa. Uma tarefa específica só pode efetuar operações de um tipo específico (criações, atualizações ou eliminações). As operações de exportação são executadas de forma análoga.

Importar

Configurar

Esta secção pressupõe que tem as seguintes entidades:

Criar um ficheiro de entrada e armazená-lo num blob

Um ficheiro de entrada contém uma lista de registos serializados em XML, um por linha. Com o SDK do Azure, o exemplo de código seguinte mostra como serializar os registos e carregá-los para o contentor de blobs:

private static async Task SerializeToBlobAsync(BlobContainerClient container, RegistrationDescription[] descriptions)
{
     StringBuilder builder = new StringBuilder();
     foreach (var registrationDescription in descriptions)
     {
          builder.AppendLine(registrationDescription.Serialize());
     }

     var inputBlob = container.GetBlobClient(INPUT_FILE_NAME);
     using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
     {
         await inputBlob.UploadAsync(stream);
     }
}

Importante

O código anterior serializa os registos na memória e, em seguida, carrega todo o fluxo para um blob. Se tiver carregado um ficheiro com mais do que apenas alguns megabytes, veja a documentação de orientação do blob do Azure sobre como executar estes passos; por exemplo, blobs de blocos.

Criar tokens de URL

Assim que o ficheiro de entrada for carregado, gere os URLs a fornecer ao hub de notificação para o ficheiro de entrada e para o diretório de saída. Pode utilizar dois contentores de blobs diferentes para entrada e saída.

static Uri GetOutputDirectoryUrl(BlobContainerClient container)
{
      Console.WriteLine(container.CanGenerateSasUri);
      BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.All, DateTime.UtcNow.AddDays(1));
      return container.GenerateSasUri(builder);
}

static Uri GetInputFileUrl(BlobContainerClient container, string filePath)
{
      Console.WriteLine(container.CanGenerateSasUri);
      BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.Read, DateTime.UtcNow.AddDays(1));
      return container.GenerateSasUri(builder);
}

Submeter o trabalho

Com os dois URLs de entrada e saída, pode agora iniciar a tarefa de lote.

NotificationHubClient client = NotificationHubClient.CreateClientFromConnectionString(CONNECTION_STRING, HUB_NAME);
var job = await client.SubmitNotificationHubJobAsync(
     new NotificationHubJob {
             JobType = NotificationHubJobType.ImportCreateRegistrations,
             OutputContainerUri = outputContainerSasUri,
             ImportFileUri = inputFileSasUri
         }
     );

long i = 10;
while (i > 0 && job.Status != NotificationHubJobStatus.Completed)
{
    job = await client.GetNotificationHubJobAsync(job.JobId);
    await Task.Delay(1000);
    i--;
}

Além dos URLs de entrada e saída, este exemplo cria um NotificationHubJob objeto que contém um JobType objeto, que pode ser um dos seguintes tipos:

  • ImportCreateRegistrations
  • ImportUpdateRegistrations
  • ImportDeleteRegistrations

Assim que a chamada estiver concluída, a tarefa é continuada pelo hub de notificação e pode verificar o respetivo estado com a chamada para GetNotificationHubJobAsync.

No final da tarefa, pode inspecionar os resultados ao observar os seguintes ficheiros no diretório de saída:

  • /<hub>/<jobid>/Failed.txt
  • /<hub>/<jobid>/Output.txt

Estes ficheiros contêm a lista de operações bem-sucedidas e falhadas do lote. O formato de ficheiro é .cvs, no qual cada linha tem o número de linha do ficheiro de entrada original e o resultado da operação (normalmente, a descrição de registo criada ou atualizada).

Código de exemplo completo

O seguinte código de exemplo importa registos para um hub de notificação.

using Microsoft.Azure.NotificationHubs;
using Azure.Storage.Blobs;
using Azure.Storage.Sas;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        private static string CONNECTION_STRING = "namespace"; 
        private static string HUB_NAME = "demohub";
        private static string INPUT_FILE_NAME = "CreateFile.txt";
        private static string STORAGE_ACCOUNT_CONNECTIONSTRING = "connectionstring";
        private static string CONTAINER_NAME = "containername";

        static async Task Main(string[] args)
        {
            var descriptions = new[]
            {
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMkUxREQFBlVTTkMwMQ"),
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMjUxREQFBlVTTkMwMQ"),
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMhUxREQFBlVTTkMwMQ"),
                new MpnsRegistrationDescription(@"http://dm2.notify.live.net/throttledthirdparty/01.00/12G9Ed13dLb5RbCii5fWzpFpAgAAAAADAQAAAAQUZm52OkJCMjg1QTg1QkZDMdUxREQFBlVTTkMwMQ"),
            };

            // Get a reference to a container named "sample-container" and then create it
            BlobContainerClient container = new BlobContainerClient(STORAGE_ACCOUNT_CONNECTIONSTRING, CONTAINER_NAME);

            await container.CreateIfNotExistsAsync();

            await SerializeToBlobAsync(container, descriptions);

            // TODO then create Sas
            var outputContainerSasUri = GetOutputDirectoryUrl(container);
            
            BlobContainerClient inputcontainer = new BlobContainerClient(STORAGE_ACCOUNT_CONNECTIONSTRING, STORAGE_ACCOUNT_CONNECTIONSTRING + "/" +         INPUT_FILE_NAME);

            var inputFileSasUri = GetInputFileUrl(inputcontainer, INPUT_FILE_NAME);


            // Import this file
            NotificationHubClient client = NotificationHubClient.CreateClientFromConnectionString(CONNECTION_STRING, HUB_NAME);
            var job = await client.SubmitNotificationHubJobAsync(
                new NotificationHubJob {
                    JobType = NotificationHubJobType.ImportCreateRegistrations,
                    OutputContainerUri = outputContainerSasUri,
                    ImportFileUri = inputFileSasUri
                }
            );

            long i = 10;
            while (i > 0 && job.Status != NotificationHubJobStatus.Completed)
            {
                job = await client.GetNotificationHubJobAsync(job.JobId);
                await Task.Delay(1000);
                i--;
            }
        }

        private static async Task SerializeToBlobAsync(BlobContainerClient container, RegistrationDescription[] descriptions)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var registrationDescription in descriptions)
            {
                builder.AppendLine(registrationDescription.Serialize());
            }

            var inputBlob = container.GetBlobClient(INPUT_FILE_NAME);
            using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(builder.ToString())))
            {
                await inputBlob.UploadAsync(stream);
            }
        }

        static Uri GetOutputDirectoryUrl(BlobContainerClient container)
        {
            Console.WriteLine(container.CanGenerateSasUri);
            BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.All, DateTime.UtcNow.AddDays(1));
            return container.GenerateSasUri(builder);
        }

        static Uri GetInputFileUrl(BlobContainerClient container, string filePath)
        {
            Console.WriteLine(container.CanGenerateSasUri);
            BlobSasBuilder builder = new BlobSasBuilder(BlobSasPermissions.Read, DateTime.UtcNow.AddDays(1));
            return container.GenerateSasUri(builder);

        }
    }
}

Exportar

A exportação do registo é semelhante à importação, com as seguintes diferenças:

  • Só precisa do URL de saída.
  • Crie um NotificationHubJob do tipo ExportRegistrations.

Fragmento de código de exemplo

Segue-se um fragmento de código de exemplo para exportar registos em Java:

// Submit an export job
NotificationHubJob job = new NotificationHubJob();
job.setJobType(NotificationHubJobType.ExportRegistrations);
job.setOutputContainerUri("container uri with SAS signature");
job = hub.submitNotificationHubJob(job);

// Wait until the job is done
while(true){
    Thread.sleep(1000);
    job = hub.getNotificationHubJob(job.getJobId());
    if(job.getJobStatus() == NotificationHubJobStatus.Completed)
        break;
}

Passos seguintes

Para saber mais sobre os registos, veja os seguintes artigos: