rpc wmi 服务不可用_golang 基础(5) RPC

微服務(wù)已經(jīng) hot 了一段時間,自己作為 web 開發(fā)人員當然也不由自主想研究研究微服務(wù),不過微服務(wù)的整個知識體系過于龐大,要掌握的概念和技術(shù)太多,一時有點吃不消。個人為了生計又沒有大塊時間去搞。不過還是難舍微服務(wù),最近學習了 go 語言,想一塊一塊地吃掉微服務(wù),先從 go 和容器入手。
我們知道微服務(wù)之間是需要相互調(diào)用和通訊的,一般會采用 RPC 來實現(xiàn)不同語言不用服務(wù)間的調(diào)用和通訊。那么我就先從 RPC 入手來學習微服務(wù)。
RPC 框架的特點
所謂的特點就是他能夠滿足那些需求,RPC 框架要實現(xiàn)以上目的需要滿足以下需求
- 序列化(GOB)語言單一
- 上下文管理(超時控制)
- 攔截器(鑒權(quán)、統(tǒng)計和限流)
- 跨語言
- 服務(wù)注冊
Call ID:由于客戶端和服務(wù)端運行在不同進程,為了讓客戶端和服務(wù)端都了解調(diào)用了哪個函數(shù),需要在兩端維護一個函數(shù)到Call ID 的映射表,客戶端根據(jù)表獲取函數(shù)的 Call ID,發(fā)起請求,服務(wù)端根據(jù) Call ID 來執(zhí)行對應(yīng)的函數(shù),返回值給客戶端。
序列化和反序列化:在本地調(diào)用時候函數(shù)是從棧中獲取參數(shù)運行函數(shù)。而遠程調(diào)用時候,如果需要在不同語言間相互調(diào)用函數(shù),需要將參數(shù)進行序列化然后以字節(jié)流方式傳遞給服務(wù)端,服務(wù)端在反序列化來得到參數(shù)
網(wǎng)絡(luò)傳輸:客戶端和服務(wù)端間的調(diào)用往往是通過網(wǎng)絡(luò)完成。只要能傳遞數(shù)據(jù)就行,與協(xié)議無關(guān),可以使用 TCP 或 UDP。gRcp 使用的 HTTP2 。
什么是 RPC ?
> ?RPC是指**遠程過程**調(diào)用,也就是說兩臺服務(wù)器A,B,一個應(yīng)用部署在A服務(wù)器上,想要調(diào)用B服務(wù)器上應(yīng)用提供的函數(shù)/方法,由于不在一個內(nèi)存空間,不能直接調(diào)用,需要通過網(wǎng)絡(luò)來表達調(diào)用的語義和傳達調(diào)用的數(shù)據(jù)。
為什么需要 RPC 呢
因為 RPC 是分布式系統(tǒng)中不同節(jié)點間流行的通訊方式,在互聯(lián)網(wǎng)時代,RPC 和 IPC 一樣成為不可或缺的基礎(chǔ)構(gòu)建。在 Go 語言的標準庫也提供了簡單的 RPC 的實現(xiàn)。
RPC 在服務(wù)間調(diào)用流程
我們通過上面圖來看,這個流程比較清晰,也不難理解。
1. 調(diào)用客戶端句柄,執(zhí)行傳送參數(shù)
2. 調(diào)用本地系統(tǒng)內(nèi)核發(fā)送網(wǎng)絡(luò)消息
3. 消息傳送至遠程機器
4. 服務(wù)器句柄得到消息并取得參數(shù)
5. 執(zhí)行遠程過程
6. 執(zhí)行過程將結(jié)果返回給服務(wù)器句柄
7. 服務(wù)器句柄返回結(jié)果,調(diào)用遠程系統(tǒng)內(nèi)核
8. 消息傳回本地主機
9. 客戶句柄由內(nèi)核接收消息
10. 客戶接收句柄返回的數(shù)據(jù)
有了上面理論基礎(chǔ),我們基于理論來實現(xiàn)。
type RPCService struct{}創(chuàng)建一個 RPCService 服務(wù),隨后將其進行注冊
func (s *RPCService) Hello(request string, reply *string) error{ *reply = "Hello " + request return nil}- 函數(shù)必須是外部可以訪問函數(shù),函數(shù)名需要首字母大寫
- 函數(shù)需要有兩個參數(shù)
? 1. 第一個參數(shù)接收的參數(shù)
? 2. 第二個參數(shù)是返回給客戶端的參數(shù),而且需要是指針類型
- 函數(shù)還需要有一個 error 返回值 ?
rpc.RegisterName("RPCService",new(RPCService))注冊rpc服務(wù)
listener, err := net.Listen("tcp",":1234")創(chuàng)建 tcp 服務(wù)端口號為 1234 用于 rpc 服務(wù)。
conn, err := listener.Accept() if err != nil{ log.Fatal("Accept error:", err) } rpc.ServeConn(conn)服務(wù)端完整代碼
package mainimport( // "fmt" "log" "net" "net/rpc")type RPCService struct{}func (s *RPCService) Hello(request string, reply *string) error{ *reply = "Hello " + request return nil}func main() { rpc.RegisterName("RPCService",new(RPCService)) listener, err := net.Listen("tcp",":1234") if err != nil{ log.Fatal("ListenTCP error:",err) } conn, err := listener.Accept() if err != nil{ log.Fatal("Accept error:", err) } rpc.ServeConn(conn)}客戶端代碼
client, err := rpc.Dial("tcp","localhost:1234")客戶端調(diào)用 RPC 服務(wù),然后通過client.Call調(diào)用具體的RPC方法。
err = client.Call("RPCService.Hello","World",&reply)在調(diào)用client.Call時,第一個參數(shù)是用點號鏈接的RPC服務(wù)名字和方法名字,第二和第三個參數(shù)分別我們定義RPC方法的兩個參數(shù)。
package mainimport( "fmt" "log" "net/rpc")func main() { client, err := rpc.Dial("tcp","localhost:1234") if err != nil{ log.Fatal("dialing:", err) } var reply string err = client.Call("RPCService.Hello","World",&reply) if err != nil{ log.Fatal("call Hello method of RPCService:",err) } fmt.Println(reply)}我們先后啟動服務(wù)端和客戶端就可以看到下面效果
Hello World下面更加貼近實際來寫一個基于 HTTP 的 RPC 服務(wù),服務(wù)提供兩個數(shù)四則運算。
rpcService := new(RPCService) rpc.Register(rpcService) rpc.HandleHTTP()這里的注冊方式略有不同,但是大同小異相信大家一看就懂。
服務(wù)端完整代碼
package mainimport( "errors" "fmt" "net/http" "net/rpc")type Args struct{ A, B int}type Quotient struct{ Quo, Rem int}type RPCService intfunc (t *RPCService) Add(args *Args, reply *int) error{ *reply = args.A - args.B return nil}func (t *RPCService) Multiply(args *Args, reply *int) error{ *reply = args.A * args.B return nil}func (t *RPCService) Divide(args *Args, quo *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}func main() { rpcService := new(RPCService) rpc.Register(rpcService) rpc.HandleHTTP() err := http.ListenAndServe(":1234",nil) if err != nil{ fmt.Println(err.Error()) }}客戶端代碼
```go
package mainimport( "fmt" "log" "net/rpc" "os")type Args struct{ A, B int}type Quotient struct{ Quo, Rem int}func main() { if len(os.Args) != 2{ fmt.Println("Usage: ", os.Args[0],"server") os.Exit(1) } serverAddress := os.Args[1] client, err := rpc.DialHTTP("tcp",serverAddress + ":1234") if err != nil { log.Fatal("dialing: ", err) } args := Args{17, 8} var reply int err = client.Call("RPCService.Add",args, &reply) if err != nil{ log.Fatal("RPCService error: ", err) } fmt.Printf("RPCService: %d + %d = %d\n", args.A, args.B, &reply) var quot Quotient err = client.Call("RPCService.Divide",args, ") if err != nil{ log.Fatal("RPCService error: ",err) } fmt.Printf("RPCService: %d/%d=%d remainder %d\n",args.A, args.B, quot.Quo,quot.Rem)}```
```
RPCService: 17 + 8 = 824634312296RPCService: 17/8=2 remainder 1```
其實在實際開發(fā)中我們還需要對其進行改造,例如讓 rpc 請求可以獲得一個 context 對象,其中包含用戶信息等,然后可以對 rpc 進行超時處理。
#### JSONRPC
Go語言內(nèi)置的 RPC 框架已經(jīng)支持在 Http 協(xié)議上提供 RPC 服務(wù)。但是 Http 服務(wù)內(nèi)置采用了 GOB 協(xié)議。編碼不是 JSON 編碼,不方便其他語言調(diào)用。不過 go 提供 JsonRPC 的 RPC 服務(wù)支持,我們來看一看怎么用代碼實現(xiàn)。
服務(wù)端代碼
```go
package mainimport( "errors" "fmt" "net" "net/rpc" "net/rpc/jsonrpc" // "os")type Args struct{ A, B int}type Quotient struct{ Quo, Rem int}type RPCService intfunc (t *RPCService) Multiply(args *Args, reply *int) error{ *reply = args.A * args.B return nil}func (t *RPCService) Divide(args *Args, quo *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}func main() { rpcService := new(RPCService) rpc.Register(rpcService) tcpAddr, err := net.ResolveTCPAddr("tcp",":1234") checkError(err) listener, err := net.ListenTCP("tcp",tcpAddr) checkError(err) for{ conn, err := listener.Accept() if err != nil{ continue } jsonrpc.ServeConn(conn) }}func checkError(err error){ if err != nil{ fmt.Println("Fatal error ", err.Error()) }}客戶端代碼
package mainimport( "fmt" "log" "net/rpc/jsonrpc" "os")type Args struct{ A, B int}type Quotient struct{ Quo, Rem int}func main() { if len(os.Args) != 2{ fmt.Println("Usage: ", os.Args[0],"server") log.Fatal(1) } serverAddress := os.Args[1] client, err := jsonrpc.Dial("tcp",serverAddress + ":1234") if err != nil { log.Fatal("dialing: ", err) } args := Args{17, 8} var quot Quotient err = client.Call("RPCService.Divide",args, ") if err != nil{ log.Fatal("RPCService error: ",err) } fmt.Printf("RPCService: %d/%d=%d remainder %d\n",args.A, args.B, quot.Quo,quot.Rem)}對原有RPC進行改造,添加一些輔助的功能
上下文管理
通過net/rpcj進行改造,可以添加一個上下文對象
func (t *RPCService) Add(c contex.Context,args *Args, reply *int) error{ *reply = args.A - args.B return nil}我們看一看這里有一個 Context 接口有些方法需要在 RPC 中進行實現(xiàn),這里我們用 TCP 需要記錄時間,通過 Context 可以獲得一些額外而必要信息。
type Context interface{ ctx.Context Now() time.Time Seq() uint64 ServiceMethod() string User() string}type rpcCtx struct{}func NewContext(c ctx.Context, u, m string, s uint64) Context{ }?超時控制
進行超時,通過超時控制避免過多請求阻塞,通過超時服務(wù)取消 rpc 請求。
func (t *RPCService) Timeout(c contex.Context, args *RPCTimeout, reply *struct{}) error{ log.Printf("Timeout: timeout=%s seq=%d\n",args.T, c.Seq()) time.Sleep(args.T) return nil}總結(jié)
以上是生活随笔為你收集整理的rpc wmi 服务不可用_golang 基础(5) RPC的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux route 刷新_linux
- 下一篇: @echo off是什么意思_为什么执行