Go-Micro微服务框架使用

925 阅读6分钟

本文暂不介绍GRPC,只介绍通过HTTP方式进行服务间的API调用。且所有示例均采用Go Mod方式管理依赖包。

1.Go-Micro微服务框架简介

Go-Micro类似Java中的SpringCloud,虽然生态上有一些区别,但是可以类比比较。官当Github地址为 github.com/micro/go-mi…,下载安装依赖命令:

go get -u github.com/micro/go-micro

2.创建Web服务

Golang本身提供了丰富的http包,并且Gin等Web框架实现一个Web服务,但是为了贴合Go-Micro框架的统一规范,所以需要通过使用Go-Micro的github.com/micro/go-micro/web包来创建一个Web服务。

第一种方式:

直接通过web包创建一个服务,参数可选,也比较简单易懂,请自行Ctrl+左键查看源码。创建服务后可以直接像原生的http包一样使用。

func main() {
	service := web.NewService(
		web.Name("cas"),
		web.Address(":8001"),
	)
	service.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
		// handler
	})
	service.Run()
}

第二种方式: 原生的http包在很多情况下处理并不是非常高效,只是提供了一些基础功能,所以Go-Micro提供集成第三方Web框架如:Gin。首先下载安装:

go get -u "github.com/gin-gonic/gin"

Go-Micro整合Gin示例:

func main() {
	engine := gin.Default()
	engine.GET("/hello", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "hello,world",
		})
	})
	service := web.NewService(
		web.Name("cas"),
		web.Address(":8001"),
		web.Handler(engine),
	)
	service.Run()
}

除了上述的方式直接指定Web服务的相关参数,我们也可以通过命令行参数启动Web服务,这样我们可以在命令行通过指定不同的服务名和端口,快速的多开同一个服务。虽然实际开发部署环境很少用这种方式,但在我们自己调试服务注册与发现时,会非常有用。   示例如下,只需要调用Init()方法即可,可自行查看源码,源码中可查看有关的命令函参数名:

func main() {
  engine := gin.Default()
  engine.GET("/hello", func(c *gin.Context) {
  	c.JSON(http.StatusOK, gin.H{
  		"msg": "hello,world",
  	})
  })
  service := web.NewService(
  	//web.Name("cas"),
  	//web.Address(":80"),
  	web.Handler(engine),
  )
  service.Init()
  service.Run()
}

启动命令:

go run main.go -server_name cas -server_address :8001

3.服务注册

3.1 Consul

以Consul作为注册中心,官方下载地址:www.consul.io/downloads.h…

Windows环境安装:

  1. 直接下载后将consul.exe解压出来;
  2. 将其所解压到的目录文件名添加到path环境变量;
  3. 在 cmd 命令窗口中执行:consul agent -dev命令即可启动Consul服务
  4. Consul 自带 UI 管理界面,访问地址:http://localhost:8500 ,可以看到当前注册的服务: 在这里插入图片描述

Linux环境安装:

  1. 直接通过官网Linux版本Download处对应的地址下载:wget https://releases.hashicorp.com/consul/1.9.0/consul_1.9.0_linux_amd64.zip
  2. 解压:unzip consul_1.9.0_linux_amd64.zip
  3. 修改权限:chmod 777 consul
  4. 整理一下,将consul文件夹复制到/usr/local/bin/目录下:cp consul /usr/local/bin/
  5. 输入consul命令查看安装是否成功:consul version
  6. 启动consul,并通过-client配置外网主机可访问:consul agent -dev -client=0.0.0.0

当然其实最方便的就是直接通过docker拉个镜像,直接映射端口跑起来就可以了~

服务注册到Consul:

在安装好Consul并启动好服务后,下载Go-Micro提供的插件包go-plugins

go get -u github.com/micro/go-plugins

该包包含了Consul和其他的众多插件(如:eureka等),基本的使用方法是使用consul.NewRegistry()接口创建注册中心对象,然后将其集成到Go-Micro提供的Web服务配置中:

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"
	"net/http"
)

func main() {
	consulReg := consul.NewRegistry(registry.Addrs(":8500"))
	engine := gin.Default()
	engine.GET("/hello", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "hello,world",
		})
	})
	service := web.NewService(
		web.Name("cas"),
		web.Address(":8001"),
		web.Registry(consulReg),
		web.Handler(engine),
	)
	service.Init()
	service.Run()
}

可以在Consul管理UI看到服务已经注册成功~ 在这里插入图片描述

3.2 etcd

4 服务发现

从注册中心拿服务的方法也比较简单,简单几步即可拿到服务信息:

  1. 同服务注册一样,首先创建Consul注册中心对象:consul.NewRegistry()
  2. 通过Consul对象调用GetService("cas")方法拿到服务节点切片,参数为注册的服务名,所以在上文注册服务时指定的服务名很关键;
  3. 通过Go-Micro提供的selector包拿到具体的服务节点,该包提供2种常见的负载均衡算法,RoundRobin(轮询)Random(随机)。对应方法返回的是一个Next方法,Next方法调用后才返回具体的服务节点,可以自行查看源码;
  4. 拿到具体的服务节点结构体对象后,则可以通过服务的IP等信息,发起HTTP的API调用。

