文章目录
本文暂不介绍GRPC,只介绍通过HTTP方式进行服务间的API调用。且所有示例均采用Go Mod方式管理依赖包。
1.Go-Micro微服务框架简介
Go-Micro类似Java中的SpringCloud,虽然生态上有一些区别,但是可以类比比较。官当Github地址为 https://github.com/micro/go-micro,下载安装依赖命令:
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作为注册中心,官方下载地址:https://www.consul.io/downloads.html。
Windows环境安装:
- 直接下载后将
consul.exe
解压出来; - 将其所解压到的目录文件名添加到path环境变量;
- 在 cmd 命令窗口中执行:
consul agent -dev
命令即可启动Consul服务 - Consul 自带 UI 管理界面,访问地址:
http://localhost:8500
,可以看到当前注册的服务:
Linux环境安装:
- 直接通过官网Linux版本Download处对应的地址下载:
wget https://releases.hashicorp.com/consul/1.9.0/consul_1.9.0_linux_amd64.zip
; - 解压:
unzip consul_1.9.0_linux_amd64.zip
; - 修改权限:
chmod 777 consul
; - 整理一下,将
consul
文件夹复制到/usr/local/bin/
目录下:cp consul /usr/local/bin/
; - 输入consul命令查看安装是否成功:
consul version
; - 启动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 服务发现
从注册中心拿服务的方法也比较简单,简单几步即可拿到服务信息:
- 同服务注册一样,首先创建Consul注册中心对象:
consul.NewRegistry()
; - 通过Consul对象调用
GetService("cas")
方法拿到服务节点切片,参数为注册的服务名,所以在上文注册服务时指定的服务名很关键; - 通过Go-Micro提供的
selector
包拿到具体的服务节点,该包提供2种常见的负载均衡算法,RoundRobin(轮询)
和Random(随机)
。对应方法返回的是一个Next
方法,Next
方法调用后才返回具体的服务节点,可以自行查看源码; - 拿到具体的服务节点结构体对象后,则可以通过服务的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,
})
})