1. 程式人生 > >go微服務系列(三) - 服務呼叫(http)

go微服務系列(三) - 服務呼叫(http)

- [1. 關於服務呼叫](#head1) - [2. 基本方式呼叫服務](#head2) - [3. 服務呼叫正確姿勢(初步)](#head3) - [3.1 服務端程式碼](#head4) - [3.2 客戶端呼叫(重要)](#head5) ## 1. 關於服務呼叫 這裡的服務呼叫,我們呼叫的可以是`http api`也可以是`gRPC`等。主要意思就是呼叫我們從`consul`獲取到的服務的API。 下面的所有示例以`RESTful HTTP API`為例 ## 2. 基本方式呼叫服務 我們在服務發現之後,肯定要呼叫發現之後的服務,這裡的服務可以是http的`RESTful API`也可以是`RPC`服務等,這裡以前面的定義的`productService`的`RESTful API`作為被呼叫者 > 其實就是用獲取到的服務地址,調API 下面要演示的是使用標準庫`net/http`的`httpclient`進行的比較原始的請求API的方法 **被呼叫的API** - EndPoint > /v1/list **服務呼叫的程式碼** ```go func main() { // 1.連線到consul cr := consul.NewRegistry(registry.Addrs("47.100.220.174:8500")) // 2.根據service name獲取對應的微服務列表 services, err := cr.GetService("productService") if err != nil { log.Fatal("cannot get service list") } // 3.使用random隨機獲取其中一個例項 next := selector.RoundRobin(services) svc, err := next() if err != nil { log.Fatal("cannot get service") } fmt.Println("[測試輸出]:", svc.Address) // 4. 請求獲取到的服務的API方法 resp, err := RequestApi(http.MethodGet, svc.Address, "/v1/list", nil) if err != nil { log.Fatal("request api failed") } fmt.Println("[請求API結果]:", resp) } // 簡單封裝一個請求api的方法 func RequestApi(method string, host string, path string, body io.Reader) (string, error) { // 1.如果沒有http開頭就給它加一個 if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") { host = "http://" + host } // 2. 新建一個request req, _ := http.NewRequest(method, host+path, body) // 3. 新建httpclient,並且傳入request client := http.DefaultClient res, err := client.Do(req) if err != nil { return "", err } defer res.Body.Close() // 4. 獲取請求結果 buff, err := ioutil.ReadAll(res.Body) if err != nil { return "", err } return string(buff), nil } ``` 如下可以呼叫成功: ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghkxg3qazaj3131081ab2.jpg) ## 3. 服務呼叫正確姿勢(初步) 上面我們呼叫api的方式是沒什麼問題,但是有缺點就是 - 但是假如有多個微服務,**每個微服務都會有很多重複的基礎設施**,go-micro就把這部分抽取出來,弄了一個plugin > https://github.com/micro/go-plugins 按照官方的說法: > go-plugins使您可以交換基礎設施結構,而不必重寫所有程式碼。這樣就可以在多個環境中執行相同的軟體,而無需進行大量工作 檢視`go-plugins`的組成部分,client中有http api ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghkyj0xz16j30ki0e8760.jpg) ### 3.1 服務端程式碼 服務端程式碼跟之前的差不太多 ```go package main import ( "github.com/gin-gonic/gin" "github.com/micro/go-micro/registry" "github.com/micro/go-micro/web" "github.com/micro/go-plugins/registry/consul" "gomicro-quickstart/product_service/model" "net/http" ) func main() { // 新增consul地址 cr := consul.NewRegistry(registry.Addrs("127.0.0.1:8500")) // 使用gin作為路由 router := gin.Default() v1 := router.Group("v1") { v1.POST("list", func(c *gin.Context) { var req ProdRequest if err := c.Bind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": "模型繫結失敗", }) c.Abort() return } c.JSON(http.StatusOK, gin.H{ "data": model.NewProductList(req.Size), }) }) } server := web.NewService( web.Name("ProductService"), // 當前微服務服務名 web.Registry(cr), // 註冊到consul web.Address(":8001"), // 埠 web.Metadata(map[string]string{"protocol": "http"}), // 元資訊 web.Handler(router)) // 路由 _ = server.Init() _ = server.Run() } type ProdRequest struct { Size int `json:"size"` } ``` 下面是返回的model物件程式碼 ```go package model import "strconv" type Product struct { Id int Name string } func NewProduct(id int, name string) *Product { return &Product{ Id: id, Name: name, } } func NewProductList(count int) []*Product { products := make([]*Product, 0) for i := 0; i < count; i++ { products = append(products, NewProduct(i+1, "productName"+strconv.Itoa(i+1))) } return products } ``` ### 3.2 客戶端呼叫(重要) 這裡使用了`go-plugins`中`client`下的`http`的包,優點是 - 可以直接通過服務名來呼叫服務,省去了`getService`的步驟 ```go package main import ( "context" "fmt" "log" "github.com/micro/go-micro/client" "github.com/micro/go-micro/client/selector" "github.com/micro/go-micro/registry" "github.com/micro/go-plugins/client/http" "github.com/micro/go-plugins/registry/consul" ) func main() { // 1. 註冊consul地址 cr := consul.NewRegistry(registry.Addrs("47.100.220.174:8500")) // 2. 例項化selector mySelector := selector.NewSelector( selector.Registry(cr), // 傳入上面的consul selector.SetStrategy(selector.RoundRobin), // 指定獲取例項的演算法 ) // 3. 請求服務 resp, err := callByGoPlugin(mySelector) if err != nil { log.Fatal("request API failed", err) } fmt.Printf("[服務呼叫結果]:\r\n %v", resp) } func callByGoPlugin(s selector.Selector) (map[string]interface{}, error) { // 1. 呼叫`go-plugins/client/http`包的函式獲取它們提供的httpClient gopluginClient := http.NewClient( client.Selector(s), // 傳入上面的selector client.ContentType("application/json"), // 指定contentType ) // 2. 新建請求物件,傳入: (1)服務名 (2)endpoint (3)請求引數 req := gopluginClient.NewRequest("ProductService", "/v1/list", map[string]interface{}{"size": 6}) // 3. 新建響應物件,並call請求,獲取響應 var resp map[string]interface{} err := gopluginClient.Call(context.Background(), req, &resp) if err != nil { return nil, err } return resp, nil } ``` 客戶端呼叫結果: ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghm4xki1krj31a406x0