一. GRPC微服务集群概念
上一节讲解了consul集群: [golang 微服务] 5. 微服务服务发现介绍,安装以及consul的使用,Consul集群,这样的话,当一台server挂掉之后,集群就会从另一台server中获取服务,这就保证了客户端访问consul集群的负载均衡性. 这里还有一个问题: 就是当终端的对应的 微服务挂掉了,consul集群server就不能访问对应的微服务了,这个怎么办呢?这就引入了 GRPC微服务集群, 那什么是GRPC微服务集群呢?
把一个GRPC微服务部署到多台不同的服务器上的功能,就叫 GRPC微服务集群, 这样当 其中一个微服务挂掉后 ,consul就会访问另外服务器上对应的微服务,从而实现 微服务的负载均衡
GRPC微服务集群主要实现的是 微服务的负载均衡,实现同样的微服务部署在不同的服务器的功能,结合Consul搭建GRPC微服务集群非常简单:
同一个微服务的不同应用使用同样的注册名
同一个微服务的不同应用注册服务的时候使用不同的ID
下面通过代码展示来更进一步了解微服务集群
二.代码展示
注销相关服务
在consul ui上查看是否存在goods服务,如果有,则先注销之前的goods服务,代码见 [golang 微服务] 5. 微服务服务发现介绍,安装以及consul的使用,Consul集群,注销后就没有对应的微服务了,界面展示如下:
点击client-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服务图示:
客户端进行调度
客户端请求微服务, 要达到这样的效果: 一个客户请求访问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集群