Back to blog

IoT Edge Computing & Kiến Trúc Fog

iotedge-computingarchitecturebackendtutorial
IoT Edge Computing & Kiến Trúc Fog

Chào mừng bạn đến với Phase 3 của Lộ Trình IoT Patterns & Strategies! Trong Phase 1, chúng ta đã khám phá kiến trúc phân tầng IoT và các loại thiết bị. Trong Phase 2, chúng ta đã đào sâu vào các giao thức — MQTT, CoAP, AMQP, và WebSocket. Giờ câu hỏi là: xử lý diễn ra ở đâu?

Gửi mọi sensor reading từ 100.000 thiết bị thẳng lên cloud nghe có vẻ đơn giản — cho đến khi bạn thấy hóa đơn bandwidth, con số latency, và chuyện gì xảy ra khi internet sập. Edge computing đảo ngược mô hình — xử lý dữ liệu gần nơi nó được sinh ra, chỉ gửi lên cloud những gì cloud thực sự cần.

Đến cuối bài viết này, bạn sẽ hiểu khi nào dùng edge, fog, hay cloud processing, cách triển khai các edge patterns phổ biến, và cách giữ hệ thống chạy khi mất kết nối.

Bạn Sẽ Học Được Gì

✅ Hiểu tại sao edge computing quan trọng cho IoT (latency, bandwidth, resilience)
✅ So sánh kiến trúc edge vs fog vs cloud với trade-offs rõ ràng
✅ Triển khai data filtering, aggregation, và store-and-forward tại edge
✅ Đánh giá các edge runtimes: AWS Greengrass, Azure IoT Edge, K3s, Docker
✅ Thiết kế fog computing layers cho hệ thống IoT phân cấp
✅ Xây dựng ứng dụng IoT offline-first sync lại khi có kết nối
✅ Chạy ML inference tại edge cho quyết định real-time


Tại Sao Edge Computing Quan Trọng Cho IoT

Vấn Đề Của Mô Hình Cloud-Only

Hãy xem một nhà máy có 10.000 sensor, mỗi cái gửi readings mỗi giây:

10.000 sensors × 1 reading/giây × 200 bytes = 2 MB/giây = 172 GB/ngày

Giờ nhân với chi phí mạng, chi phí cloud compute, và 50-200ms latency round-trip lên cloud. Một cảm biến rung phát hiện hỏng thiết bị cần phản hồi trong dưới 10ms — chứ không phải 200ms sau khi đi vòng qua us-east-1.

Ba Lý Do Nên Xử Lý Tại Edge

1. Latency — Một số quyết định không thể chờ cloud.

Tình HuốngLatency Yêu CầuCloud Khả Thi?
Cảnh báo bảo trì dự đoán< 100msCó thể
Tránh va chạm cánh tay robot< 10msKhông
Điều chỉnh đèn giao thông thông minh< 50msKhông
Báo cáo giám sát môi trường< 5 phútCó
Tổng hợp tiêu thụ năng lượng thángHàng giờCó

2. Bandwidth — Dữ liệu sensor thô tốn kém khi truyền.

3. Resilience — Internet sẽ sập. Nhà máy của bạn không nên dừng.

Edge processing đảm bảo các hoạt động quan trọng tiếp tục trong khi mất mạng. Quyết định ảnh hưởng đến an toàn, kiểm soát chất lượng, và điều chỉnh real-time xảy ra cục bộ — cloud nhận bản tóm tắt khi kết nối trở lại.


Edge vs Fog vs Cloud

Ba thuật ngữ này hay bị dùng lẫn lộn, nhưng chúng đại diện cho các tầng riêng biệt với trách nhiệm khác nhau:

Bảng So Sánh

Khía CạnhEdgeFogCloud
Vị tríTrên hoặc cạnh thiết bịMạng cục bộ (gateway, on-prem server)Trung tâm dữ liệu từ xa
Latency< 10ms10-100ms50-500ms
Sức mạnh tính toánHạn chế (ARM, microcontroller)Trung bình (x86 server, GPU)Không giới hạn (auto-scale)
Lưu trữPhạm vi GBPhạm vi TBPhạm vi PB
Kết nốiCó thể hoạt động offlineCần mạng cục bộCần internet
Hardware ví dụRaspberry Pi, Jetson Nano, ESP32On-prem server, industrial PCAWS, Azure, GCP
Loại xử lýLọc, ngưỡng, ML đơn giảnTổng hợp, phân tích khu vực, model inferenceTraining, phân tích toàn cục, truy vấn lịch sử
Chi phíThấp mỗi thiết bị, scale tuyến tínhTrung bình (hạ tầng chia sẻ)Trả theo dùng, có thể tăng đột biến

Khi Nào Dùng Mỗi Tầng


Edge Computing Patterns

Pattern 1: Data Filtering

Edge pattern đơn giản nhất — bỏ data bạn không cần trước khi nó rời thiết bị.

// Edge data filter — chạy trên gateway device
interface SensorReading {
  deviceId: string;
  timestamp: number;
  temperature: number;
  humidity: number;
  vibration: number;
}
 
class EdgeDataFilter {
  private lastSent: Map<string, SensorReading> = new Map();
 
  // Chỉ forward readings thay đổi đáng kể
  shouldForward(reading: SensorReading): boolean {
    const last = this.lastSent.get(reading.deviceId);
 
    if (!last) {
      this.lastSent.set(reading.deviceId, reading);
      return true; // Luôn forward reading đầu tiên
    }
 
    const tempDelta = Math.abs(reading.temperature - last.temperature);
    const humidDelta = Math.abs(reading.humidity - last.humidity);
    const vibDelta = Math.abs(reading.vibration - last.vibration);
 
    // Forward chỉ khi thay đổi vượt ngưỡng
    if (tempDelta > 0.5 || humidDelta > 2.0 || vibDelta > 10.0) {
      this.lastSent.set(reading.deviceId, reading);
      return true;
    }
 
    return false; // Bỏ qua — không đủ đáng kể
  }
}
 
