go微服務系列(三) - 服務調用(http)

1. 關於服務調用

這裏的服務調用,咱們調用的能夠是http api也能夠是gRPC等。主要意思就是調用咱們從consul獲取到的服務的API。git

下面的全部示例以RESTful HTTP API爲例github

2. 基本方式調用服務

咱們在服務發現以後,確定要調用發現以後的服務,這裏的服務能夠是http的RESTful API也能夠是RPC服務等,這裏之前面的定義的productServiceRESTful API做爲被調用者web

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

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

被調用的APIapi

  • EndPoint

/v1/listapp

服務調用的代碼dom

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
}

客戶端調用結果:

相關文章
相關標籤/搜索