最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文吃透Go的內(nèi)置RPC原理

 更新時(shí)間:2023年03月03日 08:43:56   作者:捉蟲大師  
這篇文章主要為大家詳細(xì)介紹了Go語言中內(nèi)置RPC的原理。說起?RPC?大家想到的一般是框架,Go?作為編程語言竟然還內(nèi)置了?RPC,著實(shí)讓我有些吃鯨,本文就來一起聊聊吧

從一個(gè) Demo 入手

為了快速進(jìn)入狀態(tài),我們先搞一個(gè) Demo,當(dāng)然這個(gè) Demo 是參考 Go 源碼 src/net/rpc/server.go,做了一丟丟的修改。

首先定義請求的入?yún)⒑统鰠ⅲ?/p>

package common

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

接著在定義一個(gè)對(duì)象,并給這個(gè)對(duì)象寫兩個(gè)方法

type Arith struct{}

func (t *Arith) Multiply(args *common.Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Divide(args *common.Args, quo *common.Quotient) error {
	if args.B == 0 {
		return errors.New("divide by zero")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

然后起一個(gè) RPC server:

func main() {
	arith := new(Arith)
	rpc.Register(arith)
	rpc.HandleHTTP()
	l, e := net.Listen("tcp", ":9876")
	if e != nil {
		panic(e)
	}

	go http.Serve(l, nil)

	var wg sync.WaitGroup
	wg.Add(1)
	wg.Wait()
}

最后初始化 RPC Client,并發(fā)起調(diào)用:

func main() {
	client, err := rpc.DialHTTP("tcp", "127.0.0.1:9876")
	if err != nil {
		panic(err)
	}

	args := common.Args{A: 7, B: 8}
	var reply int
  // 同步調(diào)用
	err = client.Call("Arith.Multiply", &args, &reply)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Call Arith: %d * %d = %d\n", args.A, args.B, reply)

  // 異步調(diào)用
	quotient := new(common.Quotient)
	divCall := client.Go("Arith.Divide", args, quotient, nil)
	replyCall := <-divCall.Done

	fmt.Printf("Go Divide: %d divide %d = %+v %+v\n", args.A, args.B, replyCall.Reply, quotient)
}

如果不出意外,RPC 調(diào)用成功

這 RPC 嗎

在剖析原理之前,我們先想想什么是 RPC?

RPC 是 Remote Procedure Call 的縮寫,一般翻譯為遠(yuǎn)程過程調(diào)用,不過我覺得這個(gè)翻譯有點(diǎn)難懂,啥叫過程?如果查一下 Procedure,就能發(fā)現(xiàn)它就是應(yīng)用程序的意思。

所以翻譯過來應(yīng)該是調(diào)用遠(yuǎn)程程序,說人話就是調(diào)用的方法不在本地,不能通過內(nèi)存尋址找到,只能通過遠(yuǎn)程通信來調(diào)用。

一般來說 RPC 框架存在的意義是讓你調(diào)用遠(yuǎn)程方法像調(diào)用本地方法一樣方便,也就是將復(fù)雜的編解碼、通信過程都封裝起來,讓代碼寫起來更簡單。

說到這里其實(shí)我想吐槽一下,網(wǎng)上經(jīng)常有文章說,既然有 Http,為什么還要有 RPC?如果你理解 RPC,我相信你不會(huì)問出這樣的問題,他們是兩個(gè)維度的東西,RPC 關(guān)注的是遠(yuǎn)程調(diào)用的封裝,Http 是一種協(xié)議,RPC 沒有規(guī)定通信協(xié)議,RPC 也可以使用 Http,這不矛盾。這種問法就好像在問既然有了蘋果手機(jī),為什么還要有中國移動(dòng)?

扯遠(yuǎn)了,我們回頭看一下上述的例子是否符合我們對(duì) RPC 的定義。

  • 首先是遠(yuǎn)程調(diào)用,我們是開了一個(gè) Server,監(jiān)聽了9876端口,然后 Client 與之通信,將這兩個(gè)程序部署在兩臺(tái)機(jī)器上,只要網(wǎng)絡(luò)是通的,照樣可以正常工作
  • 其次它符合調(diào)用遠(yuǎn)程方法像調(diào)用本地方法一樣方便,代碼中沒有處理編解碼,也沒有處理通信,只不過方法名以參數(shù)的形式傳入,和一般的 RPC 稍有不同,倒是很像 Dubbo 的泛化調(diào)用

綜上兩點(diǎn),這很 RPC。

下面我將用兩段內(nèi)容分別剖析 Go 內(nèi)置的 RPC Server 與 Client 的原理,來看看 Go 是如何實(shí)現(xiàn)一個(gè) RPC 的。

RPC Server 原理

注冊服務(wù)

這里的服務(wù)指的是一個(gè)具有公開方法的對(duì)象,比如上面 Demo 中的 Arith,只需要調(diào)用 Register 就能注冊

rpc.Register(arith)

注冊完成了以下動(dòng)作:

  • 利用反射獲取這個(gè)對(duì)象的類型、類名、值、以及公開方法
  • 將其包裝為 service 對(duì)象,并存在 server 的 serviceMap 中,serviceMap 的 key 默認(rèn)為類名,比如這里是Arith,也可以調(diào)用另一個(gè)注冊方法 RegisterName 來自定義名稱

注冊 Http Handle

這里你可能會(huì)問,為啥 RPC 要注冊 Http Handle。沒錯(cuò),Go 內(nèi)置的 RPC 通信是基于 Http 協(xié)議的,所以需要注冊。只需要一行代碼:

rpc.HandleHTTP()

它調(diào)用的是 Http 的 Handle 方法,也就是 HandleFunc 的底層實(shí)現(xiàn),這塊如果不清楚,可以看我之前的文章《一文讀懂 Go Http Server 原理》。

它注冊了兩個(gè)特殊的 Path:/_goRPC_ 和 /debug/rpc,其中有一個(gè)是 Debug 專用,當(dāng)然也可以自定義。

邏輯處理

注冊時(shí)傳入了 RPC 的 server 對(duì)象,這個(gè)對(duì)象必須實(shí)現(xiàn) Handler 的 ServeHTTP 接口,也就是 RPC 的處理邏輯入口在這個(gè) ServeHTTP 中:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

我們看 RPC Server 是如何實(shí)現(xiàn)這個(gè)接口的:

// ServeHTTP implements an http.Handler that answers RPC requests.
func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// ①
  if req.Method != "CONNECT" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusMethodNotAllowed)
		io.WriteString(w, "405 must CONNECT\n")
		return
	}
  // ②
	conn, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
		return
	}
  // ③
	io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
	// ④
	server.ServeConn(conn)
}

