The GoFrame framework includes multiple microservice components and provides an easy-to-use GRPC scaffold module and tools. The scaffold is implemented by the grpcx community package: https://github.com/gogf/gf/tree/master/contrib/rpc/grpcx which contains multiple modules.

Server - Server

The server is maintained by the grpcx.Server module, used for creating and maintaining server objects. Example usage: https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/server/main.go

The creation of the server often involves using configuration files. For an introduction to server configuration files, please refer to the chapter: GRPC Server Configuration

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  4. "github.com/gogf/gf/example/rpc/grpcx/basic/controller"
  5. )
  6. func main() {
  7. s := grpcx.Server.New()
  8. controller.Register(s)
  9. s.Run()
  10. }

Client - Client

The client is maintained by the grpcx.Client module, used for creating and maintaining client objects. Example usage: https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/client/main.go

In most scenarios, service access is based on service names.

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  4. "github.com/gogf/gf/example/rpc/grpcx/basic/protobuf"
  5. "github.com/gogf/gf/v2/frame/g"
  6. "github.com/gogf/gf/v2/os/gctx"
  7. )
  8. func main() {
  9. var (
  10. ctx = gctx.New()
  11. conn = grpcx.Client.MustNewGrpcClientConn("demo")
  12. client = protobuf.NewGreeterClient(conn)
  13. )
  14. res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
  15. if err != nil {
  16. g.Log().Error(ctx, err)
  17. return
  18. }
  19. g.Log().Debug(ctx, "Response:", res.Message)
  20. }

Scaffold Components - 图1warning

Common Issue

Should the Client object that is created and reused be saved?

Each grpc Client object actually corresponds to access to a target service. This object should be saved and reused, rather than creating a new Client object each time, as this can improve efficiency, reduce resource usage, and be GC-friendly.

Context - Ctx

The context is maintained by the grpcx.Ctx module, used for custom data transfer between clients and servers, as well as between services. It includes the following commonly used methods:

  1. func (c Ctx) NewIncoming(ctx context.Context, data ...g.Map) context.Context
  2. func (c Ctx) NewOutgoing(ctx context.Context, data ...g.Map) context.Context
  3. func (c Ctx) IncomingToOutgoing(ctx context.Context, keys ...string) context.Context
  4. func (c Ctx) IncomingMap(ctx context.Context) *gmap.Map
  5. func (c Ctx) OutgoingMap(ctx context.Context) *gmap.Map
  6. func (c Ctx) SetIncoming(ctx context.Context, data g.Map) context.Context
  7. func (c Ctx) SetOutgoing(ctx context.Context, data g.Map) context.Context

The Outgoing is used on the client side to indicate custom data to be transmitted to the server, usually composed of Key-Value pairs in Map data format. The Incoming is used on the server side to indicate data received from the client. Some framework-related embedded information, such as trace information and client version information, is also written into the Incoming key-value pairs. Example usage:

server.go

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  4. "github.com/gogf/gf/example/rpc/grpcx/ctx/controller"
  5. )
  6. func main() {
  7. s := grpcx.Server.New()
  8. controller.Register(s)
  9. s.Run()
  10. }

controller.go

  1. package controller
  2. import (
  3. "context"
  4. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  5. "github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf"
  6. "github.com/gogf/gf/v2/frame/g"
  7. )
  8. type Controller struct {
  9. protobuf.UnimplementedGreeterServer
  10. }
  11. func Register(s *grpcx.GrpcServer) {
  12. protobuf.RegisterGreeterServer(s.Server, &Controller{})
  13. }
  14. // SayHello implements helloworld.GreeterServer
  15. func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) {
  16. m := grpcx.Ctx.IncomingMap(ctx)
  17. g.Log().Infof(ctx, `incoming data: %v`, m.Map())
  18. return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil
  19. }

client.go

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  4. "github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf"
  5. "github.com/gogf/gf/v2/frame/g"
  6. "github.com/gogf/gf/v2/os/gctx"
  7. )
  8. func main() {
  9. var (
  10. conn = grpcx.Client.MustNewGrpcClientConn("demo")
  11. client = protobuf.NewGreeterClient(conn)
  12. ctx = grpcx.Ctx.NewOutgoing(gctx.New(), g.Map{
  13. "UserId": "1000",
  14. "UserName": "john",
  15. })
  16. )
  17. g.Log().Infof(ctx, `outgoing data: %v`, grpcx.Ctx.OutgoingMap(ctx).Map())
  18. res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
  19. if err != nil {
  20. g.Log().Error(ctx, err)
  21. return
  22. }
  23. g.Log().Debug(ctx, "Response:", res.Message)
  24. }

