1. 程式人生 > 其它 >golang RPC入門

golang RPC入門

1. RPC簡介

RPC是遠端系統呼叫的縮寫,通俗地講就是呼叫遠處的一個函式,可能是一個檔案內的不同函式,也可能是一個機器上另一個程序的函式,也可能是遠處機器上的函式。

RPC是分散式系統中不同節點之間的通訊方式,Go的標準庫也實現了一個簡單的RPC。

 

2. RPC簡單使用

首先構造一個HelloService型別,其中的Hello方法用於實現列印功能,Login實現簡單的使用者驗證

其中RPC方法必須滿足golang的RPC規則:

  • 方法只能有兩個可序列化的引數,其中第二個引數是指標型別
  • 返回一個error
  • 必須是公開的方法,首字母大寫
type HelloService struct {
    conn    net.Conn
    isLogin bool
}

// Hello: func (p *HelloService) Hello(request string, reply *string) error { if !p.isLogin { return fmt.Errorf("please login") } *reply = "hello:" + request + ",from" + p.conn.RemoteAddr().String() return nil } // Login: 提供使用者登入驗證 func (p *HelloService) Login(request string, reply *string
) error { if request != "user:password" { return fmt.Errorf("auth failed") } log.Println("login ok") p.isLogin = true return nil }

 

然後我們可以將HelloService型別的物件註冊為一個RPC服務:

func main() {

    // 開啟監聽
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal(
"ListenTCP error:", err) } for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go func() { defer conn.Close() p := rpc.NewServer() // RegisterName呼叫會將物件型別中所有滿足RPC規則的物件方法註冊為RPC函式 // 所有註冊的方法會放在HelloService服務空間之下 p.Register(&HelloService{conn: conn}) // ServeConn函式在conn這個TCP連線上為對方提供RPC服務 p.ServeConn(conn) }() } }

 

下面是客戶端請求HelloService服務的程式碼:

func main() {

    // 撥號RPC服務
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal("Dail error:", err)
    }

    // 通過Call()呼叫RPC的具體方法
    var reply string
    err = client.Call("HelloService.Login", "user:password", &reply)
    if err != nil {
        log.Fatal(err)
    }
    err = client.Call("HelloService.Hello", "client", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}

 

我們在終端開啟server和client,看看會發生什麼:

go run server.go
go run client.go

##################
hello:client,from[::1]:56769

 

3. 跨語言的RPC

標準庫的RPC預設採用go語言特有的Gob編碼,因此從其他語言呼叫Go語言實現的RPC服務比較困難。

go語言的RPC框架有兩個比較有特色的設計:一個是RPC資料打包時可以通過外掛實現自定義的編碼和解碼;另一個是RPC建立在抽象的io.ReadWriteCloser介面之上,我們可以將RPC架設在不同的通訊協議之上。

我們利用net/rpc/jsonrpc實現一個跨語言的RPC。

func main() {
    rpc.RegisterName("HelloService", new(HelloService))
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal(err)
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatal(err)
        }

        // 使用rpc.ServeCodec代替rpc.ServeConn函式
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

 

func main() {
    conn, err := net.Dial("tcp", "localhost:1234")
    if err != nil {
        log.Fatal(err)
    }

    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

    var reply string
    err = client.Call("HelloService.Hello", "hello", &reply)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply)
}

 

無論採用什麼樣的語言,只要遵循一致的json對映結構,以同樣的流程就可以實現和go語言編寫的RPC服務進行通訊

 

4. HTTP上的RPC服務

我們嘗試在HTTP協議上提供jsonrpc服務

新的RPC服務其實就是一個類似於REST規範的介面,接收請求並採用相應的處理流程:

type HelloService struct{}

func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello: " + request
    return nil
}

func main() {
    rpc.RegisterName("HelloService", new(HelloService))
    http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
        var conn io.ReadWriteCloser = struct {
            io.Writer
            io.ReadCloser
        }{
            ReadCloser: r.Body,
            Writer:     w,
        }
        rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
    })

    http.ListenAndServe(":1234", nil)
}

 

RPC服務架設在/jsonrpc路徑,在處理函式中基於http.ResponseWriter和*http.Request型別的引數構造一個io.ReadWriteCloser型別的conn通道,然後基於conn構建針對伺服器端的json編碼解碼器,最後通過rpc.ServeRequest( )函式為每次請求處理一次RPC方法呼叫。

讓我們啟動RPC服務,並開啟postman,測試我們的RPC服務:

 

可以清楚的看到我們POST請求的body和response資訊,都是json格式的,其實這兩種格式基本都是固定寫法。

因為在內部都是使用類似的結構體來封裝的:

type clientRequest struct{
  Method  string            `json:"method"`
  Params  []interface{} `json:"params"`
  Id          uint64          `json:"id"`
}
type serverResponse struct{
  Id *json.RawMessage `json:"id"`
  Result interface{} `json:"result"`
  Error interface{} `json:"error"`  
}