PHP 服务治理:服务注册与发现 (Consul/Etcd) – 嘿,你的服务在哪儿呢?
各位观众,各位朋友,欢迎来到“老码农夜话”! 👴 今天我们来聊聊一个在分布式系统中非常重要的话题:服务注册与发现。
想象一下,你开了一家小餐馆,叫“代码餐厅”。 🍔🍟🍕 刚开始生意不错,一间小门面就够了。 可是随着顾客越来越多,你不得不开了分店,甚至在不同的城市开了连锁店。 这时候问题来了:
- 顾客怎么知道哪家分店提供什么菜?
- 如果一家分店因为停电暂停营业了,顾客怎么知道?
- 如果我想增加一家新的分店,或者替换掉一台老旧的服务器,需要手动修改所有客户端的配置吗?
这就是服务注册与发现要解决的问题! 在微服务架构中,服务就像这些分店一样,数量众多,动态变化。 如果没有一个中心化的机制来管理这些服务,那简直就是一场灾难! 🤯
那什么是服务注册与发现呢?
简单来说,就是让服务能够主动把自己“注册”到一个中心化的“注册中心”,然后让其他服务能够通过这个注册中心“发现”自己需要的服务。 就像你把你的餐馆信息登记在“大众点评”上,顾客就可以通过大众点评找到你。
为什么要用服务注册与发现?
不用它,你试试? 😜 除非你想手动维护一份巨大的服务地址列表,并且每次服务发生变化都要手动修改所有客户端的配置。 这不仅费时费力,而且容易出错。
服务注册与发现的优点:
- 动态性: 服务可以随时注册和注销,无需手动修改配置。
- 弹性: 服务实例可以根据负载自动扩容和缩容。
- 高可用性: 注册中心通常具有高可用性,可以保证服务的可用性。
- 解耦: 服务消费者不需要关心服务提供者的具体位置,只需要知道服务名称即可。
常见的服务注册与发现方案
目前比较流行的服务注册与发现方案有很多,比如:
- Consul: HashiCorp 出品的,功能强大,易于使用,支持多种语言。
- Etcd: CoreOS 出品的,基于 Raft 协议,具有强一致性。
- ZooKeeper: Apache Hadoop 项目的一部分,历史悠久,稳定性高。
- Eureka: Netflix 开源的,主要用于 Java 应用,现在已经停止维护。
- Kubernetes DNS: Kubernetes 内置的服务发现机制。
今天我们主要介绍 Consul 和 Etcd 这两种方案,并结合 PHP 语言进行讲解。
Consul: 瑞士军刀般的工具
Consul 是一个功能非常强大的工具,不仅可以作为服务注册与发现的中心,还可以作为配置中心、健康检查工具等。 它使用 Raft 协议保证数据的一致性,并且提供了友好的 HTTP API 和 CLI 工具。
Consul 的基本概念
- Service: 服务,比如一个 API 服务,一个数据库服务等。
- Node: 节点,通常是一个服务器,上面运行着一个或多个服务。
- Check: 健康检查,用于检查服务的健康状态。
- KV Store: 键值存储,用于存储配置信息。
- Catalog: 服务目录,存储所有注册的服务的信息。
Consul 的架构
Consul 采用 Client/Server 架构。 Server 负责存储数据,处理请求,维护集群状态。 Client 负责与 Server 通信,并提供服务注册、发现等功能。
使用 Consul 进行服务注册与发现
-
安装 Consul:
首先,你需要安装 Consul。 你可以从 Consul 官网下载预编译的二进制文件,也可以使用包管理器安装。
# 以 Linux 为例 wget https://releases.hashicorp.com/consul/1.14.4/consul_1.14.4_linux_amd64.zip unzip consul_1.14.4_linux_amd64.zip sudo mv consul /usr/local/bin -
启动 Consul Server:
启动 Consul Server,可以使用
consul agent -server -bootstrap-expect=1 -data-dir=/tmp/consul -node=node1 -address=127.0.0.1命令。-server: 指定以 Server 模式启动。-bootstrap-expect=1: 指定集群中 Server 的数量。-data-dir: 指定数据存储目录。-node: 指定节点名称。-address: 指定监听地址。
-
启动 Consul Client:
启动 Consul Client,可以使用
consul agent -data-dir=/tmp/consul -node=node2 -join=127.0.0.1命令。-join: 指定加入的 Consul Server 的地址。
-
服务注册:
你可以使用 HTTP API 或者配置文件来注册服务。 这里我们使用 HTTP API 演示。
<?php $serviceName = 'my-api-service'; $serviceAddress = '127.0.0.1'; $servicePort = 8080; $consulAddress = '127.0.0.1:8500'; // Consul 的地址 $registrationData = [ 'ID' => $serviceName . '-' . uniqid(), // 服务 ID,必须唯一 'Name' => $serviceName, 'Address' => $serviceAddress, 'Port' => $servicePort, 'Check' => [ 'HTTP' => 'http://' . $serviceAddress . ':' . $servicePort . '/health', // 健康检查 endpoint 'Interval' => '10s', // 检查间隔 'Timeout' => '5s', // 超时时间 ], ]; $ch = curl_init('http://' . $consulAddress . '/v1/agent/service/register'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($registrationData)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode == 200) { echo "Service registered successfully!n"; } else { echo "Failed to register service: " . $result . "n"; } ?>这个 PHP 代码片段向 Consul 注册了一个名为
my-api-service的服务。 它指定了服务的地址、端口和健康检查 endpoint。 Consul 会定期调用健康检查 endpoint,如果 endpoint 返回 200 OK,则认为服务是健康的。 -
服务发现:
你可以使用 HTTP API 或者 DNS 查询来发现服务。 这里我们使用 HTTP API 演示。
<?php $serviceName = 'my-api-service'; $consulAddress = '127.0.0.1:8500'; $ch = curl_init('http://' . $consulAddress . '/v1/health/service/' . $serviceName); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode == 200) { $services = json_decode($result, true); if (count($services) > 0) { foreach ($services as $service) { $address = $service['Service']['Address']; $port = $service['Service']['Port']; echo "Found service: $address:$portn"; } } else { echo "No service found.n"; } } else { echo "Failed to discover service: " . $result . "n"; } ?>这段 PHP 代码从 Consul 获取了
my-api-service的所有健康实例。 然后它遍历这些实例,并打印出它们的地址和端口。
Consul 的优点
- 功能强大: 集成了服务注册与发现、配置管理、健康检查等功能。
- 易于使用: 提供了友好的 HTTP API 和 CLI 工具。
- 支持多种语言: 提供了多种语言的客户端库。
- 高可用性: 基于 Raft 协议,具有高可用性。
Consul 的缺点
- 复杂性: 功能太多,学习曲线较陡峭。
- 性能: 在大规模集群中,性能可能不如 Etcd。
Etcd: 分布式键值存储的王者
Etcd 是一个分布式键值存储系统,最初由 CoreOS 开发,现在是 CNCF 的一个项目。 它使用 Raft 协议保证数据的一致性,并且提供了简单的 HTTP API 和 gRPC API。 虽然本身是键值存储,但其强大的watch机制和目录结构,使其非常适合做服务注册与发现。
Etcd 的基本概念
- Key-Value: 键值对,用于存储数据。
- Watch: 监听,用于监听键值的变化。
- Lease: 租约,用于管理键值的生命周期。
- Election: 选举,用于选举 leader。
Etcd 的架构
Etcd 采用 Leader/Follower 架构。 Leader 负责处理所有写请求,并将数据同步到 Follower。 Follower 负责处理读请求,并在 Leader 发生故障时参与选举。
使用 Etcd 进行服务注册与发现
-
安装 Etcd:
首先,你需要安装 Etcd。 你可以从 Etcd 官网下载预编译的二进制文件,也可以使用包管理器安装。
# 以 Linux 为例 wget https://github.com/etcd-io/etcd/releases/download/v3.5.7/etcd-v3.5.7-linux-amd64.tar.gz tar xvf etcd-v3.5.7-linux-amd64.tar.gz cd etcd-v3.5.7-linux-amd64 sudo cp etcd etcdctl /usr/local/bin -
启动 Etcd Server:
启动 Etcd Server,可以使用
etcd --name s1 --data-dir=/tmp/etcd-data.tmp --listen-client-urls http://localhost:2379 --advertise-client-urls http://localhost:2379 --listen-peer-urls http://localhost:2380 --initial-advertise-peer-urls http://localhost:2380 --initial-cluster s1=http://localhost:2380 --initial-cluster-token tkn --initial-cluster-state new命令。--name: 指定节点名称。--data-dir: 指定数据存储目录。--listen-client-urls: 指定监听客户端的地址。--advertise-client-urls: 指定向其他节点广播的客户端地址。--listen-peer-urls: 指定监听节点间通信的地址。--initial-advertise-peer-urls: 指定向其他节点广播的节点间通信地址。--initial-cluster: 指定初始集群成员。--initial-cluster-token: 指定集群 token。--initial-cluster-state: 指定集群状态(new 或者 existing)。
-
服务注册:
你可以使用 HTTP API 或者 gRPC API 来注册服务。 这里我们使用 HTTP API 演示。
<?php $serviceName = 'my-api-service'; $serviceAddress = '127.0.0.1'; $servicePort = 8080; $etcdAddress = '127.0.0.1:2379'; // Etcd 的地址 $serviceId = uniqid(); // 唯一ID,用于租约 // 服务注册的 key $key = '/services/' . $serviceName . '/' . $serviceId; $value = json_encode(['address' => $serviceAddress, 'port' => $servicePort]); // 创建租约,保证服务存活 $leaseTTL = 10; // 租约过期时间,单位秒 $leaseId = createLease($etcdAddress, $leaseTTL); // 将服务信息写入 Etcd,并绑定租约 putWithLease($etcdAddress, $key, $value, $leaseId); // 定期续租约,防止服务过期 $interval = $leaseTTL / 2; // 续约间隔,小于租约时间 while (true) { sleep($interval); keepAliveLease($etcdAddress, $leaseId); echo "Lease renewed for service: $serviceName - $serviceIdn"; } // 函数:创建租约 function createLease($etcdAddress, $ttl) { $ch = curl_init('http://' . $etcdAddress . '/v3/lease/grant'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['TTL' => $ttl])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode == 200) { $data = json_decode($result, true); return $data['ID']; } else { die("Failed to create lease: " . $result . "n"); } } // 函数:写入带有租约的 key-value function putWithLease($etcdAddress, $key, $value, $leaseId) { $ch = curl_init('http://' . $etcdAddress . '/v3/kv/put'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['key' => base64_encode($key), 'value' => base64_encode($value), 'lease' => $leaseId])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode != 200) { die("Failed to put key-value with lease: " . $result . "n"); } } // 函数:续租约 function keepAliveLease($etcdAddress, $leaseId) { $ch = curl_init('http://' . $etcdAddress . '/v3/lease/keepalive'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['ID' => $leaseId])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode != 200) { die("Failed to keep alive lease: " . $result . "n"); } } ?>这段 PHP 代码向 Etcd 注册了一个名为
my-api-service的服务。 它使用 Lease(租约)机制来保证服务的存活。 服务注册的时候,会创建一个租约,并将服务信息写入 Etcd,并绑定这个租约。 然后,服务会定期续租约,如果租约过期,Etcd 会自动删除服务信息。 这样可以保证 Etcd 中只存储健康的服务实例。 注意,Etcd 的 HTTP API 使用 base64 编码 key 和 value。 -
服务发现:
你可以使用 HTTP API 或者 gRPC API 来发现服务。 这里我们使用 HTTP API 演示。
<?php $serviceName = 'my-api-service'; $etcdAddress = '127.0.0.1:2379'; $keyPrefix = '/services/' . $serviceName . '/'; $ch = curl_init('http://' . $etcdAddress . '/v3/kv/range'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['key' => base64_encode($keyPrefix), 'range_end' => base64_encode($keyPrefix . chr(0xff))])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode == 200) { $data = json_decode($result, true); if (isset($data['kvs'])) { foreach ($data['kvs'] as $kv) { $key = base64_decode($kv['key']); $value = base64_decode($kv['value']); $serviceInfo = json_decode($value, true); $address = $serviceInfo['address']; $port = $serviceInfo['port']; echo "Found service: $address:$portn"; } } else { echo "No service found.n"; } } else { echo "Failed to discover service: " . $result . "n"; } ?>这段 PHP 代码从 Etcd 获取了
my-api-service的所有实例。 它通过指定 key 的前缀,并设置range_end来获取所有以该前缀开头的 key。 然后它遍历这些 key,并打印出它们的地址和端口。
Etcd 的优点
- 简单: 架构简单,易于理解。
- 高性能: 基于 Raft 协议,具有高性能。
- 强一致性: 保证数据的一致性。
- Watch 机制: 方便实现服务状态的监听和动态更新。
Etcd 的缺点
- 功能单一: 只提供键值存储功能。
- API 相对复杂: 需要处理 Base64 编码。
Consul vs Etcd: 选哪个?
| 特性 | Consul | Etcd |
|---|---|---|
| 功能 | 服务注册与发现、配置管理、健康检查等 | 键值存储 |
| 易用性 | 简单易用,API 友好 | 相对复杂,API 需要 Base64 编码 |
| 性能 | 中等 | 较高 |
| 一致性 | 最终一致性 | 强一致性 |
| 使用场景 | 中小型规模,需要多种功能的场景 | 大规模集群,对一致性要求高的场景 |
| 健康检查 | 内置,支持 HTTP、TCP、Script 等多种方式 | 需要自行实现,例如通过 lease 机制 |
| 配置管理 | 内置 KV Store | 作为键值存储,可以存储配置信息 |
总的来说,如果你需要一个功能强大的、易于使用的服务注册与发现工具,并且对一致性要求不高,那么 Consul 是一个不错的选择。 如果你更关心性能和一致性,并且只需要键值存储功能,那么 Etcd 更适合你。
PHP 客户端库
为了更方便地使用 Consul 和 Etcd,你可以使用一些 PHP 客户端库。
- Consul:
guzzlehttp/guzzle(使用 Guzzle HTTP 客户端封装 Consul API) - Etcd:
etcd-php/etcd(基于 gRPC 实现的 Etcd 客户端)
这些客户端库可以简化服务注册与发现的流程,让你更专注于业务逻辑的开发。
总结
服务注册与发现是微服务架构中不可或缺的一部分。 通过使用 Consul 或者 Etcd,你可以构建一个更加健壮、弹性的分布式系统。 选择哪个方案取决于你的具体需求和场景。
希望今天的分享对你有所帮助! 下次再见! 👋