go-micro client 客户端

  go-micro 支持很多通信协议:http、tcp、grpc等,支持的编码方式也很多有json、protobuf、bytes、jsonrpc等。也可以根据自己的需要实现通信协议和编码方式。go-micro 默认的通信协议是http,默认的编码方式是protobuf。

  主要代码定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 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,减少生成和销毁的开销,不够用的话就另外生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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,
    }
}

  

  调用接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
}

  

posted @   林锅  阅读(595)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示