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 环境准备
安装 Go(版本 1.16+)
安装 Protocol Buffers 编译器
1 2 3 4 5
| brew install protobuf
sudo apt-get install protobuf-compiler
|
安装 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 环境准备
安装 PHP(版本 7.4+)
安装 gRPC 扩展
1 2 3 4 5
| sudo pecl install grpc
echo "extension=grpc.so" >> /etc/php/7.4/cli/php.ini
|
安装 Composer
1 2
| curl -sS https://getcomposer.org/installer | php sudo mv composer.phar /usr/local/bin/composer
|
安装 gRPC PHP 插件
1 2 3 4 5
| 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/ │ │ ├── user.pb.go │ │ └── user_grpc.pb.go │ └── user.proto └── php_project/grpc/ ├── client.php ├── composer.json ├── user.proto └── generated/ ├── GPBMetadata/ │ └── User.php └── 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
| package main
import ( "context" "go_project/grpc/go_grpc/go_grpc" "log" "net"
"google.golang.org/grpc" )
type server struct { go_grpc.UnimplementedUserServiceServer }
func (s *server) GetUserInfo(ctx context.Context, req *go_grpc.GetUserRequest) (*go_grpc.User, error) { 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) } 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. 更新自动加载
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
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') { if (!extension_loaded('grpc')) { throw new Exception('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 调用异常
🎉 总结
通过本流程,我们成功实现了:
- ✅ 环境搭建: 安装了 Go、PHP 及相关 gRPC 依赖
- ✅ 协议定义: 使用 Protocol Buffers 定义服务接口
- ✅ 服务端开发: 实现了 Go gRPC 服务,提供用户信息查询
- ✅ 客户端开发: 实现了 PHP gRPC 客户端,调用 Go 服务
- ✅ 跨语言通信: 验证了 Go 和 PHP 之间的高效通信