Client
是一个用于请求服务的接口,支持通过Transport
进行Request/Response
,以及通过Broker
进行发布订阅的功能。同时也支持全双工的steam
请求。
type Client interface {
Init(...Option) error
Options() Options
NewMessage(topic string, msg interface{}, opts ...MessageOption) Message
NewRequest(service, endpoint 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
}
接下来我们就一起看看默认的实现
var (
// DefaultClient is a default client to use out of the box
DefaultClient Client = newRpcClient()
// DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryOnError
// DefaultRetries is the default number of times a request is tried
DefaultRetries = 1
// DefaultRequestTimeout is the default request timeout
DefaultRequestTimeout = time.Second * 5
// DefaultPoolSize sets the connection pool size
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl
DefaultPoolTTL = time.Minute
// NewClient returns a new client
NewClient func(...Option) Client = newRpcClient
)
使用newRpcClient
实例化一个rpcClient
实例
type rpcClient struct {
seq uint64
once atomic.Value
opts Options
pool pool.Pool
}
func newRpcClient(opt ...Option) Client {
opts := NewOptions(opt...)
p := pool.NewPool(
pool.Size(opts.PoolSize),
pool.TTL(opts.PoolTTL),
pool.Transport(opts.Transport),
)
rc := &rpcClient{
opts: opts,
pool: p,
seq: 0,
}
rc.once.Store(false)
c := Client(rc)
// wrap in reverse
for i := len(opts.Wrappers); i > 0; i-- {
c = opts.Wrappers[i-1](c)
}
return c
}
在rpcClient
实例中,我们一起来看看他是如何实现的同步请求的,由于该方法内容较多,我们摘选部分重点代码进行阅读
- 首先复制一份
CallOptions
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
2.负载均衡一个发起请求调用的节点。
next, err := r.next(request, callOpts)
if err != nil {
return err
}
3.检查是否进行了超时请求配置
d, ok := ctx.Deadline()
if !ok {
// no deadline so we create a new one
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
defer cancel()
} else {
// got a deadline so no need to setup context
// but we need to set the timeout we pass along
opt := WithRequestTimeout(time.Until(d))
opt(&callOpts)
}
4,复制一份call方法,并以逆序的方式包装上的调用中间件。
rcall := r.call
for i := len(callOpts.CallWrappers); i > 0; i-- {
rcall = callOpts.CallWrappers[i-1](rcall)
}
5.定义一个内部的函数变量在请求重试的时候进行调用,该函数变量进行真正意义上的方法调用就是我们上个步骤中的call方法拷贝。
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()
service := request.Service()
if err != nil {
if err == selector.ErrNotFound {
return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
}
// make the call
err = rcall(ctx, node, request, response, callOpts)
r.opts.Selector.Mark(service, node, err)
return err
}
如果我们请求service
使用的是代理的话,就不会进行重试retires
置位0.
在重试的逻辑中,
我们首先进行第一次方法调用,结果返回值放到chan error
类型的管道中,
如果我们从chan error
取出的err为空,说明调用正常,退出Call
方法,并返回nil
,否则进行重新调用rcall
,重试次数限定为我们opts
中的Retries
配置。
在第四步中,进行了的call
方法复制时,复制的是rpcClient
实例上的call
方法,具体我们来看看这个方法的实现逻辑,
codec := newRpcCodec(msg, c, cf, "")
rsp := &rpcResponse{
socket: c,
codec: codec,
}
构建一个rpcResponse
,并使用该rsp
进行实例化一个rpcStream
stream := &rpcStream{
id: fmt.Sprintf("%v", seq),
context: ctx,
request: req,
response: rsp,
codec: codec,
closed: make(chan bool),
release: func(err error) { r.pool.Release(c, err) },
sendEOS: false,
}
// close the stream on exiting this function
defer stream.Close()
新起一个协程,用于全双工的发送请求以及接收响应
go func() {
defer func() {
if r := recover(); r != nil {
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
}
}()
// send request
if err := stream.Send(req.Body()); err != nil {
ch <- err
return
}
// recv request
if err := stream.Recv(resp); err != nil {
ch <- err
return
}
// success
ch <- nil
}()
最后根据结果来控制方法的异常处理
var grr error
select {
case err := <-ch:
return err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
// set the stream error
if grr != nil {
stream.Lock()
stream.err = grr
stream.Unlock()
return grr
}
以上就是整个请求的处理过程。我们通过一个示例来看看如何实现一个client
func call(i int, c client.Client) {
// Create new request to service go.micro.srv.example, method Example.Call
req := c.NewRequest("go.micro.srv.example", "Example.Call", &example.Request{
Name: "John",
})
// create context with metadata
ctx := metadata.NewContext(context.Background(), map[string]string{
"X-User-Id": "john",
"X-From-Id": "script",
})
rsp := &example.Response{}
// Call service
if err := c.Call(ctx, req, rsp); err != nil {
fmt.Println("call err: ", err, rsp)
return
}
fmt.Println("Call:", i, "rsp:", rsp.Msg)
}
func main() {
// 这个是使用micro框架启动的一个服务
service := micro.NewService()
service.Init()
fmt.Println("\n--- Call example ---")
for i := 0; i < 10; i++ {
call(i, service.Client())
}
}