Golang RPC實踐

摘要: 整體上來講,HTTP每次請求比較浪費資源的。雖然HTTP也是走在TCP上面的,可是HTTP請求本身添加了不少本身的信息,所以會消耗帶寬資源。因此一些公司就是用RPC做爲內部應用的通訊協議。原文bash

若是你對Go也感興趣, 能夠關注個人公衆號: GoGuider網絡

RPC

RPC(Remote Procedure Call,遠程過程調用)是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡細節的應用程序通訊協議。RPC協議構建於TCP或UDP,或者是HTTP上。異步

在Go中,標準庫提供的net/rpc包實現了RPC協議須要的相關細節,開發者能夠很方便的使用該包編寫RPC的服務端和客戶端程序。async

從上圖看, RPC自己就是一個client-server模型。tcp

下面列舉一個實例代碼, 來了解RPC調用過程ide

server.go

package main

import (
	"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 main

import (
	"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.go

go run client.go

複製代碼

運行結果

Multiply:7*8=56
繼續等待....
繼續等待....
繼續等待....
7+8=15,出執行
複製代碼

調用過程解析

server端學習

  • rpc服務註冊了一個Math對象 公開方法供客戶端調用
  • 採用http協議做爲rpc調用的載體, 處理請求

client端ui

  • 調用rpc服務端提供的方法以前,先與rpc服務端創建鏈接
  • 使用Call 方法調用遠程方法

延伸

其實細心的朋友會注意到client.go 裏面有client.Call 和 client.Go 調用;spa

查看源碼能夠看到client.Call 底層就是調用的client.Go.net

// 部分源碼:

/ 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 } 複製代碼

參考文章

相關文章
相關標籤/搜索