一.服务发现介绍
引入
上一节讲解了使用 gRPC创建微服务,客户端的一个接口可能需要调用 N个服务,而不同服务可能存在 不同的服务器,这时,客户端就必须知道所有服务的 网络位置(ip+port),来进行连接服务器操作,如下图所示:
以往的做法是把服务的地址放在配置文件或者数据库中,这样就有以下几个问题:
需要配置N个服务的网络位置,加大配置的复杂性
服务的网络位置变化,需要改变每个调用者的配置
集群的情况下,难以做负载(反向代理的方式除外)
总结起来一句话: 服务多了,配置很麻烦,问题一大堆
所以现在就选择服务发现来解决这些问题,具体设计如下:
与之前解决方法不同的是,加了个 服务发现模块,服务端把当前自己的网络位置 注册到服务发现模块(这里注册的意思就是告诉),服务发现就以 K-V的方式记录下,K一般是 服务名,V就是 IP:PORT,服务发现模块 定时的轮询查看这些服务能不能访问的了(这就是 健康检查),客户端在调用服务A-N的时候,就跑去服务发现模块问下它们的网络位置,然后再调用它们的服务,这样的方式就可以解决上面的问题了, 客户端完全不需要记录这些服务的网络位置, 客户端和服务端完全解耦!
常见的服务发现框架有:Etcd、mdns、Consul、Zookeeper
consul: 常应用于grpc 、 go-micro 中
mdns:以前mdns是go-micro中默认自带的服务发现go-micro的默认服务发现也是consul
etcd:k8s 内嵌的服务发现
zookeeper:java中较常用
这里选择服务发现框架consul来做一个详细介绍
consul介绍
Consul是Go语言写的开源 的服务器发现软件,用于实现 分布式系统的服务发现与配置,包含 多个组件,作为一个整体,它为基础设施提供服务发现和服务配置的工具,提供以下关键特性:
服务发现:consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册,consul采用Raft一致性协议算法来保证服务的高可用,使用GOSSIP协议管理成员和广播消息
健康检查:健康检测使consul可以快速的告警在集群中的操作,和服务发现的集成,可以防止服务转发到故障的服务上面(心跳机制)
键/值存储:一个用来存储动态配置的系统,提供简单的HTTP接口,可以在任何地方操作
多数据中心方案支持:无需复杂的配置,即可支持任意数量的区域
简易安装:安装包仅包含一个二进制文件,支持跨平台,可与Docker等轻量级容器实现无缝对接
提供Web管理界面:官方提供web管理界面
官方建议:最好是三台或者三台以上的consul在运行,同名服务最好是三台或三台以上,默认可
以搭建集群
官网:https://www.consul.io/
Git地址:https://github.com/hashicorp/consul
consul安装
Consul用Golang实现,因此具有天然可移植性 (支持 Linux、windows和macOS),安装包仅包含一个可执行文件, Consul安装非常简单,只需要下载对应系统的软件包并解压后就可使用
(1). windows电脑安装consul
1).下载consul
下载地址: https://www.consul.io/downloads
2).解压consul到一个目录
把consul解压到E盘
3).配置consul到环境变量
a.电脑左下方设置选项
b.选择系统
c.关于->高级系统设置
d.选择'环境变量'
e.配置consul到环境变量
4).测试consul是否安装成功
重启计算机,命令行运行 consul -v, 出现如下代码,说明安装成功
(2).Mac电脑安装consul
方法一
运行以下命令即可
brew tap hashicorp/tap
brew install hashicorp/tap/consul
方法二
和windows类似
1).下载consul
2).减压consul到一个目录 配置consul到环境变量
或者下载解压后,将文件放置在 /usr/local/bin 目录下
3).查看安装情况
consul --version
(3).Linux电脑安装consul
方法一
Ubuntu安装步骤
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com
$(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install consul
CentOS安装步骤
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo
https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install consul
方法二
和windows/mac安装类似
1).下载consul
2).减压consul到一个目录 配置consul到环境变量
[root@localhost ~]# mkdir -p usr/local/consul
[root@localhost ~]# unzip consul_1.11.4_linux_arm64.zip -d /usr/local/consul/
[root@localhost ~]# cd /usr/local/consul/
[root@localhost ~]# ./consul -v
如果要全局使用请把consul加入到环境变量,或者下载解压后,将文件放置在 /usr/local/bin 目录下
3).安装验证
安装 Consul后,通过执行 consul命令,可以看到命令列表的输出
ok,consul在各个环境安装完成
二. consul的使用
consul的角色
完成consul的安装后,必须运行 agent, agent可以运行为 server模式、 client模式或者 dev模式, 每个数据中心至少必须拥 有一台serve,建议在一个集群中有 3或者5个server,因为部署单一server,在出现失败时,也许会不可避免的出现数据丢失
client客户端角色: 将 HTTP 和 DNS 接口请求转发给局域网内的Server服务端集群
server服务端角色 :保存配置信息、实现高可用集群、在局域网内与本地客户端通讯、通过广域网与其他数据中心通讯等, 每个数据中心(集群)的 server 数量推荐为3个或是5个
开发模式:主要用于开发阶段(dev模式也是server模式)
consul安装好之后,在使用之前,先了解一下consul都有哪些命令,使用命令 consul -h可以查看consul支持的所有参数,而且每个参数里面还支持其他参数
C:\Users\zhoupenghui>consul -h
Usage: consul [--version] [--help] <command> [<args>]
Available commands are:
acl Interact with Consul's ACLs
agent Runs a Consul agent
catalog Interact with the catalog
config Interact with Consul's Centralized Configurations
connect Interact with Consul Connect
debug Records a debugging archive for operators
event Fire a new event
exec Executes a command on Consul nodes
force-leave Forces a member of the cluster to enter the "left" state
info Provides debugging information for operators.
intention Interact with Connect service intentions
join Tell Consul agent to join cluster
keygen Generates a new encryption key
keyring Manages gossip layer encryption keys
kv Interact with the key-value store
leave Gracefully leaves the Consul cluster and shuts down
lock Execute a command holding a lock
login Login to Consul using an auth method
logout Destroy a Consul token created with login
maint Controls node or service maintenance mode
members Lists the members of a Consul cluster
monitor Stream logs from a Consul agent
operator Provides cluster-level tools for Consul operators
peering Create and manage peering connections between Consul clusters
reload Triggers the agent to reload configuration files
rtt Estimates network round trip time between nodes
services Interact with services
snapshot Saves, restores and inspects snapshots of Consul server state
tls Builtin helpers for creating CAs and certificates
troubleshoot CLI tools for troubleshooting Consul service mesh
validate Validate config files/directories
version Prints the Consul version
watch Watch for changes in Consul
agent参数说明:指令是consul的核心,它运行agent来维护成员的重要信息、运行检查、服务宣布、查询处理等等
consul agent -dev 开发者模式启动consul
开发阶段可以使用下面命令启动consul ,执行 consul agent -dev 启动了一个consul 服务端
consul agent -dev
访问 http://localhost:8500 可以打开Web管理界面,如下:
consul和gRPC结合使用
(1).启动consul
代码中结合gRPC和consul使用时,需要先 启动consul ,开发阶段通过 consul agent -dev 启动consul,在操作consul时使用的是 github.com/hashicorp/consul 这个包,需要先下载,可以通过命令 go get -u -v github.com/hashicorp/consul,或者在import中引入 github.com/hashicorp/consul/api后,使用go mod tidy来加载
(2).注册一个服务到consul上
把上一节中的 hello grpc服务注册到consul上,找到server/hello/main.go,编辑,代码如下:
增加了 consul服务相关代码,其它地方保存不变
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"github.com/hashicorp/consul/api"
"serverHello/proto/helloService"
)
//rpc远程调用的接口,需要实现hello.proto中定义的Hello服务接口,以及里面的方法
//1.定义远程调用的结构体和方法,这个结构体需要实现HelloServer的接口
type Hello struct{}
//SayHello方法参考hello.pb.go中的接口
/*
// HelloServer is the server API for Hello service.
type HelloServer interface {
// 通过rpc来指定远程调用的方法:
// SayHello方法, 这个方法里面实现对传入的参数HelloReq, 以及返回的参数HelloRes进行约束
SayHello(context.Context, *HelloReq) (*HelloRes, error)
}
*/
func (this Hello) SayHello(c context.Context, req *helloService.HelloReq) (*helloService.HelloRes, error) {
fmt.Println(req)
return &helloService.HelloRes{
Message: "你好" + req.Name,
}, nil
}
func main() {
//------------------------- consul服务相关----------------------
//注册consul服务
//1、初始化consul配置
consulConfig := api.DefaultConfig()
//设置consul服务器地址: 默认127.0.0.1:8500, 如果consul部署到其它服务器上,则填写其它服务器地址
//consulConfig.Address = "127.0.0.1:8500"
//2、获取consul操作对象
consulClient, _ := api.NewClient(consulConfig)
// 3、配置注册服务的参数
agentService := api.AgentServiceRegistration{
ID: "1", // 服务id,顺序填写即可
Tags: []string{"test"}, // tag标签
Name: "HelloService", //服务名称, 注册到服务发现(consul)的K
Port: 8080, // 端口号: 需要与下面的监听, 指定 IP、port一致
Address: "127.0.0.1", // 当前微服务部署地址: 结合Port在consul设置为V: 需要与下面的监听, 指定 IP、port一致
Check: &api.AgentServiceCheck{ //健康检测
TCP: "127.0.0.1:8080", //前微服务部署地址,端口 : 需要与下面的监听, 指定 IP、port一致
Timeout: "5s", // 超时时间
Interval: "30s", // 循环检测间隔时间
},
}
//4、注册服务到consul上
consulClient.Agent().ServiceRegister(&agentService)
//------------------------- grpc相关----------------------
//1. 初始一个 grpc 对象
grpcServer := grpc.NewServer()
//2. 注册服务
//helloService.RegisterHelloServer(grpcServer, &Hello{})
// &Hello{}和 new(Hello)相同
helloService.RegisterHelloServer(grpcServer, new(Hello))
//3. 设置监听, 指定 IP、port
listener, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
fmt.Println(err)
}
// 4退出关闭监听
defer listener.Close()
//5、启动服务
grpcServer.Serve(listener)
}
效果展示:
运行该main..go
consul界面展示效果如下:
说明HelloService微服务注册到了consul这个服务发现中了
(3).在客户端使用服务发现获取hello微服务相关
把上一节中的 hello grpc服务客户端代码修改一下,通过consul获取hello微服务相关,找到server/client/main.go,编辑,增加了 consul服务相关代码,在连接服务器的时候,使用consul返回的微服务地址, 其它地方保存不变,代码如下:
package main
import (
"client/proto/helloService"
"context"
"fmt"
"google.golang.org/grpc"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc/credentials/insecure"
"strconv"
)
func main() {
//----------------------------consul相关---------------------------
//初始化consul配置, 客户端服务器需要一致
consulConfig := api.DefaultConfig()
//设置consul服务器地址: 默认127.0.0.1:8500, 如果consul部署到其它服务器上,则填写其它服务器地址
//consulConfig.Address = "127.0.0.1:8500"
//2、获取consul操作对象
consulClient, _ := api.NewClient(consulConfig) //目前先屏蔽error,也可以获取error进行错误处理
//3、获取consul服务发现地址,返回的ServiceEntry是一个结构体数组
//参数说明:service:服务名称,服务端设置的那个Name, tag:标签,服务端设置的那个Tags,, passingOnly bool, q: 参数
serviceEntry, _, _ := consulClient.Health().Service("HelloService", "test", false, nil)
//打印地址
fmt.Println(serviceEntry[0].Service.Address)
fmt.Println(serviceEntry[0].Service.Port)
//拼接地址
//strconv.Itoa: int转string型
address := serviceEntry[0].Service.Address + ":" + strconv.Itoa(serviceEntry[0].Service.Port)
//----------------------------hello微服务相关------------------------------
// 1、连接服务器
/*
credentials.NewClientTLSFromFile :从输入的证书文件中为客户端构造TLS凭证。
grpc.WithTransportCredentials :配置连接级别的安全凭证(例如,TLS/SSL),返回一个
DialOption,用于连接服务器。
*/
//把上面拼接的地址放入下面
grpcClient, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err)
}
//2、注册客户端
client := helloService.NewHelloClient(grpcClient)
//3、调用服务端函数, 实现HelloClient接口:SayHello()
/*
// HelloClient is the client API for Hello service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type HelloClient interface {
// 通过rpc来指定远程调用的方法:
// SayHello方法, 这个方法里面实现对传入的参数HelloReq, 以及返回的参数HelloRes进行约束
SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloRes, error)
}
*/
res, err1 := client.SayHello(context.Background(), &helloService.HelloReq{
Name: "张三",
})
if err1 != nil {
fmt.Printf("调用服务端代码失败: %s", err1)
return
}
fmt.Printf("%#v\r\n", res)
fmt.Printf("调用成功: %s", res.Message)
}
(4).校验客户端-服务发现-服务端功能是否成功
启动consul
见上面操作
启动服务端
见上面操作
启动客户端
校验成功
(5).测试不同服务端口下,客户端调用hello微服务操作
修改服务端hello微服务main.go中的端口号,改为8081,然后重新运行该微服务,再使用客户端请求服务发现,看看返回的hello微服务对应的端口号发生变化没有,如果发生变化了,说明操作成功,以此为案例,开发者可以把微服务服务端部署到不同服务器上,并指定对应的端口,从而实现微服务的负载均衡操作
F:\www\go-data\src\go_code\micro\grpc_demo\server\hello>go run .\main.go
name:"张三"
(6)实现goods微服务-服务发现-客户端操作
把上一节 goods微服务注册到consul上,找到server/goods/main.go,编辑,代码如下:
增加了 consul服务相关代码,其它地方保存不变
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: "增加数据成功",
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 = "127.0.0.1:8500"
//2、获取consul操作对象
consulClient, _ := api.NewClient(consulConfig)
// 3、配置注册服务的参数
agentService := api.AgentServiceRegistration{
ID: "1", // 服务id,顺序填写即可
Tags: []string{"test"}, // tag标签
Name: "GoodsService", //服务名称, 注册到服务发现(consul)的K
Port: 8080, // 端口号: 需要与下面的监听, 指定 IP、port一致
Address: "127.0.0.1", // 当前微服务部署地址: 结合Port在consul设置为V: 需要与下面的监听, 指定 IP、port一致
Check: &api.AgentServiceCheck{ //健康检测
TCP: "127.0.0.1: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", "127.0.0.1:8080")
if err != nil {
fmt.Println(err)
}
// 4退出关闭监听
defer listener.Close()
//5、启动服务
grpcServer.Serve(listener)
}
效果展示:
运行该main..go
consul界面展示效果如下:
说明GoodsService微服务注册到了consul这个服务发现中了
(7).在客户端使用服务发现获取goods微服务相关
把上一节中的 goods grpc服务注册客户端代码修改一下,通过consul获取goods微服务相关,找到server/goods/main.go,编辑,增加了 consul服务相关代码,在连接服务器的时候,使用consul返回的微服务地址, 其它地方保存不变,代码如下:
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 = "127.0.0.1:8500"
//2、获取consul操作对象
consulClient, _ := api.NewClient(consulConfig) //目前先屏蔽error,也可以获取error进行错误处理
//3、获取consul服务发现地址,返回的ServiceEntry是一个结构体数组
//参数说明:service:服务名称,服务端设置的那个Name, tag:标签,服务端设置的那个Tags,, passingOnly bool, q: 参数
//serviceEntry, _, _ := consulClient.Health().Service("HelloService", "test", false, nil)
//打印地址
//fmt.Println(serviceEntry[0].Service.Address)
//fmt.Println(serviceEntry[0].Service.Port)
//拼接地址
//strconv.Itoa: int转string型
//address := serviceEntry[0].Service.Address + ":" + strconv.Itoa(serviceEntry[0].Service.Port)
//----------------------------hello微服务相关------------------------------
// 1、连接服务器
/*
credentials.NewClientTLSFromFile :从输入的证书文件中为客户端构造TLS凭证。
grpc.WithTransportCredentials :配置连接级别的安全凭证(例如,TLS/SSL),返回一个
DialOption,用于连接服务器。
*/
//把上面拼接的地址放入下面
//grpcClient, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
//if err != nil {
// fmt.Println(err)
//}
//2、注册客户端
//client := helloService.NewHelloClient(grpcClient)
//3、调用服务端函数, 实现HelloClient接口:SayHello()
/*
// HelloClient is the client API for Hello service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type HelloClient interface {
// 通过rpc来指定远程调用的方法:
// SayHello方法, 这个方法里面实现对传入的参数HelloReq, 以及返回的参数HelloRes进行约束
SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloRes, error)
}
*/
//res, err1 := client.SayHello(context.Background(), &helloService.HelloReq{
// Name: "张三",
//})
//if err1 != nil {
// fmt.Printf("调用服务端代码失败: %s", err1)
// return
//}
//
//fmt.Printf("%#v\r\n", res)
//fmt.Printf("调用成功: %s", res.Message)
----------------------------goods微服务相关--------------------------
//3、获取consul服务发现地址,返回的ServiceEntry是一个结构体数组
//参数说明:service:服务名称,服务端设置的那个Name, tag:标签,服务端设置的那个Tags,, passingOnly bool, q: 参数
serviceGoodsEntry, _, _ := consulClient.Health().Service("GoodsService", "test", false, nil)
//打印地址
fmt.Println(serviceGoodsEntry[0].Service.Address)
fmt.Println(serviceGoodsEntry[0].Service.Port)
//拼接地址
//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])
}
}
(8).校验客户端-服务发现-服务端功能是否成功
启动consul
见上面操作
启动服务端
见上面操作
启动客户端
校验成功
(9).注销服务
func main(){
//初始化consul配置,客户端服务器需要一致
consulConfig := api.DefaultConfig()
//获取consul操作对象
registerClient,_ := api.NewClient(consulConfig)
//注销服务ServiceDeregister(ServerID),ServerID: 微服务服务端服务发现id
registerClient.Agent().ServiceDeregister("1")
}
三.consul集群,监控检查,服务发现
通过上面例子已经可以实现 注册服务、 发现服务了,下面给大家详细的讲解一下consul中的 服务端以及 客户端
简介
Consul 是 HashiCorp 公司推出的开源工具,用于实现 分布式系统的服务发现与配置,Consul 是 分布式的、 高可用的、 可横向扩展的,完成consul的安装后,必须运行 agent代理, agent可以运行为 server模式、 client模式
服务模式(server模式): 主要参与维护集群状态,响应RPC查询,与其他数据中心交换WAN gossip ,以及向上级或远程数据中心转发查询,并且会将数据持久化,推荐使用3到5台机器
客户模式(client模式):客户模式下ConsulAgent是一个非常轻量级的进程,它消耗最小的资源开销和少量的网络带宽,提供注册服务,运行健康检查,并将查询转发给服务器,客户端是相对无状态的,客户端执行的唯一后台活动是LANgossip池,不负责数据的持久化,客户模式不能单独存在,必须要有一个服务模式的Consul
数据中心:一个数据中心由多个Server和Client模式Consul组成,多个数据中心通过WAN通信,每个数据中心(数据中心是一个大型的计算机系统集群,通常由成千上万台计算机、存储设备、网络设备、电源设备、冷却设备等组成,以提供高效、可靠的计算和存储能力,主要功能是提供云计算、虚拟化、存储和数据处理等服务,以满足企业、政府机构和个人的计算和数据存储需求,是现代企业和政府机构不可或缺的基础设施之一)至少必须拥有一台server,建议在一个集群中有3或者5个server,部署单一server,在出现失败时,会不可避免的出现数据丢失
图片上 datacenter 分成上下两个部分, 但是这两个部分又不是完全隔离的,他们之间通过 WAN GOSSIP 进行报文交互,单个 datacenter 中,节点被划分成两种颜色, 红色的 server, 紫色的 client, 他们之间通过 GRPC 进行通信(业务数据), 除此之外, Client 和 Server 之间通过还有一条 LAN Gosssip 进行通信,比如,当 Server 节点增加,或者 down 机后,Client 可以获取对应的 Server列表,去除或者增加 Server 列表,同一个 Consul agent 程序,启动的时候,通过制定不同的参数来运行 Server 和 Client 模式,也就是说 client 和 server 本质上都是 Client Agent
一个client是一个非常 轻量级的进程,用于 注册服务,运行 健康检查和 转发对server的查询,每个数据中心 至少必须拥有一个server,agent必须在集群中的每个主机上运行,
注意:
server也可以用于注册服务,比如前面运行的 consul agent -dev ,但是正式上线后一般 通过client注册服务,使用保存配置信息、实现高可用集群、通过广域网与其他数据中心通讯等
Server功能
参与共识仲裁(raft)
存储机器状态(日志存储)
处理查询
维护周边(LAN/WAN) 节点之间的关系
Client功能
负责通过该节点注册到 Consul 微服务的健康检查
将客户端的注册请求和查询转换为 server 的 RPC 请求
维护周边各节点(LAN/WAN) 的关系
Client-Server 模式
Consul 的架构升级为 client-Server 模式,服务注册不再向 Consul-Server进行注册,而是向服务所在机器的 Consul-client 进行注册,通过 Consul-Client 将注册信息同步到 Consul-Server 机器中
纯 Server 模式
Zookeeper 就是这种模,client server 本质上都是 consul 的 agent, 只是 角色不同,架构图如下:
架构的问题
纯 server 模式架构的问题
高可用服务实例注册时配置的 consul-host 是负载均衡地址,服务注册到集群后,由集群中的一个节点负责对该实例进行健康检查,假如有这样的情况:服务A,服务B,都注册到 Service1 ,也就是由 Service1 对 服务A,服务B 进行健康检查,当 Service1 宕机时,这样会导致 服务A,服务B 在注册列表中消失,导致无法无法访问到,但是其实服务本身没有问题。服务注销:当服务从注册中心注销时,由于是负载均衡的,可能会导致服务注销失败,因为要在Service1 上注销,有可能负载均衡到 Service2 上进行注销,导致注销失败,解决的办法:遍历集群中所有节点进行注销
Client-Server 架构的问题
高可用服务实例向本级别 consul-client 进行注册,如果 consul-client 不可用,只影响 consul-client 所在机器的服务实例,不影响另外一台机器上的实例。服务注销:服务注销值需要在 consul-client 上进行注销,不需要经过负载均衡
基于 Client-Server架构服务注册时序图
服务端口
8300: 只存在于Server模式,选取Leader节点(Raft协议),为Leader节点和Client节点的提供RPC调用
8301: LAN网中集群数据同步的通信端口(Gossip协议),也是加入集群的通信端口
8302: 只存在于Server模式,WAN网中集群数据同步的通信端口(Gossip协议),也是加入集群的通信端口,主要支持数据中心与数据中心之间交互通过WLAN(8302端口)
8500: 提供Http服务(或web界面)
8600: 提供DNS服务端口
8301和8302接口作用类似,主要区分在于8301用于LAN网络,8302用于WAN网络,它们都可以用于加入consul集群(数据中心一致就是在一个集群),将各数据中心连接则使用8302端口
实现原理
核心原理在于亮点:
集群信息之间的高效同步机制,保障拓扑变动与控制信号的及时同步
server 集群内日志存储的强一致性
他们主要基于两个协议来实现:
Gossip 协议,在集群内消息传递
使用 raft 协议保障日志的一致性
案例讲解
架构图
步骤
(1).部署集群
首先需要有一个正常的Consul集群,有Server,有 Leader(主服务领导),这里在服务器Server1、Server2、Server3上分别部署了Consul Server
(2).选举Leader节点
假设选举了Server2上的 Consul Server 节点为Leader,这些服务器上最好只部署Consul程序,以尽量维护Consul Server的稳定
(3).注册服务
然后在服务器Server5和Server7上通过Consul Client分别注册Service A、B、C,这里每个Service 分别部署在了 两个服务器上,这样可以避免Service的 单点问题,服务注册到Consul可以通过 HTTP API(8500 端口)的方式,也可以通过 Consul 配置文件的方式
(4).Consul Client转发注册消息
Consul Client 可以认为是无状态的,它将注册信息通过 RPC转发到Consul Server,服务信息保存在Server的各个节点中,并且通过 Raft实现了 强一致性
(5).服务发起通信请求
最后在服务器Server6中Service D需要访问Service B,这时候Service D首先访问本机Consul Client提供的HTTP API,本机Client会将请求转发到 Consul Server,Consul Server查询到Service B当前的信息返回,最终Service D拿到了Service B的所有部署的IP和端口,然后就可以选择Service B的其中一个部署并向其发起请求了
集群配置
准备四个虚拟机,安装好consul等相关软件(安装见上面讲解),三个虚拟机作为服务端(ip参考:192.168.1.129,192.168.1.130,192.168.1.131,),一个作为客户端(ip参考:192.168.1.132), 虚拟机查询consul如下:
其他虚拟机也是一致的,进行集群配置测试操作后
(1).启动服务端
启动服务端命令如下:
$ consul agent -server -bootstrap-expect 3 -node=server_01 -
bind=192.168.1.129 -ui -data-dir=/root/usr/local/consul/data -client 0.0.0.0
$ consul agent -server -bootstrap-expect 3 -node=server_02 -
bind=192.168.1.130 -ui -data-dir=/root/usr/local/consul/data -client 0.0.0.0
$ consul agent -server -bootstrap-expect 3 -node=server_03 -
bind=192.168.1.131 -ui -data-dir=/root/usr/local/consul/data -client 0.0.0.0
#额外参数:
-advertise=106.52.1.126 -datacenter=myDataCenter-CentOS -enable-script-checks=true -config-dir=/etc/consul.d/consul.d -server-port=8300 -serf-lan-port=8301 -serf-wan-port=8302 -http-port=8500 -dns-port=8600
需要先在/etc/下面创建consul.d目录,上面参数说明:
-server : 定义agent运行在 server模式,表示以Server模式启动,没有设置-server表示Client方式启动
-bootstrap-expect :在一个 datacenter(数据中心)中期望提供的server节点数目,当该值提供的时候, consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap,通俗来讲,就是 构成集群的最小数量
共用(注意:bootstrap-expect值必须是server的数量)
-bind :集群通信的ip,该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0 ,表示所有ip
-node :节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名
-ui : 启动web界面 :8500,启动后台管理,默认 http://ip:8500访问
-rejoin :使consul启动的时候加入集群中。
-config-dir :配置文件目录,里面所有以.json结尾的文件都会被加载
-client :consul服务侦听地址,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1所以不对外提供服务,如果要对外提供服务改成0.0.0.0, ,表示所有ip
data-dir :提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录
必须是稳定的,系统重启后都继续存在,通俗来讲就是 数据存放目录
额外参数
enable-script-checks=true: 允许使用脚本进行监控检查
advertise=IP: 告诉集群其他节点你通过这个ip来和我通信,默认使用bind绑定的ip
datacenter=dataCenterName: 数据中心名称
server-port=8300:选取Leader节点(raft协议通信)和提供RPC调用的
serf-lan-port=8301: 集群通信端口,用在LAN网
serf-wan-port=8302: 数据中心通信端口,用在WAN网
http-port=8500: 提供Http服务的端口
dns-port=8600: 提供Dns服务的端口
(2).启动客户端
运行cosnul agent以client模式
$ consul agent -data-dir=/root/usr/local/consul/data -node=client-01 -
bind=192.168.1.132 -ui -client 0.0.0.0
(3).关联集群
以server-01为Leader,分别在server-02、server-03、client-01节点上面运行下面命令建立集群关系
consul join 192.168.1.129
注意:分别 关闭对应服务器的防火墙(systemctl stop firewalld),或者允许对应端口
(4).具体操作
启动服务端
把下面命令分别在对应虚拟机上运行
上面三条命令分别在对应虚拟机运行成功后,再启动客户端
启动客户端
把下面命令分别在对应虚拟机上运行
操作完后,三台服务端,一台客户端就启动完成了,
关联
以server-01(192.168.1.129)为Leader,分别在server-02、server-03、client-01节点上面运行下面命令建立集群关系, consul join 192.168.1.129
consul join 192.168.1.129
这样以server-01为Leader的集群就创建好了,浏览器打开web ui,可以看见:在server-01的服务器上,关联了三个服务(2个服务端,1个客户端),图示如下:
好了,集群就搭建好了
(5).查看consul成员和集群状态
consul members
节点 网络地址 状态 类型 版本 协议 数据中心 分管部分
Node Address Status Type Build Protocol DC Partition Segment
Consul集群实现微服务
以上面代码为案例,先注册服务到sever里面,把Hello微服务注册到server-03这个consul中,代码只需修改consulConfig.Address=192.138.1.132,以及Hello微服务启动的地址,比如把127.0.0.1修改为本机地址192.168.1.111,这样才能让server-03和微服务服务器通讯,Goods微服务也类似操作
注意:需要配置consul集群地址,其中 consulConfig.Address = "192.168.234.132:8500" 为集群客户端地址 AgentServiceRegistration 中的地址需要和当前程序运行服务器的地址统一起来
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"github.com/hashicorp/consul/api"
"serverHello/proto/helloService"
)
//rpc远程调用的接口,需要实现hello.proto中定义的Hello服务接口,以及里面的方法
//1.定义远程调用的结构体和方法,这个结构体需要实现HelloServer的接口
type Hello struct{}
//SayHello方法参考hello.pb.go中的接口
/*
// HelloServer is the server API for Hello service.
type HelloServer interface {
// 通过rpc来指定远程调用的方法:
// SayHello方法, 这个方法里面实现对传入的参数HelloReq, 以及返回的参数HelloRes进行约束
SayHello(context.Context, *HelloReq) (*HelloRes, error)
}
*/
func (this Hello) SayHello(c context.Context, req *helloService.HelloReq) (*helloService.HelloRes, error) {
fmt.Println(req)
return &helloService.HelloRes{
Message: "你好" + req.Name,
}, 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{"test"}, // tag标签
Name: "HelloService", //服务名称, 注册到服务发现(consul)的K
Port: 8082, // 端口号: 需要与下面的监听, 指定 IP、port一致
Address: "192.1678.1.111", // 当前微服务部署地址: 结合Port在consul设置为V: 需要与下面的监听, 指定 IP、port一致
Check: &api.AgentServiceCheck{ //健康检测
TCP: "192.1678.1.111:8082", //前微服务部署地址,端口 : 需要与下面的监听, 指定 IP、port一致
Timeout: "5s", // 超时时间
Interval: "30s", // 循环检测间隔时间
},
}
//4、注册服务到consul上
consulClient.Agent().ServiceRegister(&agentService)
//------------------------- grpc相关----------------------
//1. 初始一个 grpc 对象
grpcServer := grpc.NewServer()
//2. 注册服务
//helloService.RegisterHelloServer(grpcServer, &Hello{})
// &Hello{}和 new(Hello)相同
helloService.RegisterHelloServer(grpcServer, new(Hello))
//3. 设置监听, 指定 IP、port
listener, err := net.Listen("tcp", "192.1678.1.111:8082")
if err != nil {
fmt.Println(err)
}
// 4退出关闭监听
defer listener.Close()
//5、启动服务
grpcServer.Serve(listener)
}
修改完后,和上面启动微服务操作一致,启动完后,查看,如下:Leader上已经注册好了相关微服务了
然后修改微服务客服端代码,让consulConfig.Address = "192.168.1.132:8500"(一般连接的是consul客户端),这样就访问到consul的客户端client-01了,从客户端就可以获取微服务对应数据了
退出集群
可以使用 Ctrl-C 优雅的关闭Agent,中断Agent之后可以看到服务离开了集群并关闭,在退出中,Consul提醒其他集群成员,这个节点离开了,如果强行杀掉进程,集群的其他成员应该能检测到这个节点失效了,当一个成员离开,他的服务和检测也会从目录中移除,当一个成员失效了,他的健康状况被简单的标记为危险,但是不会从目录中移除,Consul会 自动尝试对失效的节点进行重连,允许他从某些网络条件下恢复过来,离开的节点则不会再继续联系,此外,如果一个agent作为一个服务器,一个优雅的离开是很重要的,可以避免引起潜在的可用性故障影响达成一致性协议
停止server-01 测试程序是否正常,在对应consul服务优雅的退出运行命令 consul leave即可
然后使用命令consul members在其他三台consul服务器上查看server-01是否停止
server-01停止了,在浏览器上,consul web server-01查询,发现打不开了
而在其他server consul web ui 上可以打开,并且Leader已经发生变化(因为server-01停止了), 这样就实现了负载均衡操作,客户端访问微服务就不会挂掉
上面讲解的是一个客户端的案例,当然也可以一个微服务对应一个客户端,这样当一个服务器挂掉的话,就只会影响一个微服务,其他的微服务不会受到影响
[上一节][golang 微服务] 4. gRPC介绍,Protobuf结合gRPC 创建微服务