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
服務呼叫的程式碼
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
按照官方的說法:
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-plugins
中client
下的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
}
客戶端呼叫結果: