配置ThinkPHP8与Consul API的完美融合:轻松实现服务发现与配置

PHP与Consul API的完美融合

优化后的文档在结构逻辑代码规范性可读性上进行了提升,按“概述→准备工作→核心实现→API参考”的流程重组,同时修复代码语法问题、统一格式规范。

1. 概述

在分布式系统中,服务发现与配置管理是保障系统稳定性的核心环节。Consul 作为功能全面的中间件,可提供服务注册/发现、健康检查、KV配置存储等能力;PHP 则是 Web 开发领域的主流语言,广泛应用于业务系统开发。

本文聚焦 PHP 与 Consul API 的实际融合方案,通过依赖引入、客户端配置、核心功能编码,完整实现服务查询、健康检查等关键场景,为分布式 PHP 系统提供基础支撑。

2. 准备工作

2.1 安装 Consul PHP 依赖

使用 Composer 引入成熟的 Consul API 客户端库,避免重复开发 HTTP 请求逻辑。

1
composer require dcarbone/php-consul-api

2.2 配置 Consul 客户端

统一管理 Consul 连接参数,避免硬编码,支持后续环境切换(如开发/生产环境)。

1
2
3
4
5
6
7
8
9
10
<?php
// 路径建议:config/consul.php(以 ThinkPHP 框架为例,其他框架可调整路径)
return [
'base_uri' => "http://127.0.0.1:8500", // Consul Agent 地址(默认端口8500)
'token' => "07c5745c-d0d1-3f02-d441-8252661ce925", // ACL Token(无ACL可留空)
'get_single_api' => "/v1/health/service/", // 单个服务健康查询接口
'get_all_api' => "/v1/catalog/services", // 所有服务列表接口
'get_single_old_api'=> "/v1/catalog/service/", // 单个服务基础信息接口
'get_all_health_api'=> "/v1/agent/checks" // 所有服务健康检查接口(补充原配置缺失项)
];

2.3 配置日志通道

为 Consul 相关操作单独配置日志,便于问题排查(以 ThinkPHP 日志配置为例)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 路径:config/log.php
return [
// 其他日志配置...
'channels' => [
// 新增 Consul 日志通道
'consul' => [
'type' => 'File', // 日志存储类型(文件)
'path' => runtime_path() . 'log/consul/', // 日志存储路径
'single' => false, // 不使用单文件日志(按日期拆分)
'apart_level' => ['error'], // 错误日志单独拆分
'max_files' => 30, // 日志保留30天
'level' => ['info', 'error', 'debug'], // 日志级别
'json' => false, // 不使用JSON格式
'realtime_write' => true, // 实时写入日志
'format' => '[%s][%s] %s' // 日志格式:[时间][级别] 内容
],
]
];

3. 核心功能实现

3.1 查询指定服务健康状态

API 端点/v1/health/service/<service-name>
功能:根据服务名查询实例,支持过滤仅健康实例,返回服务ID、地址、健康检查结果等关键信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?php
/**
* Consul 服务操作类(建议放在 app/service/ConsulService.php)
*/
namespace app\service;

use think\Config;
use think\Log;

class ConsulService
{
private $BaseURI;

public function __construct()
{
// 初始化 Consul 基础地址
$this->BaseURI = Config::get('consul.base_uri');
}

/**
* 查询指定服务的健康状态
* @param string $serviceName 服务名(如 KongService)
* @param bool $onlyHealthy 是否仅返回健康实例(默认true)
* @param bool $sslpem 是否验证SSL证书(默认false,HTTP场景无需开启)
* @return array 结果数组(code=0为成功,包含服务列表)
*/
public function queryServices(string $serviceName, bool $onlyHealthy = true, bool $sslpem = false)
{
$apiPath = Config::get('consul.get_single_api');
$token = Config::get('consul.token', '');
$passingParam = $onlyHealthy ? 'true' : 'false';
// 拼接请求URL(含健康过滤参数)
$requestUrl = "{$this->BaseURI}{$apiPath}{$serviceName}?passing={$passingParam}";
// 若有ACL Token,追加Token参数
if (!empty($token)) {
$requestUrl .= "&token={$token}";
}

// 记录请求日志
Log::channel("consul")->info("Consul 服务健康查询请求URL: {$requestUrl}");

// 初始化CURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $requestUrl,
CURLOPT_RETURNTRANSFER => true, // 结果返回为字符串
CURLOPT_TIMEOUT => 5, // 超时时间5秒(避免阻塞业务)
CURLOPT_SSL_VERIFYPEER => $sslpem, // 是否验证SSL证书
CURLOPT_SSL_VERIFYHOST => $sslpem ? 2 : false, // SSL主机验证(2=严格验证)
CURLOPT_HTTPHEADER => ['Accept-Charset: utf-8'] // 指定UTF-8编码
]);

