练习 - 构建 Azure 函数以模拟遥测数据

已完成

在我们的示例中,我们使用事件溯源。 让我们构建一个函数,该函数将模拟遥测数据并将其发送到事件中心。 稍后,另一个函数可以侦听此事件,对其进行处理,然后将其存储到使用 Azure Cosmos DB 创建的数据库中。

Visualization of event sourcing for buying coffee at a coffee shop.

准备环境

让我们定义一些环境变量,使下面的所有命令尽可能简短易懂。 定义 <value> 占位符,然后在终端或命令行工具中粘贴并运行以下命令:

RESOURCE_GROUP=<value>
EVENT_HUB_NAMESPACE=<value>
EVENT_HUB_NAME=<value>
EVENT_HUB_AUTHORIZATION_RULE=<value>
COSMOS_DB_ACCOUNT=<value>
STORAGE_ACCOUNT=<value>
FUNCTION_APP=<value>
LOCATION=<value>

注意

若要设置 LOCATION 变量,可以查看 az functionapp list-consumption-locations 命令并取最接近的值。

创建所需的组件

在 Azure 上预配资源需要一些时间。 让我们尽早开始创建组件,以避免在以后长时间等待。

创建资源组

最好将训练、概念证明或原型的所有资源绑定到一个资源组中。 这样,你就可以用一个命令方便地清理所有使用过的服务。 若要在指定位置创建资源组,请在终端中运行以下命令:

az group create \
    --name $RESOURCE_GROUP \
    --location $LOCATION

创建和配置事件中心

对于事件中心,需要指定其应侦听的命名空间。 此外,你需要将授权规则配置为 ListenSend

az eventhubs namespace create \
    --resource-group $RESOURCE_GROUP \
    --name $EVENT_HUB_NAMESPACE
az eventhubs eventhub create \
    --resource-group $RESOURCE_GROUP \
    --name $EVENT_HUB_NAME \
    --namespace-name $EVENT_HUB_NAMESPACE \
az eventhubs eventhub authorization-rule create \
    --resource-group $RESOURCE_GROUP \
    --name $EVENT_HUB_AUTHORIZATION_RULE \
    --eventhub-name $EVENT_HUB_NAME \
    --namespace-name $EVENT_HUB_NAMESPACE \
    --rights Listen Send

构建、配置和部署 Azure 函数

为使此示例尽可能真实,请创建一个 Azure 函数并模拟遥测数据。 你也可以将 IoT 设备绑定到 Azure 函数,这样就可以获得真实的数据。 由于此函数是生成事件的函数,因此让我们添加一个 p 或 -p 标志。

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

注意

请使用 functions-version 4,因为 2 和 3 已于 2022 年 12 月停用。

az functionapp create 命令创建函数应用程序时,它还会创建一个具有相同名称的 Application Insights 资源。 我们稍后将使用该资源进行监视。

若要检索存储帐户和事件中心的连接字符串,请使用以下命令将其保存在环境变量中,然后使用 echo 命令显示它们。

AZURE_WEB_JOBS_STORAGE=$( \
    az storage account show-connection-string \
        --resource-group $RESOURCE_GROUP \
        --name $STORAGE_ACCOUNT"p" \
        --query connectionString \
        --output tsv)
echo $AZURE_WEB_JOBS_STORAGE
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

若要将这些连接字符串存储在 Azure 函数帐户的应用程序设置中,请在终端运行以下命令:

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

现在,你的 Azure 资源事件中心和 Azure 函数已创建并配置为一起有序工作。

接下来,利用 Maven 创建一个本地函数项目。

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

此命令在 telemetry-functions-producer 文件夹内生成多个文件:

  • 具有预定义 Azure 依赖项的 pom.xml 生成文件。
  • 用于保存本地部署和手动测试的应用程序设置的 local.settings.json 文件。
  • 支持 Azure Functions 扩展捆绑包的 host.json 文件。
  • 包含默认 HTTP 触发器函数的 Function.java 文件。
  • 此 Learn 模块不使用的一些测试文件。

我们不会用到此学习模块中的测试文件,因此可随意将其删除。

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

对于本地执行,需检索应用程序设置并将其存储在 local.settings.json 文件中。 可以通过运行 fetch-app-settings 命令自动执行此操作。

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

接下来,打开 Function.java 文件并将其中的内容替换为以下代码:

package com.learn;

import com.microsoft.azure.functions.annotation.EventHubOutput;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.TimerTrigger;
import com.microsoft.azure.functions.ExecutionContext;
public class Function {

    @FunctionName("generateSensorData")
    @EventHubOutput(
        name = "event",
        eventHubName = "", // blank because the value is included in the connection string
        connection = "EventHubConnectionString")
    public TelemetryItem generateSensorData(
        @TimerTrigger(
            name = "timerInfo",
            schedule = "*/10 * * * * *") // every 10 seconds
            String timerInfo,
        final ExecutionContext context) {
            context.getLogger().info("Java Timer trigger function executed at: " + java.time.LocalDateTime.now());
            double temperature = Math.random() * 100;
            double pressure = Math.random() * 50;
        return new TelemetryItem(temperature, pressure);
    }
}

generateSensorData 函数模拟向事件中心发送温度和压力读数的传感器。 计时器触发器每 10 秒运行一次该函数,事件中心输出绑定将返回值发送到事件中心。

当事件中心收到消息时,它会生成一个事件。

此函数使用的数据是使用名为 TelemetryItem 的类存储的,你需要实现此类。 在与 Function.java 相同的位置创建名为 TelemetryItem.java 的新文件,并添加以下代码:

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

在本地运行

在本地运行 Azure Functions 时,它们已传输到世界各地! 此外,还可以在 Azure 门户中查看它们。

mvn clean package
mvn azure-functions:run

在某些生成和启动消息后,每次运行函数时,将看到类似于以下示例的输出:

[2021-01-19T16:25:40.005Z] Executing 'Functions.generateSensorData' (Reason='Timer fired at 2021-01-19T17:25:40.0044630+01:00', Id=fcf567a3-03ec-4159-9714-aa4449861b30)
[2021-01-19T16:25:40.011Z] Java Timer trigger function executed at: 2021-01-19T17:25:40.009405
[2021-01-19T16:25:40.013Z] Function "generateSensorData" (Id: fcf567a3-03ec-4159-9714-aa4449861b30) invoked by Java Worker
[2021-01-19T16:25:40.048Z] Executed 'Functions.generateSensorData' (Succeeded, Id=fcf567a3-03ec-4159-9714-aa4449861b30, Duration=43ms)

注意

在 Azure 云中部署和运行函数之前,可以通过本地计算机将事件发送到世界各地! 这对于开发、调试和本地测试非常有用。

部署到 Azure

请运行 mvn azure-functions:deploy 命令,在 Azure 上触发部署,然后继续。

mvn azure-functions:deploy