Azure Functions 输出绑定深入探究:在 IoT-For-Beginners 交通项目中用 Blob 存储输出绑定保存 GPS 数据
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
本篇文章是 IoT-For-Beginners 课程
3-transport/lessons/2-store-location-data(存储位置数据)一课课后作业(assignment.md)的深入展开。该作业的核心任务是:理解 Azure Functions 的触发器和绑定机制,掌握通过function.json配置Blob 存储输出绑定(Blob storage output binding),让函数代码只需从main函数返回 blob 即可自动写入 Azure Blob 存储。读完本文,你将掌握函数绑定(trigger / input / output)的基础概念、function.json的配置语法、输出绑定与 Python SDK 两种写入存储方案的取舍,并能对照本仓库内的完整代码完成自己的绑定配置实验。
一、作业背景:为什么需要函数绑定
在上一课中,GPS 传感器采集到的经纬度数据被发送到 Azure IoT Hub。为了在下一课中把这些数据绘制到地图上,这些遥测事件需要被服务端代码接住并持久化——这正是本课 "warm path(温路径)" 数据流的一部分:数据到达云端后先落盘存储,供短期报告和分析使用。
在 3-transport/lessons/2-store-location-data/README.md 中,官方给出了两条连接存储的路线:
- 在函数代码内部,用 Blob 存储 Python SDK 连接存储账户并把数据写成 blob;
- 使用输出函数绑定(output function binding),把函数的返回值绑定到 Blob 存储,由运行时自动保存。
本课正文采用了第 1 种(Python SDK)方案;而本次作业要求你自行研究第 2 种方案——读懂 Azure Functions 官方文档中关于 "Triggers and Bindings" 与 "Blob storage output binding" 的说明,弄清楚如何配置输出绑定,并把它实际跑通。这是一种典型的 "课堂给方案、作业考原理" 的设计:能理解绑定的声明式配置,才真正理解 Azure Functions 的运行模型。
二、什么是 Azure Functions 的触发器和绑定
Azure Functions 的绑定模型把"函数与外部服务的数据交互"从代码中抽离出来,声明式地放在配置文件里。绑定分为三类:
- Trigger(触发器):定义函数被调用的时机,是函数的入口事件源。一个函数有且只有一个触发器。
- Input binding(输入绑定):为函数提供额外的只读数据输入。
- Output binding(输出绑定):把函数的结果(如返回值)自动写入目标服务,无需在代码里手写连接逻辑。
在本仓库的函数项目中,main函数的入口正是一个 Event Hub 触发器,声明在 function.json 中:
{ "scriptFile": "__init__.py", "bindings": [ { "type": "eventHubTrigger", "name": "events", "direction": "in", "eventHubName": "samples-workitems", "connection": "IOT_HUB_CONNECTION_STRING", "cardinality": "many", "consumerGroup": "$Default", "dataType": "binary" } ] }字段含义拆解:
| 字段 | 作用 |
|---|---|
type | 绑定类型,这里是eventHubTrigger(Event Hub 兼容端点触发器) |
name | 绑定在代码中的参数名,events对应main(events: List[func.EventHubEvent])的入参 |
direction | 数据方向,in表示输入/触发器 |
eventHubName | Event Hub 名称(示例值为占位符samples-workitems) |
connection | 存放连接字符串的环境变量名,指向local.settings.json或云上 Application Settings 中的IOT_HUB_CONNECTION_STRING |
cardinality | many表示批量接收事件(对应List[func.EventHubEvent]),one表示单条 |
consumerGroup | 消费组,默认$Default |
dataType | 数据类型,binary表示以二进制传入 |
关键点在于:触发器只负责"把事件送进函数",事件落盘存储则需要在bindings数组中追加一个方向为out的绑定条目。这正是作业要求你研究的内容。
三、function.json中声明 Blob 存储输出绑定
Azure Functions 的绑定配置遵循统一模型,输出绑定与输入绑定同处一个bindings数组,只需满足三点:
type设为 Blob 存储相关类型(对 Python v2 之前的 v1/v2 模型为blob);direction设为out;name与代码中main函数的参数名一致。
一个典型的 Blob 输出绑定条目如下(结合本作业任务与 官方输出绑定文档 的说明整理,供你对照自己的实现):
{ "type": "blob", "name": "outputBlob", "direction": "out", "path": "gps-data/{deviceId}/{rand-guid}.json", "connection": "STORAGE_CONNECTION_STRING" }path:blob 的容器与文件名模板。这里的{deviceId}来自函数签名中的绑定表达式(binding expression),{rand-guid}是系统提供的随机 GUID——无需自己用uuid生成文件名,运行时自动展开。connection:存储账户连接字符串所在的环境变量名(本仓库中即STORAGE_CONNECTION_STRING,见 local.settings.json)。
配置完成后,在__init__.py的main函数签名中加入同名参数,函数return一个字节串或字符串即可自动写入 blob:
import azure.functions as func def main(events: func.EventHubEvent, outputBlob: func.Out[str]): body = events.get_body().decode('utf-8') # 把结果通过输出绑定写回 blob 存储 outputBlob.set(json.dumps({"gps": json.loads(body)["gps"]}))如果采用"返回值绑定"的写法(不使用func.Out参数而是直接 return),则函数返回的内容会按path写入存储;返回None时不会产生任何写入。这正是作业描述中"通过从main函数返回 blob 来保存到 blob 存储"的含义。
四、两种写入方案在仓库中的实际对照
为了帮助你完成作业,可以对比本仓库已经实现好的SDK 方案(课堂正文路线),它就是你实现输出绑定方案时的参照物。
4.1 SDK 方案:手写连接与上传
仓库的 iot-hub-trigger/init.py 完整实现了"用 Python SDK 写 blob":
def get_or_create_container(name): connection_str = os.environ['STORAGE_CONNECTION_STRING'] blob_service_client = BlobServiceClient.from_connection_string(connection_str) for container in blob_service_client.list_containers(): if container.name == name: return blob_service_client.get_container_client(container.name) return blob_service_client.create_container(name, public_access=PublicAccess.Container) def main(events: List[func.EventHubEvent]): for event in events: logging.info('Python EventHub trigger processed an event: %s', event.get_body().decode('utf-8')) device_id = event.iothub_metadata['connection-device-id'] blob_name = f'{device_id}/{str(uuid.uuid1())}.json' container_client = get_or_create_container('gps-data') blob = container_client.get_blob_client(blob_name) event_body = json.loads(event.get_body().decode('utf-8')) blob_body = { 'device_id' : device_id, 'timestamp' : event.iothub_metadata['enqueuedtime'], 'gps': event_body['gps'] } logging.info(f'Writing blob to {blob_name} - {blob_body}') blob.upload_blob(json.dumps(blob_body).encode('utf-8'))这套代码里能看到 SDK 方案的全部细节:
- 连接串从
os.environ['STORAGE_CONNECTION_STRING']读取,本地开发时由 local.settings.json 注入,云端部署后由 Application Settings 注入; - 容器不存在时自动创建:
get_or_create_container先遍历list_containers(),找不到再create_container(..., public_access=PublicAccess.Container)。给容器开放公共访问权限,是为了下一课在地图上可视化 GPS 数据时可以直接查询; - blob 名按设备分文件夹:
f'{device_id}/{str(uuid.uuid1())}.json',例如gps-sensor/a9487ac2-b9cf-11eb-b5cd-1e00621e3648.json; - blob 内容结构为
{"device_id": ..., "timestamp": <入队时间>, "gps": {"lat": ..., "lon": ...}}。特别地,时间戳取event.iothub_metadata['enqueuedtime'](消息入队时间)而非当前时间——如果 Functions 应用未运行,消息可能在 Hub 上滞留一段时间,用入队时间才能还原真实发送时刻。
4.2 输出绑定方案:声明式自动写入
对照之下,输出绑定方案的目标是删掉上面这段样板代码:不再 importBlobServiceClient、不再管理连接串、不再手工upload_blob,只需在function.json里加一个out绑定,并在函数签名/返回值中暴露该绑定。两者的能力对比如下:
| 维度 | Python SDK 方案(本课正文) | 输出绑定方案(本次作业) |
|---|---|---|
| 连接管理 | 代码中从环境变量读取并创建客户端 | 运行时根据connection字段自动处理 |
| 容器创建 | 需手写get_or_create_container | 需预先创建容器(或依赖绑定路径) |
| blob 命名 | 代码中用uuid拼路径 | 绑定path模板 +{rand-guid}表达式 |
| 代码侵入 | 高,需要存储相关 import 与异常处理 | 低,只关心业务数据组装 |
| 灵活性 | 高,可自由控制容器/权限/批量逻辑 | 较低,按声明模板写入 |
五、本地开发环境的配套配置
无论走哪条路线,本地开发都依赖同一个配置骨架,值得一并掌握:
- local.settings.json:本地密钥与运行时配置,
FUNCTIONS_WORKER_RUNTIME为python,AzureWebJobsStorage使用UseDevelopmentStorage=true(指向 Azurite 本地存储模拟器),IOT_HUB_CONNECTION_STRING与STORAGE_CONNECTION_STRING分别供给触发器和存储绑定使用; - host.json:Functions 宿主配置,版本
2.0,并通过extensionBundle引入Microsoft.Azure.Functions.ExtensionBundle([2.*, 3.0.0)),绑定扩展无需单独安装 NuGet 包——这正是绑定声明式配置得以简化运行成本的关键; - requirements.txt:Python 依赖清单,注意注释明确提醒不要包含
azure-functions-worker(会与 Functions 平台冲突),只需azure-functions与azure-storage-blob。
六、作业验收标准与自查清单
作业 assignment.md 给出的评估标准(Rubric)是一张单维度表格,围绕"是否成功配置 Blob 存储输出绑定"分级:
| 标准 | 优秀(Exemplary) | 合格(Adequate) | 需改进(Needs Improvement) |
|---|---|---|---|
| 配置 Blob 存储输出绑定 | 能配置输出绑定、返回 blob,且 blob 成功写入存储 | 能配置输出绑定、或能返回 blob,但未能成功写入存储 | 完全无法配置输出绑定 |
对照此标准,完成作业时可按下述步骤自查:
- 确认绑定声明正确:
function.json的bindings数组中包含type: "blob"、direction: "out"的条目,且path指向gps-data容器; - 确认连接串生效:
local.settings.json中STORAGE_CONNECTION_STRING的值来自az storage account show-connection-string --output table --name <storage_name>命令的输出;函数签名与function.json的name字段一致; - 确认返回值被写入:运行 Functions 应用的同时让虚拟设备(code/virtual-device/gps-sensor/app.py)持续发送 GPS 遥测(JSON 格式为
{"gps": {"lat": ..., "lon": ...}}),然后用az storage blob list --container-name gps-data --output table --account-name <storage_name> --account-key <key1>检查容器中是否出现新 blob,并用az storage blob download下载校验内容; - 注意调试陷阱:运行 Functions 应用时不要同时运行
az iot hub monitor-events抢占 Event Hub 消费;输出绑定模式下若返回None则不会写入任何 blob,这是最常见的"配置正确却无数据"原因。
七、结语
本次作业的价值在于把"数据如何从 IoT Hub 流到 Blob 存储"这件事从"调用代码"提升到"声明配置"的认知层次。function.json中的每个绑定条目都是一种契约:触发器声明数据从哪里来,输出绑定声明数据到哪里去。你在 3-transport/lessons/2-store-location-data/README.md 中已经看到了 SDK 路线的完整实现,而作业要你独立走通输出绑定路线——两条路线殊途同归,最终都服务于下一课"在地图上可视化车辆轨迹"的 warm path 数据流。
提示:本作业涉及的微软官方文档(Azure Functions 触发器与绑定概念、Blob 存储绑定概览、Blob 存储输出绑定)建议直接访问微软文档站点阅读,注意其中的 Python 版本标签页与绑定属性参考。本文档源自课程翻译版本,作业原文以 3-transport/lessons/2-store-location-data/assignment.md 为准。
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考