Skip to main content
Version: 2.8.x(Latest)

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

package main

import (
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/basic/controller"
)

func main() {
s := grpcx.Server.New()
controller.Register(s)
s.Run()
}

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.

package main

import (
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/basic/protobuf"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)

func main() {
var (
ctx = gctx.New()
conn = grpcx.Client.MustNewGrpcClientConn("demo")
client = protobuf.NewGreeterClient(conn)
)
res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Message)
}
warning

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:

func (c Ctx) NewIncoming(ctx context.Context, data ...g.Map) context.Context
func (c Ctx) NewOutgoing(ctx context.Context, data ...g.Map) context.Context
func (c Ctx) IncomingToOutgoing(ctx context.Context, keys ...string) context.Context
func (c Ctx) IncomingMap(ctx context.Context) *gmap.Map
func (c Ctx) OutgoingMap(ctx context.Context) *gmap.Map
func (c Ctx) SetIncoming(ctx context.Context, data g.Map) context.Context
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

package main

import (
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/ctx/controller"
)

func main() {
s := grpcx.Server.New()
controller.Register(s)
s.Run()
}

controller.go

package controller

import (
"context"

"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf"
"github.com/gogf/gf/v2/frame/g"
)

type Controller struct {
protobuf.UnimplementedGreeterServer
}

func Register(s *grpcx.GrpcServer) {
protobuf.RegisterGreeterServer(s.Server, &Controller{})
}

// SayHello implements helloworld.GreeterServer
func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) {
m := grpcx.Ctx.IncomingMap(ctx)
g.Log().Infof(ctx, `incoming data: %v`, m.Map())
return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil
}

client.go

package main

import (
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)

func main() {
var (
conn = grpcx.Client.MustNewGrpcClientConn("demo")
client = protobuf.NewGreeterClient(conn)
ctx = grpcx.Ctx.NewOutgoing(gctx.New(), g.Map{
"UserId": "1000",
"UserName": "john",
})
)
g.Log().Infof(ctx, `outgoing data: %v`, grpcx.Ctx.OutgoingMap(ctx).Map())
res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Message)
}

After execution, the server outputs:

$ go run server.go
2023-03-15 19:28:45.331 [DEBU] set default registry using file registry as no custom registry set
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]}
2023-03-15 19:28:45.332 [INFO] pid[83753]: grpc server started listening on [:51707]
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]
2023-03-15 19:28:54.093 {d049db1a39944c1711bd9f37d58a88f9} /protobuf.Greeter/SayHello, 0.049ms, name:"World", message:"Hello World"

Client output:

$ go run client.go
2023-03-15 19:28:54.087 [INFO] {d049db1a39944c1711bd9f37d58a88f9} outgoing data: map[userid:1000 username:john]
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}]
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

package main

import (
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/balancer/controller"
)

func main() {
s := grpcx.Server.New()
controller.Register(s)
s.Run()
}

client.go

package main

import (
"context"

"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/balancer/protobuf"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)

func main() {
var (
ctx context.Context
conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom())
client = protobuf.NewGreeterClient(conn)
)
for i := 0; i < 10; i++ {
ctx = gctx.New()
res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Message)
}
}

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:

$ go run server.go
2023-03-15 19:50:44.801 [DEBU] set default registry using file registry as no custom registry set
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]}
2023-03-15 19:50:44.802 [INFO] pid[89290]: grpc server started listening on [:53962]
2023-03-15 19:50:57.282 {7025612f6d954c17c5f335051bf10899} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.283 {60567c2f6d954c17c7f335052ce05185} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.285 {f8d09b2f6d954c17ccf33505dff1a4ea} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.287 {f0fab02f6d954c17cdf33505438b2c80} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"

server2 terminal output:

$ go run server.go
2023-03-15 19:50:51.720 [DEBU] set default registry using file registry as no custom registry set
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]}
2023-03-15 19:50:51.722 [INFO] pid[89351]: grpc server started listening on [:53973]
2023-03-15 19:50:57.280 {b89a0d2f6d954c17c4f33505a046817c} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.282 {28bf732f6d954c17c6f33505adedff5f} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.283 {9876832f6d954c17c8f3350580ed535b} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.284 {684e8b2f6d954c17c9f33505d56e4b05} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.284 {c045912f6d954c17caf3350599006197} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"
2023-03-15 19:50:57.284 {500a972f6d954c17cbf33505252b0e01} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"

Client terminal output:

$ go run client.go
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}]
2023-03-15 19:50:57.281 [DEBU] {b89a0d2f6d954c17c4f33505a046817c} Response: Hello World
2023-03-15 19:50:57.282 [DEBU] {7025612f6d954c17c5f335051bf10899} Response: Hello World
2023-03-15 19:50:57.282 [DEBU] {28bf732f6d954c17c6f33505adedff5f} Response: Hello World
2023-03-15 19:50:57.283 [DEBU] {60567c2f6d954c17c7f335052ce05185} Response: Hello World
2023-03-15 19:50:57.283 [DEBU] {9876832f6d954c17c8f3350580ed535b} Response: Hello World
2023-03-15 19:50:57.284 [DEBU] {684e8b2f6d954c17c9f33505d56e4b05} Response: Hello World
2023-03-15 19:50:57.284 [DEBU] {c045912f6d954c17caf3350599006197} Response: Hello World
2023-03-15 19:50:57.285 [DEBU] {500a972f6d954c17cbf33505252b0e01} Response: Hello World
2023-03-15 19:50:57.286 [DEBU] {f8d09b2f6d954c17ccf33505dff1a4ea} Response: Hello World
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

package main

import (
"github.com/gogf/gf/contrib/registry/etcd/v2"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/resolver/controller"
)

func main() {
grpcx.Resolver.Register(etcd.New("127.0.0.1:2379"))

s := grpcx.Server.New()
controller.Register(s)
s.Run()
}

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

package main

import (
"github.com/gogf/gf/contrib/registry/etcd/v2"
"github.com/gogf/gf/contrib/rpc/grpcx/v2"
"github.com/gogf/gf/example/rpc/grpcx/resolver/protobuf"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)

func main() {
grpcx.Resolver.Register(etcd.New("127.0.0.1:2379"))

var (
ctx = gctx.New()
conn = grpcx.Client.MustNewGrpcClientConn("demo")
client = protobuf.NewGreeterClient(conn)
)
res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"})
if err != nil {
g.Log().Error(ctx, err)
return
}
g.Log().Debug(ctx, "Response:", res.Message)
}

Start etcd:

$ etcd
{"level":"info","ts":"2023-03-15T20:02:50.966+0800","caller":"etcdmain/etcd.go:73","msg":"Running: ","args":["etcd"]}
{"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"}
{"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"}
{"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"}
{"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"]}
{"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"]}
...

Run server.go:

$ go run server.go
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]}
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"
2023-03-15 20:02:19.516 [INFO] pid[92040]: grpc server started listening on [:55066]
2023-03-15 20:02:29.310 {88c4c04e0e964c17dddec53b1adb54f7} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World"

Run client.go:

$ go run client.go
2023-03-15 20:02:29.297 [DEBU] Watch key "/service/default/default/demo/latest/"
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}]
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}]
2023-03-15 20:02:29.310 [DEBU] {88c4c04e0e964c17dddec53b1adb54f7} Response: Hello World