PHP Academy · 课时

服务通信:REST 和 gRPC

以同步且高效的方式连接服务

第 2 / 4 课13 个步骤

服务通信:REST 和 gRPC 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。

REST 与 gRPC

服务需要进行同步通信:请求发出后,返回一个响应。两种主流选择是基于 HTTP/JSON 的 REST和基于 HTTP/2 + 协议缓冲区的 gRPC。它们针对的目标不同——REST 注重覆盖范围和易读性,gRPC 注重速度和严格的契约。

本课将从 PHP 的角度介绍两者,以及应该如何选择。

REST:通用语言

REST 通过 URL 表示资源,并使用 HTTP 方法和状态码表达语义。它的优势包括工具普及、可缓存、可以使用 curl 调试,以及无需特殊客户端。它的不足包括 JSON 冗长、没有强制模式,并且只支持请求与响应。

对于公共接口和面向浏览器的端点,REST 几乎总是正确的选择。

调用 REST 服务

请使用符合 PSR-18 的 HTTP 客户端(此处使用 Guzzle)。始终设置连接超时和请求超时——对缓慢同级服务发起无上限的调用,可能耗尽您的 PHP-FPM 工作进程,并使故障级联扩散。

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;

$http = new Client([
    'base_uri'        => 'http://customers-svc/',
    'connect_timeout' => 1.0,  // never block forever on connect
    'timeout'         => 3.0,  // total request budget
    'http_errors'     => false,
]);

$res = $http->get('customers/42', ['headers' => ['Accept' => 'application/json']]);
if ($res->getStatusCode() === 200) {
    $customer = json_decode((string) $res->getBody(), true);
    echo $customer['email'] . "\n";
}

状态码就是契约

在服务间的 REST 通信中,HTTP 状态码就是您的错误协议。请有意识地处理它们:

  • 2xx 表示成功;4xx 表示调用方出错(不要盲目重试);5xx 或超时表示可以重试。
  • 使用 409 表示冲突,使用 422 表示校验失败,使用 429 表示速率限制(遵守 Retry-After)。

重试 400 只会浪费调用;采用退避策略重试 503 才是正确做法。

<?php
function isRetryable(int $status): bool {
    return $status === 0          // timeout/connection error
        || $status === 429
        || ($status >= 500 && $status !== 501);
}
var_dump(isRetryable(503)); // true
var_dump(isRetryable(400)); // false

gRPC:契约优先且高性能

gRPC 使用协议缓冲区:您在 .proto 文件中定义服务和消息,然后生成强类型的客户端与服务器桩代码。借助 HTTP/2 和二进制协议缓冲区,它比 JSON 紧凑得多、延迟也低得多,并且支持流式传输。

syntax = "proto3";
package customers;

service Customers {
  rpc GetCustomer (GetCustomerRequest) returns (Customer);
}

message GetCustomerRequest { string id = 1; }
message Customer {
  string id = 1;
  string email = 2;
  int32  loyalty_points = 3;
}

生成 PHP 桩代码

请安装 gRPC PHP 扩展和 protoc 插件,然后根据 .proto 生成客户端类。PHP 可以通过 ext-grpc 充当功能完整的 gRPC 客户端;运行原生 PHP gRPC 服务器通常需要 Roadrunner 或 Swoole。

pecl install grpc
composer require grpc/grpc google/protobuf

protoc --proto_path=. \
  --php_out=./generated \
  --grpc_out=./generated \
  --plugin=protoc-gen-grpc=$(which grpc_php_plugin) \
  customers.proto

一次 gRPC 客户端调用

生成的桩代码会为您提供类型明确的请求和响应。一次 gRPC 调用会返回消息和一个 status 对象——在信任响应之前,务必先检查状态码。

<?php
require 'vendor/autoload.php';
use Customers\CustomersClient;
use Customers\GetCustomerRequest;
use Grpc\ChannelCredentials;

$client = new CustomersClient('customers-svc:50051', [
    'credentials' => ChannelCredentials::createInsecure(),
]);