After execution, the server outputs:

  1. $ go run server.go
  2. 2023-03-15 19:28:45.331 [DEBU] set default registry using file registry as no custom registry set
  3. 2023-03-15 19:28:45.331 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:51707 Metadata:map[protocol:grpc]}
  4. 2023-03-15 19:28:45.332 [INFO] pid[83753]: grpc server started listening on [:51707]
  5. 2023-03-15 19:28:54.093 [INFO] {d049db1a39944c1711bd9f37d58a88f9} incoming data: map[:authority:service/default/default/demo/latest/ content-type:application/grpc traceparent:00-d049db1a39944c1711bd9f37d58a88f9-adbd2ba657ffea45-01 user-agent:grpc-go/1.49.0 userid:1000 username:john]
  6. 2023-03-15 19:28:54.093 {d049db1a39944c1711bd9f37d58a88f9} /protobuf.Greeter/SayHello, 0.049ms, name:"World", message:"Hello World"

Client output:

  1. $ go run client.go
  2. 2023-03-15 19:28:54.087 [INFO] {d049db1a39944c1711bd9f37d58a88f9} outgoing data: map[userid:1000 username:john]
  3. 2023-03-15 19:28:54.089 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:51707","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}]
  4. 2023-03-15 19:28:54.093 [DEBU] {d049db1a39944c1711bd9f37d58a88f9} Response: Hello World

Load Balancer - Balancer

Load balancing is maintained by the grpcx.Balancer module, used to implement how requests are made when the server has multiple access addresses. If the client does not set a load balancing strategy, the default round-robin strategy is used. For detailed information about load balancing, please refer to the chapter: Service Load Balancing

Here, we use the random strategy as an example, where the random strategy will make the number of requests received by different servers relatively random.

server.go

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  4. "github.com/gogf/gf/example/rpc/grpcx/balancer/controller"
  5. )
  6. func main() {
  7. s := grpcx.Server.New()
  8. controller.Register(s)
  9. s.Run()
  10. }

client.go

  1. package main
  2. import (
  3. "context"
  4. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  5. "github.com/gogf/gf/example/rpc/grpcx/balancer/protobuf"
  6. "github.com/gogf/gf/v2/frame/g"
  7. "github.com/gogf/gf/v2/os/gctx"
  8. )
  9. func main() {
  10. var (
  11. ctx context.Context
  12. conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom())
  13. client = protobuf.NewGreeterClient(conn)
  14. )
  15. for i := 0; i < 10; i++ {
  16. ctx = gctx.New()
  17. res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
  18. if err != nil {
  19. g.Log().Error(ctx, err)
  20. return
  21. }
  22. g.Log().Debug(ctx, "Response:", res.Message)
  23. }
  24. }

Here, grpcx.Balancer.WithRandom() indicates the use of a random request strategy.

Start two server.go instances, then run the client.go client, and observe the servers’ request logs:

server1 terminal output:

  1. $ go run server.go
  2. 2023-03-15 19:50:44.801 [DEBU] set default registry using file registry as no custom registry set
  3. 2023-03-15 19:50:44.802 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53962 Metadata:map[protocol:grpc]}
  4. 2023-03-15 19:50:44.802 [INFO] pid[89290]: grpc server started listening on [:53962]
  5. 2023-03-15 19:50:57.282 {7025612f6d954c17c5f335051bf10899} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World"
  6. 2023-03-15 19:50:57.283 {60567c2f6d954c17c7f335052ce05185} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
  7. 2023-03-15 19:50:57.285 {f8d09b2f6d954c17ccf33505dff1a4ea} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
  8. 2023-03-15 19:50:57.287 {f0fab02f6d954c17cdf33505438b2c80} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"

server2 terminal output:

  1. $ go run server.go
  2. 2023-03-15 19:50:51.720 [DEBU] set default registry using file registry as no custom registry set
  3. 2023-03-15 19:50:51.721 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53973 Metadata:map[protocol:grpc]}
  4. 2023-03-15 19:50:51.722 [INFO] pid[89351]: grpc server started listening on [:53973]
  5. 2023-03-15 19:50:57.280 {b89a0d2f6d954c17c4f33505a046817c} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
  6. 2023-03-15 19:50:57.282 {28bf732f6d954c17c6f33505adedff5f} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
  7. 2023-03-15 19:50:57.283 {9876832f6d954c17c8f3350580ed535b} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
  8. 2023-03-15 19:50:57.284 {684e8b2f6d954c17c9f33505d56e4b05} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"
  9. 2023-03-15 19:50:57.284 {c045912f6d954c17caf3350599006197} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"
  10. 2023-03-15 19:50:57.284 {500a972f6d954c17cbf33505252b0e01} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"

