[golang microservice] 6. GRPC microservice cluster + Consul cluster + grpc-consul-resolver case demonstration

1. The concept of GRPC microservice cluster

The previous section explained the consul cluster: [golang microservice] 5. Introduction to microservice service discovery, installation and use of consul, Consul cluster , in this case, when one server hangs up, the cluster will start from another server Obtaining services ensures that the client accesses the load balance of the consul cluster. There is another problem here: when the corresponding microservice of the terminal hangs up , the consul cluster server cannot access the corresponding microservice. What should I do? What? This introduces the GRPC microservice cluster, so what is the GRPC microservice cluster?
The function of deploying a GRPC micro-service to multiple different servers is called GRPC micro-service cluster, so that when one of the micro-services hangs up , consul will access the corresponding micro-services on other servers, so as to realize the micro-service load balancing
The GRPC microservice cluster mainly realizes the load balancing of microservices , and realizes the function of deploying the same microservice on different servers. It is very simple to build a GRPC microservice cluster in combination with Consul:
  • Different applications of the same microservice use the same registration name

  • Different applications of the same microservice use different IDs when registering services

Let's take a closer look at the microservice cluster through the code display

2. Code display

  1. Cancellation of related services

在consul ui上查看是否存在goods服务,如果有,则先注销之前的goods服务,代码见 [golang 微服务] 5. 微服务服务发现介绍,安装以及consul的使用,Consul集群,注销后就没有对应的微服务了,界面展示如下:

点击client-1进入,查看里面的服务,发现服务没有了

  1. 进行微服务集群的部署

以goods微服务举例,把server/goods部署到2台服务器上,修改main.go中代码,以示区分不同服务器的同一个微服务
比如:把goods部署到一台192.168.1.111上,main.go代码如下:
package main

import (
    "context"
    "fmt"
    "github.com/hashicorp/consul/api"
    "goods/proto/goodsService"
    "net"
    "google.golang.org/grpc"
    "strconv"
)

//rpc远程调用的接口,需要实现goods.proto中定义的Goods服务接口,以及里面的方法
//1.定义远程调用的结构体和方法,这个结构体需要实现GoodsServer的接口

type Goods struct{}

//GoodsServer方法参考goods.pb.go中的接口
/*
// GoodsServer is the server API for Goods service.
type GoodsServer interface {
    // 通过rpc来指定远程调用的方法:
    // AddGoods方法:增加商品, 这个方法里面实现对传入的参数AddGoodsReq, 以及返回的参数AddGoodsRes进行约束
    AddGoods(context.Context, *AddGoodsReq) (*AddGoodsRes, error)
    // 获取商品列表: GetGoodsReq 参数可为空, 返回参数GetGoodsRes是一个商品相关的切片
    GetGoods(context.Context, *GetGoodsReq) (*GetGoodsRes, error)
}
*/
//增加商品数据
func (this Goods) AddGoods(c context.Context, req *goodsService.AddGoodsReq) (*goodsService.AddGoodsRes, error) {
    fmt.Println(req)
    //模拟返回操作,正式项目在这里进行数据库的操作即可,根据操作结果,返回相关数据
    return &goodsService.AddGoodsRes{
        Message: "第一个goods微服务-增加数据成功",
        Success: true,
    }, nil
}

//获取商品列表
func (g Goods) GetGoods(c context.Context, req *goodsService.GetGoodsReq) (*goodsService.GetGoodsRes, error) {
    //  GoodsList []*GoodsModel
    var tempList []*goodsService.GoodsModel  //定义返回的商品列表切片
    //模拟从数据库中获取商品的请求,循环结果,把商品相关数据放入tempList切片中
    for i := 0; i < 10; i++ {
        tempList = append(tempList, &goodsService.GoodsModel{
            Title:   "商品" + strconv.Itoa(i),  // strconv.Itoa(i): 整型转字符串类型
            Price:   float64(i),  //float64(i): 强制转换整型为浮点型
            Content: "测试商品内容" + strconv.Itoa(i),
        })
    }
    return &goodsService.GetGoodsRes{
        GoodsList: tempList,
    }, nil
}

