Go 服务与 PHP 服务通过 gRPC 互通

Go 服务与 PHP 服务通过 gRPC 互通

📖 背景介绍

gRPC(Google Remote Procedure Call)是由 Google 开发的高性能、开源的通用 RPC 框架。它基于 HTTP/2 协议,使用 Protocol Buffers 作为接口描述语言,支持多种编程语言之间的高效通信。

在微服务架构中,不同服务可能使用不同的编程语言开发。本文将详细介绍如何让 Go 服务和 PHP 服务通过 gRPC 进行通信。

🎯 项目目标

  • 创建一个 Go gRPC 服务端,提供用户信息查询接口
  • 创建一个 PHP gRPC 客户端,调用 Go 服务获取用户信息
  • 实现跨语言的高效通信

🛠️ 基础依赖安装

机器配置

MacBook Pro 2021
Go 1.23.2
PHP 7.2 (已启用 php_grpc 扩展)
Composer V2

Go 环境准备

  1. 安装 Go(版本 1.16+)

  2. 安装 Protocol Buffers 编译器

    1
    2
    3
    4
    5
    # macOS
    brew install protobuf

    # Ubuntu/Debian
    sudo apt-get install protobuf-compiler
  3. 安装 Go gRPC 相关包

    1
    2
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

PHP 环境准备

  1. 安装 PHP(版本 7.4+)

  2. 安装 gRPC 扩展

    1
    2
    3
    4
    5
    # 使用 PECL 安装
    sudo pecl install grpc

    # 在 php.ini 中启用扩展
    echo "extension=grpc.so" >> /etc/php/7.4/cli/php.ini
  3. 安装 Composer

    1
    2
    curl -sS https://getcomposer.org/installer | php
    sudo mv composer.phar /usr/local/bin/composer
  4. 安装 gRPC PHP 插件

    1
    2
    3
    4
    5
    # macOS
    brew install grpc

    # 验证安装
    which grpc_php_plugin

📋 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
learn/
├── go_project/grpc/go_grpc/
│ ├── main/
│ │ └── main.go # Go gRPC 服务端
│ ├── go_grpc/
│ │ ├── user.pb.go # Go protobuf 消息定义
│ │ └── user_grpc.pb.go # Go gRPC 服务定义
│ └── user.proto # Protocol Buffers 定义文件
└── php_project/grpc/
├── client.php # PHP gRPC 客户端
├── composer.json # PHP 依赖管理
├── user.proto # Protocol Buffers 定义文件
└── generated/ # PHP 生成的代码
├── GPBMetadata/
│ └── User.php # PHP protobuf 元数据
└── Go_grpc/
├── GetUserRequest.php
├── User.php
└── UserServiceClient.php

🔧 Protocol Buffers 定义

首先创建 user.proto 文件,定义服务接口和消息格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
syntax = "proto3";

package go_grpc;

option go_package = "go_project/grpc/go_grpc/go_grpc";
option php_namespace = "Go_grpc";

// 用户服务定义
service UserService {
// 获取用户信息
rpc GetUserInfo(GetUserRequest) returns (User);
}

// 获取用户请求
message GetUserRequest {
string id = 1;
}

// 用户信息
message User {
string name = 1;
int32 age = 2;
}

🚀 Go 服务端实现

1. 生成 Go 代码

1
2
3
4
cd go_project/grpc
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
user.proto

2. 实现 Go gRPC 服务

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
// main.go
package main

import (
"context"
"go_project/grpc/go_grpc/go_grpc"
"log"
"net"

"google.golang.org/grpc"
)

// 服务器结构体,实现 UserServiceServer 接口
type server struct {
go_grpc.UnimplementedUserServiceServer
}

// 实现 GetUserInfo 方法
func (s *server) GetUserInfo(ctx context.Context, req *go_grpc.GetUserRequest) (*go_grpc.User, error) {
// 根据请求ID返回用户信息
log.Printf("收到获取用户信息请求,用户ID: %s", req.GetId())

return &go_grpc.User{
Name: "张三",
Age: 25,
}, nil
}