我對(duì)這段代碼標(biāo)了號(hào),逐一看:

①:限制了請求的 Method 必須是 CONNECT,如果不是則直接返回錯(cuò)誤,這么做是為什么?看下 Method 字段的注釋就恍然大悟:Go 的 Http Client 是發(fā)不出 CONNECT 的請求,也就是 RPC 的 Server 是沒辦法通過 Go 的 Http Client 訪問,限制必須得使用 RPC Client

type Request struct {
	// Method specifies the HTTP method (GET, POST, PUT, etc.).
	// For client requests, an empty string means GET.
	//
	// Go's HTTP client does not support sending a request with
	// the CONNECT method. See the documentation on Transport for
	// details.
	Method string
}

②:Hijack 是劫持 Http 的連接,劫持后需要手動(dòng)處理連接的關(guān)閉,這個(gè)操作是為了復(fù)用連接

③:先寫一行響應(yīng):

"HTTP/1.0 200 Connected to Go RPC \n\n"

④:開始真正的處理,這里段比較長,大致做了如下幾點(diǎn)事情:

準(zhǔn)備好數(shù)據(jù)、編解碼器

在一個(gè)大循環(huán)里處理每一個(gè)請求,處理流程是:

  • 讀出請求,包括要調(diào)用的service,參數(shù)等
  • 通過反射異步地調(diào)用對(duì)應(yīng)的方法
  • 將執(zhí)行結(jié)果編碼寫回連接

說到這里,代碼中有個(gè)對(duì)象池的設(shè)計(jì)挺巧妙,這里展開說說。

在高并發(fā)下,Server 端的 Request 對(duì)象和 Response 對(duì)象會(huì)頻繁地創(chuàng)建,這里用了隊(duì)列來實(shí)現(xiàn)了對(duì)象池。以 Request 對(duì)象池做個(gè)介紹,在 Server 對(duì)象中有一個(gè) Request 指針,Request 中有個(gè) next 指針