示例代码和运行结果:

package main

import (
	"github.com/micro/go-micro/client/selector"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-plugins/registry/consul"
	"log"
)

func main() {
	consulReg := consul.NewRegistry(registry.Addrs(":8500"))
	service, err := consulReg.GetService("cas")
	if err != nil {
		log.Fatal("get service from consul err :",err)
	}
	node, err := selector.RoundRobin(service)()
	if err != nil {
		log.Fatal("get service node err :",err)
	}
	log.Printf("service node : %+v", node)
}
//运行打印的log内容
2020-12-09 10:50:46.501937 I | service node : &{Id:ec20b3c9-7531-4921-89bc-73ee7e233525 Address:192.168.152.1:8002 Metadata:map[]}

如果启动时发生依赖报错,请参考我的另一篇博文对该问题的解决:Go-Micro启动依赖报错解决方法 。如要查看轮询或随机效果,可以自己写一个for循环间隔获取节点,查看节点信息的变化情况~

5.服务调用

5.1 HTTP基本调用方式

最基础的就是通过Golang官方提供的http包发起HTTP请求。直接在上文服务发现示例代码的基础上,在拿到服务节点信息后,发情API请求。

package main

import (
	"fmt"
	"github.com/micro/go-micro/client/selector"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-plugins/registry/consul"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

func main() {
	consulReg := consul.NewRegistry(registry.Addrs(":8500"))
	// 服务发现
	service, err := consulReg.GetService("cas")
	if err != nil {
		log.Fatalf("get service from consul err : %+v",err)
	}
	node, err := selector.RoundRobin(service)()
	if err != nil {
		log.Fatalf("get service node err : %+v",err)
	}
	log.Printf("service node : %+v", node)
	// 服务调用
	url := fmt.Sprintf("http://%s/hello", node.Address)
	reqBody := strings.NewReader("")
	request, err := http.NewRequest(http.MethodGet, url, reqBody)
	if err != nil {
		log.Fatalf("create request err : %+v",err)
	}
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		log.Fatalf("send request err : %+v",err)
	}
	defer response.Body.Close()
	rspBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatalf("read  response body err : %+v",err)
	}
	log.Printf("rsp body : %+v", string(rspBody))
}

//运行结果:
//2020-12-09 11:45:16.422562 I | service node : &{Id:bda22dae-9bc2-46db-b325-bcc9ab2b6778 Address:192.168.152.1:8001 Metadata:map[]}
//2020-12-09 11:45:16.436525 I | rsp body : {"msg":"hello,world"}

5.1 Go-Micro中的HTTP调用方式(推荐)

这种方式也是使用http包,但和上述Golang官方提供的不同,这种方式使用的是Go-Micro的插件包go-plugins,具体为:"github.com/micro/go-plugins/client/http"包,go-plugins包除了有http client的基本功能,还支持Selector参数,自动选取服务,并支持json、protobuf等数据格式。

上述的基础调用方式可以明显发现调用过程其实还是比较繁琐的,而这种方法流程上简化了很多,插件对一些步骤进行了封装和自动化处理,步骤主要为将注册中心放到Selector选择器中,然后基于选择器创建httpClient,通过该客户端来发起请求,调用服务。需要注意的是,插件默认认为服务调用都是通过POST请求的方式,所以指定的接口一定要为POST:

package main

import (
	"context"
	"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"
	"log"
)

func main() {
	consulReg := consul.NewRegistry(registry.Addrs(":8500"))
	selector := selector.NewSelector(
		selector.Registry(consulReg),
		selector.SetStrategy(selector.RoundRobin),
	)
	httpClient := http.NewClient(
		// 选择器
		client.Selector(selector),
		// 响应格式默认格式protobuf,设置为json
		client.ContentType("application/json"),
		)
	req := map[string]string{}
	request := httpClient.NewRequest("cas", "/hello", req)
	rsp := map[string]interface{}{}
	err := httpClient.Call(context.Background(), request, &rsp)
	if err != nil {
		log.Fatalf("request err: %+v", err)
	}
	log.Printf("%+v",rsp)
}

注意:如果出现运行报错:{"id":"go.micro.client","code":500,"detail":"none available","status":"Internal Server Error"} ,可以查看我的另一篇博文:Go-Micro客户端请求报500错误的解决方法,对该问题有详细的分析和解决。

其他需要注意的是服务调用这边只支持json形式传参,所以如Gin中请使用Bind()的方式获取请求参数:

engine := gin.Default()
	engine.POST("/hello", func(c *gin.Context) {
		req := struct {
			Name string `json:"name"`
		}{}
		c.BindJSON(&req)
		c.JSON(http.StatusOK, gin.H{
			"msg": "hello,world",
			"name": req.Name,
		})
	})