// Sử dụng trên edge gateway
const filter = new EdgeDataFilter();
const readings = getIncomingSensorReadings(); // Từ MQTT subscription
 
for (const reading of readings) {
  if (filter.shouldForward(reading)) {
    publishToCloud(reading); // Forward lên cloud MQTT broker
  }
  // Âm thầm bỏ qua readings không đáng kể
}

Kết quả: Nếu nhiệt độ dao động ±0.1°C, bạn gửi một reading thay vì hàng trăm. Giảm thường thấy: 70-95% ít messages hơn.

Pattern 2: Edge Aggregation

Thay vì gửi mọi raw reading, tính toán bản tóm tắt cục bộ:

interface AggregatedReading {
  deviceId: string;
  windowStart: number;
  windowEnd: number;
  temperature: {
    min: number;
    max: number;
    avg: number;
    count: number;
  };
  humidity: {
    min: number;
    max: number;
    avg: number;
    count: number;
  };
}
 
class EdgeAggregator {
  private windows: Map<string, SensorReading[]> = new Map();
  private windowDuration = 60_000; // Cửa sổ 1 phút
 
  addReading(reading: SensorReading): void {
    const key = reading.deviceId;
    if (!this.windows.has(key)) {
      this.windows.set(key, []);
    }
    this.windows.get(key)!.push(reading);
  }
 
  flush(deviceId: string): AggregatedReading | null {
    const readings = this.windows.get(deviceId);
    if (!readings || readings.length === 0) return null;
 
    const temps = readings.map(r => r.temperature);
    const humids = readings.map(r => r.humidity);
 
    const result: AggregatedReading = {
      deviceId,
      windowStart: readings[0].timestamp,
      windowEnd: readings[readings.length - 1].timestamp,
      temperature: {
        min: Math.min(...temps),
        max: Math.max(...temps),
        avg: temps.reduce((a, b) => a + b, 0) / temps.length,
        count: temps.length,
      },
      humidity: {
        min: Math.min(...humids),
        max: Math.max(...humids),
        avg: humids.reduce((a, b) => a + b, 0) / humids.length,
        count: humids.length,
      },
    };
 
    this.windows.set(deviceId, []); // Reset cửa sổ
    return result;
  }
}
 
// Mỗi 60 giây, flush aggregates lên cloud
setInterval(() => {
  for (const deviceId of aggregator.getAllDeviceIds()) {
    const summary = aggregator.flush(deviceId);
    if (summary) {
      publishToCloud(summary); // 1 message thay vì 60
    }
  }
}, 60_000);

60 raw readings mỗi phút → 1 aggregated message. Cloud nhận min/max/avg — đủ cho dashboards và trend analysis.

Pattern 3: Threshold Alerting

Phản ứng với điều kiện quan trọng ngay lập tức tại edge mà không cần chờ cloud:

interface Alert {
  deviceId: string;
  type: 'warning' | 'critical';
  metric: string;
  value: number;
  threshold: number;
  timestamp: number;
  message: string;
}
 
interface ThresholdRule {
  metric: string;
  warningThreshold: number;
  criticalThreshold: number;
  direction: 'above' | 'below';
}
 
class EdgeAlertEngine {
  private rules: ThresholdRule[] = [
    { metric: 'temperature', warningThreshold: 35, criticalThreshold: 45, direction: 'above' },
    { metric: 'temperature', warningThreshold: 5, criticalThreshold: 0, direction: 'below' },
    { metric: 'vibration', warningThreshold: 50, criticalThreshold: 80, direction: 'above' },
    { metric: 'humidity', warningThreshold: 80, criticalThreshold: 95, direction: 'above' },
  ];
 
  // Debounce: không spam alerts
  private lastAlert: Map<string, number> = new Map();
  private cooldownMs = 30_000; // 30 giây cooldown mỗi device+metric
 
  evaluate(reading: SensorReading): Alert[] {
    const alerts: Alert[] = [];
 
    for (const rule of this.rules) {
      const value = reading[rule.metric as keyof SensorReading] as number;
      if (typeof value !== 'number') continue;
 
      const key = `${reading.deviceId}:${rule.metric}:${rule.direction}`;
      const lastTime = this.lastAlert.get(key) || 0;
 
      if (Date.now() - lastTime < this.cooldownMs) continue;
 
      let triggered = false;
      let type: 'warning' | 'critical' = 'warning';
      let threshold = rule.warningThreshold;
 
      if (rule.direction === 'above') {
        if (value >= rule.criticalThreshold) {
          triggered = true;
          type = 'critical';
          threshold = rule.criticalThreshold;
        } else if (value >= rule.warningThreshold) {
          triggered = true;
          type = 'warning';
        }
      } else {
        if (value <= rule.criticalThreshold) {
          triggered = true;
          type = 'critical';
          threshold = rule.criticalThreshold;
        } else if (value <= rule.warningThreshold) {
          triggered = true;
          type = 'warning';
        }
      }
 
      if (triggered) {
        this.lastAlert.set(key, Date.now());
        alerts.push({
          deviceId: reading.deviceId,
          type,
          metric: rule.metric,
          value,
          threshold,
          timestamp: Date.now(),
          message: `${rule.metric} ${rule.direction} ${threshold}: ${value}`,
        });
      }
    }
 
    return alerts;
  }
}
 
