go-micro教程 — 第二章 go-micro v3 使用Gin、Etcd
前言
注意
:本文使用的Go版本为 go1.17.6
使用 1.18.x
版本或其他版本在操作时总是碰到各种问题,比如依赖下载异常、版本不一致等问题。当然也可能是我电脑的问题。
参考文档:go 微服务之go-micro v3+gin
本文代码:https://gitee.com/XiMuQi/go-micro-demo,对三、四、五步的操作均打了标签。
1 启动Etcd集群
在使用Etcd作为注册中心前需要先有Etcd节点或者Etcd集群,Etcd集群的安装配置及启动,详见:Etcd教程 — 第四章 Etcd集群安全配置。
2 创建项目并安装相关依赖
注意
:2.3到 2.6 步骤执行完同时会在 ${GOPATH}\bin
下生成exe文件。
## 2.1 创建项目
本文创建的项目名称为 go-micro-demo
2.2 初始化项目
go mod init go-micro-demo
2.3 安装 proto
2.4 安装 protoc-gen-go
go get github.com/golang/protobuf/protoc-gen-go
2.5 安装 protoc-gen-micro
注意
:是安装 asim
下的而不是micro
下的,因为micro
下的始终下载不了,这个也是go micro 3.0 框架。
go get github.com/asim/go-micro/cmd/protoc-gen-micro/v3
2.6 安装micro v3 构建工具
- 需要用到Micro 3.0 的micro工具,主要是用于快速构建micro项目,但是不使用这个的配置,用下面2的
go install github.com/micro/micro/v3@latest
- 下载go micro 3.0 库,下面库没有上面micro构建工具
go get github.com/asim/go-micro/v3
2.7 安装gin
go get -u github.com/gin-gonic/gin
3 开发项目
3.1 创建web
模块
在 go-micro-demo
下创建web
文件夹
3.2 进入web
文件夹
创建main.go
文件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
const addr = ":9000"
func Index(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"message": "Gin访问成功",
})
}
func main() {
r := gin.Default()
r.Handle("GET", "/", Index)
if err := r.Run(addr); err != nil {
fmt.Println("err")
}
}
然后执行 go run .
或者在Goland中执行main函数,启动web服务。
启动成功后,在浏览器访问 http://127.0.0.1:9000, 得到如下响应:
// 20220704141435
// http://127.0.0.1:9000/
{
"message": "Gin访问成功"
}
3.3 创建services下的子模块
在 go-micro-demo\services
下执行micro new test
命令,创建后端测试
服务模块。
H:\Goland\go-micro-demo\services>micro new test
Creating service test
.
├── micro.mu
├── main.go
├── generate.go
├── handler
│ └── test.go
├── proto
│ └── test.proto
├── Dockerfile
├── Makefile
├── README.md
├── .gitignore
└── go.mod
download protoc zip packages (protoc-$VERSION-$PLATFORM.zip) and install:
visit https://github.com/protocolbuffers/protobuf/releases
compile the proto file test.proto:
cd test
make init
go mod vendor
make proto
3.4 删除go.mod文件
删除services\test
模块下的go.mod
文件,统一使用go-micro-demo
下的go.mod
。
3.5 根据proto生成pb文件
3.5.1 修改 test.proto
主要是修改go_package
指定生成pb文件的路经,这里是将 ./改为 ../
即可。
syntax = "proto3";
package test;
//option go_package = "./proto;test";
//将 ./改为 ../
option go_package = "../proto;test";
service Test {
rpc Call(Request) returns (Response) {}
rpc Stream(StreamingRequest) returns (stream StreamingResponse) {}
rpc PingPong(stream Ping) returns (stream Pong) {}
}
message Message {
string say = 1;
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
message StreamingRequest {
int64 count = 1;
}
message StreamingResponse {
int64 count = 1;
}
message Ping {
int64 stroke = 1;
}
message Pong {
int64 stroke = 1;
}
3.5.2 生成pb文件
进入到 go-micro-demo\services\test\proto
。执行生成命令:
protoc --proto_path=. --micro_out=. --go_out=. *.proto
执行完后可以在go-micro-demo\services\test\proto
看到生成的test.pb.go
和test.pb.micro.go
文件。
3.6 修改handler/test.go文件
修改 micro-demo\services\test\handler
下的 test.go
主要是修改引入的 pb文件位置。
import (
"context"
log "github.com/micro/micro/v3/service/logger"
test "go-micro-demo/services/test/proto"//test "test/proto"
)
3.7 修改services/test/main.go文件
- 修改引入的pb文件位置。
- 将原来
micro
下的包替换成asim
下的。
package main
import (
//修改 1
"go-micro-demo/services/test/handler" //"test/handler"
pb "go-micro-demo/services/test/proto" //pb "test/proto"
//修改 2 替换micro为asim
service "github.com/asim/go-micro/v3" //"github.com/micro/micro/v3/service"
"github.com/asim/go-micro/v3/logger" //"github.com/micro/micro/v3/service/logger"
)
func main() {
//修改 3
srv := service.NewService( // service.New
service.Name("test"),
service.Version("latest"),
)
// Register handler
//修改 4
_ = pb.RegisterTestHandler(srv.Server(), new(handler.Test))
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}
然后执行 go run .
或者在Goland中执行main函数,启动名为test
的服务。
如果启动时报:
handler\test.go:6:2: no required module provides package github.com/micro/micro/v3/service/logger; to add it:
go get github.com/micro/micro/v3/service/logger
执行go mod tidy
后重启。
启动成功后,控制台显示内容:
API server listening at: 127.0.0.1:58596
2022-07-04 14:50:44 file=v3@v3.7.1/service.go:206 level=info Starting [service] test
2022-07-04 14:50:44 file=server/rpc_server.go:820 level=info Transport [http] Listening on [::]:58603
2022-07-04 14:50:44 file=server/rpc_server.go:840 level=info Broker [http] Connected to 127.0.0.1:58604
2022-07-04 14:50:45 file=server/rpc_server.go:654 level=info Registry [mdns] Registering node: test-69072bf3-9123-4177-88cb-9d898a8219e5
※3.8 通过web模块直接调用services下的test服务
3.8.1 创建web/handler/testHandler.go
文件
package handler
import (
"github.com/asim/go-micro/v3"
"github.com/gin-gonic/gin"
testpb "go-micro-demo/services/test/proto"
"net/http"
)
func Index(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"message": "index",
})
}
func ServiceOne(c *gin.Context) {
service := micro.NewService()
service.Init()
// 创建微服务客户端
client := testpb.NewTestService("test", service.Client())
// 调用服务
rsp, err := client.Call(c, &testpb.Request{
Name: c.Query("key"),
})
if err != nil {
c.JSON(200, gin.H{"code": 500, "msg": err.Error()})
return
}
c.JSON(200, gin.H{"code": 200, "msg": rsp.Msg})
}
3.8.2 修改web/main.go文件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"go-micro-demo/web/handler"
"net/http"
)
const addr = ":9000"
func Index(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"message": "Gin访问成功",
})
}
func main() {
r := gin.Default()
r.Handle("GET", "/", Index)
r.Handle("GET", "/test-req", handler.ServiceOne)
if err := r.Run(addr); err != nil {
fmt.Println("err")
}
}
然后执行 go run .
或者在Goland中执行main函数,重新启动web服务。
启动结果:
[GIN-debug] GET / --> main.Index (3 handlers)
[GIN-debug] GET /test-req --> go-micro-demo/web/handler.ServiceOne (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :9000
浏览器访问http://127.0.0.1:9000/test-req?key=哈哈
,得到如下响应:
// 20220704150031
// http://127.0.0.1:9000/test-req?key=%E5%93%88%E5%93%88
{
"code": 200,
"msg": "Hello 哈哈"
}
4 使用consul作为注册中心注册服务
4.1 安装配置consul
安装配置 consul,详见 Consul安装。
启动consul后,使用浏览器访问http://192.168.1.222:8500
,如果能访问成功则说明consul正常。
4.2 项目加入consul包
go get -u github.com/asim/go-micro/plugins/registry/consul/v3
4.3 修改services/test/main.go
- 加入 consul包。
- 配置 consul 的地址及注册的名称。
- 修改 注册服务的方式为consul方式。
主要修改或加入的地方 consul 1
、consul 2
、consul 3
package main
import (
//修改 1
"go-micro-demo/services/test/handler" //"test/handler"
pb "go-micro-demo/services/test/proto" //pb "test/proto"
//修改 2 替换micro为asim
service "github.com/asim/go-micro/v3" //"github.com/micro/micro/v3/service"
"github.com/asim/go-micro/v3/logger" //"github.com/micro/micro/v3/service/logger"
//consul 1
"github.com/asim/go-micro/plugins/registry/consul/v3"
"github.com/asim/go-micro/v3/registry"
)
//consul 2
const (
ServerName = "consul-test"
ConsulAddr = "192.168.1.222:8500"
)
func main() {
// consul 3
consulReg := consul.NewRegistry(
registry.Addrs(ConsulAddr),
)
srv := service.NewService(
service.Name(ServerName), // 服务名字
service.Registry(consulReg),// 注册中心
)
// Register handler
//修改 4
_ = pb.RegisterTestHandler(srv.Server(), new(handler.Test))
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}
如果引入的包报红,执行go mod tidy
即可。
执行go run .,重新启动services/test模块的名为consul-test的服务。然后访问Consul 的UI界面:http://192.168.1.222:8500,可以看到服务名称为consul-test
的节点已经注册到了consul注册中心
上。
4.4 修改web/handler/testHandler.go
主要修改或加入的地方 consul 1
、consul 2
、consul 3
package handler
import (
"github.com/asim/go-micro/v3"
"github.com/gin-gonic/gin"
testpb "go-micro-demo/services/test/proto"
"net/http"
//consul 1
"github.com/asim/go-micro/plugins/registry/consul/v3"
"github.com/asim/go-micro/v3/registry"
)
func Index(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"message": "index",
})
}
func ServiceOne(c *gin.Context) {
//consul 2
consulReg := consul.NewRegistry(
registry.Addrs("192.168.1.222:8500"),
)
service := micro.NewService(
micro.Registry(consulReg), //设置注册中心
)
service.Init()
//consul 3 创建微服务客户端【将服务名称从test改为为consul-test】
client := testpb.NewTestService("consul-test", service.Client())
// 调用服务
rsp, err := client.Call(c, &testpb.Request{
Name: c.Query("key"),
})
if err != nil {
c.JSON(200, gin.H{"code": 500, "msg": err.Error()})
return
}
c.JSON(200, gin.H{"code": 200, "msg": rsp.Msg})
}
执行go run .,重启web。
4.5 验证
浏览器访问http://127.0.0.1:9000/test-req?key=哈哈
,得到如下响应:
// 20220704212337
// http://127.0.0.1:9000/test-req?key=%E5%93%88%E5%93%88
{
"code": 200,
"msg": "Hello 哈哈"
}
4.6 关停 Consul
如果关停Consul服务,再重新访问http://127.0.0.1:9000/test-req?key=哈哈
,会得到如下响应:
// 20220704212448
// http://127.0.0.1:9000/test-req?key=%E5%93%88%E5%93%88
{
"code": 500,
"msg": "{\"id\":\"go.micro.client\",\"code\":500,\"detail\":\"error selecting consul-test node: Get \\\"http: //192.168.1.222: 8500/v1/health/service/consul-test?stale=\\\": dial tcp 192.168.1.222:8500: connectex: No connection could be made because the target machine actively refused it.\",\"status\":\"Internal Server Error\"}"
}
客户端和服务端的控制台同样显示如下信息,等到Consul恢复正常即会停止报错:
2022-07-04T21:26:03.837+0800 [ERROR] watch: Watch errored: type=services error="Get "http://192.168.1.222:8500/v1/catalog/services": dial tcp 192.168.1.222:8500: connectex: No conn
ection could be made because the target machine actively refused it." retry=1m20s
2022-07-04T21:26:54.163+0800 [ERROR] watch: Watch errored: type=service error="Get "http://192.168.1.222:8500/v1/health/service/consul-test": dial tcp 192.168.1.222:8500: connectex
: No connection could be made because the target machine actively refused it." retry=2m5s
2022-07-04T21:27:25.895+0800 [ERROR] watch: Watch errored: type=services error="Get "http://192.168.1.222:8500/v1/catalog/services": dial tcp 192.168.1.222:8500: connectex: No conn
ection could be made because the target machine actively refused it." retry=2m5s
5 使用etcd作为注册中心
5.1 项目加入etcd包
go get -u "github.com/asim/go-micro/plugins/registry/etcd/v3"
5.2 修改services/test/main.go
将consul
替换成etcd
。
- 加入
etcd
包。 - 配置
etcd
的地址及注册的名称。 - 修改注册服务的方式为
etcd
方式。
主要修改或加入的地方 etcd 1
、etcd 2
、etcd 3
package main
import (
//修改 1
"go-micro-demo/services/test/handler" //"test/handler"
pb "go-micro-demo/services/test/proto" //pb "test/proto"
//修改 2 替换micro为asim
service "github.com/asim/go-micro/v3" //"github.com/micro/micro/v3/service"
"github.com/asim/go-micro/v3/logger" //"github.com/micro/micro/v3/service/logger"
//etcd 1
"github.com/asim/go-micro/plugins/registry/etcd/v3"
"github.com/asim/go-micro/v3/registry"
)
//etcd 2
const (
ServerName = "etcd-test"
EtcdAddr = "192.168.1.221:2379"
)
func main() {
//etcd 3
etcdReg := etcd.NewRegistry(
registry.Addrs(EtcdAddr),
)
srv := service.NewService(
service.Name(ServerName), // 服务名字
service.Registry(etcdReg), // 注册中心
)
// Register handler
//修改 4
_ = pb.RegisterTestHandler(srv.Server(), new(handler.Test))
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}
5.3 修改web/handler/testHandler.go
package handler
import (
"github.com/asim/go-micro/plugins/registry/consul/v3"
"github.com/asim/go-micro/v3"
"github.com/gin-gonic/gin"
testpb "go-micro-demo/services/test/proto"
"net/http"
//etcd 1
"github.com/asim/go-micro/plugins/registry/etcd/v3"
"github.com/asim/go-micro/v3/registry"
)
func Index(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"message": "index",
})
}
func ServiceOne(c *gin.Context) {
// etcd 2
etcdReg := etcd.NewRegistry(
registry.Addrs("192.168.1.221:2379"),
)
service := micro.NewService(
micro.Registry(etcdReg), //设置注册中心
)
service.Init()
//etcd 3 创建微服务客户端【将服务名称从consul-test改为etcd-test】
client := testpb.NewTestService("etcd-test", service.Client())
// 调用服务
rsp, err := client.Call(c, &testpb.Request{
Name: c.Query("key"),
})
if err != nil {
c.JSON(200, gin.H{"code": 500, "msg": err.Error()})
return
}
c.JSON(200, gin.H{"code": 200, "msg": rsp.Msg})
}
执行go run .,重启web。
5.4 验证
浏览器端发送 http://127.0.0.1:9000/test-req?key=哈哈
,返回结果:
// 20220704215208
// http://127.0.0.1:9000/test-req?key=%E5%93%88%E5%93%88
{
"code": 200,
"msg": "Hello 哈哈"
}
5.5 关停 Etcd
如果关停Etcd服务,再重新访问 http://127.0.0.1:9000/test-req?key=哈哈
,会得到如下响应:
// 20220704215457
// http://127.0.0.1:9000/test-req?key=%E5%93%88%E5%93%88
{
"code": 500,
"msg": "{\"id\":\"go.micro.client\",\"code\":500,\"detail\":\"error selecting etcd-test node: context deadline exceeded\",\"status\":\"Internal Server Error\"}"
}
客户端的控制台同样显示如下信息:
{"level":"warn","ts":"2022-07-04T21:54:57.129+0800","logger":"etcd-client","caller":"v3@v3.5.0/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"etcd-endp
oints://0xc000024c40/#initially=[192.168.1.221:2379]","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error: connection error:
desc = \"transport: Error while dialing dial tcp 192.168.1.221:2379: connectex: No connection could be made because the target machine actively refused it.\""}
注意
:go-micro需要等到Etcd恢复正常后重启相关服务才能正常使用。
5.6 查看服务注册情况
Etcd虽然没有和Consul一样的UI界面查看服务注册情况。但是可以通过查看指定前缀的所有键值对来查看go-micro的注册信息:
etcdctl get --prefix /micro
#或
etcdctl get --prefix /micro/registry
go-micro 注册时的节点ID信息:
/micro/registry/etcd-test/etcd-test-3bbd2f13-d24e-41dd-9b4e-e8f5b601a549
注意:后面的etcd-test-3bbd2f13-d24e-41dd-9b4e-e8f5b601a549
是和services/test/main.go
运行后在控制台显示的节点ID完全一致。
API server listening at: 127.0.0.1:58099
2022-07-04 23:11:48 file=v3@v3.7.1/service.go:206 level=info Starting [service] etcd-test
2022-07-04 23:11:48 file=server/rpc_server.go:820 level=info Transport [http] Listening on [::]:58104
2022-07-04 23:11:48 file=server/rpc_server.go:840 level=info Broker [http] Connected to 127.0.0.1:58105
2022-07-04 23:11:48 file=server/rpc_server.go:654 level=info Registry [etcd] Registering node: etcd-test-3bbd2f13-d24e-41dd-9b4e-e8
f5b601a549
其余注册信息:
/micro/registry/etcd-test/etcd-test-3bbd2f13-d24e-41dd-9b4e-e8f5b601a549
{
"name":"etcd-test",
"version":"latest",
"metadata":null,
"endpoints":[
{
"name":"Test.Call",
"request":{
"name":"Request",
"ty pe":"Request",
"values":[
{
"name":"MessageState",
"type":"MessageState",
"values":[
{
"name":"NoUnkeyedLiterals",
"type":"NoUnkeyedLiterals",
"values":null
},
{
"name":"DoNotCompare",
"type":"DoNotCompare",
"values":null
},
{
"name":"DoNotCopy",
"type":"DoNotCopy",
"values":null
},
{
"name":"MessageInfo",
"type":"MessageInfo",
"values":null
}
]
},
{
"name":"int32",
"type":"int32",
"va lues":null
},
{
"name":"unknownFields",
"type":"[]uint8",
"values":null
},
{
"name":"name",
"type":"string",
"values":null
}
]
},
"re sponse":{
"name":"Response",
"type":"Response",
"values":[
{
"name":"MessageState",
"type":"MessageState",
"values":[
{
"name":"NoUnkeyedLiterals",
"type":"NoUnkeyedLiterals",
"values":null
},
{
"name":"DoNotCompare",
"type":"DoNotCompare",
"values":null
},
{
"name":"DoNotCopy",
"type":"DoNotCopy",
"values":null
},
{
"name":"MessageInfo",
"type":"MessageInfo",
"values":null
}
]
},
{
"n ame":"int32",
"type":"int32",
"values":null
},
{
"name":"unknownFields",
"type":"[]uint8",
"values":null
},
{
"name":"msg",
"type":"string",
"values":null
}
]
},
"metadata":{
}
},
{
"name":"Test.PingPong",
"request":{
"name":"Context",
"type":"Context",
"values":null
},
"response":{
"name":"Stream",
"type":"Stream",
"values":null
},
"metadata":{
"stream":"true"
}
},
{
"name":"Test.Stream",
" request":{
"name":"Context",
"type":"Context",
"values":null
},
"response":{
"name":"Stream",
"type":"Stream",
"values":null
},
" metadata":{
"stream":"true"
}
}
],
"nodes":[
{
"id":"etcd-test-3bbd2f13-d24e-41dd-9b4e-e8f5b601a549",
"address":"192.168.91.1:58104",
"metadata":{
"broker":"http",
"protocol":"mucp",
"registry":"etcd",
"server":"mucp",
"transport":"http"
}
}
]
}