准备工作

推荐使用kratos工具快速生成带grpc的项目,如我们生成一个叫kratos-demo的项目。

  1. kratos new kratos-demo --proto

pb文件

创建项目成功后,进入api目录下可以看到api.protoapi.pb.gogenerate.go文件,其中:

  • api.proto是gRPC server的描述文件
  • api.pb.go是基于api.proto生成的代码文件
  • generate.go是用于kratos tool protoc执行go generate进行代码生成的临时文件

接下来可以将以上三个文件全部删除或者保留generate.go,之后编写自己的proto文件,确认proto无误后,进行代码生成:

  • 可直接执行kratos tool protoc,该命令会调用protoc工具生成.pb.go文件
  • generate.go没删除,也可以执行go generate命令,将调用kratos tool protoc工具进行代码生成

kratos工具请看

如没看kprotoc文档,请看下面这段话

kratos tool protoc用于快速生成pb.go文件,但目前windows和Linux需要先自己安装protoc工具,具体请看protoc说明

注册server

进入internal/server/grpc目录打开server.go文件,可以看到以下代码,只需要替换以下注释内容就可以启动一个gRPC服务。

  1. package grpc
  2. import (
  3. pb "kratos-demo/api"
  4. "kratos-demo/internal/service"
  5. "github.com/go-kratos/kratos/pkg/conf/paladin"
  6. "github.com/go-kratos/kratos/pkg/net/rpc/warden"
  7. )
  8. // New new a grpc server.
  9. func New(svc *service.Service) *warden.Server {
  10. var rc struct {
  11. Server *warden.ServerConfig
  12. }
  13. if err := paladin.Get("grpc.toml").UnmarshalTOML(&rc); err != nil {
  14. if err != paladin.ErrNotExist {
  15. panic(err)
  16. }
  17. }
  18. ws := warden.NewServer(rc.Server)
  19. // 注意替换这里:
  20. // RegisterDemoServer方法是在"api"目录下代码生成的
  21. // 对应proto文件内自定义的service名字,请使用正确方法名替换
  22. pb.RegisterDemoServer(ws.Server(), svc)
  23. ws, err := ws.Start()
  24. if err != nil {
  25. panic(err)
  26. }
  27. return ws
  28. }

注册注意

  1. // SayHello grpc demo func.
  2. func (s *Service) SayHello(ctx context.Context, req *pb.HelloReq) (reply *empty.Empty, err error) {
  3. reply = new(empty.Empty)
  4. fmt.Printf("hello %s", req.Name)
  5. return
  6. }

请进入internal/service内找到SayHello方法,注意方法的入参和出参,都是按照gRPC的方法声明对应的:

  • 第一个参数必须是context.Context,第二个必须是proto内定义的message对应生成的结构体
  • 第一个返回值必须是proto内定义的message对应生成的结构体,第二个参数必须是error
  • 在http框架bm中,如果共用proto文件生成bm代码,那么也可以直接使用该service方法

建议service严格按照此格式声明方法使其能够在bm和warden内共用。

client调用

请进入internal/dao方法内,一般对资源的处理都会在这一层封装。
对于client端,前提必须有对应proto文件生成的代码,那么有两种选择:

  • 拷贝proto文件到自己项目下并且执行代码生成
  • 直接import服务端的api package

这也是业务代码我们加了一层internal的关系,服务对外暴露的只有接口

不管哪一种方式,以下初始化gRPC client的代码建议伴随生成的代码存放在统一目录下:

  1. package dao
  2. import (
  3. "context"
  4. "github.com/go-kratos/kratos/pkg/net/rpc/warden"
  5. "google.golang.org/grpc"
  6. )
  7. // target server addrs.
  8. const target = "direct://default/127.0.0.1:9000,127.0.0.1:9091" // NOTE: example
  9. // NewClient new member grpc client
  10. func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (DemoClient, error) {
  11. client := warden.NewClient(cfg, opts...)
  12. conn, err := client.Dial(context.Background(), target)
  13. if err != nil {
  14. return nil, err
  15. }
  16. // 注意替换这里:
  17. // NewDemoClient方法是在"api"目录下代码生成的
  18. // 对应proto文件内自定义的service名字,请使用正确方法名替换
  19. return NewDemoClient(conn), nil
  20. }

其中,target为gRPC用于服务发现的目标,使用标准url资源格式提供给resolver用于服务发现。warden默认使用direct直连方式,直接与server端进行连接。如果在使用其他服务发现组件请看warden服务发现

有了初始化Client的代码,我们的Dao对象即可进行初始化和使用,以下以直接import服务端api包为例:

  1. package dao
  2. import(
  3. demoapi "kratos-demo/api"
  4. grpcempty "github.com/golang/protobuf/ptypes/empty"
  5. "github.com/go-kratos/kratos/pkg/net/rpc/warden"
  6. "github.com/pkg/errors"
  7. )
  8. type Dao struct{
  9. demoClient demoapi.DemoClient
  10. }
  11. // New account dao.
  12. func New() (d *Dao) {
  13. cfg := &warden.ClientConfig{}
  14. paladin.Get("grpc.toml").UnmarshalTOML(cfg)
  15. d = &Dao{}
  16. var err error
  17. if d.demoClient, err = demoapi.NewClient(cfg); err != nil {
  18. panic(err)
  19. }
  20. return
  21. }
  22. // SayHello say hello.
  23. func (d *Dao) SayHello(c context.Context, req *demoapi.HelloReq) (resp *grpcempty.Empty, err error) {
  24. if resp, err = d.demoClient.SayHello(c, req); err != nil {
  25. err = errors.Wrapf(err, "%v", arg)
  26. }
  27. return
  28. }

如此在internal/service层就可以进行资源的方法调用。

扩展阅读

warden拦截器
warden基于pb生成
warden服务发现
warden负载均衡