// Trên edge gateway
const alertEngine = new EdgeAlertEngine();
 
function processReading(reading: SensorReading) {
  const alerts = alertEngine.evaluate(reading);
 
  for (const alert of alerts) {
    // Phản ứng cục bộ — không cần cloud
    if (alert.type === 'critical') {
      triggerLocalBuzzer();
      activateEmergencyShutdown(alert.deviceId);
    }
 
    // Cũng forward alert lên cloud để logging
    publishToCloud(alert);
  }
}

Lợi ích chính: Còi báo kêu trong < 5ms thay vì chờ 200ms+ cho cloud round trip.

Pattern 4: Store-and-Forward

Buffer dữ liệu cục bộ khi mạng sập, flush khi khôi phục:

import * as fs from 'fs';
import * as path from 'path';
 
interface QueuedMessage {
  topic: string;
  payload: string;
  timestamp: number;
  retries: number;
}
 
class StoreAndForward {
  private queue: QueuedMessage[] = [];
  private maxQueueSize = 10_000;
  private persistPath: string;
  private isOnline = true;
 
  constructor(persistDir: string) {
    this.persistPath = path.join(persistDir, 'message-queue.json');
    this.loadFromDisk(); // Khôi phục sau khi restart
  }
 
  async send(topic: string, payload: string): Promise<void> {
    if (this.isOnline) {
      try {
        await this.publishToCloud(topic, payload);
        return; // Gửi thành công
      } catch {
        this.isOnline = false;
        console.log('Mạng sập — chuyển sang store-and-forward');
      }
    }
 
    // Lưu cục bộ
    this.enqueue({ topic, payload, timestamp: Date.now(), retries: 0 });
  }
 
  private enqueue(msg: QueuedMessage): void {
    if (this.queue.length >= this.maxQueueSize) {
      // Bỏ messages cũ nhất không quan trọng (FIFO eviction)
      this.queue.shift();
    }
    this.queue.push(msg);
    this.saveToDisk(); // Sống sót qua device restarts
  }
 
  async flush(): Promise<void> {
    if (this.queue.length === 0) return;
 
    console.log(`Flushing ${this.queue.length} queued messages...`);
    const failed: QueuedMessage[] = [];
 
    for (const msg of this.queue) {
      try {
        await this.publishToCloud(msg.topic, msg.payload);
      } catch {
        if (msg.retries < 3) {
          msg.retries++;
          failed.push(msg);
        } else {
          console.log(`Bỏ message sau 3 lần retry: ${msg.topic}`);
        }
      }
    }
 
    this.queue = failed;
    this.saveToDisk();
 
    if (failed.length === 0) {
      this.isOnline = true;
      console.log('Tất cả queued messages đã flush thành công');
    }
  }
 
  private saveToDisk(): void {
    fs.writeFileSync(this.persistPath, JSON.stringify(this.queue));
  }
 
  private loadFromDisk(): void {
    try {
      const data = fs.readFileSync(this.persistPath, 'utf-8');
      this.queue = JSON.parse(data);
      console.log(`Đã load ${this.queue.length} messages từ disk`);
    } catch {
      this.queue = [];
    }
  }
 
  private async publishToCloud(topic: string, payload: string): Promise<void> {
    // Actual MQTT/HTTP publish lên cloud broker
    throw new Error('Not implemented — dùng MQTT client của bạn ở đây');
  }
}
 
// Sử dụng
const saf = new StoreAndForward('/var/edge/data');
 
// Hoạt động bình thường — gửi hoặc queue tự động
await saf.send('factory/zone-1/temperature', JSON.stringify(reading));
 
// Kiểm tra kết nối định kỳ và flush
setInterval(async () => {
  if (await checkConnectivity()) {
    await saf.flush();
  }
}, 30_000); // Kiểm tra mỗi 30 giây

Kiến Trúc Fog Computing

Fog computing nằm giữa edge và cloud — thường là on-premise servers hoặc gateways tổng hợp dữ liệu từ nhiều edge devices. Hãy nghĩ nó như một hub xử lý khu vực.

Khi Nào Cần Fog

Edge devices bị giới hạn — một Raspberry Pi không thể chạy ML models phức tạp hay tương quan dữ liệu từ 500 sensors. Nhưng gửi mọi thứ lên cloud thì lãng phí. Tầng fog xử lý:

  • Tương quan liên thiết bị: "Nhiệt độ tăng đột biến ở sensor A có liên quan đến rung tăng ở sensor B không?"
  • ML inference khu vực: Chạy pre-trained models trên on-prem GPU server
  • Dashboard cục bộ: Công nhân trên sàn nhà máy cần xem real-time mà không phụ thuộc internet
  • Dịch giao thức: Bridge thiết bị MQTT, CoAP, và Modbus thành luồng dữ liệu thống nhất

Triển Khai Fog Node

Một fog node thường chạy như service trên on-premise server, thu thập từ nhiều edge devices:

import mqtt from 'mqtt';
 
interface ZoneData {
  zone: string;
  devices: Map<string, SensorReading[]>;
  lastAggregation: number;
}
 
class FogNode {
  private zones: Map<string, ZoneData> = new Map();
  private edgeBroker: mqtt.MqttClient;
  private cloudBroker: mqtt.MqttClient;
 