type Server struct {
	...
	freeReq    *Request
	..
}

type Request struct {
	ServiceMethod string 
	Seq           uint64
	next          *Request
}

在讀取請求時(shí)需要這個(gè)對(duì)象,如果池中沒有對(duì)象,則 new 一個(gè)出來,有的話就拿到,并將 Server 中的指針指向 next:

func (server *Server) getRequest() *Request {
	server.reqLock.Lock()
	req := server.freeReq
	if req == nil {
		req = new(Request)
	} else {
		server.freeReq = req.next
		*req = Request{}
	}
	server.reqLock.Unlock()
	return req
}

請求處理完成時(shí),釋放這個(gè)對(duì)象,插入到鏈表的頭部

func (server *Server) freeRequest(req *Request) {
	server.reqLock.Lock()
	req.next = server.freeReq
	server.freeReq = req
	server.reqLock.Unlock()
}

畫個(gè)圖整體感受下:

回到正題,Client 和 Server 之間只有一條連接,如果是異步執(zhí)行,怎么保證返回的數(shù)據(jù)是正確的呢?這里先不說,如果一次性說完了,下一節(jié)的 Client 就沒啥可說的了,你說是吧?

RPC Client 原理

Client 使用第一步是 New 一個(gè) Client 對(duì)象,在這一步,它偷偷起了一個(gè)協(xié)程,干什么呢?用來讀取 Server 端的返回,這也是 Go 慣用的伎倆。

每一次 Client 的調(diào)用都被封裝為一個(gè) Call 對(duì)象,包含了調(diào)用的方法、參數(shù)、響應(yīng)、錯(cuò)誤、是否完成。

同時(shí) Client 對(duì)象有一個(gè) pending map,key 為請求的遞增序號(hào),當(dāng) Client 發(fā)起調(diào)用時(shí),將序號(hào)自增,并把當(dāng)前的 Call 對(duì)象放到 pending map 中,然后再向連接寫入請求。

寫入的請求先后分別為 Request 和參數(shù),可以理解為 header 和 body,其中 Request 就包含了 Client 的請求自增序號(hào)。

Server 端響應(yīng)時(shí)把這個(gè)序號(hào)帶回去,Client 接收響應(yīng)時(shí)讀出返回?cái)?shù)據(jù),再去 pending map 里找到對(duì)應(yīng)的請求,通知給對(duì)應(yīng)的阻塞協(xié)程。

這不就能把請求和響應(yīng)串到一起了嗎?這一招很多 RPC 框架也是這么玩的。

Client 、Server 流程都走完,但我們忽略了編解碼細(xì)節(jié),Go RPC 默認(rèn)使用 gob 編解碼器,這里也稍微介紹下 gob。

gob 編解碼

gob 是 Go 實(shí)現(xiàn)的一個(gè) Go 親和的協(xié)議,可以簡單理解這個(gè)協(xié)議只能在 Go 中用。Go Client RPC 對(duì)編解碼接口的定義如下:

type ClientCodec interface {
	WriteRequest(*Request, interface{}) error
	ReadResponseHeader(*Response) error
	ReadResponseBody(interface{}) error

	Close() error
}

同理,Server 端也有一個(gè)定義:

type ServerCodec interface {
	ReadRequestHeader(*Request) error
	ReadRequestBody(interface{}) error
	WriteResponse(*Response, interface{}) error
  
	Close() error
}

gob 是其一個(gè)實(shí)現(xiàn),這里只看 Client:

func (c *gobClientCodec) WriteRequest(r *Request, body interface{}) (err error) {
	if err = c.enc.Encode(r); err != nil {
		return
	}
	if err = c.enc.Encode(body); err != nil {
		return
	}
	return c.encBuf.Flush()
}

func (c *gobClientCodec) ReadResponseHeader(r *Response) error {
	return c.dec.Decode(r)
}

func (c *gobClientCodec) ReadResponseBody(body interface{}) error {
	return c.dec.Decode(body)
}