// 执行请求并处理结果
$response = curl_exec($ch);
$curlError = curl_errno($ch) ? curl_error($ch) : '';
curl_close($ch);

// 1. 处理CURL错误
if ($curlError) {
$errMsg = "Consul 服务查询CURL失败: {$curlError}";
Log::channel("consul")->error($errMsg);
return ['code' => -1, 'msg' => $errMsg, 'data' => []];
}

// 2. 处理JSON解析错误
$serviceList = json_decode($response, true);
$jsonError = json_last_error();
if ($jsonError !== JSON_ERROR_NONE) {
$errMsg = $this->getJsonErrorMsg($jsonError);
Log::channel("consul")->error("Consul 响应JSON解析失败: {$errMsg} | 原始响应: {$response}");
return ['code' => -2, 'msg' => $errMsg, 'raw_response' => $response, 'data' => []];
}

// 3. 格式化返回结果(提取关键字段,避免冗余)
$formattedData = [];
foreach ($serviceList as $item) {
$service = $item['Service'] ?? [];
$healthCheck = $item['Checks'][1] ?? []; // 取服务级健康检查(非节点级)

$formattedData[] = [
'service_id' => $service['ID'] ?? '', // 服务唯一ID
'service_name' => $service['Service'] ?? '', // 服务名
'tags' => $service['Tags'] ?? [], // 服务标签(如api、rpc)
'address' => $service['Address'] ?? '', // 服务IP地址
'port' => $service['Port'] ?? 0, // 服务端口(补充原代码缺失项)
'health_output' => isset($healthCheck['Output']) ? substr($healthCheck['Output'], 0, 50) : '' // 健康检查结果(截取50字符)
];
}

return [
'code' => 0,
'msg' => '服务健康查询成功',
'count' => count($formattedData),
'data' => $formattedData
];
}

/**
* 解析JSON错误码为可读信息
* @param int $errorCode JSON错误码(json_last_error()返回值)
* @return string 错误描述
*/
private function getJsonErrorMsg(int $errorCode): string
{
$errorMap = [
JSON_ERROR_DEPTH => 'JSON深度超过限制',
JSON_ERROR_SYNTAX => 'JSON语法错误(如括号不闭合、多余逗号)',
JSON_ERROR_UTF8 => 'UTF-8编码错误(如乱码)',
JSON_ERROR_CTRL_CHAR => '非法控制字符',
JSON_ERROR_STATE_MISMATCH => 'JSON状态不匹配',
JSON_ERROR_RECURSION => 'JSON包含递归引用',
JSON_ERROR_INF_OR_NAN => 'JSON包含INF/NAN值',
JSON_ERROR_UNSUPPORTED_TYPE => '包含不支持的数据类型(如资源)'
];
return $errorMap[$errorCode] ?? "未知JSON错误(错误码: {$errorCode})";
}
}

Consul服务健康查询结果示例

3.2 查询所有服务列表

API 端点/v1/catalog/services
功能:获取Consul集群中所有已注册的服务名及标签,用于快速遍历服务全集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
// 续接 ConsulService 类
public function getAllServiceName(bool $sslpem = false)
{
$apiPath = Config::get('consul.get_all_api');
$requestUrl = "{$this->BaseURI}{$apiPath}";
$token = Config::get('consul.token', '');

Log::channel("consul")->info("Consul 所有服务列表请求URL: {$requestUrl}");

// 初始化CURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $requestUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => $sslpem,
CURLOPT_SSL_VERIFYHOST => $sslpem ? 2 : 0,
CURLOPT_HTTPHEADER => [
'Accept-Charset: utf-8',
'X-Consul-Token: ' . $token // ACL Token通过Header传递(更规范)
]
]);