  constructor(edgeBrokerUrl: string, cloudBrokerUrl: string) {
    // Kết nối MQTT broker cục bộ (edge devices publish tại đây)
    this.edgeBroker = mqtt.connect(edgeBrokerUrl);
    // Kết nối cloud MQTT broker (fog publishes aggregates lên đây)
    this.cloudBroker = mqtt.connect(cloudBrokerUrl);
 
    this.setupSubscriptions();
    this.startAggregationLoop();
  }
 
  private setupSubscriptions(): void {
    // Subscribe tất cả zone data từ edge devices
    this.edgeBroker.subscribe('factory/+/+/data');
 
    this.edgeBroker.on('message', (topic, message) => {
      // topic: factory/{zone}/{device}/data
      const parts = topic.split('/');
      const zone = parts[1];
      const deviceId = parts[2];
      const reading: SensorReading = JSON.parse(message.toString());
 
      this.ingestReading(zone, deviceId, reading);
    });
  }
 
  private ingestReading(zone: string, deviceId: string, reading: SensorReading): void {
    if (!this.zones.has(zone)) {
      this.zones.set(zone, {
        zone,
        devices: new Map(),
        lastAggregation: Date.now(),
      });
    }
 
    const zoneData = this.zones.get(zone)!;
    if (!zoneData.devices.has(deviceId)) {
      zoneData.devices.set(deviceId, []);
    }
    zoneData.devices.get(deviceId)!.push(reading);
 
    // Chạy cross-device anomaly detection
    this.detectCrossDeviceAnomalies(zone);
  }
 
  private detectCrossDeviceAnomalies(zone: string): void {
    const zoneData = this.zones.get(zone);
    if (!zoneData) return;
 
    // Ví dụ: tương quan nhiệt độ và rung giữa các thiết bị
    const latestReadings: SensorReading[] = [];
    for (const [, readings] of zoneData.devices) {
      if (readings.length > 0) {
        latestReadings.push(readings[readings.length - 1]);
      }
    }
 
    const avgTemp = latestReadings.reduce((sum, r) => sum + r.temperature, 0)
                    / latestReadings.length;
    const maxVibration = Math.max(...latestReadings.map(r => r.vibration));
 
    // Alert cấp zone: nhiệt độ cao + rung cao = có thể hỏng thiết bị
    if (avgTemp > 40 && maxVibration > 60) {
      this.cloudBroker.publish(
        `alerts/factory/${zone}/anomaly`,
        JSON.stringify({
          type: 'cross_device_anomaly',
          zone,
          avgTemp,
          maxVibration,
          timestamp: Date.now(),
          message: `Zone ${zone}: nhiệt độ cao (${avgTemp.toFixed(1)}°C) + rung (${maxVibration})`,
        })
      );
    }
  }
 
  private startAggregationLoop(): void {
    // Mỗi 5 phút, aggregate và gửi lên cloud
    setInterval(() => {
      for (const [zone, zoneData] of this.zones) {
        const summary = this.aggregateZone(zone, zoneData);
 
        this.cloudBroker.publish(
          `factory/${zone}/summary`,
          JSON.stringify(summary)
        );
 
        // Reset device readings sau aggregation
        for (const [deviceId] of zoneData.devices) {
          zoneData.devices.set(deviceId, []);
        }
        zoneData.lastAggregation = Date.now();
      }
    }, 5 * 60_000);
  }
 
  private aggregateZone(zone: string, data: ZoneData) {
    const deviceSummaries: Record<string, any> = {};
 
    for (const [deviceId, readings] of data.devices) {
      if (readings.length === 0) continue;
 
      const temps = readings.map(r => r.temperature);
      deviceSummaries[deviceId] = {
        readingCount: readings.length,
        temperature: {
          min: Math.min(...temps),
          max: Math.max(...temps),
          avg: temps.reduce((a, b) => a + b, 0) / temps.length,
        },
      };
    }
 
    return {
      zone,
      windowStart: data.lastAggregation,
      windowEnd: Date.now(),
      deviceCount: data.devices.size,
      devices: deviceSummaries,
    };
  }
}
 
// Khởi động fog node
const fog = new FogNode(
  'mqtt://local-broker:1883',   // Edge devices publish tại đây
  'mqtt://cloud-broker:8883'    // Fog publishes aggregates lên đây
);

Các Lựa Chọn Edge Runtime

Khi quyết định chạy code tại edge, bạn cần một runtime — cách deploy, quản lý, và cập nhật phần mềm trên edge devices. Đây là so sánh các lựa chọn chính:

Bảng So Sánh

Tính NăngAWS GreengrassAzure IoT EdgeK3sDocker on Edge
Nhà cung cấpAWSMicrosoftRancher/SUSEDocker Inc
Tích hợp cloudSâu AWS (IoT Core, Lambda, S3)Sâu Azure (IoT Hub, Functions)Bất kỳ (cloud-agnostic)Bất kỳ
Mô hình lập trìnhLambda functions, containersModules (Docker containers)Pods (Kubernetes)Containers
Hardware tối thiểu1 GHz ARM, 128 MB RAM1 GHz x86/ARM, 256 MB RAM512 MB RAM, 1 CPU512 MB RAM
OTA updatesTích hợp (Greengrass deployments)Tích hợp (IoT Hub deployments)Helm charts, kubectldocker compose pull
Messaging cục bộLocal MQTT broker + IPCRoute messages giữa modulesTùy chọn (tự cài)Tùy chọn
Hỗ trợ offlineCó (local Lambda execution)Có (modules chạy cục bộ)Có (pods chạy cục bộ)Có
Quản lý thiết bịAWS IoT Device ShadowAzure Device TwinManual hoặc RancherManual
Chi phíFree tier + trả cho cloud servicesFree tier + trả cho cloud servicesMiễn phí (open source)Miễn phí (open source)
Phù hợp nhấtĐội ngũ dùng AWSĐội ngũ dùng AzureCloud-agnostic, đội KubernetesDeployments đơn giản, prototypes

