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

微服務已經 hot 了一段時間,自己作為 web 開發人員當然也不由自主想研究研究微服務,不過微服務的整個知識體系過于龐大,要掌握的概念和技術太多,一時有點吃不消。個人為了生計又沒有大塊時間去搞。不過還是難舍微服務,最近學習了 go 語言,想一塊一塊地吃掉微服務,先從 go 和容器入手。
我們知道微服務之間是需要相互調用和通訊的,一般會采用 RPC 來實現不同語言不用服務間的調用和通訊。那么我就先從 RPC 入手來學習微服務。
RPC 框架的特點
所謂的特點就是他能夠滿足那些需求,RPC 框架要實現以上目的需要滿足以下需求
- 序列化(GOB)語言單一
- 上下文管理(超時控制)
- 攔截器(鑒權、統計和限流)
- 跨語言
- 服務注冊
Call ID:由于客戶端和服務端運行在不同進程,為了讓客戶端和服務端都了解調用了哪個函數,需要在兩端維護一個函數到Call ID 的映射表,客戶端根據表獲取函數的 Call ID,發起請求,服務端根據 Call ID 來執行對應的函數,返回值給客戶端。
序列化和反序列化:在本地調用時候函數是從棧中獲取參數運行函數。而遠程調用時候,如果需要在不同語言間相互調用函數,需要將參數進行序列化然后以字節流方式傳遞給服務端,服務端在反序列化來得到參數
網絡傳輸:客戶端和服務端間的調用往往是通過網絡完成。只要能傳遞數據就行,與協議無關,可以使用 TCP 或 UDP。gRcp 使用的 HTTP2 。
什么是 RPC ?
> ?RPC是指**遠程過程**調用,也就是說兩臺服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數/方法,由于不在一個內存空間,不能直接調用,需要通過網絡來表達調用的語義和傳達調用的數據。
為什么需要 RPC 呢
因為 RPC 是分布式系統中不同節點間流行的通訊方式,在互聯網時代,RPC 和 IPC 一樣成為不可或缺的基礎構建。在 Go 語言的標準庫也提供了簡單的 RPC 的實現。
RPC 在服務間調用流程
我們通過上面圖來看,這個流程比較清晰,也不難理解。
1. 調用客戶端句柄,執行傳送參數
2. 調用本地系統內核發送網絡消息
3. 消息傳送至遠程機器
4. 服務器句柄得到消息并取得參數
5. 執行遠程過程
6. 執行過程將結果返回給服務器句柄
7. 服務器句柄返回結果,調用遠程系統內核
8. 消息傳回本地主機
9. 客戶句柄由內核接收消息
10. 客戶接收句柄返回的數據
有了上面理論基礎,我們基于理論來實現。
type RPCService struct{}創建一個 RPCService 服務,隨后將其進行注冊
func (s *RPCService) Hello(request string, reply *string) error{ *reply = "Hello " + request return nil}- 函數必須是外部可以訪問函數,函數名需要首字母大寫
- 函數需要有兩個參數
? 1. 第一個參數接收的參數
? 2. 第二個參數是返回給客戶端的參數,而且需要是指針類型
- 函數還需要有一個 error 返回值 ?
rpc.RegisterName("RPCService",new(RPCService))注冊rpc服務
listener, err := net.Listen("tcp",":1234")創建 tcp 服務端口號為 1234 用于 rpc 服務。
conn, err := listener.Accept() if err != nil{ log.Fatal("Accept error:", err) } rpc.ServeConn(conn)服務端完整代碼
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")客戶端調用 RPC 服務,然后通過client.Call調用具體的RPC方法。
err = client.Call("RPCService.Hello","World",&reply)在調用client.Call時,第一個參數是用點號鏈接的RPC服務名字和方法名字,第二和第三個參數分別我們定義RPC方法的兩個參數。
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)}我們先后啟動服務端和客戶端就可以看到下面效果
Hello World下面更加貼近實際來寫一個基于 HTTP 的 RPC 服務,服務提供兩個數四則運算。
rpcService := new(RPCService) rpc.Register(rpcService) rpc.HandleHTTP()這里的注冊方式略有不同,但是大同小異相信大家一看就懂。
服務端完整代碼
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```
其實在實際開發中我們還需要對其進行改造,例如讓 rpc 請求可以獲得一個 context 對象,其中包含用戶信息等,然后可以對 rpc 進行超時處理。
#### JSONRPC
Go語言內置的 RPC 框架已經支持在 Http 協議上提供 RPC 服務。但是 Http 服務內置采用了 GOB 協議。編碼不是 JSON 編碼,不方便其他語言調用。不過 go 提供 JsonRPC 的 RPC 服務支持,我們來看一看怎么用代碼實現。
服務端代碼
```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 中進行實現,這里我們用 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{ }?超時控制
進行超時,通過超時控制避免過多請求阻塞,通過超時服務取消 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}總結
以上是生活随笔為你收集整理的rpc wmi 服务不可用_golang 基础(5) RPC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux route 刷新_linux
- 下一篇: @echo off是什么意思_为什么执行