摘要: 总体上来说,HTTP每次请求比较浪费资源的。虽然HTTP也是走在TCP上面的,但是HTTP请求自己添加了很多自己的信息,因此会消耗带宽资源。所以一些公司就是用RPC作为内部应用的通信协议。原文

如果你对Go也感兴趣, 可以关注我的公众号: GoGuider

RPC

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。RPC协议构建于TCP或UDP,或者是HTTP上。

在Go中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。

从上图看, RPC本身就是一个client-server模型。

下面列举一个实例代码, 来了解RPC调用过程

server.go

package mainimport (    "fmt"    "log"    "net"    "net/http"    "net/rpc"    "os"    "time")type Args struct {    A, B int}type Math int//计算乘积func (t *Math) Multiply(args *Args, reply *int) error {    time.Sleep(time.Second * 3) //睡1秒,同步调用会等待,异步会先往下执行    *reply = args.A * args.B    fmt.Println("Multiply")    return nil}//计算和func (t *Math) Sum(args *Args, reply *int) error {    time.Sleep(time.Second * 3)    *reply = args.A + args.B    fmt.Println("Sum")    return nil}func main() {    //创建对象    math := new(Math)    //rpc服务注册了一个Math对象 公开方法供客户端调用    rpc.Register(math)    //指定rpc的传输协议 这里采用http协议作为rpc调用的载体 也可以用rpc.ServeConn处理单个连接请求    rpc.HandleHTTP()    l, e := net.Listen("tcp", ":1234")    if e != nil {        log.Fatal("listen error", e)    }    go http.Serve(l, nil)    os.Stdin.Read(make([]byte, 1))}

client.go

package mainimport (    "fmt"    "log"    "net/rpc"    "time")type Args struct {    A, B int}func main() {    //调用rpc服务端提供的方法之前,先与rpc服务端建立连接    client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")    if err != nil {        log.Fatal("dialHttp error", err)        return    }    //同步调用服务端提供的方法    args := &Args{7, 8}    var reply int    //可以查看源码 其实Call同步调用是用异步调用实现的。后续再详细学习    err = client.Call("Math.Multiply", args, &reply) //这里会阻塞三秒    if err != nil {        log.Fatal("call Math.Multiply error", err)    }    fmt.Printf("Multiply:%d*%d=%d\n", args.A, args.B, reply)    //异步调用    var sum int    divCall := client.Go("Math.Sum", args, &sum, nil)    //使用select模型监听通道有数据时执行,否则执行后续程序    for {        select {        case <-divCall.Done:            fmt.Printf("%d+%d是%d, 退出执行!", args.A, args.B, sum)            return        default:            fmt.Println("继续等待....")            time.Sleep(time.Second * 1)        }    }}

运行命令

go run server.gogo run client.go

运行结果

Multiply:7*8=56继续等待....继续等待....继续等待....7+8=15,出执行

调用过程解析

server端

  • rpc服务注册了一个Math对象 公开方法供客户端调用
  • 采用http协议作为rpc调用的载体, 处理请求

client端

  • 调用rpc服务端提供的方法之前,先与rpc服务端建立连接
  • 使用Call 方法调用远程方法

延伸

其实细心的朋友会注意到client.go 里面有client.Call 和 client.Go 调用;

查看源码可以看到client.Call 底层就是调用的client.Go

// 部分源码:/ Go invokes the function asynchronously. It returns the Call structure representing// the invocation. The done channel will signal when the call is complete by returning// the same Call object. If done is nil, Go will allocate a new channel.// If non-nil, done must be buffered or Go will deliberately crash.func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {    call := new(Call)    call.ServiceMethod = serviceMethod    call.Args = args    call.Reply = reply    if done == nil {        done = make(chan *Call, 10) // buffered.    } else {        // If caller passes done != nil, it must arrange that        // done has enough buffer for the number of simultaneous        // RPCs that will be using that channel. If the channel        // is totally unbuffered, it's best not to run at all.        if cap(done) == 0 {            log.Panic("rpc: done channel is unbuffered")        }    }    call.Done = done    client.send(call)    return call}// Call invokes the named function, waits for it to complete, and returns its error status.func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {    call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done    return call.Error}

参考文章

  • gRPC官方文档
  • gRPC中文文档