AWS IoT Greengrass

Greengrass cho phép bạn chạy Lambda functions và Docker containers trên edge devices, với tích hợp liền mạch vào AWS IoT Core:

# greengrass-deployment.yaml
# Greengrass V2 component deployment
RecipeFormatVersion: "2020-01-25"
ComponentName: com.factory.temperature-filter
ComponentVersion: "1.0.0"
Manifests:
  - Platform:
      os: linux
    Lifecycle:
      run: |
        python3 {artifacts:path}/filter.py
    Artifacts:
      - URI: s3://my-bucket/components/filter.py
ComponentDependencies:
  aws.greengrass.Nucleus:
    VersionRequirement: ">=2.0.0"
# filter.py — Greengrass V2 component
import json
import awsiot.greengrasscoreipc as ipc
 
ipc_client = ipc.connect()
 
def on_message(event):
    message = json.loads(event.message.payload.decode())
    temperature = message.get('temperature', 0)
 
    # Chỉ forward readings đáng kể lên cloud
    if temperature > 35 or temperature < 5:
        ipc_client.publish_to_iot_core(
            topic_name=f"factory/alerts/{message['deviceId']}",
            qos='AT_LEAST_ONCE',
            payload=json.dumps({
                'alert': 'temperature_anomaly',
                'value': temperature,
                'deviceId': message['deviceId'],
            })
        )
 
# Subscribe topic MQTT cục bộ
ipc_client.subscribe_to_topic(
    topic='factory/+/temperature',
    on_stream_event=on_message,
)

Azure IoT Edge

Azure IoT Edge chạy containerized modules trên edge devices, quản lý qua Azure IoT Hub:

// deployment.json — Azure IoT Edge deployment manifest
{
  "modulesContent": {
    "$edgeAgent": {
      "properties.desired": {
        "modules": {
          "temperatureFilter": {
            "type": "docker",
            "settings": {
              "image": "myregistry.azurecr.io/temp-filter:1.0",
              "createOptions": "{\"HostConfig\":{\"Privileged\":true}}"
            },
            "status": "running",
            "restartPolicy": "always"
          }
        }
      }
    },
    "$edgeHub": {
      "properties.desired": {
        "routes": {
          "filterToCloud": "FROM /messages/modules/temperatureFilter/outputs/alerts INTO $upstream",
          "sensorToFilter": "FROM /messages/modules/sensor/* INTO BrokeredEndpoint(\"/modules/temperatureFilter/inputs/data\")"
        }
      }
    }
  }
}

K3s — Kubernetes Nhẹ Tại Edge

K3s là bản phân phối Kubernetes được CNCF chứng nhận, xây dựng cho edge và IoT. Nó gọt Kubernetes xuống một binary duy nhất dưới 100 MB:

# Cài K3s trên edge device (một lệnh duy nhất)
curl -sfL https://get.k3s.io | sh -
 
# Kiểm tra đang chạy
sudo k3s kubectl get nodes
# NAME          STATUS   ROLES                  AGE   VERSION
# edge-gw-01   Ready    control-plane,master   30s   v1.28.4+k3s1
# edge-processor.yaml — Deploy edge processing dưới dạng Kubernetes pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: edge-data-processor
  namespace: iot
spec:
  replicas: 1
  selector:
    matchLabels:
      app: edge-processor
  template:
    metadata:
      labels:
        app: edge-processor
    spec:
      containers:
      - name: processor
        image: myregistry/edge-processor:1.0
        resources:
          limits:
            cpu: "500m"
            memory: "256Mi"
          requests:
            cpu: "100m"
            memory: "128Mi"
        env:
        - name: MQTT_BROKER
          value: "mqtt://mosquitto:1883"
        - name: CLOUD_BROKER
          value: "mqtts://cloud-broker:8883"
        volumeMounts:
        - name: edge-data
          mountPath: /var/edge/data
      volumes:
      - name: edge-data
        hostPath:
          path: /var/edge/data
          type: DirectoryOrCreate
---
# Local MQTT broker dưới dạng Kubernetes service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mosquitto
  namespace: iot
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mosquitto
  template:
    metadata:
      labels:
        app: mosquitto
    spec:
      containers:
      - name: mosquitto
        image: eclipse-mosquitto:2
        ports:
        - containerPort: 1883
        volumeMounts:
        - name: config
          mountPath: /mosquitto/config
      volumes:
      - name: config
        configMap:
          name: mosquitto-config
---
apiVersion: v1
kind: Service
metadata:
  name: mosquitto
  namespace: iot
spec:
  selector:
    app: mosquitto
  ports:
  - port: 1883
    targetPort: 1883

Tại sao K3s cho IoT edge?

  • Binary duy nhất, ~100 MB — chạy trên Raspberry Pi
  • Không bị lock-in cloud vendor
  • Full Kubernetes API — dùng Helm, kubectl, GitOps
  • Tích hợp SQLite thay vì etcd (nhẹ hơn)
  • Tự động TLS, traefik ingress đi kèm

Docker Compose on Edge

Cho deployments đơn giản hơn mà Kubernetes thì quá mức cần thiết:

