1. 关于服务调用

这里的服务调用,我们调用的可以是http api也可以是gRPC等。主要意思就是调用我们从consul获取到的服务的API。

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

2. 基本方式调用服务

我们在服务发现之后,肯定要调用发现之后的服务,这里的服务可以是http的RESTful API也可以是RPC服务等,这里以前面的定义的productServiceRESTful API作为被调用者

下面要演示的是使用标准库net/httphttpclient进行的比较原始的请求API的方法

被调用的API

  • EndPoint

服务调用的代码

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
}

如下可以调用成功:

go微服务系列(三) - 服务调用(http)-LMLPHP

3. 服务调用正确姿势(初步)

上面我们调用api的方式是没什么问题,但是有缺点就是

  • 但是假如有多个微服务,每个微服务都会有很多重复的基础设施,go-micro就把这部分抽取出来,弄了一个plugin

按照官方的说法:

查看go-plugins的组成部分,client中有http api

go微服务系列(三) - 服务调用(http)-LMLPHP

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
}

客户端调用结果:

go微服务系列(三) - 服务调用(http)-LMLPHP

08-11 10:06