Build microservice with gRPC in Golang

David Chou

We are Umbo Computer Vision

We build autonomous video security system

Cyberlink

TrendMicro

Umbo CV

Google I/O Extend

Golang Taipei

 

A high performance, open source, general purpose standards-based, feature-rich RPC framework.

O(10^10) RPCs per second at Google

gRPC: Motivation

  • Google has its internal RPC called Stubby
  • All applications and systems built using RPCs
  • Over 10^10 RPCs per second
  • Tightly couple with internal infrastructure
  • Not suitable for open source community

gRPC: Status

  • A high performance, universal RPC framework

  • Google open sourced it in Feb 2015

  • Being 1.0 in Aug 2016
  • Latest version: v1.3.2
  • Already been adpoted in
    • CoreOS, Netflix, Square, Cisco, Juniper

gRPC: Features

  • Bi-directional streaming over HTTP/2
  • Support multi-language, multi-platform
  • Simple service definition framework

Bi-directional streaming over HTTP/2

  • Single TCP connection for each client-server pair
  • Support bi-directional streaming

Support multi-language, multi-platform

  • Use protoc as the code generator
  • Native implementations in C/C++, Java, and Go
  • C stack wrapped by C#, Node, ObjC, Python, Ruby, PHP
  • Platforms supported: Linux, MacOS, Windows, Android, iOS
  • Currently, no browser side support. #8682

Simple service definition framework

  • Use Protocol Buffer IDL

  • Service definition

    • Generate server and client stub code

  • Message definition

    • Binary format

    • Much better performace than json

syntax = "proto3";
package pb;

message EchoMessage {
    string msg = 1;
}

service EchoService {
    rpc Echo(EchoMessage) returns (EchoMessage);
}

Protobuf IDL: EchoService


BenchmarkToJSON_1000_Director-2            500  2512808 ns/op  560427 B/op  9682 allocs/op
BenchmarkToPB_1000_Director-2             2000  1338410 ns/op  196743 B/op  3052 allocs/op

BenchmarkToJSONUnmarshal_1000_Director-4  1000  1279297 ns/op  403746 B/op  5144 allocs/op
BenchmarkToPBUnmarshal_1000_Director-4    3000   489585 ns/op  202256 B/op  5522 allocs/op

Json vs Protobuf performance

  • Marshalling: 2x faster, 65% less memory
  • Unmarshalling: 2.6x faster, 50% less memory

Example: DemoService

gRPC Service Definition


message Demo {
   string value = 1;
}

service DemoService {

  rpc SimpleRPC(Demo) returns (Demo);

  rpc ServerStream(Demo) returns (stream Demo);

  rpc ClientStream(stream Demo) returns (Demo);

  rpc Bidirectional(stream Demo) returns (stream Demo);

}

gRPC Service Definition

  • gRPC supprots 4 kinds of service methods
    • Unary RPC
    • Server streaming RPC
    • Client streaming RPC
    • Bidirectional RPC

Generate client and server code

  • Install relevant tools
  • Run protoc to generate client/server interfaces
    • $ protoc --go_out=plugins=grpc:. demo.proto
    • protoc will generate demo.pb.go

demo.pb.go


type Demo struct {
    Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
}

// Client API for DemoService service
type DemoServiceClient interface {
    SimpleRPC(ctx context.Context, in *Demo, opts ...grpc.CallOption) (*Demo, error)
    ServerStream(ctx context.Context, in *Demo, opts ...grpc.CallOption) (DemoService_ServerStreamClient, error)
    ClientStream(ctx context.Context, opts ...grpc.CallOption) (DemoService_ClientStreamClient, error)
    Bidirectional(ctx context.Context, opts ...grpc.CallOption) (DemoService_BidirectionalClient, error)
}

// Implementation of DemoService client
type demoServiceClient struct {
	cc *grpc.ClientConn
}

func NewDemoServiceClient(cc *grpc.ClientConn) DemoServiceClient {
    ...
}

// Server API for DemoService service
type DemoServiceServer interface {
    SimpleRPC(context.Context, *Demo) (*Demo, error)
    ServerStream(*Demo, DemoService_ServerStreamServer) error
    ClientStream(DemoService_ClientStreamServer) error
    Bidirectional(DemoService_BidirectionalServer) error
}

Go Server


type server struct{}
func (this *server) SimpleRPC(c context.Context, msg *Demo) (*Demo, error) {
	msg.Value = "Hello" + msg.Value
	return msg, nil
}
func (this *server) ServerStream(msg *Demo, stream DemoService_ServerStreamServer) error {
	for i := 0; i < 10; i++ {
		err := stream.Send(&Demo{value: "Hello"})
		if err != nil {
			fmt.Printf("err: %s\n", err.Error())
		}
		time.Sleep(100 * time.Millisecond)
	}
	return nil
}
func main() {
	lis, err := net.Listen("tcp", "localhost:12345")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	RegisterDemoServiceServer(grpcServer, &server{})
	grpcServer.Serve(lis)
}

Go Client: SimpleRPC

func main() {
    grpcAddr := "localhost:12345"
    conn, err := grpc.Dial(grpcAddr)
    if err != nil {
        log.Fatalf("Dial(%s) = %v", grpcAddr, err)
    }
    defer conn.Close()

    client := NewDemoServiceClient(conn)
    msg := &Demo{value: "World"}
    reply, err := client.SimpleRPC(context.Background(), msg)
    if err != nil {
        log.Fatalf("SimpleRPC(%#v) failed with %v", msg, err)
    }
    println("received message " + reply.Value)
}

Go Client: ServerStream

func main() {
    grpcAddr := "localhost:12345"
    conn, err := grpc.Dial(grpcAddr)
    if err != nil {
        log.Fatalf("Dial(%s) = %v", grpcAddr, err)
    }
    defer conn.Close()

    client := NewDemoServiceClient(conn)
    stream, err := client.ServerStream(context.TODO(), &Demo{
        Value: "Hello",
    })
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            break
        }
        fmt.Printf("%+v\n", msg)
    }
}

Let's go deeper

gRPC build-in authentication

Two types of Credentials objects

  • Channel credentials
  • Call credentials

Channel credentials

  • Credentials are attached to a Channel
  • Ex: SSL credentials

tls := credentials.NewClientTLSFromCert(nil, "")
conn, err := grpc.Dial(serverAddr, grpc.WithTransportCredentials(tls))

creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
server := grpc.NewServer(grpc.Creds(creds))
server.Serve()

TLS on server side

TLS on client side

Call credentials

  • Credentials are attached to each RPC call
  • Token based authentication, ex: OAuth, JWT
func unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    if err := authorize(ctx); err != nil {
        return err
    }
    return handler(ctx, req)
}

func authorize(ctx context.Context) error {
    if md, ok := metadata.FromContext(ctx); ok {
        if checkJWT(md["jwt"][0]) {
            return nil
        }

        return AccessDeniedErr
    }
    return EmptyMetadataErr
}

grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor))

TLS on server side

type JWTCreds struct {
    Token string
}

func (c *JWTCreds) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
    return map[string]string{
        "jwt": c.Token,
    }, nil
}

grpc.Dial(target, grpc.WithPerRPCCredentials(&JWTCreds{
    Token: "test-jwt-token",
}))

TLS on client side

gRPC load balance

Approaches to Load Balancing

  • Server-side LB
  • Client-side LB

Load Balancing in gRPC

Any Question?

Build microservice with gRPC in Golang

By Ting-Li Chou

Build microservice with gRPC in Golang

  • 608