# docker-compose.edge.yml
version: "3.8"
services:
  mosquitto:
    image: eclipse-mosquitto:2
    ports:
      - "1883:1883"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - mosquitto-data:/mosquitto/data
    restart: unless-stopped
 
  edge-processor:
    image: myregistry/edge-processor:1.0
    environment:
      - MQTT_BROKER=mqtt://mosquitto:1883
      - CLOUD_BROKER=mqtts://cloud.example.com:8883
      - STORE_PATH=/data/queue
    volumes:
      - edge-data:/data
    depends_on:
      - mosquitto
    restart: unless-stopped
 
  node-red:
    image: nodered/node-red:latest
    ports:
      - "1880:1880"
    volumes:
      - nodered-data:/data
    restart: unless-stopped
 
volumes:
  mosquitto-data:
  edge-data:
  nodered-data:
# Deploy trên edge gateway
docker compose -f docker-compose.edge.yml up -d
 
# Cập nhật một service
docker compose -f docker-compose.edge.yml pull edge-processor
docker compose -f docker-compose.edge.yml up -d edge-processor
 
# Kiểm tra trạng thái
docker compose -f docker-compose.edge.yml ps

Chiến Lược Sync Edge-to-Cloud

Edge và cloud cần đồng bộ — câu hỏi là bao nhiêu và bao lâu một lần.

So Sánh Chiến Lược

Chiến LượcĐộ Tươi Dữ LiệuBandwidthĐộ Phức TạpPhù Hợp Nhất
Streaming liên tụcReal-timeCaoThấpDeployments nhỏ, dữ liệu quan trọng
Upload batch định kỳPhút đến giờThấpTrung bìnhTelemetry, dữ liệu không quan trọng
Upload theo sự kiệnKhi thay đổiTrung bìnhTrung bìnhAlerts, thay đổi trạng thái
Hybrid (stream + batch)Hỗn hợpTrung bìnhCaoHệ thống production

Triển Khai Hybrid Sync

Hầu hết hệ thống production dùng cách tiếp cận hybrid — stream dữ liệu quan trọng, batch phần còn lại:

type Priority = 'critical' | 'normal' | 'low';
 
interface SyncConfig {
  critical: { mode: 'stream' };      // Real-time
  normal: { mode: 'batch'; intervalMs: number };  // Mỗi 5 phút
  low: { mode: 'batch'; intervalMs: number };     // Mỗi 30 phút
}
 
class HybridSync {
  private config: SyncConfig = {
    critical: { mode: 'stream' },
    normal: { mode: 'batch', intervalMs: 5 * 60_000 },
    low: { mode: 'batch', intervalMs: 30 * 60_000 },
  };
 
  private normalBatch: QueuedMessage[] = [];
  private lowBatch: QueuedMessage[] = [];
  private cloudClient: mqtt.MqttClient;
 
  constructor(cloudBrokerUrl: string) {
    this.cloudClient = mqtt.connect(cloudBrokerUrl);
 
    // Flush batch định kỳ
    setInterval(() => this.flushBatch('normal'), this.config.normal.intervalMs);
    setInterval(() => this.flushBatch('low'), this.config.low.intervalMs);
  }
 
  async sync(topic: string, payload: string, priority: Priority): Promise<void> {
    const message: QueuedMessage = { topic, payload, timestamp: Date.now(), retries: 0 };
 
    switch (priority) {
      case 'critical':
        // Stream ngay lập tức
        await this.publishNow(message);
        break;
      case 'normal':
        this.normalBatch.push(message);
        break;
      case 'low':
        this.lowBatch.push(message);
        break;
    }
  }
 
  private async publishNow(msg: QueuedMessage): Promise<void> {
    this.cloudClient.publish(msg.topic, msg.payload, { qos: 1 });
  }
 
  private flushBatch(priority: 'normal' | 'low'): void {
    const batch = priority === 'normal' ? this.normalBatch : this.lowBatch;
    if (batch.length === 0) return;
 
    // Gửi dưới dạng batch nén duy nhất
    const batchPayload = JSON.stringify({
      batchId: crypto.randomUUID(),
      priority,
      count: batch.length,
      messages: batch,
    });
 
    this.cloudClient.publish(`batch/${priority}`, batchPayload, { qos: 1 });
 
    // Xóa batch
    if (priority === 'normal') {
      this.normalBatch = [];
    } else {
      this.lowBatch = [];
    }
  }
}
 
// Sử dụng
const sync = new HybridSync('mqtts://cloud-broker:8883');
 
// Alert quan trọng — stream ngay lập tức
await sync.sync('alerts/factory/zone-1', alertPayload, 'critical');
 
// Telemetry bình thường — batch mỗi 5 phút
await sync.sync('telemetry/zone-1', telemetryPayload, 'normal');
 
// Environmental logs — batch mỗi 30 phút
await sync.sync('logs/zone-1', logPayload, 'low');

Edge ML Inference

Một trong những edge patterns mạnh mẽ nhất — chạy pre-trained machine learning models trực tiếp trên edge devices để dự đoán real-time mà không phụ thuộc cloud.

Tại Sao ML Tại Edge?

Yếu TốCloud MLEdge ML
Latency100-500ms5-50ms
Quyền riêng tưDữ liệu rời premisesDữ liệu ở local
Chi phíTrả theo inferenceChi phí hardware một lần
Kết nốiBắt buộcKhông cần
Kích thước modelKhông giới hạnBị giới hạn (phạm vi MB)

Các Use Cases Edge ML Phổ Biến

  • Anomaly detection: Dự đoán hỏng thiết bị từ pattern rung
  • Computer vision: Phát hiện lỗi trên dây chuyền sản xuất
  • Sound classification: Phát hiện tiếng máy bất thường
  • Predictive maintenance: Ước tính tuổi thọ còn lại (RUL)

Edge Inference với TensorFlow Lite