func main() {
    //------------------------- consul服务相关----------------------
    //注册consul服务
    //1、初始化consul配置
    consulConfig := api.DefaultConfig()
    //设置consul服务器地址: 默认127.0.0.1:8500, 如果consul部署到其它服务器上,则填写其它服务器地址
    consulConfig.Address = "192.168.1.132:8500"
    //2、获取consul操作对象
    consulClient, _ := api.NewClient(consulConfig)
    // 3、配置注册服务的参数
    agentService := api.AgentServiceRegistration{
        ID:      "1",  // 服务id,顺序填写即可
        Tags:    []string{"goods"},  // tag标签
        Name:    "GoodsService",  //服务名称, 注册到服务发现(consul)的K
        Port:    8080, // 端口号: 需要与下面的监听, 指定 IP、port一致
        Address: "192.168.1.111",  // 当前微服务部署    地址: 结合Port在consul设置为V: 需要与下面的监听, 指定 IP、port一致
        Check: &api.AgentServiceCheck{  //健康检测
            TCP:      "192.168.1.111:8080",  //前微服务部署地址,端口 : 需要与下面的监听, 指定 IP、port一致
            Timeout:  "5s",  // 超时时间
            Interval: "30s",  // 循环检测间隔时间
        },
    }

    //4、注册服务到consul上
    consulClient.Agent().ServiceRegister(&agentService)

    //1. 初始一个 grpc 对象
    grpcServer := grpc.NewServer()
    //2. 注册服务
    //helloService.RegisterGoodsServer(grpcServer, &Goods{})
    // &Hello{}和 new(Hello)相同
    goodsService.RegisterGoodsServer(grpcServer, new(Goods))
    //3. 设置监听, 指定 IP、port
    listener, err := net.Listen("tcp", "192.168.1.111:8080")
    if err != nil {
        fmt.Println(err)
    }
    // 4退出关闭监听
    defer listener.Close()
    //5、启动服务
    grpcServer.Serve(listener)
}
再把goods部署到另一台192.168.1.112上,main.go代码如下:
package main

import (
    "context"
    "fmt"
    "github.com/hashicorp/consul/api"
    "goods/proto/goodsService"
    "net"
    "google.golang.org/grpc"
    "strconv"
)

//rpc远程调用的接口,需要实现goods.proto中定义的Goods服务接口,以及里面的方法
//1.定义远程调用的结构体和方法,这个结构体需要实现GoodsServer的接口

type Goods struct{}

//GoodsServer方法参考goods.pb.go中的接口
/*
// GoodsServer is the server API for Goods service.
type GoodsServer interface {
    // 通过rpc来指定远程调用的方法:
    // AddGoods方法:增加商品, 这个方法里面实现对传入的参数AddGoodsReq, 以及返回的参数AddGoodsRes进行约束
    AddGoods(context.Context, *AddGoodsReq) (*AddGoodsRes, error)
    // 获取商品列表: GetGoodsReq 参数可为空, 返回参数GetGoodsRes是一个商品相关的切片
    GetGoods(context.Context, *GetGoodsReq) (*GetGoodsRes, error)
}
*/
//增加商品数据
func (this Goods) AddGoods(c context.Context, req *goodsService.AddGoodsReq) (*goodsService.AddGoodsRes, error) {
    fmt.Println(req)
    //模拟返回操作,正式项目在这里进行数据库的操作即可,根据操作结果,返回相关数据
    return &goodsService.AddGoodsRes{
        Message: "第二个goods微服务-增加数据成功",
        Success: true,
    }, nil
}

