微服務(wù)是現(xiàn)在大多數(shù)新項(xiàng)目會(huì)采用的架構(gòu)。服務(wù)端提供服務(wù),客戶端調(diào)用服務(wù)端的功能,就像調(diào)用一個(gè)函數(shù)一樣。微服務(wù)的更新升級(jí),只要接口沒有變化,客戶端完全可以無影響地升級(jí)到新功能。這就是微服務(wù)的第一個(gè)體驗(yàn)。
隨之而來的,就是每個(gè)微服務(wù)都可以用任意計(jì)算機(jī)語言實(shí)現(xiàn),方便了不同技術(shù)團(tuán)隊(duì)的協(xié)作。
而整個(gè)軟件系統(tǒng)的構(gòu)成,也不同于以往的實(shí)現(xiàn)方式了。這樣的構(gòu)成,更靈活和強(qiáng)大。當(dāng)然隨著服務(wù)數(shù)量的增加,也增加了服務(wù)的管理難度。
go語言的RPC服務(wù)實(shí)現(xiàn)起來非常簡單??梢岳胻cp或http協(xié)議來傳遞數(shù)據(jù)。
我們通過一個(gè)計(jì)算矩形面積和周長的微服務(wù)實(shí)例來學(xué)習(xí)rpc服務(wù)的編寫方法。
首先,實(shí)現(xiàn)server端(server.go),定義一個(gè)矩形
// 聲明矩形結(jié)構(gòu)體
type Rect struct {
}
再定義矩形的參數(shù)
// 聲明參數(shù)結(jié)構(gòu)體
type Params struct {
// 寬,高
Width, Height int
}
因?yàn)橛?jì)算面積和周長的RPC服務(wù)是遠(yuǎn)程調(diào)用,那么在服務(wù)端計(jì)算,通過獲取和改變內(nèi)存變量的值實(shí)現(xiàn)調(diào)用遠(yuǎn)程微服務(wù)像本地函數(shù)一樣的效果。所以,傳入的參數(shù)除了寬和高以外,還有一個(gè)供承載計(jì)算結(jié)果的整形指針。如果發(fā)生錯(cuò)誤,就返回error,無錯(cuò)誤的時(shí)候,return nil 就可以了。
// 計(jì)算矩形面積
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
// 計(jì)算周長
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
在主函數(shù)中,首先要注冊(cè)服務(wù)
// 注冊(cè)服務(wù)
rect := new(Rect)
rpc.Register(rect)
然后綁定服務(wù)到http協(xié)議
// 服務(wù)綁定http協(xié)議
rpc.HandleHTTP()
最后服務(wù)器開始監(jiān)聽服務(wù),等待客戶端來調(diào)用。這個(gè)服務(wù)我們使用8080端口。
// 監(jiān)聽服務(wù),等待客戶端調(diào)用(求面積和周長的方法)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
至此,微服務(wù)server端代碼已經(jīng)寫完。
下面開始編寫微服務(wù)client端代碼(client.go)。
client端要和server端擁有相同的參數(shù)結(jié)構(gòu)體。即
// 參數(shù)
type Params struct {
Width, Height int
}
client遠(yuǎn)程連接server,這里采用tcp協(xié)議,還需要提前知道server的地址和通訊端口。以下代碼建立遠(yuǎn)程連接,并得到一個(gè)實(shí)例rp。
// 連接遠(yuǎn)程的RPC服務(wù)
rp, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
if err != nil {
log.Println(err)
}
為了獲取計(jì)算結(jié)果和提供計(jì)算參數(shù),聲明2個(gè)變量。一個(gè)保存結(jié)果,一個(gè)保存參數(shù)。
// 結(jié)果
ret := 0
// 參數(shù)
p := Params{50 ,100}
調(diào)用遠(yuǎn)程服務(wù)(面積)
// 1. 求面積
err2 := rp.Call("Rect.Area", p, &ret)
if err2 != nil {
log.Println(err2)
}else{
fmt.Println("面積:", ret)
}
調(diào)用遠(yuǎn)程服務(wù)(周長)
// 2. 求周長
err3 := rp.Call("Rect.Perimeter", p, &ret)
if err3 != nil {
log.Println(err3)
}else {
fmt.Println("周長:", ret)
}
運(yùn)行客戶端client.go,由于此時(shí)server未啟動(dòng),所以客戶端會(huì)得到一個(gè)連接被拒絕的結(jié)果。
2021/03/21 20:23:48 dial tcp 127.0.0.1:8080: connectex: 由于目標(biāo)計(jì)算機(jī)積極拒絕,無法連接。
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x10 pc=0x109e944]
...
啟動(dòng)server端,再次運(yùn)行client端,得到運(yùn)行結(jié)果
面積: 5000
周長: 300
server.go 完整代碼
/**
* Package: rpcServer
* Description: This package is rpcServer example
* Author: Jian Junbo
* Email: junbojian@qq.com
* Date: 2021/3/21 17:31
* Copyright ?2021 Jian Junbo & Shanxi Xiyue Mancang Technology Co., Ltd. All rights reserved.
**/
package main
import (
"log"
"net/http"
"net/rpc"
)
// 聲明矩形結(jié)構(gòu)體
type Rect struct {
}
// 聲明參數(shù)結(jié)構(gòu)體
type Params struct {
// 寬,高
Width, Height int
}
// 計(jì)算矩形面積
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
// 計(jì)算周長
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
func main() {
// 注冊(cè)服務(wù)
rect := new(Rect)
rpc.Register(rect)
// 服務(wù)綁定http協(xié)議
rpc.HandleHTTP()
// 監(jiān)聽服務(wù),等待客戶端調(diào)用(求面積和周長的方法)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
client.go 完整代碼
/**
* Package: rpcClient
* Description: This package is rpcClient example.
* Author: Jian Junbo
* Email: junbojian@qq.com
* Date: 2021/3/21 18:31
* Copyright ?2021 Jian Junbo & Shanxi Xiyue Mancang Technology Co., Ltd. All rights reserved.
**/
package main
import (
"fmt"
"log"
"net/rpc"
)
// 參數(shù)
type Params struct {
Width, Height int
}
// 主函數(shù)調(diào)用服務(wù)
func main() {
// 連接遠(yuǎn)程的RPC服務(wù)
rp, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
if err != nil {
log.Println(err)
}
// * 調(diào)用服務(wù)方法
// 結(jié)果
ret := 0
// 參數(shù)
p := Params{50 ,100}
// 1. 求面積
err2 := rp.Call("Rect.Area", p, &ret)
if err2 != nil {
log.Println(err2)
}else{
fmt.Println("面積:", ret)
}
// 2. 求周長
err3 := rp.Call("Rect.Perimeter", p, &ret)
if err3 != nil {
log.Println(err3)
}else {
fmt.Println("周長:", ret)
}
}