$req = (new GetCustomerRequest())->setId('42');
[$reply, $status] = $client->GetCustomer($req)->wait();

if ($status->code === \Grpc\STATUS_OK) {
    echo $reply->getEmail(), "\n";
} else {
    fwrite(STDERR, "gRPC error: {$status->details}\n");
}

模式演进

只要遵守相关规则,协议缓冲区就能支持向前和向后兼容:

  • 绝不要重复使用或更改字段编号。请使用新编号添加新字段。
  • 将删除的字段标记为 reserved,以防该编号被重新使用。
  • 旧客户端会忽略未知字段;缺失字段会采用其类型的默认值。

JSON/REST 不会免费提供这些能力——您必须通过约定来保证兼容性(理想情况下,还应配合共享的 OpenAPI 模式和契约测试)。

message Customer {
  string id = 1;
  string email = 2;
  reserved 3;            // old 'loyalty_points', never reuse 3
  reserved "loyalty_points";
  string display_name = 4; // new field, safe additive change
}

流式传输

gRPC 支持四种调用类型;REST 原生只支持第一种:

  • 一元调用——一个请求,一个响应。
  • 服务器流式传输——一个请求,一系列响应(例如实时更新)。
  • 客户端流式传输——一系列请求,一个响应(例如批量上传)。
  • 双向流式传输——双方同时进行流式传输。

如果您的用例涉及推送或长期数据流,gRPC 流式传输比轮询 REST 端点更合适。

传播上下文与截止时间

同步调用会形成调用链,因此每个请求都必须携带两项内容:用于端到端追踪的关联 ID/追踪 ID,以及防止缓慢末端服务让整条链路一直挂起的截止时间。gRPC 原生支持截止时间;在 REST 中,您可以通过向下游传递不断缩减的超时预算来模拟这一机制。

<?php
// REST: shrink the remaining budget as the call chain deepens
function forwardHeaders(array $incoming, float $remainingMs): array {
    return [
        'X-Correlation-Id' => $incoming['X-Correlation-Id'] ?? bin2hex(random_bytes(8)),
        // downstream must finish within what's left of our budget
        'X-Timeout-Ms'     => (string) max(0, (int) $remainingMs),
    ];
}
print_r(forwardHeaders(['X-Correlation-Id' => 'trace-9'], 1500));

如何选择

一种实用的选择指南:

  • 对于公共接口或合作伙伴接口、浏览器客户端、简单 CRUD、便捷调试以及广泛的缓存支持,请选择REST。
  • 对于内部的高流量、低延迟服务间调用、严格的类型化契约以及流式传输,请选择gRPC。

许多系统会同时使用两者:在网关之后的服务之间使用 gRPC,在面向外部世界的边缘使用 REST。不要强迫一种工具承担另一种工具的职责。

快速检查

将协议与用例匹配。

回顾

同步服务通信:

  • REST/JSON——通用、易调试、可缓存;状态码就是契约;务必设置超时。
  • gRPC/协议缓冲区——契约优先、紧凑、高速并支持流式传输;生成类型明确的 PHP 桩代码。
  • 根据状态码或 gRPC 状态码判断是否可重试;绝不要重试客户端错误。
  • 以增量方式演进模式——绝不要重复使用协议缓冲区字段编号。
  • 在边缘使用 REST、在内部服务之间使用 gRPC,是一种常见且可靠的划分方式。

下一步:通过网关和服务发现,为所有这些服务进行路由和定位。

免费开始

用 AI 导师学习 PHP — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
49
课程
195

常见问题解答

「服务通信:REST 和 gRPC」课时是免费的吗?

是的 — 「服务通信:REST 和 gRPC」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。

「服务通信:REST 和 gRPC」这节课中我会学到什么?

以同步且高效的方式连接服务 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 PHP Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「服务通信:REST 和 gRPC」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 PHP Academy 课中编写并运行代码吗?

能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 从单体架构到微服务
  2. 服务通信:REST 和 gRPC
  3. API 网关与服务发现
  4. 韧性:熔断器与重试
← 返回 PHP Academy