PHP + Swoole 6.x 千万级 IoT 边缘计算与实时数据中枢

  架构总览(10M 设备)

  设备 →[MQTT Broker 集群 x N] →[Kafka/NSQ] →[Swoole 流处理 Worker]
                  ↓                                    ↓
           [边缘规则引擎]                    [Redis Cluster] [TDengine] [ClickHouse]
                  ↓                                   ↓
                                            [WebSocket 推送集群] →前端/App

  核心容量公式:单 Swoole 节点稳定承载 80W~120W 长连接(64GB 内存),10M 需 10~16 个 Broker 节点 + 一致性哈希分片。

  最优库选型(2026 现状)

  ┌────────────┬────────────────────────────┬─────────────────────────────────┐
  │     层     │             库             │              原因               │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ 协程引擎   │ swoole/swoole-src 6.x      │ 原生协程、Hook 全栈             │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ MQTT 协议  │ simps/mqtt                 │ 纯 PHP、支持 MQTT 5.0、零依赖   │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ 微服务底座 │ hyperf/hyperf 3.x          │ 注解路由、AOP、连接池工业级     │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ 时序库     │ taosdata/tdengine-php      │ 单机千万 TPS 写入               │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ 列存分析   │ smi2/phpclickhouse         │ OLAP 聚合                       │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ MQ         │ longlang/phpkafka          │ 纯协程 Kafka,无 librdkafka 依赖 │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ Redis      │ swoole 内置 + hyperf/redis │ 协程化连接池                    │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ 限流熔断   │ hyperf/circuit-breaker     │ 滑窗+令牌桶                     │
  ├────────────┼────────────────────────────┼─────────────────────────────────┤
  │ 监控       │ hyperf/metric + Prometheus │ 拉模式埋点                      │
  └────────────┴────────────────────────────┴─────────────────────────────────┘

  composer.json

  {
      "require": {
          "php": "^8.3",
          "ext-swoole": "^6.0",
          "simps/mqtt": "^1.5",
          "hyperf/hyperf": "^3.1",
          "hyperf/redis": "^3.1",
          "hyperf/db": "^3.1",
          "hyperf/async-queue": "^3.1",
          "hyperf/circuit-breaker": "^3.1",
          "hyperf/metric": "^3.1",
          "longlang/phpkafka": "^1.2",
          "smi2/phpclickhouse": "^1.5"
      }
  }

  ---
  1. MQTT Broker(承载百万长连接)

  <?php
  // MqttBroker.php  ——单节点百万连接、横向扩展到千万
  declare(strict_types=1);

  use Swoole\Server;
  use Swoole\Coroutine\Channel;
  use Simps\MQTT\Protocol\Types;
  use Simps\MQTT\Protocol\V5;
  use Simps\MQTT\Hex\ReasonCode;

  class MqttBroker
  {
      private Server $server;
      private array $sessions = [];          // fd => ['client_id','topics'=>[]]
      private array $topicTree = [];         // topic => [fd => qos]
      private Channel $pipeline;             // 数据管道 →流处理
      private \Redis $redis;                 // 跨节点订阅同步

      public function __construct(string $host = '0.0.0.0', int $port = 1883)
      {
          $this->server = new Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
          $this->server->set([
              'open_mqtt_protocol'   => true,
              'worker_num'           => swoole_cpu_num() * 2,
              'reactor_num'          => swoole_cpu_num() * 2,
              'max_connection'       => 1_200_000,        // 单机百万
              'socket_buffer_size'   => 256 * 1024 * 1024,
              'buffer_output_size'   => 32  * 1024 * 1024,
              'tcp_fastopen'         => true,
              'enable_reuse_port'    => true,
              'enable_coroutine'     => true,
              'hook_flags'           => SWOOLE_HOOK_ALL,
              'tcp_keepalive'        => true,
              'heartbeat_check_interval' => 30,
              'heartbeat_idle_time'      => 90,
          ]);

          $this->pipeline = new Channel(100_000);

          $this->server->on('Receive', [$this, 'onReceive']);
          $this->server->on('Close',   [$this, 'onClose']);
          $this->server->on('WorkerStart', [$this, 'onWorkerStart']);
      }

      public function onWorkerStart(Server $server, int $wid): void
      {
          $this->redis = new \Redis();
          $this->redis->pconnect('redis-cluster', 6379);

          // 后台流处理协程:批量落 Kafka / TDengine
          go(function () {
              $batch = [];
              while (true) {
                  $msg = $this->pipeline->pop(0.05);
                  if ($msg) $batch[] = $msg;
                  if (count($batch) >= 500 || (!empty($batch) && $msg === false)) {
                      StreamPipeline::flush($batch);   // 见 §3
                      $batch = [];
                  }
              }
          });
      }

      public function onReceive(Server $serv, int $fd, int $rid, string $data): void
      {
          try {
              $pkt = V5::unpack($data);
          } catch (\Throwable $e) { $serv->close($fd); return; }

          match ($pkt['type']) {
              Types::CONNECT     => $this->handleConnect($fd, $pkt),
              Types::PUBLISH     => $this->handlePublish($fd, $pkt),
              Types::SUBSCRIBE   => $this->handleSubscribe($fd, $pkt),
              Types::PINGREQ     => $serv->send($fd, V5::pack(['type'=>Types::PINGRESP])),
              Types::DISCONNECT  => $serv->close($fd),
              default            => null,
          };
      }

      private function handleConnect(int $fd, array $pkt): void
      {
          // TODO: 鉴权 (JWT / device_secret),此处仅示意
          $this->sessions[$fd] = ['client_id'=>$pkt['client_id'],'topics'=>[]];
          $this->server->send($fd, V5::pack([
              'type'=>Types::CONNACK,'code'=>ReasonCode::SUCCESS,'session_present'=>0,
          ]));
      }

      private function handlePublish(int $fd, array $pkt): void
      {
          // 1. 投递到本地订阅者
          foreach ($this->topicTree[$pkt['topic']] ?? [] as $sfd => $qos) {
              $this->server->exist($sfd) && $this->server->send($sfd, V5::pack($pkt));
          }
          // 2. 跨节点扩散(共享订阅靠 Redis Pub/Sub)
          $this->redis->publish('mqtt:fanout', json_encode($pkt));
          // 3. 进入数据管道
          $this->pipeline->push([
              'device' => $this->sessions[$fd]['client_id'] ?? '',
              'topic'  => $pkt['topic'],
              'payload'=> $pkt['message'],
              'ts'     => hrtime(true),
          ], 0.01);
      }

      private function handleSubscribe(int $fd, array $pkt): void
      {
          $codes = [];
          foreach ($pkt['topics'] as $topic => $qos) {
              $this->topicTree[$topic][$fd] = $qos;
              $this->sessions[$fd]['topics'][] = $topic;
              $codes[] = $qos;
          }
          $this->server->send($fd, V5::pack([
              'type'=>Types::SUBACK,'message_id'=>$pkt['message_id'],'codes'=>$codes,
          ]));
      }

      public function onClose(Server $serv, int $fd): void
      {
          foreach ($this->sessions[$fd]['topics'] ?? [] as $t) unset($this->topicTree[$t][$fd]);
          unset($this->sessions[$fd]);
      }

      public function start(): void { $this->server->start(); }
  }

  (new MqttBroker())->start();

  重大变化:
  - Swoole 6 的 hook_flags = SWOOLE_HOOK_ALL 让 PDO/Redis/cURL 自动协程化,无需 Co\Redis。
  - enable_reuse_port 让多进程共享 1883 端口,单机吞吐线性扩展。
  - Channel 替代旧版 Task,延迟从 ms 降到 μs。

  ---
  2. 边缘规则引擎(CEP / 复杂事件处理)

  <?php
  // EdgeRuleEngine.php ——在边缘节点本地完成 80% 过滤,降低中心带宽
  class EdgeRuleEngine
  {
      /** 规则结构:[ ['expr'=>'temp>80 && humid<30','action'=>'alarm'] ] */
      private array $rules;
      private array $windows = [];          // 滑动窗口聚合状态

      public function __construct(array $rules) { $this->rules = $rules; }

      public function eval(array $event): ?array
      {
          // 1. 滑窗聚合 (5 秒平均)
          $key = $event['device'].':'.$event['topic'];
          $this->windows[$key][] = ['v'=>$event['payload'],'t'=>$event['ts']];
          $cut = hrtime(true) - 5_000_000_000;
          $this->windows[$key] = array_filter($this->windows[$key], fn($x)=>$x['t']>$cut);

          // 2. 规则匹配 (沙箱 eval ——生产改为 symfony/expression-language)
          $ctx = json_decode($event['payload'], true) ?: [];
          foreach ($this->rules as $r) {
              $ok = (bool)@eval('return '.$this->safe($r['expr'], $ctx).';');
              if ($ok) return ['action'=>$r['action'],'event'=>$event];
          }
          return null;
      }
      private function safe(string $expr, array $ctx): string
      {
          return preg_replace_callback('/\b([a-z_]\w*)\b/i',
              fn($m)=>isset($ctx[$m[1]]) ? var_export($ctx[$m[1]],true) : $m[0], $expr);
      }
  }

  ▎ 生产建议替换为 symfony/expression-language 或 flow-php/etl,杜绝 eval 注入。

  ---
  3. 流处理管道:Kafka + TDengine + Redis 三写

  <?php
  // StreamPipeline.php
  use longlang\phpkafka\Producer\Producer;
  use longlang\phpkafka\Producer\ProduceMessage;
  use longlang\phpkafka\Producer\ProducerConfig;

  class StreamPipeline
  {
      private static ?Producer $kafka = null;
      private static ?\Redis   $redis = null;
      private static ?\PDO     $tdengine = null;     // TDengine 兼容 PDO

      public static function boot(): void
      {
          $cfg = new ProducerConfig();
          $cfg->setBootstrapServer('kafka:9092');
          $cfg->setAcks(-1);
          $cfg->setUpdateBrokers(true);
          self::$kafka = new Producer($cfg);

          self::$redis = new \Redis();
          self::$redis->pconnect('redis-cluster', 6379);

          self::$tdengine = new \PDO('taos:host=tdengine;port=6030','root','taosdata');
      }

      public static function flush(array $batch): void
      {
          if (!self::$kafka) self::boot();

          // 1. Kafka —解耦下游消费者
          $msgs = array_map(fn($e)=>new ProduceMessage('iot.raw', json_encode($e), $e['device']), $batch);
          self::$kafka->sendBatch($msgs);

          // 2. Redis —设备最新值 (热数据,毫秒查询)
          $pipe = self::$redis->multi(\Redis::PIPELINE);
          foreach ($batch as $e) $pipe->hSet('dev:'.$e['device'], $e['topic'], $e['payload']);
          $pipe->exec();

          // 3. TDengine —时序长期存储,自动分片
          $stmt = self::$tdengine->prepare(
              'INSERT INTO iot.dev_? USING iot.devices TAGS (?) VALUES (?,?)');
          foreach ($batch as $e) {
              $stmt->execute([$e['device'],$e['device'],
                              (int)($e['ts']/1_000_000), $e['payload']]);
          }
      }
  }

  重大变化:
  - phpkafka 是 纯 PHP 协程实现,无需 librdkafka 扩展,Docker 部署体积减半。
  - TDengine 替代 InfluxDB:相同硬件下写入快 5~10 倍,SQL 兼容。
  - Redis Pipeline 把 N 次 RTT 合并为 1 次,批量 500 条 ≈1ms。

  ---
  4. WebSocket 实时推送集群

  <?php
  // PushServer.php
  use Swoole\WebSocket\Server;

  class PushServer
  {
      private Server $ws;
      public function __construct() {
          $this->ws = new Server('0.0.0.0', 9501);
          $this->ws->set(['worker_num'=>swoole_cpu_num()*2,
                          'open_websocket_compression'=>true,
                          'enable_coroutine'=>true]);
          $this->ws->on('open',    fn($s,$req)=>$s->push($req->fd, '{"hello":1}'));
          $this->ws->on('message', fn($s,$f)=>null);
          $this->ws->on('workerStart', function() {
              // 订阅 Redis Pub/Sub:分布式扇出
              go(function () {
                  $r = new \Redis();
                  $r->pconnect('redis-cluster', 6379);
                  $r->subscribe(['iot.push'], function ($r,$ch,$msg) {
                      foreach ($this->ws->connections as $fd)
                          $this->ws->isEstablished($fd) && $this->ws->push($fd, $msg);
                  });
              });
          });
      }
      public function start(){ $this->ws->start(); }
  }
  (new PushServer())->start();

  ---
  5. 部署拓扑(K8s)

  # 关键参数,非完整 yaml
  mqtt-broker:    replicas: 16,  resource: 8C/32G,  hostNetwork: true
  stream-worker:  replicas: 32,  从 Kafka iot.raw 消费 →CEP →落库
  ws-push:        replicas: 8,   StickySession + Redis Pub/Sub 扇出
  redis-cluster:  6 主 6 从,maxmemory-policy=allkeys-lru
  tdengine:       3 节点 vnode=16,副本 2
  kafka:          5 broker, partitions=128

  内核调优(每个 Broker 节点 sysctl):
  net.core.somaxconn = 65535
  net.ipv4.tcp_max_syn_backlog = 65535
  net.ipv4.ip_local_port_range = 1024 65535
  fs.file-max = 2000000
  nofile = 1048576       # ulimit

  ---
  端到端关键变化总结

  1. 协程全栈 Hook:Swoole 6 的 SWOOLE_HOOK_ALL 让所有同步库变协程,代码风格回到 PHP 同步,性能=Go。
  2. 边缘下沉:规则引擎在边缘网关跑,只把命中事件回传中心,带宽节省 80%。
  3. 三层存储:Redis(秒级热) + TDengine(时序) + ClickHouse(分析),各司其职。
  4. 纯 PHP MQ:phpkafka 摆脱 C 扩展,容器镜像 < 80MB。
  5. 横向 = 分片:Broker 用一致性哈希按 client_id 分片,千万设备 = 多节点平铺。
  6. 背压:Channel 容量 + Kafka acks=-1,任何环节堵塞自动反向限流,不丢数据。
Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