Client terminal output:

  1. $ go run client.go
  2. 2023-03-15 19:50:57.278 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:53962","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null},{"Addr":"10.35.12.81:53973","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}]
  3. 2023-03-15 19:50:57.281 [DEBU] {b89a0d2f6d954c17c4f33505a046817c} Response: Hello World
  4. 2023-03-15 19:50:57.282 [DEBU] {7025612f6d954c17c5f335051bf10899} Response: Hello World
  5. 2023-03-15 19:50:57.282 [DEBU] {28bf732f6d954c17c6f33505adedff5f} Response: Hello World
  6. 2023-03-15 19:50:57.283 [DEBU] {60567c2f6d954c17c7f335052ce05185} Response: Hello World
  7. 2023-03-15 19:50:57.283 [DEBU] {9876832f6d954c17c8f3350580ed535b} Response: Hello World
  8. 2023-03-15 19:50:57.284 [DEBU] {684e8b2f6d954c17c9f33505d56e4b05} Response: Hello World
  9. 2023-03-15 19:50:57.284 [DEBU] {c045912f6d954c17caf3350599006197} Response: Hello World
  10. 2023-03-15 19:50:57.285 [DEBU] {500a972f6d954c17cbf33505252b0e01} Response: Hello World
  11. 2023-03-15 19:50:57.286 [DEBU] {f8d09b2f6d954c17ccf33505dff1a4ea} Response: Hello World
  12. 2023-03-15 19:50:57.287 [DEBU] {f0fab02f6d954c17cdf33505438b2c80} Response: Hello World

You can see that the client sent 10 requests, and the two servers each received 4 and 6 requests, respectively, with the load of requests being relatively random.

Service Discovery - Resolver

Service discovery is maintained by the grpcx.Resolver module, used for GRPC service name resolution. The grpcx component uses the local file system for service discovery by default, only for testing purposes. For production environments, other service discovery components are required. A simple example:

server.go

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/registry/etcd/v2"
  4. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  5. "github.com/gogf/gf/example/rpc/grpcx/resolver/controller"
  6. )
  7. func main() {
  8. grpcx.Resolver.Register(etcd.New("127.0.0.1:2379"))
  9. s := grpcx.Server.New()
  10. controller.Register(s)
  11. s.Run()
  12. }

The grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) is used to set the service discovery component to etcd, which only supports GRPC protocol.

client.go

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/registry/etcd/v2"
  4. "github.com/gogf/gf/contrib/rpc/grpcx/v2"
  5. "github.com/gogf/gf/example/rpc/grpcx/resolver/protobuf"
  6. "github.com/gogf/gf/v2/frame/g"
  7. "github.com/gogf/gf/v2/os/gctx"
  8. )
  9. func main() {
  10. grpcx.Resolver.Register(etcd.New("127.0.0.1:2379"))
  11. var (
  12. ctx = gctx.New()
  13. conn = grpcx.Client.MustNewGrpcClientConn("demo")
  14. client = protobuf.NewGreeterClient(conn)
  15. )
  16. res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
  17. if err != nil {
  18. g.Log().Error(ctx, err)
  19. return
  20. }
  21. g.Log().Debug(ctx, "Response:", res.Message)
  22. }

Start etcd:

  1. $ etcd
  2. {"level":"info","ts":"2023-03-15T20:02:50.966+0800","caller":"etcdmain/etcd.go:73","msg":"Running: ","args":["etcd"]}
  3. {"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:100","msg":"failed to detect default host","error":"default host not supported on darwin_amd64"}
  4. {"level":"warn","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:105","msg":"'data-dir' was empty; using default","data-dir":"default.etcd"}
  5. {"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:116","msg":"server has been already initialized","data-dir":"default.etcd","dir-type":"member"}
  6. {"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"embed/etcd.go:124","msg":"configuring peer listeners","listen-peer-urls":["http://localhost:2380"]}
  7. {"level":"info","ts":"2023-03-15T20:02:50.968+0800","caller":"embed/etcd.go:132","msg":"configuring client listeners","listen-client-urls":["http://localhost:2379"]}
  8. ...

Run server.go:

  1. $ go run server.go
  2. 2023-03-15 20:02:19.472 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:55066 Metadata:map[protocol:grpc]}
  3. 2023-03-15 20:02:19.516 [DEBU] etcd put success with key "/service/default/default/demo/latest/10.35.12.81:55066", value "{"protocol":"grpc"}", lease "7587869265929863945"
  4. 2023-03-15 20:02:19.516 [INFO] pid[92040]: grpc server started listening on [:55066]
  5. 2023-03-15 20:02:29.310 {88c4c04e0e964c17dddec53b1adb54f7} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"

Run client.go:

  1. $ go run client.go
  2. 2023-03-15 20:02:29.297 [DEBU] Watch key "/service/default/default/demo/latest/"
  3. 2023-03-15 20:02:29.307 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:55066","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}]
  4. 2023-03-15 20:02:29.308 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:55066","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}]
  5. 2023-03-15 20:02:29.310 [DEBU] {88c4c04e0e964c17dddec53b1adb54f7} Response: Hello World