//获取商品列表
func (g Goods) GetGoods(c context.Context, req *goodsService.GetGoodsReq) (*goodsService.GetGoodsRes, error) {
    //  GoodsList []*GoodsModel
    var tempList []*goodsService.GoodsModel  //定义返回的商品列表切片
    //模拟从数据库中获取商品的请求,循环结果,把商品相关数据放入tempList切片中
    for i := 0; i < 10; i++ {
        tempList = append(tempList, &goodsService.GoodsModel{
            Title:   "商品" + strconv.Itoa(i),  // strconv.Itoa(i): 整型转字符串类型
            Price:   float64(i),  //float64(i): 强制转换整型为浮点型
            Content: "测试商品内容" + strconv.Itoa(i),
        })
    }
    return &goodsService.GetGoodsRes{
        GoodsList: tempList,
    }, nil
}

func main() {
    //------------------------- consul服务相关----------------------
    //注册consul服务
    //1、初始化consul配置
    consulConfig := api.DefaultConfig()
    //设置consul服务器地址: 默认127.0.0.1:8500, 如果consul部署到其它服务器上,则填写其它服务器地址
    consulConfig.Address = "192.168.1.132:8500"
    //2、获取consul操作对象
    consulClient, _ := api.NewClient(consulConfig)
    // 3、配置注册服务的参数
    agentService := api.AgentServiceRegistration{
        ID:      "2",  // 服务id,顺序填写即可
        Tags:    []string{"goods"},  // tag标签
        Name:    "GoodsService",  //服务名称, 注册到服务发现(consul)的K
        Port:    8080, // 端口号: 需要与下面的监听, 指定 IP、port一致
        Address: "192.168.1.112",  // 当前微服务部署    地址: 结合Port在consul设置为V: 需要与下面的监听, 指定 IP、port一致
        Check: &api.AgentServiceCheck{  //健康检测
            TCP:      "192.168.1.112:8080",  //前微服务部署地址,端口 : 需要与下面的监听, 指定 IP、port一致
            Timeout:  "5s",  // 超时时间
            Interval: "30s",  // 循环检测间隔时间
        },
    }

    //4、注册服务到consul上
    consulClient.Agent().ServiceRegister(&agentService)

    //1. 初始一个 grpc 对象
    grpcServer := grpc.NewServer()
    //2. 注册服务
    //helloService.RegisterGoodsServer(grpcServer, &Goods{})
    // &Hello{}和 new(Hello)相同
    goodsService.RegisterGoodsServer(grpcServer, new(Goods))
    //3. 设置监听, 指定 IP、port
    listener, err := net.Listen("tcp", "192.168.1.112:8080")
    if err != nil {
        fmt.Println(err)
    }
    // 4退出关闭监听
    defer listener.Close()
    //5、启动服务
    grpcServer.Serve(listener)
}
这两台服务器上的代码 不同之处: ID一定要不一样 ,Port为对应服务器上的端口号 ,Address为对应服务器的ip,Name(服务名称),标签(Tag)一定要一致,其他地方不变, 这样 负载均衡的微服务集群就准备好了
然后在这两台服务器上启动服务: go run main.go,这样就注册goods微服务到consul服务发现集群中了,如下图:

GoodsService服务图示:

  1. 客户端进行调度

客户端请求微服务, 要达到这样的效果: 一个客户请求访问192.168.1.111这台goods微服务,另一个客户请求访问192.168.1.112这台goods微服务; 要实现该效果,就要在客户端这边修改相关代码,有两种方式, 如下:

方式一

在客户端client/main.go代码中操作: 在拼接地址步骤时, 写一个算法(随机取获取返回的几个地址的切片),不直接取值
package main

import (
    "client/proto/goodsService"
    "context"
    "fmt"
    "github.com/hashicorp/consul/api"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "strconv"
)

