Go-micro学习笔记(1)http调用
1. go-micro体验
1package main
2
3import (
4 "fmt"
5 "github.com/micro/go-micro/web"
6 "net/http"
7)
8
9func main(){
10 service := web.NewService(web.Address("127.0.0.1:8000"))
11 // 使用http原生的handlefunc
12 service.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
13 w.Write([]byte("hello world"))
14 })
15 err := service.Run()
16 if err != nil {
17 fmt.Println(err)
18 }
19}
测试结果
1curl http://127.0.0.1:8000
2hello world
2. 使用web框架gin
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5 "github.com/micro/go-micro/web"
6 "net/http"
7)
8
9func main() {
10 r := gin.Default()
11 r.Handle("GET", "/", func(c *gin.Context) {
12 c.JSON(http.StatusOK, gin.H{
13 "code": http.StatusOK,
14 })
15 })
16 service := web.NewService(
17 web.Name("demo_service"),
18 web.Address("127.0.0.1:8000"),
19 web.Handler(r),
20 )
21 service.Run()
22}
测试结果
1curl http://127.0.0.1:8000
2{"code":200}
3. 使用consul作为服务发现
这里需要你的consul是正常运行的,不然连接不上consul程序会直接退出。
最简单的方法启动单机版本的consul
1consul agent -dev
main.go
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5 "github.com/micro/go-micro/registry"
6 "github.com/micro/go-micro/web"
7 "github.com/micro/go-plugins/registry/consul"
8 "net/http"
9)
10
11// consul 服务注册
12func main(){
13 consulReg := consul.NewRegistry(
14 registry.Addrs("127.0.0.1:8500"))
15
16 r := gin.Default()
17 r.Handle("GET", "/", func(c *gin.Context) {
18 c.JSON(http.StatusOK, gin.H{
19 "code": http.StatusOK,
20 })
21 })
22 service := web.NewService(
23 web.Address("127.0.0.1:8000"),
24 web.Handler(r),
25 web.Registry(consulReg),
26 )
27 service.Run()
28}
运行之后服务并没有不一样,只是可以通过consul发现到这个服务.
4. 调用函数返回json数据
main.go
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5 "github.com/micro/go-micro/registry"
6 "github.com/micro/go-micro/web"
7 "github.com/micro/go-plugins/registry/consul"
8 "net/http"
9)
10
11// 调用函数返回json数据
12func main(){
13
14 consulReg := consul.NewRegistry(
15 registry.Addrs("127.0.0.1:8500"))
16
17 r := gin.Default()
18 // 路由分组
19 v1Group := r.Group("/v1")
20 {
21 v1Group.Handle("GET", "/prods", func(c *gin.Context) {
22 c.JSON(http.StatusOK, NewProdList(5))
23 })
24 }
25
26 service := web.NewService(
27 web.Name("ProdSrv"),
28 web.Address("127.0.0.1:8000"),
29 web.Handler(r),
30 web.Registry(consulReg),
31 )
32 service.Run()
33}
prod.go
1package main
2
3import "strconv"
4
5type ProdModel struct {
6 ProdId int
7 ProdName string
8}
9
10func NewProd(id int, name string) *ProdModel{
11 return &ProdModel{ProdId:id, ProdName:name}
12}
13
14func NewProdList(n int) []*ProdModel{
15 ret := make([]*ProdModel,0)
16 for i:=0 ; i<n ; i++ {
17 ret = append(ret, NewProd(100+i, "Prod"+strconv.Itoa(i)))
18 }
19 return ret
20}
运行
1go mod init
2go run main.go prod.go
访问测试
1curl http://127.0.0.1:8000/v1/prods
2[{"ProdId":100,"ProdName":"Prod0"},{"ProdId":101,"ProdName":"Prod1"},{"ProdId":102,"ProdName":"Prod2"},{"ProdId":103,"ProdName":"Prod3"},{"ProdId":104,"ProdName":"Prod4"}]
5. consul服务发现
前提是要启动consul并运行前面的ProdSrv服务
1package main
2
3import (
4 "github.com/micro/go-micro/client/selector"
5 "github.com/micro/go-micro/registry"
6 "github.com/micro/go-plugins/registry/consul"
7 "log"
8)
9
10// consul 服务发现 selector随机选择
11func main() {
12 // consul连接句柄
13 consulReg := consul.NewRegistry(
14 registry.Addrs("127.0.0.1:8500"))
15
16 // 获取服务
17 getService, err := consulReg.GetService("ProdSrv")
18 if err != nil {
19 log.Fatalf("get service failed, err:%v\n", err)
20 return
21 }
22
23 next := selector.Random(getService)
24
25 node, err := next()
26 if err != nil {
27 log.Fatalln(err)
28 return
29 }
30
31 // 打印服务节点信息
32 log.Println(node.Id, node.Address, node.Metadata)
33}
34
测试结果
12020-03-20 14:37:44.934162 I | 03f124a4-eee9-4349-890d-ed35b49c872a 127.0.0.1:8000 map[]
6. 改造成通过命令行参数启动
为了方便测试,需要在同一个服务同一台机器上运行多个,这个实现起来很简单。初始化之后增加Init(),并取消固定端口设置。然后运行的时候通过--server_address
指定端口或者MICRO_SERVER_ADDRESS
环境变量进行设置。
1func main(){
2
3 consulReg := consul.NewRegistry(
4 registry.Addrs("127.0.0.1:8500"))
5
6 r := gin.Default()
7 // 路由分组
8 v1Group := r.Group("/v1")
9 {
10 v1Group.Handle("GET", "/prods", func(c *gin.Context) {
11 c.JSON(http.StatusOK, NewProdList(5))
12 })
13 }
14
15 service := web.NewService(
16 web.Name("ProdSrv"),
17 web.Handler(r),
18 web.Registry(consulReg),
19 )
20
21 service.Init()
22 service.Run()
23}
例如同时运行2份
1go run main.go prod.go --server_address 127.0.0.1:8000
2go run main.go prod.go --server_address 127.0.0.1:8001
7. 使用轮询调度算法调度服务
前面的服务已经运行了2份副本,我们现在使用轮询调度算法来调度
1package main
2
3import (
4 "github.com/micro/go-micro/client/selector"
5 "github.com/micro/go-micro/registry"
6 "github.com/micro/go-plugins/registry/consul"
7 "log"
8 "time"
9)
10
11// consul 通过轮询获取服务
12// 并测试其中一个节点关闭时不影响业务
13func main() {
14 // consul连接句柄
15 consulReg := consul.NewRegistry(
16 registry.Addrs("127.0.0.1:8500"))
17 for{
18 // 获取服务
19 getService, err := consulReg.GetService("ProdSrv")
20 if err != nil {
21 log.Fatalf("get service failed, err:%v\n", err)
22 return
23 }
24
25 next := selector.RoundRobin(getService)
26
27 node, err := next()
28 if err != nil {
29 log.Fatalln(err)
30 return
31 }
32
33 log.Println(node.Address)
34 time.Sleep(time.Second)
35 }
36}
测试输出结果
12020-03-20 14:48:47.048770 I | 127.0.0.1:8000
22020-03-20 14:48:48.057258 I | 127.0.0.1:8000
32020-03-20 14:48:49.063769 I | 127.0.0.1:8001
42020-03-20 14:48:50.072566 I | 127.0.0.1:8001
52020-03-20 14:48:51.081194 I | 127.0.0.1:8000
62020-03-20 14:48:52.087654 I | 127.0.0.1:8000
72020-03-20 14:48:53.092245 I | 127.0.0.1:8001
82020-03-20 14:48:54.097805 I | 127.0.0.1:8000
92020-03-20 14:48:55.103272 I | 127.0.0.1:8000
8. 服务的基本调用
1package main
2
3import (
4 "github.com/micro/go-micro/client/selector"
5 "github.com/micro/go-micro/registry"
6 "github.com/micro/go-plugins/registry/consul"
7 "io/ioutil"
8 "log"
9 "net/http"
10)
11
12// 基本方式调用后端服务
13func callAPI(addr, path, method string) (string, error) {
14 req, _ := http.NewRequest(method, "http://"+addr+path, nil)
15 client := http.DefaultClient
16 res, err := client.Do(req)
17 if err != nil {
18 return "", err
19 }
20 defer res.Body.Close()
21 buf, err := ioutil.ReadAll(res.Body)
22 if err != nil {
23 return "", err
24 }
25 return string(buf), nil
26}
27
28func main() {
29 // consul连接句柄
30 consulReg := consul.NewRegistry(
31 registry.Addrs("127.0.0.1:8500"))
32
33 // 获取服务
34 getService, err := consulReg.GetService("ProdSrv")
35 if err != nil {
36 log.Fatalf("get service failed, err:%v\n", err)
37 return
38 }
39
40 next := selector.RoundRobin(getService)
41
42 node, err := next()
43 if err != nil {
44 log.Fatalln(err)
45 return
46 }
47
48 res, err := callAPI(node.Address, "/v1/prods", "GET")
49 if err != nil {
50 log.Fatal(err)
51 return
52 }
53 log.Println(res)
54}
测试结果
12020-03-20 14:56:22.368768 I | [{"ProdId":100,"ProdName":"Prod0"},{"ProdId":101,"ProdName":"Prod1"},{"ProdId":102,"ProdName":"Prod2"},{"ProdId":103,"ProdName":"Prod3"},{"ProdId":104,"ProdName":"Prod4"}]
9. 使用http插件调用后端服务
这里对直接服务端的返回进行了改造,data返回具体的数据,数据请求方式换成POST
1v1Group := r.Group("/v1")
2 {
3 v1Group.Handle("POST", "/prods", func(c *gin.Context) {
4 c.JSON(http.StatusOK,gin.H{
5 "data": NewProdList(2),
6 } )
7 })
8 }
client.go
1package main
2
3import (
4 "context"
5 "github.com/micro/go-micro/client"
6 "github.com/micro/go-micro/client/selector"
7 "github.com/micro/go-micro/registry"
8 "github.com/micro/go-plugins/registry/consul"
9 "log"
10 "github.com/micro/go-plugins/client/http"
11)
12
13func callAPI(s selector.Selector){
14 myClient := http.NewClient(
15 client.Selector(s),
16 client.ContentType("application/json"),
17 )
18 req := myClient.NewRequest("ProdSrv","/v1/prods",map[string]string{})
19 var rsp map[string]interface{}
20 err := myClient.Call(context.Background(), req, &rsp)
21 if err != nil {
22 log.Fatal(err)
23 return
24 }
25 log.Println(rsp["data"])
26}
27
28
29func main() {
30 // consul连接句柄
31 consulReg := consul.NewRegistry(
32 registry.Addrs("127.0.0.1:8500"))
33
34 sel := selector.NewSelector(
35 selector.Registry(consulReg),
36 selector.SetStrategy(selector.RoundRobin),
37 )
38 callAPI(sel)
39
40}
测试结果
12020-03-20 15:00:03.329088 I | [map[ProdId:100 ProdName:Prod0] map[ProdId:101 ProdName:Prod1]]
10. 带参数调用服务
上面的示例是直接请求不带参数的,真实的应用场景往往都是根据不同的请求参数来返回想要的结果。这就需要对服务端进行改造。
1func main(){
2
3 consulReg := consul.NewRegistry(
4 registry.Addrs("127.0.0.1:8500"))
5
6 r := gin.Default()
7 // 路由分组
8 v1Group := r.Group("/v1")
9 {
10 v1Group.Handle("POST", "/prods", func(c *gin.Context) {
11 var pr ProdsRequest
12 // 给默认值
13 err := c.Bind(&pr)
14 if err != nil || pr.Size <=0 {
15 log.Println(err)
16 pr = ProdsRequest{Size: 2}
17 }
18 c.JSON(http.StatusOK,gin.H{
19 "data": NewProdList(pr.Size),
20 } )
21 })
22 }
23
24 service := web.NewService(
25 web.Name("ProdSrv"),
26 web.Handler(r),
27 web.Registry(consulReg),
28 )
29
30 service.Init()
31 service.Run()
32}
33
prod.go
1package main
2
3import "strconv"
4
5type ProdModel struct {
6 ProdId int
7 ProdName string
8}
9
10type ProdsRequest struct {
11 Size int `form:"size"`
12}
13
14func NewProd(id int, name string) *ProdModel{
15 return &ProdModel{ProdId:id, ProdName:name}
16}
17
18func NewProdList(n int) []*ProdModel{
19 ret := make([]*ProdModel,0)
20 for i:=0 ; i<n ; i++ {
21 ret = append(ret, NewProd(100+i, "Prod"+strconv.Itoa(i)))
22 }
23 return ret
24}
client.go
1package main
2
3import (
4 "context"
5 "github.com/micro/go-micro/client"
6 "github.com/micro/go-micro/client/selector"
7 "github.com/micro/go-micro/registry"
8 "github.com/micro/go-plugins/registry/consul"
9 "log"
10 "github.com/micro/go-plugins/client/http"
11)
12
13// consul 通过轮询获取服务
14// 使用插件 调用http api 带参数调用
15func callAPI(s selector.Selector){
16 myClient := http.NewClient(
17 client.Selector(s),
18 client.ContentType("application/json"),
19 )
20 req := myClient.NewRequest("ProdSrv","/v1/prods",map[string]interface{}{"size": 4})
21 var rsp map[string]interface{}
22 err := myClient.Call(context.Background(), req, &rsp)
23 if err != nil {
24 log.Fatal(err)
25 return
26 }
27 log.Println(rsp["data"])
28}
29
30
31func main() {
32 // consul连接句柄
33 consulReg := consul.NewRegistry(
34 registry.Addrs("127.0.0.1:8500"))
35
36 sel := selector.NewSelector(
37 selector.Registry(consulReg),
38 selector.SetStrategy(selector.RoundRobin),
39 )
40 callAPI(sel)
41
42}
测试结果上面我填的4,返回4条记录
12020-03-20 15:08:27.950250 I | [map[ProdId:100 ProdName:Prod0] map[ProdId:101 ProdName:Prod1] map[ProdId:102 ProdName:Prod2] map[ProdId:103 ProdName:Prod3]]
11. 引入protobuf生成请求响应模型
新建一个models目录用于存放protobuf生成的文件,在models目录下新建protos目录用于存放proto文件。
proto文件内容如下:
1syntax = "proto3";
2
3// 生成的包名
4package models;
5
6// 商品模型
7message ProdModel{
8 int32 ProdId = 1;
9 string ProdName = 2;
10}
11
12// 请求
13message ProdRequest{
14 int32 size = 1;
15}
16
17// 响应
18message ProdListResponse{
19 repeated ProdModel data = 1;
20}
使用protoc生成
1cd models/protos && \
2protoc --micro_out=../ --go_out=../ *.proto && cd -
修改调用端的服务,改成通过生成的models包下的进行调用
1package main
2
3import (
4 "context"
5 "github.com/micro/go-micro/client"
6 "github.com/micro/go-micro/client/selector"
7 "github.com/micro/go-micro/registry"
8 "github.com/micro/go-plugins/registry/consul"
9 "log"
10 "github.com/micro/go-plugins/client/http"
11 "micro_demo/models"
12)
13
14// consul 通过轮询获取服务
15// 调用http api 引入protobuf生成请求响应模型
16func callAPI(s selector.Selector){
17 myClient := http.NewClient(
18 client.Selector(s),
19 client.ContentType("application/json"),
20 )
21 req := myClient.NewRequest("ProdSrv","/v1/prods",
22 models.ProdRequest{Size:6})
23 var rsp models.ProdListResponse
24 err := myClient.Call(context.Background(), req, &rsp)
25 if err != nil {
26 log.Fatal(err)
27 return
28 }
29 log.Println(rsp.GetData())
30}
31
32func main() {
33 // consul连接句柄
34 consulReg := consul.NewRegistry(
35 registry.Addrs("127.0.0.1:8500"))
36
37 sel := selector.NewSelector(
38 selector.Registry(consulReg),
39 selector.SetStrategy(selector.RoundRobin),
40 )
41 callAPI(sel)
42
43}
效果跟上面的一样。
12. 处理参数模型中json tag不一致的问题
比如之前定义的ProdId,ProdName可能在json中不叫这个名字,可能是pid,pname等等,这时候需要修改protobuf生成的json tag,但是因为是自动生成的,下次改动了proto文件重新生成又覆盖掉了。这个时候我们需要用到第三方包github.com/favadi/protoc-go-inject-tag
处理方式
-
proto文件中加入
// @inject_tag:
标记1syntax = "proto3"; 2 3package models; 4 5message ProdModel{ 6 // @inject_tag: json:"pid" 7 int32 ProdId = 1; 8 // @inject_tag: json:"pname" 9 string ProdName = 2; 10} 11 12message ProdRequest{ 13 int32 size = 1; 14} 15 16message ProdListResponse{ 17 repeated ProdModel data = 1; 18}
-
生成go文件,需要带上
protoc-go-inject-tag
参数1cd models/protos && \ 2protoc --micro_out=../ --go_out=../ prods.proto && \ 3protoc-go-inject-tag --input=../prods.pb.go 4cd -
这个时候就能愉快的调用后端服务了。
- 原文作者:黄忠德
- 原文链接:https://huangzhongde.cn/post/Golang/go-micro_note1/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。