// 执行请求
$response = curl_exec($ch);
$curlError = curl_errno($ch) ? curl_error($ch) : '';
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 获取HTTP状态码
$requestTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME); // 请求耗时
curl_close($ch);

// 记录请求详情日志
Log::channel("consul")->info("Consul 所有服务列表请求结果", [
'url' => $requestUrl,
'http_code' => $httpCode,
'curl_error' => $curlError,
'request_time_ms' => round($requestTime * 1000, 2), // 转毫秒并保留2位小数
'success' => $httpCode == 200 && empty($curlError)
]);

// 错误处理
if ($curlError || $httpCode != 200) {
$errMsg = $curlError ?: "API返回非200状态码({$httpCode})";
return ['code' => -1, 'msg' => $errMsg, 'data' => []];
}

// 解析响应
$serviceList = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errMsg = "JSON解析失败: " . json_last_error_msg();
Log::channel("consul")->error($errMsg . " | 原始响应: {$response}");
return ['code' => -2, 'msg' => $errMsg, 'data' => []];
}

// 格式化返回
return [
'code' => 0,
'msg' => '所有服务列表查询成功',
'data' => [
'count' => count($serviceList),
'services' => $serviceList // 格式:["user-service"=>["api"], "order-service"=>["rpc"]]
]
];
}

Consul所有服务列表查询结果示例

3.3 查询服务基础注册信息

API 端点/v1/catalog/service/<service-name>
功能:获取服务的基础注册信息(如节点关联、服务地址等),不含健康检查结果,适用于轻量查询场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php
// 续接 ConsulService 类
public function queryServiceBasicInfo(string $serviceName, bool $sslpem = false)
{
$apiPath = Config::get('consul.get_single_old_api');
$requestUrl = "{$this->BaseURI}{$apiPath}{$serviceName}";
$token = Config::get('consul.token', '');

Log::channel("consul")->info("Consul 服务基础信息请求URL: {$requestUrl}");

// 初始化CURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $requestUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => $sslpem,
CURLOPT_SSL_VERIFYHOST => $sslpem ? 2 : 0,
CURLOPT_HTTPHEADER => [
'Accept-Charset: utf-8',
'X-Consul-Token: ' . $token
]
]);

// 执行请求
$response = curl_exec($ch);
$curlError = curl_errno($ch) ? curl_error($ch) : '';
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$requestTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);

// 日志记录
Log::channel("consul")->info("Consul 服务基础信息请求结果", [
'url' => $requestUrl,
'http_code' => $httpCode,
'curl_error' => $curlError,
'request_time_ms' => round($requestTime * 1000, 2),
'success' => $httpCode == 200 && empty($curlError)
]);

// 错误处理
if ($curlError || $httpCode != 200) {
$errMsg = $curlError ?: "API返回非200状态码({$httpCode})";
return ['code' => -1, 'msg' => $errMsg, 'data' => []];
}

// 解析响应
$serviceInfo = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errMsg = "JSON解析失败: " . json_last_error_msg();
Log::channel("consul")->error($errMsg . " | 原始响应: {$response}");
return ['code' => -2, 'msg' => $errMsg, 'data' => []];
}

// 格式化返回(提取核心字段)
$formattedInfo = [];
foreach ($serviceInfo as $item) {
$formattedInfo[] = [
'service_id' => $item['ServiceID'] ?? '',
'service_name' => $item['ServiceName'] ?? '',
'node_name' => $item['Node'] ?? '', // 服务关联的Consul节点名
'address' => $item['Address'] ?? '',
'port' => $item['ServicePort'] ?? 0,
'tags' => $item['ServiceTags'] ?? [],
'datacenter' => $item['Datacenter'] ?? '' // 服务所在数据中心(多中心场景有用)
];
}

return [
'code' => 0,
'msg' => '服务基础信息查询成功',
'data' => [
'count' => count($formattedInfo),
'service_info' => $formattedInfo
]
];
}

Consul服务基础信息查询结果示例

3.4 查询所有服务健康检查状态

