「GoCN酷Go推薦」Go原生RPC與ARPC的簡(jiǎn)單使用
什么是 RPC?
RPC叫做遠(yuǎn)程過(guò)程調(diào)用,意思是兩臺(tái)不同服務(wù)器上的服務(wù),可以互相像調(diào)用函數(shù)一樣調(diào)用。
我用HTTP API不一樣能達(dá)到同樣的效果嗎?
其實(shí)對(duì)于新人來(lái)說(shuō),兩臺(tái)服務(wù)器之間的數(shù)據(jù)交互,用HTTP提供的API真的可以解決,但效率不高,延遲也高,且連接不會(huì)復(fù)用,因?yàn)榇蠹叶贾繦TTP是無(wú)狀態(tài)傳輸協(xié)議,每次傳輸都不知道對(duì)方是誰(shuí),因此,體現(xiàn)在以下方面:
每次要獲取數(shù)據(jù)前,都會(huì)進(jìn)行三次握手確認(rèn)與四次揮手的過(guò)程。 不能建立長(zhǎng)連接進(jìn)行通信,多次請(qǐng)求 數(shù)據(jù)轉(zhuǎn)換效率低,無(wú)論是使用form表單或者json傳輸,都不如直接傳輸二進(jìn)制數(shù)據(jù)來(lái)得快,序列化與反序列化資源占用高
快速使用原生RPC
首先通訊雙方都應(yīng)擁有同樣結(jié)構(gòu)體,所以服務(wù)端與客戶端都創(chuàng)建創(chuàng)建 go_rpc -----> RpcParams.go:
package?go_rpc
type?MyWayRpc?struct{?//?客戶端要傳輸?shù)臄?shù)據(jù),也是服務(wù)端要接收的數(shù)據(jù)
???Name?string
???Age?int
}
type?MyWayRpcReply?struct{?//?服務(wù)端要返回的數(shù)據(jù),也是客戶端想要獲得的結(jié)果
???SystemInfo?string
}
服務(wù)端編寫(xiě)main.go文件 :
package?main
import?(
???"go_rpc"
???"fmt"
???"log"
???"net"
???"net/rpc"
???"runtime"
)
//?其實(shí)按照web?api中,我們應(yīng)當(dāng)把TakeMyWayRpc結(jié)構(gòu)體和?GetSystem方法寫(xiě)在單獨(dú)的控制器中,這里為了代碼演示就寫(xiě)在main包下了
type?TakeMyWayRpc?struct{}
func?(t?TakeMyWayRpc)?GetSystem(arg?*go_rpc.MyWayRpc,?result?*go_rpc.MyWayRpcReply)?error?{
???fmt.Println("客戶端發(fā)送了:",arg.Name,arg.Age)
???//返回給服務(wù)端的
???result.SystemInfo?=?runtime.GOOS
???return?nil
}
func?main()?{
???tmwr?:=?new(TakeMyWayRpc)
???err?:=?rpc.Register(tmwr)?//?注冊(cè)RPC可以一次性注冊(cè)多個(gè)RPC服務(wù),可以用FOR注冊(cè)多個(gè)結(jié)構(gòu)體,用web?api的話就是注冊(cè)多個(gè)控制器
???if?err?!=nil{
??????log.Fatalln("注冊(cè)方法時(shí)出現(xiàn)問(wèn)題:",err)
???}
???l,?err?:=?net.Listen("tcp",?":30001")?//?從這里就可以看出,實(shí)際上RPC的底層也是基于TCP鏈接的,我們這里開(kāi)放30001端口,供客戶端連入
???defer?l.Close()
???if?err?!=?nil?{
??????fmt.Println("監(jiān)聽(tīng)失敗,端口可能已經(jīng)被占用")
???}
???fmt.Println("正在監(jiān)聽(tīng)30001端口")
???for?{
??????var?conn?net.Conn
??????conn,?err?=?l.Accept()
??????if?err?!=nil{
?????????log.Fatalln("創(chuàng)建句柄失敗")
??????}
??????go?rpc.ServeConn(conn)
???}
}
開(kāi)始在另外一臺(tái)服務(wù)器上寫(xiě)客戶端的main.go:
package?main
import?(
???"go_rpc"
???"log"
???"net/rpc"
)
func?main()?{
???client?,?err?:=?rpc.Dial("tcp","服務(wù)端IP:30001")
???if?err?!=nil{
??????log.Fatalln("這里試試用錯(cuò)誤的東西:",err)
???}
???defer?client.Close()
???var?myWayRpcArg?go_rpc.MyWayRpc?//?初始化客戶端要發(fā)送的內(nèi)容
???var?myWayRpcReply?go_rpc.MyWayRpcReply?//?初始化服務(wù)端要返回的內(nèi)容
?
???//?填客戶端要發(fā)送的內(nèi)容
???myWayRpcArg.Name?=?"安彥飛啊"
???myWayRpcArg.Age?=?31
???if?err?=?client.Call("TakeMyWayRpc.GetSystem",myWayRpcArg,&myWayRpcReply);err?!=nil{?//?直接開(kāi)始發(fā)送給服務(wù)端,并獲得服務(wù)端的響應(yīng)
??????log.Fatalln("返回服務(wù)端數(shù)據(jù)錯(cuò)誤:",err)
???}
???log.Println("返回成功,",myWayRpcReply)
}
RPC演示:

