$30 off During Our Annual Pro Sale. View Details »

Rpc on Steroids with Go and grpc

Rpc on Steroids with Go and grpc

Inter-process communication faces many challenges in nowadays distributed systems. Service discovery, retries, and such need to be addressed in order to cover all the needs of a distributed scenario. Grpc provides a valid alternative to rest calls, essentially being an easy to use cross language rpc framework. On top of that, Go has proved itself to be a suitable language for those kind of applications. What it is less know is the fact that it is really easy to extend the Grpc Go implementation with features such as authentication, service discovery, tracing, retry mechanisms and more.

During this talk I will explain how Gprc works, how to use it, and then will show how to build powerful interprocess communication by leveraging Grpc full potential. The audience will walk away with a solid understanding of how Grpc internally works and several useful techniques for taking full advantage of its powerful but lesser known features.

Federico Paolinelli

October 21, 2019
Tweet

More Decks by Federico Paolinelli

Other Decks in Technology

Transcript

  1. About me • Red Hatter • Doing distributed systems for

    more than 10 years • KubeVirt networking team • Passionate about open source @fedepaol [email protected] [email protected]
  2. Examples Examples of all the concepts described during this talk

    can be found at github.com/fedepaol/talks/tree/master/grpc/examples
  3. Examples Examples of all the concepts described during this talk

    can be found at github.com/fedepaol/talks/tree/master/grpc/examples NO WARRANTY!
  4. What options do we have? • Rest • Other rpc

    frameworks (thrift, twirp, avro) • Websockets • Framed tcp
  5. Grpc & Http2 Http2 allows multiplexing of requests (streams) over

    a single tcp connection Grpc introduces channels, rpcs and messages
  6. Protocol Buffers message Movie { int32 mid = 1; string

    movie_name = 2; string movie_description = 3; string movie_style = 4; } Type Definitions
  7. Protocol Buffers message Movie { int32 mid = 1; string

    movie_name = 2; string movie_description = 3; string movie_style = 4; } service MoviesService { rpc GetMovie(MovieID) returns (Movie); rpc QueryMovie(MovieQueryParams) returns (stream Movie); } Type Definitions Interface Definitions
  8. Features • Code Generation • Binary Serialization • Type Safe

    • New fields can be added without breaking the compatibility
  9. Generated Code type Movie struct { Mid int32 MovieName string

    MovieDescription string MovieStyle string } Data Structures
  10. Generated Code func NewMoviesServiceClient(cc *grpc.ClientConn) MoviesServiceClient func (c *moviesServiceClient) GetMovie(ctx

    context.Context, in *MovieID, opts ...grpc.CallOption) (*Movie, error) Client Side type Movie struct { Mid int32 MovieName string MovieDescription string MovieStyle string } Data Structures
  11. Generated Code func NewMoviesServiceClient(cc *grpc.ClientConn) MoviesServiceClient func (c *moviesServiceClient) GetMovie(ctx

    context.Context, in *MovieID, opts ...grpc.CallOption) (*Movie, error) Client Side type MoviesServiceServer interface { GetMovie(context.Context, *MovieID) (*Movie, error) QueryMovie(*MovieQueryParams, MoviesService_QueryMovieServer) error } Server Side type Movie struct { Mid int32 MovieName string MovieDescription string MovieStyle string } Data Structures
  12. Establishing a connection conn, _ := grpc.Dial(serverAddr, grpc.WithInsecure()) client :=

    movie.NewMoviesServiceClient(conn) Client Side lis, _ := net.Listen("tcp", port) server := grpc.NewServer() movie.RegisterMoviesServiceServer(server, &MovieServer{}) server.Serve(lis) Server Side
  13. Keep Alive Not enabled by the fault, need to use

    google.golang.org/grpc/keepalive • Useful to detect disconnections where the endpoint hangs or dies • Relies on http2 ping frames Nice side effects: • Keeps a pool of healthy connections • Avoids killing idle connections
  14. Server Side Implementation func (b *MovieServer) GetMovie(ctx context.Context, id *movie.MovieID)

    (*movie.Movie, error) { movieID := id.GetMid() movie, ok := b.movies[int(movieID)] if !ok { // from google.golang.org/grpc/status return nil, status.Error(codes.NotFound, "movie not found") } return &movie, nil }
  15. Server Side Implementation (Streaming) func (b *MovieServer) QueryMovie(p *movie.MovieQueryParams, s

    movie.MoviesService_QueryMovieServer) error { for _, b := range b.movies { if strings.Contains(b.MovieDescription, p.GetQuery()) { s.Send(&b) } } return nil }
  16. Name Resolution You have a name (URI) and you want

    one (or more) addresses back to connect to. That’s done by resolver.Resolver Available out of the box: • Passthrough • Dns • Manual
  17. Build your own Resolver Implement & register a resolver.Builder •

    Build() method to return the resolver for the given target • Scheme() to declare what scheme we are registering against Inside the resolver we can then pass the list of endpoints to the clientconnection: cc.UpdateState(resolver.State{Addresses: addrs}) A nice resolver example is the etcd name resolver
  18. Load Balancing In grpc we have persistent connection. The balancing

    is done per call, while maintaining a pool of connections to choose from. Built in: • Pick First • Round Robin • Grpclb (external)
  19. External Load Balancing The external load balancer is a GRPC

    endpoint itself: service LoadBalancer { // Bidirectional rpc to get a list of servers. rpc BalanceLoad(stream LoadBalanceRequest) returns (stream LoadBalanceResponse); }
  20. Error Handling (status code) func (b *MovieServer) GetMovie(ctx context.Context, id

    *movie.MovieID) (*movie.Movie, error) { /* ... */ return nil, status.Error(codes.NotFound, "movie not found") } Server Side movie, err := client.GetMovie(ctx, id) if err != nil { st := status.Convert(err) if st != nil && st.Code() == codes.NotFound { // handle missing movie } } Client Side
  21. Error Handling (strongly typed errors) movieErr := movie.MovieError{ Reason: movie.MovieErrorCode_MOVIE_NOT_FOUND,

    Message: fmt.Sprintf("Could not find movie %v", *id), } st, _ := status.New(codes.NotFound, "not found").WithDetails(&movieErr) return nil, st.Err() Server Side res, err := client.GetMovie(ctx, id) if err != nil { st := status.Convert(err) for _, detail := range st.Details() { switch t := detail.(type) { case *movie.MovieError: // handle t payload } } } Client Side
  22. Grpc & Context flickr.com/photos/magnopere/120924833 The context package makes it easy

    to pass request-scoped values, cancellation signals and deadline across API boundaries to all the goroutines involved in handling a request.
  23. Values Propagation Context values are not propagated, but godoc.org/google.golang.org/grpc/metadata is.

    md := metadata.Pairs("key", "value") ctx := metadata.NewOutgoingContext(context.Background(), md) Client Side
  24. Values Propagation Context values are not propagated, but godoc.org/google.golang.org/grpc/metadata is.

    md := metadata.Pairs("key", "value") ctx := metadata.NewOutgoingContext(context.Background(), md) Client Side Server Side meta, ok := metadata.FromIncomingContext(ctx) value : = meta.Get("key")[0]
  25. Interceptors Same as http middleware. Two types: • Unary interceptors

    • Stream interceptors Both client side / server side
  26. Retry Unary Interceptor func RetryInterceptor(ctx context.Context, method string, req, reply

    interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { var lastErr error for attempt := uint(0); attempt < 10; attempt++ { lastErr = invoker(ctx, method, req, reply, cc, opts...) if lastErr == nil { return nil } } return lastErr }
  27. Rate Limiter Interceptor func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,

    handler grpc.UnaryHandler) (interface{}, error) { if Limit() { return nil, status.Errorf(codes.ResourceExhausted, "%s is rejected by retry limiter.", info.FullMethod) } return handler(ctx, req) }
  28. Authentication (client side) func jwtInterceptor (ctx context.Context, method string, req

    interface{}, reply interface{}, cc *grpc.ClientConn,invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { authCtx := metadata.AppendToOutgoingContext(ctx, "jwt", "bearer "+jwt.token) err := invoker(authCtx, method, req, reply, cc, opts...) if err != nil && status.Code(err) == codes.Unauthenticated { // handle unauthenticated } if err != nil { return err } } }
  29. Authentication (server side) func authInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo,

    handler grpc.UnaryHandler) (interface{}, error) { token := fetchTokenFromContext(ctx) // handle token validation return handler(ctx, req) }
  30. How to set interceptors conn, err := grpc.Dial(*addr, grpc.WithUnaryInterceptor(unaryInterceptor)) Client

    Side s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor)) Server Side
  31. Interceptor Pitfalls • Once set, an interceptor is called for

    all the methods • We need to find a way to disable (some) interceptors on method basis
  32. Other Interceptor Goodies • Tracing (opentracing, opencensus) • Logging •

    Monitoring • More at github.com/grpc-ecosystem/go-grpc-middleware
  33. Grpc Gateway service YourService { rpc Echo(StringMessage) returns (StringMessage) {

    option (google.api.http) = { post: "/v1/example/echo" body: "*" }; }
  34. Reflection Opt-in feature: As seen in: • grpcurl • grpcui

    s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) reflection.Register(s)