前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >gRPC 的使用

gRPC 的使用

作者头像
seth-shi
发布2023-12-18 15:24:57
1790
发布2023-12-18 15:24:57
举报
文章被收录于专栏:seth-shi的专栏seth-shi的专栏

前言

  • 网上有很多的安装使用教程, 由于gRPC的更新, 很多命令都是使用不了, 现在写的这篇文章也只是针对当前
  • 如果发现用不了, 最好的办法还是参考官方文档

安装

  1. 首先要安装Go
  2. HOME/.local/bin) export PATH="
  3. 安装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服务例子

  • 初始化项目
代码语言:javascript
复制
mkdir grpc-demo && cd grpc-demo
go mod init github.com/seth-shi/grpc-demo
  • go.mod
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
package enums

const (
	HttpHost = ":8080"
	GoodsHost = ":8888"
	OrderHost = ":9999"
)
  • goods/main.go
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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服务实际调用ordergoods服务去生成订单
  • 也可以通过api服务查询已经创建的订单, api实际调用order服务查询
  • 启动三个服务
代码语言:javascript
复制
go run goods/main.go
go run order/main.go
go run api/main.go
  • 运行结果
代码语言:javascript
复制
# 获取用户为 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的话, 网络问题很多, 直接把所有服务都到宿主机运行
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-02-25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 安装
  • 介绍
  • 例子
  • 一个用户订单的RPC服务例子
  • 错误
相关产品与服务
专用宿主机
专用宿主机(CVM Dedicated Host,CDH)提供用户独享的物理服务器资源,满足您资源独享、资源物理隔离、安全、合规需求。专用宿主机搭载了腾讯云虚拟化系统,购买之后,您可在其上灵活创建、管理多个自定义规格的云服务器实例,自主规划物理资源的使用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档