func main() {
    //----------------------------consul相关---------------------------
    //初始化consul配置, 客户端服务器需要一致
    consulConfig := api.DefaultConfig()
    //设置consul服务器地址: 默认127.0.0.1:8500, 如果consul部署到其它服务器上,则填写其它服务器地址
    consulConfig.Address = "192.168.1.132:8500"
    //2、获取consul操作对象
    consulClient, _ := api.NewClient(consulConfig)  //目前先屏蔽error,也可以获取error进行错误处理

    ----------------------------goods微服务相关--------------------------

    //3、获取consul服务发现地址,返回的ServiceEntry是一个结构体数组
    //参数说明:service:服务名称,服务端设置的那个Name, tag:标签,服务端设置的那个Tags,, passingOnly bool, q: 参数
    serviceGoodsEntry, _, _ := consulClient.Health().Service("GoodsService", "test", false, nil)
    
    //拼接地址: 写一个算法(随机取获取返回的几个地址的切片),不直接取值
    //strconv.Itoa: int转string型
    addressGoods := serviceGoodsEntry[0].Service.Address + ":" + strconv.Itoa(serviceGoodsEntry[0].Service.Port)

    // 1、连接服务器
    /*
        credentials.NewClientTLSFromFile :从输入的证书文件中为客户端构造TLS凭证。
        grpc.WithTransportCredentials :配置连接级别的安全凭证(例如,TLS/SSL),返回一个
        DialOption,用于连接服务器。

    */
    grpcGoodsClient, err := grpc.Dial(addressGoods, grpc.WithTransportCredentials(insecure.NewCredentials()))

    if err != nil {
        fmt.Println(err)
    }
    //2、注册客户端
    clientGoods := goodsService.NewGoodsClient(grpcGoodsClient)
    //增加
    res1, _ := clientGoods.AddGoods(context.Background(), &goodsService.AddGoodsReq{
        Goods: &goodsService.GoodsModel{
            Title:   "测试商品",
            Price:   20,
            Content: "测试商品的内容",
        },
    })
    fmt.Println(res1.Message)
    fmt.Println(res1.Success)

    //获取商品数据
    res2, _ := clientGoods.GetGoods(context.Background(), &goodsService.GetGoodsReq{})
    fmt.Printf("%#v", res2.GoodsList)

    for i := 0; i < len(res2.GoodsList); i++ {
        fmt.Printf("%#v\r\n", res2.GoodsList[i])
    }
}

方式二

使用 grpc-consul-resolver实现 域名解析, 在这里需要引入grpc-consul-resolver,直接使用命令 go mod tidy 操作
 package main

import (
    "client/proto/goodsService"
    "context"
    "fmt"
    "github.com/hashicorp/consul/api"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    _ "github.com/mbobakov/grpc-consul-resolver" // 必须引入
    "strconv"
)

func main() {
    //直接进行连接服务器拨号操作
    grpcGoodsClient, err := grpc.Dial(
        "consul://192.168.234.132:8500/GoodsService",  //服务发现地址+服务名称
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        //轮询调度策略
        grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
    )

    if err != nil {
        fmt.Println(err)
    }

    //2、注册客户端
    clientGoods := goodsService.NewGoodsClient(grpcGoodsClient)
    //增加
    res1, _ := clientGoods.AddGoods(context.Background(), &goodsService.AddGoodsReq{
        Goods: &goodsService.GoodsModel{
            Title:   "测试商品",
            Price:   20,
            Content: "测试商品的内容",
        },
    })
    fmt.Println(res1.Message)
    fmt.Println(res1.Success)

    //获取商品数据
    res2, _ := clientGoods.GetGoods(context.Background(), &goodsService.GetGoodsReq{})
    fmt.Printf("%#v", res2.GoodsList)

    for i := 0; i < len(res2.GoodsList); i++ {
        fmt.Printf("%#v\r\n", res2.GoodsList[i])
    }
}
使用 grpc-consul-resolver 进行操作,省去了consul相关操作对象连接,要方便一下

然后运行go run main.go,可以看到,该客户端调用微服务是随机的

好了,微服务集群就搭建好了,这样就实现了: 当一台微服务挂了,客户端照样能够调度访问,因为实现了微服务的负载均衡操作

[上一节][golang 微服务] 5. 微服务服务发现介绍,安装以及consul的使用,Consul集群

[下一节][golang 微服务] 7. go-micro框架介绍,go-micro脚手架,go-micro结合consul搭建微服务案例

Guess you like

Origin blog.csdn.net/zhoupenghui168/article/details/131196225