追蹤到底層就是 Encoder 的 EncodeValue 和 DecodeValue 方法,Encode 的細(xì)節(jié)我不打算寫,因?yàn)槲乙膊幌肟催@一塊,最終結(jié)果就是把結(jié)構(gòu)體編碼成了二進(jìn)制數(shù)據(jù),調(diào)用 writeMessage。

總結(jié)

本文介紹了 Go 內(nèi)置的 RPC Client 和 Server 端原理,能窺探出一點(diǎn)點(diǎn) RPC 的設(shè)計(jì),如果讓你實(shí)現(xiàn)一個(gè) RPC 是不是有些可以參考呢?

到此這篇關(guān)于一文吃透Go的內(nèi)置RPC原理的文章就介紹到這了,更多相關(guān)Go RPC內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言中你所不知道的位操作用法

    Go語言中你所不知道的位操作用法

    位運(yùn)算可能在平常的編程中使用的并不多,但涉及到底層優(yōu)化,一些算法及源碼可能會(huì)經(jīng)常遇見。下面這篇文章主要給大家介紹了關(guān)于Go語言中你所不知道的位操作用法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。
    2017-12-12
  • Go語言程序開發(fā)gRPC服務(wù)

    Go語言程序開發(fā)gRPC服務(wù)

    這篇文章主要為大家介紹了Go語言程序開發(fā)gRPC服務(wù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Go路由注冊方法詳解

    Go路由注冊方法詳解

    Go語言中,http.NewServeMux()和http.HandleFunc()是兩種不同的路由注冊方式,前者創(chuàng)建獨(dú)立的ServeMux實(shí)例,適合模塊化和分層路由,靈活性高,但啟動(dòng)服務(wù)器時(shí)需要顯式指定,后者使用全局默認(rèn)的http.DefaultServeMux,適合簡單場景,感興趣的朋友跟隨小編一起看看吧
    2025-02-02
  • go項(xiàng)目實(shí)現(xiàn)mysql接入及web?api的操作方法

    go項(xiàng)目實(shí)現(xiàn)mysql接入及web?api的操作方法

    這篇文章主要介紹了go項(xiàng)目實(shí)現(xiàn)mysql接入以及web api,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Golang使用Swagger文檔教程詳解

    Golang使用Swagger文檔教程詳解

    這篇文章主要為大家詳細(xì)介紹Golang使用Swagger文檔教程的相關(guān)知識(shí),文中通過示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以學(xué)習(xí)一下
    2023-12-12
  • Golang WebView跨平臺(tái)的桌面應(yīng)用庫的使用

    Golang WebView跨平臺(tái)的桌面應(yīng)用庫的使用

    Golang WebView是一個(gè)強(qiáng)大的桌面應(yīng)用庫,本文介紹了Golang WebView的特點(diǎn)和使用方法,并列舉示例詳細(xì)的介紹了其在實(shí)際項(xiàng)目中的應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • Go語言版本管理module以及go.sum詳解

    Go語言版本管理module以及go.sum詳解

    本文介紹了Go語言版本管理go module以及go.sum詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Golang 編譯成DLL文件的操作

    Golang 編譯成DLL文件的操作

    這篇文章主要介紹了Golang 編譯成DLL文件的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • goland遠(yuǎn)程調(diào)試k8s上容器的實(shí)現(xiàn)

    goland遠(yuǎn)程調(diào)試k8s上容器的實(shí)現(xiàn)

    本文主要介紹了goland遠(yuǎn)程調(diào)試k8s上容器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • golang中的net/http庫基本使用詳解

    golang中的net/http庫基本使用詳解

    今天給大家分享golang中的net/http庫基本使用方法,文章開頭給大家詳細(xì)介紹了標(biāo)準(zhǔn)庫net/http如何處理一個(gè)請求,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-04-04

最新評(píng)論

准格尔旗| 滨海县| 罗山县| 苍南县| 九寨沟县| 铜陵市| 台山市| 武宣县| 马关县| 滕州市| 临安市| 呼玛县| 定兴县| 甘德县| 旌德县| 东兴市| 保定市| 青铜峡市| 镇巴县| 望谟县| 馆陶县| 贺州市| 清水河县| 宁明县| 波密县| 和静县| 康乐县| 华阴市| 黎城县| 兴和县| 藁城市| 措美县| 荥经县| 叙永县| 耿马| 峨眉山市| 崇左市| 星座| 连平县| 武川县| 乐亭县|