Esercizio: Elaborare gli eventi e archiviare i dati in Azure Cosmos DB

Completato

Una seconda funzione può restare in ascolto degli eventi dello spazio dei nomi specifico nell'hub eventi di Azure ed elaborarli e archiviarli in un database creato con Azure Cosmos DB.

Creare un database con Azure Cosmos DB

Per creare il database, usare il comando az cosmosdb create. Il comando usa un account Azure Cosmos DB, un database e un contenitore SQL.

az cosmosdb create \
    --resource-group $RESOURCE_GROUP \
    --name $COSMOS_DB_ACCOUNT
az cosmosdb sql database create \
    --resource-group $RESOURCE_GROUP \
    --account-name $COSMOS_DB_ACCOUNT \
    --name TelemetryDb
az cosmosdb sql container create \
    --resource-group $RESOURCE_GROUP \
    --account-name $COSMOS_DB_ACCOUNT \
    --database-name TelemetryDb \
    --name TelemetryInfo \
    --partition-key-path '/temperatureStatus'

Per questo scenario, la temperatura è interessante. Quindi si definisce temperatureStatus come chiave di partizione.

Compilare, configurare e distribuire un'altra funzione di Azure

Con gli hub eventi, è possibile iniziare con i flussi di dati in megabyte per poi aumentare a gigabyte o terabyte. La funzionalità di aumento automatico è una delle numerose opzioni disponibili per dimensionare il numero di unità elaborate in modo da soddisfare le esigenze di utilizzo.

Le applicazioni consumer per ogni funzione hanno una vista separata del flusso di eventi. I consumer leggono il flusso in modo indipendente in base al proprio ritmo e con offset specifici.

Per questo scenario viene creata una funzione consumer di Azure come esempio. La funzione, creata seguendo le procedure consigliate, deve essere indipendente, con il proprio account di archiviazione e le associazioni per l'accoppiamento libero e la scalabilità.

az storage account create \
    --resource-group $RESOURCE_GROUP \
    --name $STORAGE_ACCOUNT"c" \
    --sku Standard_LRS
az functionapp create \
    --resource-group $RESOURCE_GROUP \
    --name $FUNCTION_APP"-c"\
    --storage-account $STORAGE_ACCOUNT"c" \
    --consumption-plan-location $LOCATION \
    --runtime java \
    --functions-version 4

Recuperare le stringhe di connessione

La funzione consumer deve essere in grado di riconoscere il proprio account di archiviazione e l'hub eventi. Deve inoltre essere sensibile al database in cui scrive gli eventi elaborati.

AZURE_WEB_JOBS_STORAGE=$( \
    az storage account show-connection-string \
        --resource-group $RESOURCE_GROUP \
        --name $STORAGE_ACCOUNT"c" \
        --query connectionString \
        --output tsv)
echo $AZURE_WEB_JOBS_STORAGE
COSMOS_DB_CONNECTION_STRING=$( \
    az cosmosdb keys list \
        --resource-group $RESOURCE_GROUP \
        --name $COSMOS_DB_ACCOUNT \
        --type connection-strings \
        --query 'connectionStrings[0].connectionString' \
        --output tsv)
echo $COSMOS_DB_CONNECTION_STRING

È possibile usare il comando echo $EVENT_HUB_CONNECTION_STRING per verificare se la variabile è ancora impostata correttamente. In caso contrario, eseguire di nuovo il comando seguente:

EVENT_HUB_CONNECTION_STRING=$( \
    az eventhubs eventhub authorization-rule keys list \
        --resource-group $RESOURCE_GROUP \
        --name $EVENT_HUB_AUTHORIZATION_RULE \
        --eventhub-name $EVENT_HUB_NAME \
        --namespace-name $EVENT_HUB_NAMESPACE \
        --query primaryConnectionString \
        --output tsv)
echo $EVENT_HUB_CONNECTION_STRING

Queste stringhe di connessione devono essere archiviate nelle impostazioni dell'applicazione per l'account Funzioni di Azure.

az functionapp config appsettings set \
    --resource-group $RESOURCE_GROUP \
    --name $FUNCTION_APP"-c" \
    --settings \
        AzureWebJobsStorage=$AZURE_WEB_JOBS_STORAGE \
        EventHubConnectionString=$EVENT_HUB_CONNECTION_STRING \
        CosmosDBConnectionString=$COSMOS_DB_CONNECTION_STRING

Nota

Per gli ambienti di produzione, è possibile usare un'istanza di Azure Key Vault per archiviare e gestire le stringhe di connessione.

Creare l'applicazione Funzioni

Prima di creare la funzione successiva, assicurarsi di essere nella cartella corretta.

cd ..
mvn archetype:generate --batch-mode \
    -DarchetypeGroupId=com.microsoft.azure \
    -DarchetypeArtifactId=azure-functions-archetype \
    -DappName=$FUNCTION_APP"-c" \
    -DresourceGroup=$RESOURCE_GROUP \
    -DappRegion=$LOCATION \
    -DappServicePlanName=$LOCATION"plan" \
    -DgroupId=com.learn \
    -DartifactId=telemetry-functions-consumer

