PHP + Consul 服务发现

  ---
  健康检查注册

  你的 PHP 服务启动时,通过 HTTP 调 Consul API 把自己注册上去:

  $data = [
      'ID' => 'php-api-1',
      'Name' => 'php-api',
      'Address' => '192.168.1.10',
      'Port' => 8080,
      'Check' => [
          'HTTP' => 'http://192.168.1.10:8080/health',  // 健康检查地址
          'Interval' => '10s',                           // 每 10 秒检查一次
          'Timeout' => '2s'                              // 超时算失败
      ]
  ];

  $ch = curl_init('http://consul:8500/v1/agent/service/register');
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  curl_exec($ch);

  Consul 会每 10 秒访问你的 /health 接口:

  // health.php
  header('Content-Type: application/json');
  // 检查数据库、Redis 等依赖
  if ($db->ping() && $redis->ping()) {
      http_response_code(200);
      echo json_encode(['status' => 'ok']);
  } else {
      http_response_code(503);  // 503 = 不健康
      echo json_encode(['status' => 'fail']);
  }

  返回 200 → 健康,其他状态码 → 不健康,Consul 会把你从服务列表里摘掉。

  ---
  服务列表的本地缓存 + TTL 刷新

  每次调用其他服务都去问 Consul 太慢,所以:

  class ConsulClient {
      private $cache = [];
      private $ttl = 30;  // 缓存 30 秒

      public function getService($name) {
          $cacheKey = "service:$name";

          // 1. 先看缓存
          if (isset($this->cache[$cacheKey])) {
              $cached = $this->cache[$cacheKey];
              if (time() - $cached['time'] < $this->ttl) {
                  return $cached['data'];  // 缓存未过期,直接用
              }
          }

          // 2. 缓存过期或不存在,查 Consul
          $url = "http://consul:8500/v1/health/service/$name?passing=true";
          $response = file_get_contents($url);
          $services = json_decode($response, true);

          // 3. 更新缓存
          $this->cache[$cacheKey] = [
              'data' => $services,
              'time' => time()
          ];

          return $services;
      }
  }

  问题:30 秒内服务挂了你也不知道。解决办法 → 长轮询。

  ---
  X-Consul-Index 长轮询

  Consul 每次返回服务列表时,响应头里带一个 X-Consul-Index,这是个版本号。

  你可以把这个版本号传回去,加上 wait 参数:

  GET /v1/health/service/php-api?index=1234&wait=60s

  意思是:"如果服务列表的版本还是 1234,就阻塞住,最多等 60 秒,直到有变化再返回"。

  完整代码:

  class ConsulClient {
      private $index = 0;  // 当前版本号

      public function watchService($name) {
          while (true) {
              $url = "http://consul:8500/v1/health/service/$name"
                   . "?passing=true&index={$this->index}&wait=60s";

              $ch = curl_init($url);
              curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
              curl_setopt($ch, CURLOPT_HEADER, true);  // 要拿响应头
              curl_setopt($ch, CURLOPT_TIMEOUT, 65);   // 比 wait 多 5 秒

              $response = curl_exec($ch);
              $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
              $headers = substr($response, 0, $headerSize);
              $body = substr($response, $headerSize);

              // 提取新的 index
              if (preg_match('/X-Consul-Index: (\d+)/', $headers, $m)) {
                  $newIndex = (int)$m[1];

                  if ($newIndex > $this->index) {
                      // 有变化!更新缓存
                      $this->index = $newIndex;
                      $services = json_decode($body, true);
                      $this->updateCache($name, $services);
                      echo "服务列表更新:index=$newIndex\n";
                  }
              }

              // 继续下一轮长轮询
          }
      }
  }

  流程:

  1. 第一次请求,Consul 立刻返回服务列表 + X-Consul-Index: 1234
  2. 第二次请求带上 index=1234&wait=60s
  3. 如果 60 秒内服务列表没变化 → Consul 60 秒后返回,index 还是 1234
  4. 如果中途有服务上线/下线 → Consul 立刻返回,index 变成 1235
  5. 你拿到新 index,更新缓存,继续下一轮

  ---
  实际用法

  启动一个后台协程/进程专门跑长轮询:

  // Swoole 协程版
  go(function() use ($consul) {
      $consul->watchService('php-api');  // 阻塞式监听
  });

  // 主业务代码
  $services = $consul->getService('php-api');  // 从缓存拿,毫秒级
  $instance = $services[array_rand($services)];  // 随机选一个
  $url = "http://{$instance['Address']}:{$instance['Port']}/api";

  ---
  三种刷新策略对比

  ┌───────────────┬─────────────┬──────────────────────┬──────────────────┐
  │     方式      │    延迟     │       网络开销       │     适用场景     │
  ├───────────────┼─────────────┼──────────────────────┼──────────────────┤
  │ 每次查 Consul │ 0           │ 高(每次请求都查)   │ 不推荐           │
  ├───────────────┼─────────────┼──────────────────────┼──────────────────┤
  │ TTL 缓存      │ 最多 TTL 秒 │ 低(定期刷新)       │ 服务变化不频繁   │
  ├───────────────┼─────────────┼──────────────────────┼──────────────────┤
  │ 长轮询        │ 秒级        │ 极低(有变化才返回) │ 需要实时感知变化 │
  └───────────────┴─────────────┴──────────────────────┴──────────────────┘

  ---
  一句话总结

  注册时告诉 Consul 你的健康检查地址,查询时用 X-Consul-Index 长轮询,有变化立刻知道,没变化就等着,省得一直问。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