為什么需要APRC:
很好,通過(guò)上面的例子,已經(jīng)可以寫(xiě)出RPC的信息交互了,但如果遇到需要服務(wù)端主動(dòng)給客戶端發(fā)消息,客戶端異步調(diào)用服務(wù)端的函數(shù),這些就不是原生RPC能夠做到的了
我們做一個(gè)ARPC的簡(jiǎn)單示例:
使用ARPC前應(yīng)當(dāng)先:
go?get?github.com/lesismal/arpc
其實(shí)ARPC看上去更像是服務(wù)端定義了一個(gè)ROUTER,客戶端去根據(jù)路由尋址找到了這個(gè)函數(shù)
首先還是看服務(wù)端的實(shí)現(xiàn),服務(wù)端main.go如下:
package?main
import?(
?"github.com/lesismal/arpc"
?"log"
?"runtime"
)
func?main()?{
?server?:=?arpc.NewServer()
?registerHandler?:=?server.Handler
?testStruct?:=?new(TestArpcStruct)
?registerHandler.Handle(?//?若這里有多個(gè),就像寫(xiě)router
???"/TestArpcStruct.GetSystem",
??testStruct.GetSystem,
??)
?server.Run(":8888")
}
type?TestArpcStruct?struct{}
func?(TestArpcStruct)?GetSystem(ctx?*arpc.Context)??{
?var?str?string
?if?err?:=?ctx.Bind(&str);err?==nil{
??log.Println(str)
??ctx.Write(runtime.GOOS)
?}
}
然后寫(xiě)客戶端的代碼:
package?main
import?(
?"github.com/lesismal/arpc"
?"log"
?"net"
?"time"
)
func?main()?{
?client?,?err?:=?arpc.NewClient(func()?(net.Conn,?error)?{
??return?net.DialTimeout("tcp","localhost:8888",3?*?time.Second)
?})
?if?err?!=nil{
??panic(err)
?}
?defer?client.Stop()
?req?:=?"hello"
?resp?:=?""
?err??=?client.Call("/TestArpcStruct.GetSystem",&req,&resp,5?*?time.Second)
?if?err?!=?nil?{
??log.Fatalf("Call?failed:?%v",?err)
?}?else?{
??log.Printf("Call?Response:?\"%v\"",?resp)
?}
}
最后也能和RPC一樣,得到東西:
客戶端發(fā)出hello,服務(wù)端str獲得了hello并打印,且像客戶端發(fā)送了當(dāng)前服務(wù)器的運(yùn)行系統(tǒng)名runtime.GOOS,客戶端:Call Response : windows/linux 這樣的結(jié)果
總結(jié)
其實(shí)ARPC還有很多其他功能,比如還可以提供給WS的調(diào)用方法等,官方壓測(cè)后性能與RPC幾乎持平,甚至比RPC更高。
我們?cè)谑褂靡豁?xiàng)新技術(shù)之前,一定要弄明白這項(xiàng)新技術(shù)為什么會(huì)被發(fā)明出來(lái),解決了什么痛點(diǎn),提升了怎樣的性能,努力思考實(shí)際應(yīng)用場(chǎng)景在哪里,比如ARPC可以用到即時(shí)通訊,可以復(fù)用連接池,不用資源一直申請(qǐng)與釋放,監(jiān)控?cái)?shù)據(jù)的實(shí)時(shí)展示等。
參考鏈接
https://github.com/lesismal/arpc
《酷Go推薦》招募:
各位Gopher同學(xué),最近我們社區(qū)打算推出一個(gè)類(lèi)似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個(gè)庫(kù)或者好的項(xiàng)目,然后寫(xiě)一點(diǎn)這個(gè)庫(kù)使用方法或者優(yōu)點(diǎn)之類(lèi)的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到
新的庫(kù),并且知道怎么用。
大概規(guī)則和每日新聞?lì)愃疲绻麍?bào)名人多的話每個(gè)人一個(gè)月輪到一次,歡迎大家報(bào)名!戳「閱讀原文」,即可報(bào)名
掃碼也可以加入 GoCN 的大家族喲~