func main() {
// 监听端口
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

// 创建 gRPC 服务器
s := grpc.NewServer()

// 注册服务
go_grpc.RegisterUserServiceServer(s, &server{})

log.Printf("server listening at %v", lis.Addr())

// 启动服务
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

3. 启动 Go 服务

1
2
cd go_project/grpc/go_grpc/main
go run main.go

🐘 PHP 客户端实现

1. 配置 Composer

1
2
3
4
5
6
7
8
9
10
11
12
{
"require": {
"grpc/grpc": "^1.42",
"google/protobuf": "^3.21"
},
"autoload": {
"psr-4": {
"GPBMetadata\\": "generated/GPBMetadata/",
"Go_grpc\\": "generated/Go_grpc/"
}
}
}

2. 安装 PHP 依赖

1
2
cd php_project/grpc
composer install

3. 生成 PHP 代码

1
2
3
4
protoc --php_out=generated \
--grpc_out=generated \
--plugin=protoc-gen-grpc=`which grpc_php_plugin` \
user.proto

4. 更新自动加载

1
composer dump-autoload

5. 实现 PHP gRPC 客户端

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
<?php
// client.php
require_once 'vendor/autoload.php';

use Go_grpc\UserServiceClient;
use Go_grpc\GetUserRequest;
use Grpc\ChannelCredentials;

class GrpcClient
{
private $client;

public function __construct($hostname = 'localhost:50051')
{
// 检查 gRPC 扩展
if (!extension_loaded('grpc')) {
throw new Exception('gRPC 扩展未安装');
}

// 创建 gRPC 客户端
$this->client = new UserServiceClient($hostname, [
'credentials' => ChannelCredentials::createInsecure(),
]);
}

/**
* 获取用户信息
*/
public function getUserInfo($userId)
{
// 创建请求
$request = new GetUserRequest();
$request->setId($userId);

// 调用远程方法
list($response, $status) = $this->client->GetUserInfo($request)->wait();

if ($status->code !== Grpc\STATUS_OK) {
throw new Exception("gRPC 调用失败: " . $status->details);
}

return $response;
}

/**
* 测试连接
*/
public function testConnection()
{
try {
echo "正在连接 gRPC 服务...\n";

$user = $this->getUserInfo('user123');

echo "✅ 连接成功!\n";
echo "用户信息:\n";
echo " 姓名: " . $user->getName() . "\n";
echo " 年龄: " . $user->getAge() . "\n";

return true;
} catch (Exception $e) {
echo "❌ 连接失败: " . $e->getMessage() . "\n";
return false;
}
}
}

// 主程序
if (!extension_loaded('grpc')) {
echo "❌ gRPC 扩展未安装\n";
echo "请使用以下命令安装:\n";
echo "sudo pecl install grpc\n";
echo "然后在 php.ini 中添加:extension=grpc.so\n";
exit(1);
}

echo "=== Go gRPC 服务 PHP 客户端测试 ===\n\n";

try {
$client = new GrpcClient();
$client->testConnection();
} catch (Exception $e) {
echo "程序异常: " . $e->getMessage() . "\n";
}

echo "\n=== 测试完成 ===\n";
?>

🧪 测试与验证

1. 启动 Go 服务

1
2
cd go_project/grpc/go_grpc/main
go run main.go

服务启动后会显示:

1
server listening at [::]:50051

2. 运行 PHP 客户端

1
2
cd php_project/grpc
php client.php

3. 预期输出

PHP 客户端输出:

1
2
3
4
5
6
7
8
9
=== Go gRPC 服务 PHP 客户端测试 ===

正在连接 gRPC 服务...
✅ 连接成功!
用户信息:
姓名: 张三
年龄: 25

=== 测试完成 ===

Go 服务端日志:

1
2
server listening at [::]:50051
收到获取用户信息请求,用户ID: user123

🔍 关键技术点解析

1. 接口实现机制

Go 服务端通过实现 UserServiceServer 接口来提供服务:

1
2
3
4
type UserServiceServer interface {
GetUserInfo(context.Context, *GetUserRequest) (*User, error)
mustEmbedUnimplementedUserServiceServer()
}

2. 自动代码生成

  • Go: 生成 *.pb.go*_grpc.pb.go 文件
  • PHP: 生成 GPBMetadata 和服务类文件

3. 元数据处理差异

  • Go: 将 protobuf 元数据直接嵌入生成的 .pb.go 文件中
  • PHP: 使用独立的 GPBMetadata 目录存储元数据

4. 错误处理

  • 检查 gRPC 扩展是否安装
  • 验证服务连接状态
  • 处理 gRPC 调用异常

🎉 总结

通过本流程,我们成功实现了:

  1. 环境搭建: 安装了 Go、PHP 及相关 gRPC 依赖
  2. 协议定义: 使用 Protocol Buffers 定义服务接口
  3. 服务端开发: 实现了 Go gRPC 服务,提供用户信息查询
  4. 客户端开发: 实现了 PHP gRPC 客户端,调用 Go 服务
  5. 跨语言通信: 验证了 Go 和 PHP 之间的高效通信

Go 服务与 PHP 服务通过 gRPC 互通
https://wuwanhao.github.io/2025/07/29/grpc_communication_tutorial/
作者
Wuuu
发布于
2025年7月29日
许可协议