前言
- 网上有很多的安装使用教程, 由于
gRPC
的更新, 很多命令都是使用不了, 现在写的这篇文章也只是针对当前 - 如果发现用不了, 最好的办法还是参考官方文档
安装
- 首先要安装Go
- 安装
protoc
编译器 https://grpc.io/docs/protoc-installation/protoc
编译器用于编译.proto
包含服务和消息定义的文件- 下载和你的操作系统和计算机体系结构
(protoc-<version>-<os><arch>.zip)
对应的 zip 文件(比如我的是WSL
系统, 下载了protoc-3.19.4-linux-x86_64.zip
) - 解压下载好的压缩包(注意不同版本名字)
unzip protoc-3.19.4-linux-x86_64.zip -d $HOME/.local
- 加入环境变量
vim ~/.bashrc
然后在最后加($HOME/.local/bin
)export PATH="$PATH:$HOME/.local/bin"
source ~/.bashrc
刷新环境变量
- 执行
protoc --version
如果出现版本就代表安装成功
- 安装
Go
的插件protoc
编译器需本身不能生成Go
代码, 需要安装此插件来生成Go
代码go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
gRPC
代码生成器插件(注: 之前包含在protoc-gen-go
)go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
介绍
gRPC
允许您定义四种服务方法:- 一元
RPC
:客户端向服务器发送单个请求并获得单个响应,就像正常的函数调用一样。 - 服务端流式:客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。
- 客户端流式:与服务端数据流模式相反,客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。
- 双向流式:双方使用读写流去发送一个消息序列,两个流独立操作,双方可以同时发送和同时接收。
- 一元
- 流式其实很像数组, 只不过流只能被动的读取(等待对端推送数据过来), 像在
Dart
中流一般是通过订阅一个回调去拿到里面的所有数据 gRPC
中有一个关键字repeated
用来声明数组, 所以纠结用stream
还是repeated
作为集合的返回- 可以参考微软的回答: gRPC 流式处理服务与重复字段
- 对于任何大小受限且能在短时间内(例如在一秒钟之内)全部生成的数据集就用
repeated
- 当数据集中的消息对象可能非常大时,最好是使用流式处理请求或响应传输这些对象。
例子
- 安装完成之后可以跟着官网的例子学习一下
一个用户订单的RPC
服务例子
- 初始化项目
mkdir grpc-demo && cd grpc-demo
go mod init github.com/seth-shi/grpc-demo
- go.mod
module github.com/seth-shi/grpc-demo
go 1.17
require (
github.com/gin-gonic/gin v1.7.7
google.golang.org/grpc v1.44.0
google.golang.org/protobuf v1.27.1
)
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
pb/goods.proto
syntax = "proto3";
package pb;
option go_package="github.com/seth-shi/grpc-demo/pb";
service Goods {
rpc Show(GoodsShowRequest) returns (GoodsData);
}
message GoodsShowRequest {
int64 id = 1;
}
message GoodsData {
int64 id = 1;
string name = 2;
double amount = 3;
}
pb/order.proto
syntax = "proto3";
package pb;
option go_package="github.com/seth-shi/grpc-demo/pb";
service Order {
rpc Index(OrderIndexRequest) returns (OrderIndexResponse);
rpc Store(OrderStoreRequest) returns (OrderData);
}
message OrderIndexRequest {
int64 userId = 1;
}
message OrderIndexResponse {
repeated OrderData data = 1;
}
message OrderStoreRequest {
int64 goodsId = 1;
int64 goodsNumber = 2;
int64 userId = 3;
double Amount = 4;
string goodsName = 5;
}
message OrderData {
int64 no = 1;
double amount = 2;
int64 number = 3;
int64 goodsId = 4;
string goodsName = 5;
}
-
执行
protoc
命令生成Go
文件protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative pb/*.proto
-
执行命令对依赖进行更新
go mod tidy
-
enums/host.go
package enums
const (
HttpHost = ":8080"
GoodsHost = ":8888"
OrderHost = ":9999"
)
goods/main.go
package main
import (
"context"
"errors"
"github.com/seth-shi/grpc-demo/enums"
"github.com/seth-shi/grpc-demo/pb"
"google.golang.org/grpc"
"log"
"net"
)
type server struct {
pb.UnimplementedGoodsServer
// 为了简单, 当住数据库
data map[int64]*pb.GoodsData
}
func newGoodsServer() *server {
return &server{data: map[int64]*pb.GoodsData{
1: {
Id: 1,
Name: "橘子",
Amount: 9.9,
},
2: {
Id: 2,
Name: "香蕉",
Amount: 8.8,
},
}}
}
func (s *server) Show(ctx context.Context, in *pb.GoodsShowRequest) (*pb.GoodsData, error) {
u, e := s.data[in.Id]
if !e {
return nil, errors.New("无此商品")
}
return u, nil
}
func main() {
lis, err := net.Listen("tcp", enums.GoodsHost)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGoodsServer(s, newGoodsServer())
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
order/main.go
package main
import (
"context"
"github.com/seth-shi/grpc-demo/enums"
"github.com/seth-shi/grpc-demo/pb"
"google.golang.org/grpc"
"log"
"net"
"sync"
)
type server struct {
pb.UnimplementedOrderServer
// 为了简单, 当住数据库
data map[int64][]*pb.OrderData
// 订单的自增 ID
id int64
sync.Mutex
}
func newOrderServer() *server {
return &server{data: make(map[int64][]*pb.OrderData)}
}
func (s *server) Index(c context.Context, r *pb.OrderIndexRequest) (*pb.OrderIndexResponse, error) {
return &pb.OrderIndexResponse{Data: s.data[r.UserId]}, nil
}
func (s *server) Store(c context.Context, r *pb.OrderStoreRequest) (*pb.OrderData, error) {
s.Lock()
defer s.Unlock()
s.id++
d := &pb.OrderData{
No: s.id,
Amount: r.Amount,
Number: r.GoodsNumber,
GoodsId: r.GoodsId,
GoodsName: r.GoodsName,
}
s.data[r.UserId] = append(s.data[r.UserId], d)
return d, nil
}
func main() {
lis, err := net.Listen("tcp", enums.OrderHost)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterOrderServer(s, newOrderServer())
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
api/main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/seth-shi/grpc-demo/enums"
"github.com/seth-shi/grpc-demo/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"strconv"
)
var goodsClient pb.GoodsClient
var orderClient pb.OrderClient
func main() {
goodsClient = createGoodsClient()
orderClient = createOrderClient()
// 注册 HTTP 路由
r := gin.Default()
r.GET("/orders", orderIndex)
r.POST("/orders", createOrder)
r.Run(enums.HttpHost)
}
func createGoodsClient() pb.GoodsClient {
conn, err := grpc.Dial(enums.GoodsHost, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
return pb.NewGoodsClient(conn)
}
func createOrderClient() pb.OrderClient {
conn, err := grpc.Dial(enums.OrderHost, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
return pb.NewOrderClient(conn)
}
type createOrderRequest struct {
UserId int64 `json:"user_id" form:"user_id" binding:"required"`
GoodsId int64 `json:"goods_id" form:"goods_id" binding:"required"`
Number int64 `json:"number" form:"number" binding:"required"`
}
type createOrderResponse struct {
No int64 `json:"no"`
Name string `json:"name"`
Number int64 `json:"number"`
GoodsId int64 `json:"goods_id"`
Amount float64 `json:"amount"`
}
func createOrder(c *gin.Context) {
// 获取输入的参数
var req createOrderRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(200, gin.H{"code": 400, "msg": err.Error()})
return
}
goods, err := goodsClient.Show(c.Request.Context(), &pb.GoodsShowRequest{Id: req.GoodsId})
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": err.Error()})
return
}
res, err := orderClient.Store(c.Request.Context(), &pb.OrderStoreRequest{
GoodsId: goods.Id,
UserId: req.UserId,
GoodsNumber: req.Number,
GoodsName: goods.Name,
Amount: float64(req.Number) * goods.Amount,
})
if err != nil {
c.JSON(200, gin.H{"code": 400, "msg": err.Error()})
return
}
c.JSON(200, gin.H{
"code": 200,
"data": createOrderResponse{
No: res.No,
Name: res.GoodsName,
Number: res.Number,
Amount: res.Amount,
GoodsId: res.GoodsId,
},
})
}
func orderIndex(c *gin.Context) {
userId, err := strconv.ParseInt(c.Query("user_id"), 10, 64)
if err != nil {
c.JSON(200, gin.H{"code": 400, "error": "无效的用户"})
return
}
res, err := orderClient.Index(c.Request.Context(), &pb.OrderIndexRequest{UserId: userId})
if err != nil {
c.JSON(200, gin.H{"code": 400, "error": err.Error()})
return
}
c.JSON(200, gin.H{
"code": 200,
"data": res.Data,
})
}
- 代码很简单, 有一个
order
服务, 一个goods
服务, 还有一个向外暴露的api
服务 - 可以通过
api
服务创建订单,api
服务实际调用order
和goods
服务去生成订单 - 也可以通过
api
服务查询已经创建的订单,api
实际调用order
服务查询 - 启动三个服务
go run goods/main.go
go run order/main.go
go run api/main.go
- 运行结果
# 获取用户为 1 的订单列表
curl --location --request GET '127.0.0.1:8080/orders?user_id=1'
## output
{
"code": 200,
"data": null
}
# 创建订单1
curl --location --request POST '127.0.0.1:8080/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": 1,
"goods_id": 1,
"number": 1
}'
## output
{
"code": 200,
"data": {
"no": 1,
"name": "橘子",
"number": 1,
"goods_id": 1,
"amount": 9.9
}
}
# 创建订单2
curl --location --request POST '127.0.0.1:8080/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": 1,
"goods_id": 2,
"number": 9
}'
## output
{
"code": 200,
"data": {
"no": 2,
"name": "香蕉",
"number": 9,
"goods_id": 2,
"amount": 79.2
}
}
# 创建订单3
curl --location --request POST '127.0.0.1:8080/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": 1,
"goods_id": 3,
"number": 9
}'
## output
{
"code": 400,
"msg": "rpc error: code = Unknown desc = 无此商品"
}
# 查询订单1
curl --location --request GET '127.0.0.1:8080/orders?user_id=1'
## output
{
"code": 200,
"data": [
{
"no": 1,
"amount": 9.9,
"number": 1,
"goodsId": 1,
"goodsName": "橘子"
},
{
"no": 2,
"amount": 79.2,
"number": 9,
"goodsId": 2,
"goodsName": "香蕉"
}
]
}
错误
- rpc error: code = Unavailable desc = connection error:
- 如果出现上面这个错误, 如果是用
WSL
的话, 网络问题很多, 直接把所有服务都到宿主机运行
- 如果出现上面这个错误, 如果是用