API 端点/v1/agent/checks
功能:获取当前Consul Agent上所有服务的健康检查结果(含节点级检查),适用于全局健康监控场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
// 续接 ConsulService 类
public function getAllServiceHealth(bool $sslpem = false)
{
$apiPath = Config::get('consul.get_all_health_api');
$requestUrl = "{$this->BaseURI}{$apiPath}";
$token = Config::get('consul.token', '');

Log::channel("consul")->info("Consul 所有服务健康检查请求URL: {$requestUrl}");

// 初始化CURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $requestUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => $sslpem,
CURLOPT_SSL_VERIFYHOST => $sslpem ? 2 : 0,
CURLOPT_HTTPHEADER => [
'Accept-Charset: utf-8',
'X-Consul-Token: ' . $token
]
]);

// 执行请求
$response = curl_exec($ch);
$curlError = curl_errno($ch) ? curl_error($ch) : '';
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$requestTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);

// 日志记录
Log::channel("consul")->info("Consul 所有服务健康检查请求结果", [
'url' => $requestUrl,
'http_code' => $httpCode,
'curl_error' => $curlError,
'request_time_ms' => round($requestTime * 1000, 2),
'success' => $httpCode == 200 && empty($curlError)
]);

// 错误处理
if ($curlError || $httpCode != 200) {
$errMsg = $curlError ?: "API返回非200状态码({$httpCode})";
return ['code' => -1, 'msg' => $errMsg, 'data' => []];
}

// 解析响应
$healthList = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$errMsg = "JSON解析失败: " . json_last_error_msg();
Log::channel("consul")->error($errMsg . " | 原始响应: {$response}");
return ['code' => -2, 'msg' => $errMsg, 'data' => []];
}

// 格式化返回(保留完整健康检查信息)
return [
'code' => 0,
'msg' => '所有服务健康检查查询成功',
'data' => [
'count' => count($healthList),
'health_checks' => $healthList // 含检查ID、状态、关联服务、输出等完整信息
]
];
}

Consul所有服务健康检查结果示例

4. Consul API 核心端点列表

整理高频使用的 Consul API 端点,按功能分类,便于开发时快速查阅。

功能分类 请求方法 API 路径 描述 关键参数/示例
服务注册与发现 PUT /v1/agent/service/register 注册服务(可关联健康检查) 请求体:{"ID":"user-svc-01","Name":"user-service","Port":8080,"Check":{"HTTP":"http://xxx/health","Interval":"10s"}}
GET /v1/catalog/services 获取集群所有服务(服务名+标签) 响应:{"user-service":["api"],"order-service":["rpc"]}
GET /v1/catalog/service/<service-name> 获取指定服务基础信息(不含健康状态) 路径示例:/v1/catalog/service/user-service
PUT /v1/agent/service/deregister/<service-id> 注销服务(需服务ID) 路径示例:/v1/agent/service/deregister/user-svc-01
健康检查 PUT /v1/agent/check/register 注册独立健康检查(节点/服务级) 请求体:`{“ID”:”disk-check”,”Name”:”Disk Usage”,”Script”:”df -h
GET /v1/agent/checks 获取当前Agent所有健康检查 响应含检查ID、状态(passing/warning/critical)、关联服务
GET /v1/health/service/<service-name>?passing=true 获取指定服务的健康实例 路径示例:/v1/health/service/user-service?passing=true
KV 配置存储 PUT /v1/kv/<key> 写入/更新KV键值对(值通过请求体传递) 路径:/v1/kv/config/db/url,请求体:jdbc:mysql://10.0.0.10:3306/user_db
GET /v1/kv/<key>?raw 读取KV键原始值(不加?raw返回Base64编码) 路径示例:/v1/kv/config/db/url?raw
GET /v1/kv/<parent-key>?recurse 递归读取父键下所有子键 路径示例:/v1/kv/config/?recurse(读取所有配置)
集群与权限 GET /v1/catalog/nodes 获取集群所有节点信息 响应含节点ID、IP、数据中心、标签
PUT /v1/acl/create 创建ACL Token(需启用ACL) 请求体:{"Name":"svc-token","Policy":"service:write,kv:read"}