# edge_inference.py — Chạy trên Raspberry Pi hoặc Jetson Nano
import numpy as np
import json
import paho.mqtt.client as mqtt
 
# TensorFlow Lite — tối ưu cho edge devices
import tflite_runtime.interpreter as tflite
 
class EdgeAnomalyDetector:
    def __init__(self, model_path: str):
        # Load model đã quantize (thường 1-10 MB)
        self.interpreter = tflite.Interpreter(model_path=model_path)
        self.interpreter.allocate_tensors()
 
        self.input_details = self.interpreter.get_input_details()
        self.output_details = self.interpreter.get_output_details()
 
        # Sliding window các readings gần đây
        self.window_size = 60  # 60 giây dữ liệu
        self.buffer = []
 
    def add_reading(self, reading: dict) -> dict | None:
        """Thêm reading và trả về dự đoán anomaly nếu window đầy."""
        self.buffer.append([
            reading['temperature'],
            reading['humidity'],
            reading['vibration'],
            reading['pressure'],
        ])
 
        if len(self.buffer) < self.window_size:
            return None  # Chưa đủ dữ liệu
 
        if len(self.buffer) > self.window_size:
            self.buffer.pop(0)  # Sliding window
 
        return self.predict()
 
    def predict(self) -> dict:
        """Chạy inference trên window hiện tại."""
        # Chuẩn bị input tensor
        input_data = np.array([self.buffer], dtype=np.float32)
        self.interpreter.set_tensor(self.input_details[0]['index'], input_data)
 
        # Chạy inference
        self.interpreter.invoke()
 
        # Lấy output
        output = self.interpreter.get_tensor(self.output_details[0]['index'])
        anomaly_score = float(output[0][0])
 
        return {
            'anomaly_score': anomaly_score,
            'is_anomaly': anomaly_score > 0.8,  # Ngưỡng
            'confidence': min(anomaly_score, 1.0 - anomaly_score) * 2,
        }
 
 
# Tích hợp MQTT
detector = EdgeAnomalyDetector('models/anomaly_detector.tflite')
client = mqtt.Client()
 
def on_message(client, userdata, msg):
    reading = json.loads(msg.payload.decode())
    result = detector.add_reading(reading)
 
    if result and result['is_anomaly']:
        # Publish anomaly alert cục bộ và lên cloud
        alert = {
            'type': 'ml_anomaly',
            'deviceId': reading['deviceId'],
            'score': result['anomaly_score'],
            'confidence': result['confidence'],
            'timestamp': reading['timestamp'],
        }
        client.publish('alerts/anomaly', json.dumps(alert))
        print(f"⚠️ Phát hiện bất thường: score={result['anomaly_score']:.3f}")
 
client.on_message = on_message
client.connect('localhost', 1883)
client.subscribe('sensors/+/data')
client.loop_forever()

Flow Cập Nhật Edge ML Model

Models cần cập nhật định kỳ khi bạn train trên thêm dữ liệu ở cloud:


Offline-First Design Patterns

Edge devices phải giả định mạng sẽ sập. Thiết kế offline-first coi kết nối là bonus, không phải yêu cầu bắt buộc.

Nguyên Tắc

  1. Xử lý local-first — Mọi quyết định quan trọng xảy ra cục bộ
  2. Idempotent operations — An toàn khi replay lúc kết nối lại
  3. Conflict resolution — Xử lý dữ liệu cũ một cách graceful
  4. Graceful degradation — Giảm chức năng, không bao giờ crash

Kiến Trúc Offline-First

Connectivity State Machine

type ConnectivityState = 'online' | 'degraded' | 'offline';
 
class ConnectivityManager {
  private state: ConnectivityState = 'online';
  private failedPings = 0;
  private maxFailedPings = 3;
  private listeners: ((state: ConnectivityState) => void)[] = [];
 
  async checkConnectivity(): Promise<ConnectivityState> {
    try {
      const start = Date.now();
      await fetch('https://cloud-broker.example.com/health', {
        signal: AbortSignal.timeout(5000),
      });
      const latency = Date.now() - start;
 
      this.failedPings = 0;
 
      if (latency > 2000) {
        this.setState('degraded');
      } else {
        this.setState('online');
      }
    } catch {
      this.failedPings++;
 
      if (this.failedPings >= this.maxFailedPings) {
        this.setState('offline');
      } else {
        this.setState('degraded');
      }
    }
 
    return this.state;
  }
 
  private setState(newState: ConnectivityState): void {
    if (this.state !== newState) {
      console.log(`Kết nối: ${this.state} → ${newState}`);
      this.state = newState;
      this.listeners.forEach(fn => fn(newState));
    }
  }
 
  onStateChange(listener: (state: ConnectivityState) => void): void {
    this.listeners.push(listener);
  }
 
  getState(): ConnectivityState {
    return this.state;
  }
}
 
// Sử dụng
const connectivity = new ConnectivityManager();
 
connectivity.onStateChange((state) => {
  switch (state) {
    case 'online':
      console.log('Kết nối đầy đủ — streaming lên cloud');
      sync.flushAllQueues();
      break;
    case 'degraded':
      console.log('Suy giảm — batch mode, chỉ critical');
      sync.switchToBatchMode();
      break;
    case 'offline':
      console.log('Offline — xử lý cục bộ only');
      sync.pauseCloudSync();
      break;
  }
});
 
// Kiểm tra mỗi 30 giây
setInterval(() => connectivity.checkConnectivity(), 30_000);

Giải Quyết Conflict Dữ Liệu

Khi edge device kết nối lại, dữ liệu cục bộ có thể conflict với trạng thái cloud (ví dụ, một operator khác đã thay đổi cấu hình):