Il comando crea un'applicazione come nell'ultimo esercizio. Si eliminano i file di test, si aggiorna local.settings.file con il comando fetch-app-settings e quindi si sostituisce il file Function.java esistente.

cd telemetry-functions-consumer
rm -r src/test

Aggiornare le impostazioni locali per l'esecuzione e il debug locali.

func azure functionapp fetch-app-settings $FUNCTION_APP"-c"

Quindi, aprire il file Function.java e sostituirne il contenuto con il codice seguente:

package com.learn;

import com.learn.TelemetryItem.status;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.OutputBinding;
import com.microsoft.azure.functions.annotation.Cardinality;
import com.microsoft.azure.functions.annotation.CosmosDBOutput;
import com.microsoft.azure.functions.annotation.EventHubTrigger;

public class Function {

    @FunctionName("processSensorData")
    public void processSensorData(
        @EventHubTrigger(
            name = "msg",
            eventHubName = "", // blank because the value is included in the connection string
            cardinality = Cardinality.ONE,
            connection = "EventHubConnectionString")
            TelemetryItem item,
        @CosmosDBOutput(
            name = "databaseOutput",
            databaseName = "TelemetryDb",
            collectionName = "TelemetryInfo",
            connectionStringSetting = "CosmosDBConnectionString")
            OutputBinding<TelemetryItem> document,
        final ExecutionContext context) {
    
        context.getLogger().info("Event hub message received: " + item.toString());
    
        if (item.getPressure() > 30) {
            item.setNormalPressure(false);
        } else {
            item.setNormalPressure(true);
        }
    
        if (item.getTemperature() < 40) {
            item.setTemperatureStatus(status.COOL);
        } else if (item.getTemperature() > 90) {
            item.setTemperatureStatus(status.HOT);
        } else {
            item.setTemperatureStatus(status.WARM);
        }
    
        document.setValue(item);
    }
}

Creare un altro nuovo file denominato TelemetryItem.java nello stesso percorso di Function.java e aggiungere il codice seguente:

package com.learn;

public class TelemetryItem {

    private String id;
    private double temperature;
    private double pressure;
    private boolean isNormalPressure;
    private status temperatureStatus;
    static enum status {
        COOL,
        WARM,
        HOT
    }

    public TelemetryItem(double temperature, double pressure) {
        this.temperature = temperature;
        this.pressure = pressure;
    }

    public String getId() {
        return id;
    }

    public double getTemperature() {
        return temperature;
    }

    public double getPressure() {
        return pressure;
    }

    @Override
    public String toString() {
        return "TelemetryItem={id=" + id + ",temperature="
            + temperature + ",pressure=" + pressure + "}";
    }

    public boolean isNormalPressure() {
        return isNormalPressure;
    }

    public void setNormalPressure(boolean isNormal) {
        this.isNormalPressure = isNormal;
    }

    public status getTemperatureStatus() {
        return temperatureStatus;
    }

    public void setTemperatureStatus(status temperatureStatus) {
        this.temperatureStatus = temperatureStatus;
    }
}

Quando l'hub eventi riceve il messaggio, genera un evento. La funzione processSensorData viene eseguita quando riceve l'evento. Elabora quindi i dati dell'evento e usa un'associazione di output di Azure Cosmos DB per inviare i risultati al database. Si usa nuovamente la classe TelemetryItem.java. Gli oggetti TelemetryItem possono essere considerati come il contratto gestito dal consumer tra i partecipanti di questo sistema guidato dagli eventi.

Eseguire localmente

Con Funzioni di Azure è possibile ricevere eventi da tutto il mondo. Sì, è anche possibile ricevere eventi localmente nel proprio computer di sviluppo.

mvn clean package
mvn azure-functions:run

Dopo i messaggi di compilazione e di avvio, vengono visualizzati gli eventi in ingresso quando viene eseguita la funzione:

[2021-01-19T16:45:24.709Z] Executing 'Functions.processSensorData' (Reason='(null)', Id=87354afa-abf4-4963-bd44-0c1421048240)
[2021-01-19T16:45:24.712Z] Event hub message received: TelemetryItem={id=null,temperature=21.653044570769897,pressure=36.061288095436126}
[2021-01-19T16:45:24.712Z] Function "processSensorData" (Id: 87354afa-abf4-4963-bd44-0c1421048240) invoked by Java Worker

Nel portale di Azure passare al proprio account Azure Cosmos DB. Selezionare Esplora dati, TelemetryInfo e quindi Elementi per visualizzare i dati quando arrivano.

Screenshot that shows TelemetryInfo in Azure Cosmos DB Data Explorer.

Distribuire in Azure

A questo punto, è possibile spostare l'intero carico di lavoro nel cloud. Per distribuire le funzioni in Funzioni di Azure, usare il comando mvn azure-functions:deploy di Maven. Assicurarsi di essere ancora nel repository corretto, telemetry-functions.

mvn azure-functions:deploy

Ottimo! L'intero scenario di telemetria è stato distribuito inviando i dati a un hub eventi e usando i dati con una funzione indipendente diversa. La funzione elabora i dati e poi archivia il risultato in un database creato con Azure Cosmos DB. Come è possibile verificare che l'applicazione soddisfi i requisiti predefiniti? Usando il monitoraggio.