gRPC + Protobuf 跨语言微服务

大白话先说清楚

PHP(Swoole) ←──gRPC──→ Go服务
PHP(Swoole) ←──gRPC──→ Java服务
PHP(Swoole) ←──gRPC──→ Python服务

说白了就是:
各种语言的服务之间打电话,
Protobuf是"电话语言"(都听得懂),
gRPC是"电话线"(HTTP/2传输)

为什么不用HTTP+JSON?

JSON:  {"name":"张三","age":18}  → 字符串,慢,包大
Protobuf: 二进制压缩            → 快,包小3-10倍

第一步:装东西

# 装protobuf扩展
pecl install protobuf

# 装grpc扩展  
pecl install grpc

# 装composer包
composer require grpc/grpc google/protobuf

第二步:写 .proto 文件(定义接口)

// user.proto
syntax = "proto3";
package user;

// 定义服务:有个查用户的方法
service UserService {
    rpc GetUser (UserRequest) returns (UserResponse);
}

// 入参
message UserRequest {
    int32 id = 1;
}

// 出参
message UserResponse {
    int32  id   = 1;
    string name = 2;
    int32  age  = 3;
}

第三步:生成PHP代码

# 用protoc生成PHP文件
protoc --php_out=./gen --grpc_out=./gen \
  --plugin=protoc-gen-grpc=`which grpc_php_plugin` \
  user.proto

# 生成后目录长这样:
# gen/User/UserRequest.php
# gen/User/UserResponse.php  
# gen/User/UserServiceClient.php

第四步:PHP gRPC 服务端(Swoole驱动)

<?php
// server.php
require __DIR__ . '/vendor/autoload.php';

use User\UserRequest;
use User\UserResponse;

// 假数据库
$users = [
    1 => ['name' => '张三', 'age' => 18],
    2 => ['name' => '李四', 'age' => 25],
];

$server = new Swoole\Http\Server('0.0.0.0', 9503, SWOOLE_BASE);

$server->set([
    'open_http2_protocol' => true, // 开HTTP/2,gRPC必须
    'ssl_cert_file'       => null,
    'ssl_key_file'        => null,
]);

$server->on('request', function ($req, $resp) use ($users) {
    // gRPC路径格式:/包名.服务名/方法名
    if ($req->server['request_uri'] !== '/user.UserService/GetUser') {
        $resp->status(404);
        $resp->end();
        return;
    }

    // 解析Protobuf二进制body(前5字节是gRPC帧头)
    $body = substr($req->rawContent(), 5);
    $request = new UserRequest();
    $request->mergeFromString($body);

    $id   = $request->getId();
    $info = $users[$id] ?? ['name' => '未知', 'age' => 0];

    // 构建响应
    $response = new UserResponse();
    $response->setId($id);
    $response->setName($info['name']);
    $response->setAge($info['age']);

    // 序列化 + 加5字节gRPC帧头
    $payload = $response->serializeToString();
    $body    = pack('CN', 0, strlen($payload)) . $payload;

    $resp->header('content-type', 'application/grpc');
    $resp->header('grpc-status', '0');
    $resp->end($body);
});

echo "gRPC服务跑在 9503\n";
$server->start();

第五步:PHP gRPC 客户端(调Go/Java的服务)

<?php
// client.php
require __DIR__ . '/vendor/autoload.php';

use User\UserServiceClient;
use User\UserRequest;

// 连Go或Java写的gRPC服务
$client = new UserServiceClient('go服务的IP:9504', [
    'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);

$request = new UserRequest();
$request->setId(1);

// 发请求
[$response, $status] = $client->GetUser($request)->wait();

if ($status->code === Grpc\STATUS_OK) {
    echo "名字:" . $response->getName() . "\n";
    echo "年龄:" . $response->getAge() . "\n";
} else {
    echo "出错了:" . $status->details . "\n";
}

Go那边长什么样(对照理解)

// go_server.go
// Go实现同一个proto的服务端,PHP可以直接调它
type server struct{}

func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
    return &pb.UserResponse{Id: req.Id, Name: "张三", Age: 18}, nil
}

// 启动
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, &server{})
grpcServer.Serve(listener) // 监听9504

整体通信矩阵

┌─────────────────────────────────────┐
│           .proto 文件               │
│  (所有语言共同遵守的接口协议)      │
└──────────┬──────────────────────────┘
           │ protoc 生成各语言代码
    ┌──────┴───────┐
    ↓              ↓
PHP+Swoole      Go/Java/Python
(服务端/客户端)  (服务端/客户端)
    ↕   HTTP/2+Protobuf二进制   ↕
    └──────────────────────────┘

核心三句话

1. .proto  → 定好接口,所有语言照着实现
2. protoc  → 自动生成各语言的代码,不用手写
3. Swoole  → 开 open_http2_protocol=true 支撑gRPC传输
Logo

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

更多推荐