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

寶樹吶發表於2020-08-10

1. 關於服務呼叫

這裡的服務呼叫,我們呼叫的可以是http api也可以是gRPC等。主要意思就是呼叫我們從consul獲取到的服務的API。

下面的所有示例以RESTful HTTP API為例

2. 基本方式呼叫服務

我們在服務發現之後,肯定要呼叫發現之後的服務,這裡的服務可以是http的RESTful API也可以是RPC服務等,這裡以前面的定義的productServiceRESTful API作為被呼叫者

其實就是用獲取到的服務地址,調API

下面要演示的是使用標準庫net/httphttpclient進行的比較原始的請求API的方法

被呼叫的API

  • EndPoint

/v1/list

服務呼叫的程式碼

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
}

如下可以呼叫成功:

3. 服務呼叫正確姿勢(初步)

上面我們呼叫api的方式是沒什麼問題,但是有缺點就是

  • 但是假如有多個微服務,每個微服務都會有很多重複的基礎設施,go-micro就把這部分抽取出來,弄了一個plugin

https://github.com/micro/go-plugins

按照官方的說法:

go-plugins使您可以交換基礎設施結構,而不必重寫所有程式碼。這樣就可以在多個環境中執行相同的軟體,而無需進行大量工作

檢視go-plugins的組成部分,client中有http api

3.1 服務端程式碼

服務端程式碼跟之前的差不太多

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物件程式碼

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-pluginsclient下的http的包,優點是

  • 可以直接通過服務名來呼叫服務,省去了getService的步驟
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
}

客戶端呼叫結果:

相關文章