Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Yegor Myskin – HOW WE MAKE GRPC

Yegor Myskin – HOW WE MAKE GRPC

Avatar for GolangMoscow

GolangMoscow

August 29, 2019
Tweet

More Decks by GolangMoscow

Other Decks in Programming

Transcript

  1. Go в PropellerAds – 60+ сервисов написанных на Go –

    5+ лет в продакшене – 600 000 запросов на подбор рекламы в секунду(в пиках)
  2. service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); } message

    HelloRequest { string greeting = 1; string name = 2; } message HelloResponse { string reply = 1; } type HelloServiceServer interface { SayHello(context.Context, *HelloRequest) (*HelloResponse, error) } type HelloServiceClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloR error) } type HelloRequest struct { Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omite Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` } type HelloResponse struct { Reply string `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"` }
  3. Server-side streaming func (s *Server) GetAudiences( req *api.GetAudiencesRequest, stream api.StorageService_GetAudiencesServer,

    ) error { rows, err := s.db.QueryContext(stream.Context(), query) if err != nil { return err } defer rows.Close() for rows.Next() { var audience api.Audience err := rows.ScanStruct(&audience) if err != nil { return err } if err := stream.Send(&audience); err != nil { return err } }
  4. Vendoring общего репозитория с proto – Невозможно расширять методами proto-сущности

    – Сложно вносить изменения в клиент и сервер – Не ясна ответственность между командами
  5. Vendoring репозитория сервера – Стало легче вносить изменения в сервер

    – Разработчик клиента все также ждет релиза сервера
  6. Vendoring через копирование proto – Полная свобода разрабатывать клиент и

    сервер параллельно – Любой пользователь может расширять сущности своими методами
  7. Makefile: proto: docker run --rm protoc -it -v `pwd`:/project \

    protoc --proto_path=./api/ \ --gogofaster_out=plugins=grpc,paths=source_relative:./api \ ./api /*.proto ... > make proto
  8. Makefile: proto: docker run --rm protoc -it -v `pwd`:/project \

    protoc --proto_path=./api/ \ -I ${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/ third_party/googleapis \ --grpc-gateway_out=logtostderr=true,paths=source_relative:./api ./api /*.proto ... > make proto
  9. Пример кода на Go ctx, span := trace.StartSpan(parentCtx, "getUser") user,

    err := GetUser(id) if span.IsRecordingEvents() { encodedResponse, _ := json.Marshal(user)
 span.AddAttributes(
 trace.StringAttribute("response", string(encodedResponse)) ) if err != nil { span.AddAttributes(trace.StringAttribute("error", err.Error())) } } span.End()
  10. // Connection gets active connection from the pool. func (p

    *Pool) Connection() manager.Connection { b := p.connections.Load().(*instance) // atomic.Value cnt := len(*b) if cnt == 0 { return nil } next := atomic.AddInt64(&p.cntr, 1) idx := next % int64(cnt) return (*b)[idx].getCurrent() // gets grpc connection to the instance }
  11. // Connection gets active connection from the pool. func (p

    *Pool) Connection() manager.Connection { b := p.connections.Load().(*instance) // atomic.Value cnt := len(*b) if cnt == 0 { return nil } next := atomic.AddInt64(&p.cntr, 1) idx := next % int64(cnt) return (*b)[idx].getNext() // gets one of grpc connections to the instance } func (i *instance) getNext() manager.Connection { next := atomic.AddInt64(&b.cntr, 1) idx := next % int64(b.size) return i.connections[idx] }
  12. Bond tuning /etc/sysconfig/network-scripts/ifcfg-bond0: BONDING_OPTS="mode=802.3ad miimon=100 xmit_hash_policy=layer2" (source MAC XOR destination

    MAC) % slave count BONDING_OPTS="mode=802.3ad miimon=100 xmit_hash_policy=layer3+4" ((source port XOR dest port) XOR ((source IP XOR dest IP) AND 0xffff) % slave count
  13. Snappy compressing // Compressor is used for compressing and decompressing

    when sending or // receiving messages. type Compressor interface { Compress(w io.Writer) (io.WriteCloser, error) Decompress(r io.Reader) (io.Reader, error) Name() string } encoding.RegisterCompressor(newCompressor())
  14. message User { uint64 id = 1; string login =

    2; bool active = 3; string about = 4; int64 last_login = 5; // ... } message User { uint64 id = 1; bool active = 3; } Server Client
  15. type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler)

    (resp interface{}, err error) type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error Interceptors: funcs
  16. Interceptors: protobuf generation message User { uint64 id = 1;

    bool active = 3; } type User struct { Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Active bool `protobuf:"varint,3,opt,name=active,proto3" json:"active,omitempty"` }
  17. Interceptors: create fields scheme message User { uint64 id =

    1; bool active = 3; } 1-1 1-3 Fields: BASE64(HASH(Fields)) Session: Metadata: Proto:
  18. Interceptors: send metadata func withOutgoingMetadata(ctx context.Context) metadata.MD { md, ok

    := metadata.FromOutgoingContext(ctx) if ok { md = md.Copy() } else { md = metadata.New(map[string]string{}) } return md } // ... somewhere in interceptor md := withOutgoingMetadata(ctx) md.Set(headerFieldsKey, cl.Fields ...)
  19. Interceptors: receive err code import "google.golang.org/grpc/status" // inside interceptor err

    := invoker(metadata.NewOutgoingContext(ctx, md), method, req, reply, cc, opts ...) if err != nil { code, _ := status.FromError(err) if code.Code() == codes.Unauthenticated { // retry again } }
  20. Итого – Мониторинг сети с GRPC особенно полезен – Если

    выедается интерфейс - используем неск. коннектов – Использование компрессии может выиграть даже немного CPU – Один интерсептор может сэкономить много денег :)