[golang 微服务] 6. GRPC微服务集群+Consul集群+grpc-consul-resolver案例演示

一. GRPC微服务集群概念

上一节讲解了consul集群: [golang 微服务] 5. 微服务服务发现介绍,安装以及consul的使用,Consul集群,这样的话,当一台server挂掉之后,集群就会从另一台server中获取服务,这就保证了客户端访问consul集群的负载均衡性. 这里还有一个问题: 就是当终端的对应的 微服务挂掉了,consul集群server就不能访问对应的微服务了,这个怎么办呢?这就引入了 GRPC微服务集群, 那什么是GRPC微服务集群呢?
把一个GRPC微服务部署到多台不同的服务器上的功能,就叫 GRPC微服务集群, 这样当 其中一个微服务挂掉后 ,consul就会访问另外服务器上对应的微服务,从而实现 微服务的负载均衡
GRPC微服务集群主要实现的是 微服务的负载均衡,实现同样的微服务部署在不同的服务器的功能,结合Consul搭建GRPC微服务集群非常简单:
  • 同一个微服务的不同应用使用同样的注册名

  • 同一个微服务的不同应用注册服务的时候使用不同的ID

下面通过代码展示来更进一步了解微服务集群

二.代码展示

  1. 注销相关服务

在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搭建微服务案例

猜你喜欢

转载自blog.csdn.net/zhoupenghui168/article/details/131196225