go-micro client 客户端
go-micro 支持很多通信协议:http、tcp、grpc等,支持的编码方式也很多有json、protobuf、bytes、jsonrpc等。也可以根据自己的需要实现通信协议和编码方式。go-micro 默认的通信协议是http,默认的编码方式是protobuf。
主要代码定义如下:
// Client is the interface used to make requests to services. // It supports Request/Response via Transport and Publishing via the Broker. // It also supports bidiectional streaming of requests. type Client interface { Init(...Option) error Options() Options NewMessage(topic string, msg interface{}, opts ...MessageOption) Message NewRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) Publish(ctx context.Context, msg Message, opts ...PublishOption) error String() string } // Message is the interface for publishing asynchronously type Message interface { Topic() string Payload() interface{} ContentType() string } // Request is the interface for a synchronous request used by Call or Stream type Request interface { Service() string Method() string ContentType() string Request() interface{} // indicates whether the request will be a streaming one rather than unary Stream() bool } // Stream is the inteface for a bidirectional synchronous stream type Stream interface { Context() context.Context Request() Request Send(interface{}) error Recv(interface{}) error Error() error Close() error } // Option used by the Client type Option func(*Options) // CallOption used by Call or Stream type CallOption func(*CallOptions) // PublishOption used by Publish type PublishOption func(*PublishOptions) // MessageOption used by NewMessage type MessageOption func(*MessageOptions) // RequestOption used by NewRequest type RequestOption func(*RequestOptions)
client的连接使用了pool,减少生成和销毁的开销,不够用的话就另外生成:
func newPool(size int, ttl time.Duration) *pool { return &pool{ size: size, ttl: int64(ttl.Seconds()), conns: make(map[string][]*poolConn), } } // NoOp the Close since we manage it func (p *poolConn) Close() error { return nil } func (p *pool) getConn(addr string, tr transport.Transport, opts ...transport.DialOption) (*poolConn, error) { p.Lock() conns := p.conns[addr] now := time.Now().Unix() // while we have conns check age and then return one // otherwise we'll create a new conn for len(conns) > 0 { conn := conns[len(conns)-1] conns = conns[:len(conns)-1] p.conns[addr] = conns // if conn is old kill it and move on if d := now - conn.created; d > p.ttl { conn.Client.Close() continue } // we got a good conn, lets unlock and return it p.Unlock() return conn, nil } p.Unlock() // create new conn c, err := tr.Dial(addr, opts...) if err != nil { return nil, err } return &poolConn{c, time.Now().Unix()}, nil } func (p *pool) release(addr string, conn *poolConn, err error) { // don't store the conn if it has errored if err != nil { conn.Client.Close() return } // otherwise put it back for reuse p.Lock() conns := p.conns[addr] if len(conns) >= p.size { p.Unlock() conn.Client.Close() return } p.conns[addr] = append(conns, conn) p.Unlock() }
封装request:
type rpcRequest struct { service string method string contentType string request interface{} opts RequestOptions } func newRequest(service, method string, request interface{}, contentType string, reqOpts ...RequestOption) Request { var opts RequestOptions for _, o := range reqOpts { o(&opts) } // set the content-type specified if len(opts.ContentType) > 0 { contentType = opts.ContentType } return &rpcRequest{ service: service, method: method, request: request, contentType: contentType, opts: opts, } }
调用接口:
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error { // make a copy of call opts callOpts := r.opts.CallOptions for _, opt := range opts { opt(&callOpts) } next, err := r.next(request, callOpts) if err != nil { return err } // check if we already have a deadline d, ok := ctx.Deadline() if !ok { // no deadline so we create a new one ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout) } else { // got a deadline so no need to setup context // but we need to set the timeout we pass along opt := WithRequestTimeout(d.Sub(time.Now())) opt(&callOpts) } // should we noop right here? select { case <-ctx.Done(): return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408) default: } // make copy of call method rcall := r.call // wrap the call in reverse for i := len(callOpts.CallWrappers); i > 0; i-- { rcall = callOpts.CallWrappers[i-1](rcall) } // return errors.New("go.micro.client", "request timeout", 408) call := func(i int) error { // call backoff first. Someone may want an initial start delay t, err := callOpts.Backoff(ctx, request, i) if err != nil { return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error()) } // only sleep if greater than 0 if t.Seconds() > 0 { time.Sleep(t) } // select next node node, err := next() if err != nil && err == selector.ErrNotFound { return errors.NotFound("go.micro.client", "service %s: %v", request.Service(), err.Error()) } else if err != nil { return errors.InternalServerError("go.micro.client", "error getting next %s node: %v", request.Service(), err.Error()) } // set the address address := node.Address if node.Port > 0 { address = fmt.Sprintf("%s:%d", address, node.Port) } // make the call err = rcall(ctx, address, request, response, callOpts) r.opts.Selector.Mark(request.Service(), node, err) return err } ch := make(chan error, callOpts.Retries) var gerr error for i := 0; i <= callOpts.Retries; i++ { go func(i int) { ch <- call(i) }(i) select { case <-ctx.Done(): return errors.New("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()), 408) case err := <-ch: // if the call succeeded lets bail early if err == nil { return nil } retry, rerr := callOpts.Retry(ctx, request, i, err) if rerr != nil { return rerr } if !retry { return err } gerr = err } } return gerr }
you are the best!