interface VersionedData<T> {
  data: T;
  version: number;
  lastModified: number;
  source: 'edge' | 'cloud';
}
 
type ConflictStrategy = 'cloud-wins' | 'edge-wins' | 'latest-wins' | 'merge';
 
class ConflictResolver {
  resolve<T>(
    local: VersionedData<T>,
    remote: VersionedData<T>,
    strategy: ConflictStrategy
  ): VersionedData<T> {
    // Không có conflict — versions khớp
    if (local.version === remote.version) return local;
 
    switch (strategy) {
      case 'cloud-wins':
        return remote;
 
      case 'edge-wins':
        return local;
 
      case 'latest-wins':
        return local.lastModified > remote.lastModified ? local : remote;
 
      case 'merge':
        // Logic merge tùy chỉnh — phụ thuộc vào kiểu dữ liệu
        return this.mergeData(local, remote);
 
      default:
        return remote; // Default an toàn
    }
  }
 
  private mergeData<T>(
    local: VersionedData<T>,
    remote: VersionedData<T>
  ): VersionedData<T> {
    // Cho sensor configs: dùng cloud cho thresholds, edge cho calibration
    const merged = {
      ...remote.data,   // Cloud settings được ưu tiên
      ...local.data,    // Edge overrides (ví dụ: calibration cục bộ)
    };
 
    return {
      data: merged as T,
      version: Math.max(local.version, remote.version) + 1,
      lastModified: Date.now(),
      source: 'edge',
    };
  }
}

Lỗi Thường Gặp Của Người Mới

Lỗi 1: Gửi Mọi Thứ Lên Cloud

❌ 10.000 sensors → 172 GB/ngày raw data → cloud → $$$
 
✅ 10.000 sensors → edge filter → 5 GB/ngày summaries → cloud → $

Cách sửa: Bắt đầu với câu hỏi "cloud thực sự cần gì?" Thường thì là aggregates, alerts, và tóm tắt mỗi giờ — không phải mọi raw reading.

Lỗi 2: Không Có Chiến Lược Offline

❌ Mạng sập → edge device báo lỗi → mất dữ liệu
 
✅ Mạng sập → store-and-forward → auto-flush khi kết nối lại

Cách sửa: Coi mạng là không đáng tin cậy từ ngày đầu. Triển khai store-and-forward trước khi bạn nghĩ mình cần — bạn sẽ cần.

Lỗi 3: Over-Engineering Edge

❌ Chạy Kubernetes + Kafka + PostgreSQL trên Raspberry Pi
 
✅ Chạy Node.js process đơn giản + SQLite + MQTT trên Raspberry Pi

Cách sửa: Khớp runtime với hardware. Pi có 1-8 GB RAM — Docker Compose thường là quá đủ rồi. Dành Kubernetes cho on-prem servers ở tầng fog.

Lỗi 4: Bỏ Qua Edge Security

❌ Edge device chạy root, không TLS, password mặc định, ports mở
 
✅ Least privilege, mTLS, credentials riêng mỗi thiết bị, firewall rules

Cách sửa: Edge devices có thể tiếp cận vật lý — chúng cần nhiều security hơn, không phải ít. Chúng ta sẽ nói chi tiết về IoT security trong bài viết tương lai.

Lỗi 5: Không Validate Model Tại Edge

❌ Push ML model mới → thay thế cái cũ ngay → predictions sai
 
✅ Push ML model mới → chạy shadow cùng cái cũ → validate → swap

Cách sửa: Luôn chạy models mới ở shadow mode trước. So sánh outputs với model hiện tại trong giai đoạn validation trước khi chuyển.


Tóm Tắt và Bài Học Chính

✅ Edge computing giảm latency, bandwidth, và phụ thuộc cloud — xử lý gần nguồn
✅ Edge, fog, và cloud là các tầng riêng biệt — edge cho lọc, fog cho phối hợp, cloud cho phân tích toàn cục
✅ Data filtering tại edge tiết kiệm 70-95% bandwidth — chỉ forward thay đổi đáng kể và aggregates
✅ Store-and-forward là thiết yếu — buffer dữ liệu cục bộ khi mạng sập, flush khi kết nối lại
✅ Threshold alerting tại edge cho phản hồi dưới 10ms — quan trọng cho an toàn và bảo vệ thiết bị
✅ Khớp runtime với hardware — Docker Compose cho deployments đơn giản, K3s cho đội Kubernetes, Greengrass/IoT Edge cho đội dùng cloud vendor
✅ Edge ML inference chạy trong 5-50ms — anomaly detection, phát hiện lỗi, predictive maintenance không cần cloud
✅ Thiết kế offline-first từ ngày đầu — coi kết nối là bonus, không phải yêu cầu bắt buộc
✅ Validate ML models ở shadow mode — không bao giờ hot-swap model mà không comparison testing


Tiếp Theo

Bây giờ bạn đã hiểu xử lý diễn ra ở đâu — edge, fog, hay cloud — đã đến lúc đào sâu vào giao thức cung cấp sức mạnh cho hầu hết giao tiếp IoT.

Tiếp: IOT-5: Deep Dive: MQTT — Giao Thức Messaging IoT — MQTT v3.1.1 vs v5.0, topic design patterns, QoS trade-offs chuyên sâu, broker clustering (EMQX, HiveMQ), authentication và authorization, và scaling MQTT cho hàng triệu kết nối.


Bài Viết Liên Quan


Đây là Phần 4 của series IoT Patterns & Strategies. Bắt đầu từ đầu hoặc nhảy đến bất kỳ chủ đề nào bạn quan tâm.

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.