目录
一、注册中心
1.1 服务注册与发现
-
在使用微服务后,调用都变成了服务间的调用。
-
服务间调用需要知道IP、端口等信息。
-
在没有微服务之前,我们的调用信息一般都是写死在调用方的配置文件里(有的公司把这些信息写到数据库等公共的地方,以方便维护)。
-
由于业务的复杂,每个服务可能依赖N个其他服务,如果某个服务的IP,端口等信息发生变更,那么所有依赖该服务的服务的配置文件都要去修改,这样显然太麻烦了。
-
有些服务为了负载是有个多个实例的,而且可能是随时会调整实例的数量。如果每次调整实例数量都要去修改其他服务的配置并重启那太麻烦了。
-
为了解决这个问题,就有了注册中心
-
假设我们有服务A需要调用服务B,并且有服务注册发现组件R。整个大致流程将变成3步:
- 服务B启动时,向注册中心注册资金
- 服务A从从注册中心拉取服务B的信息
- 服务A调用服务B
- 有了服务注册发现组件之后,当修改A服务信息的时候再也不用去修改其他相关服务了
[](http://photo.liuqingzheng.top/20220521-image-20220521004920992 .png)
1.2 分布式一致性算法
-
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的
-
一致性协议算法主要有:Paxos、Raft、ZAB、Gossip
-
Paxos算法是Leslie Lamport在1990年提出的一种基于消息传递的一致性算法,非常难以理解,基于Paxos协议的数据同步与传统主备方式最大的区别在于:Paxos只需超过半数的副本在线且相互通信正常,就可以保证服务的持续可用,且数据不丢失。
-
Raft是斯坦福大学的Diego Ongaro、John Ousterhout两个人以易理解为目标设计的一致性算法,已经有了十几种语言的Raft算法实现框架,较为出名的有etcd,Google的Kubernetes也是用了etcd作为他的服务发现框架。
-
Raft是Paxos的简化版,与Paxos相比,Raft强调的是易理解、易实现,Raft和Paxos一样只要保证超过半数的节点正常就能够提供服务。这篇文章 《ETCD教程-2.Raft协议》 详细讲解了Raft原理,非常有意思,感兴趣的同学可以看看。
-
ZooKeeper Atomic Broadcast (ZAB, ZooKeeper原子消息广播协议)是ZooKeeper实现分布式数据一致性的核心算法,ZAB借鉴Paxos算法,但又不像Paxos算法那样,是一种通用的分布式一致性算法,它是一种特别为ZooKeeper专门设计的支持崩溃恢复的原子广播协议
1.3 注册中心选型
-
5种常用的注册中心,分别为Zookeeper、Eureka、Nacos、Consul和ETCD
-
具体参照:https://blog.csdn.net/lml200701158/article/details/123153513
[](http://photo.liuqingzheng.top/20220521-image-20220521010452074 .png)
二、Consul
2.1 介绍
-
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。
-
Consul 使用起来也较为简单,使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合
Consul 主要特征
2.2 consul特点
CP模型,使用 Raft 算法来保证强一致性,不保证可用性;
支持服务注册与发现、健康检查、KV Store功能。
支持多数据中心,可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。
支持安全服务通信,Consul可以为服务生成和分发TLS证书,以建立相互的TLS连接。
支持 http 和 dns 协议接口;
官方提供 web 管理界面
2.3 安装
(1)普通安装
i. win
//1 下载
//2 加入环境变量
//3 执行
consul agent -dev
ii. mac
//1 下载
//2 加入环境变量
//3 执行
consul agent -dev
(2)docker安装
//1 使用docker下载consul
//同步时间:ntpdate cn.pool.ntp.org
docker pull docker.io/library/consul
//2 修改consul tag
docker tag docker.io/library/consul consul
//3 启动
docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0
//4 浏览器中访问web页面
// 8500端口:http端口,我们进行服务注册发现使用http端口
// 8600端口:dns端口
//5 dig 命令,通过域名解析地址和端口
//dig命令dns地址为10.0.0.102,
//端口为:8600,
//域名为:service的名字.service.consul ---》自动生成
//查询类型指定为为srv
dig @10.0.0.102 -p 8600 consul.service.consul SRV
[](http://photo.liuqingzheng.top/20220528-image-20220521012903958 .png)
三、常用API
3.1 服务注册
// put 请求
// 地址:/v1/agent/service/register
// Service Registration Flags
-name:The name of the service to register-->服务的名字
-id:The ID of the service. This will default to -name if not set--》id不设置与name一致
-tag value - Associate a tag with the service instance. This flag can be specified multiples times.-->服务的标签
-address - The address of the service. If this isn't specified, it will default to the address registered with the local agent.
-port - The port of the service.
(1)使用postman注册演示
// 向http://10.0.0.102:8500/v1/agent/service/register发送put请求
// body体数据为
{
"Name":"lqz",
"ID":"ddd",
"Tags":["lqz","web"],
"Address":"127.0.0.1",
"Port":8080
}
[](http://photo.liuqingzheng.top/20220528-image-20220521020045128 .png)
(2)Go 语言注册
package main
import "fmt"
import consulapi "github.com/hashicorp/consul/api"
const (
consulAddress = "10.0.0.102:8500"
)
func RegisterConsul(localIP string, localPort int, name string,id string, tags []string) error {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = consulAddress
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
// 创建注册到consul的服务到
registration := new(consulapi.AgentServiceRegistration)
registration.ID = id
registration.Name = name //根据这个名称来找这个服务
registration.Port = localPort
//registration.Tags = []string{"lqz", "web"} //这个就是一个标签,可以根据这个来找这个服务,相当于V1.1这种
registration.Tags = tags //这个就是一个标签,可以根据这个来找这个服务,相当于V1.1这种
registration.Address = localIP
// 增加consul健康检查回调函数
check := new(consulapi.AgentServiceCheck)
check.HTTP = fmt.Sprintf("http://%s:%d/health", registration.Address, registration.Port)
check.Timeout = "5s" //超时
check.Interval = "5s" //健康检查频率
check.DeregisterCriticalServiceAfter = "30s" // 故障检查失败30s后 consul自动将注册服务删除
registration.Check = check
// 注册服务到consul
err = client.Agent().ServiceRegister(registration)
if err != nil {
return err
}
return nil
}
func main() {
// 1 注册
RegisterConsul("192.168.1.1",8080,"lqz_web","lqz_web",[]string{"lqz","web"})
}
3.2 服务删除
(1)使用postman注册演示
// 向 http://10.0.0.102:8500/v1/agent/service/deregister/ddd发送put请求
(2)go代码
func DeleteService() error {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = consulAddress
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
err = client.Agent().ServiceDeregister("qqq")
if err != nil {
return err
}
return nil
}
3.3 设置健康检查
3.4 获取服务
(1)获取所有
func GetAllService() (map[string]*consulapi.AgentService, error) {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = consulAddress
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
res, err := client.Agent().Services()
if err != nil {
return nil, err
}
return res, nil
}
(2)过滤服务
func FilterService() (map[string]*consulapi.AgentService, error) {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = consulAddress
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
//res, err := client.Agent().ServicesWithFilter(`Service=="lqz-web03"`)// 按服务名字过滤
res, err := client.Agent().ServicesWithFilter(`Port==8087`) // 按端口过滤
if err != nil {
return nil, err
}
return res, nil
}
3.5 同一个服务注册多次
name一样,id不一样会把name一样的放到一起
3.6 gRPC注册服务和健康检查
gRPC预留了健康检查的proto接口,并且实现了,我们只需要引入注册到微服务中即可
https://github.com/grpc/grpc/blob/master/doc/health-checking.md
通过proto生成的go文件路径:google.golang.org/grpc/health/grpc_health_v1
健康检查代码路径:google.golang.org/grpc/health
package main
import (
"context"
"fmt"
consulapi "github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"grpc_proto_demo/proto_default_demo/proto"
"net"
)
type GreeterServer struct {
}
// proto 的service中只写了一个方法,现在只写一个,如果写了多个,都要实现
func (h GreeterServer) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
// 接收客户端发送过来的数据,打印出来
fmt.Println("客户端传入的名字是:", in.Name)
fmt.Println("客户端传入的年龄是:", in.Age)
fmt.Println("客户端传入的女孩们是:", in.Girls)
// 返回给客户端
return &proto.HelloResponse{
Reply: "服务端给你回复",
}, nil
}
// 服务端代码
func main() {
// 第一步:new一个server
g := grpc.NewServer()
// 第二步:生成一个结构体对象
s := GreeterServer{}
// 第三步: 把s注册到g对象中
proto.RegisterGreeterServer(g, &s)
// 第四步:启动服务,监听端口
lis, error := net.Listen("tcp", "192.168.31.226:50052")
if error != nil {
panic("启动服务异常")
}
//******** 注册grpc服务和设置健康检查***开始*****
// 1 设置健康检查
//health.NewServer()具体实现grpc已经帮我们写好了
grpc_health_v1.RegisterHealthServer(g,health.NewServer())
// 2 注册grpc服务---》需要使用机器的真实ip
RegisterConsul("192.168.31.226",50052,"grpc_test","grpc_test001",[]string{"grpc","lqz"})
//******** 注册grpc服务和设置健康检查***结束*****
g.Serve(lis)
}
func RegisterConsul(localIP string, localPort int, name string,id string, tags []string) error {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = "10.0.0.102:8500"
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
// 创建注册到consul的服务到
registration := new(consulapi.AgentServiceRegistration)
registration.ID = id
registration.Name = name //根据这个名称来找这个服务
registration.Port = localPort
//registration.Tags = []string{"lqz", "web"} //这个就是一个标签,可以根据这个来找这个服务,相当于V1.1这种
registration.Tags = tags //这个就是一个标签,可以根据这个来找这个服务,相当于V1.1这种
registration.Address = localIP
// 增加consul健康检查回调函数
check := new(consulapi.AgentServiceCheck)
check.GRPC = "192.168.31.226:50052" // 健康检查地址只需要写grpc服务地址端口即可,会自动检查
check.Timeout = "5s" //超时
check.Interval = "5s" //健康检查频率
check.DeregisterCriticalServiceAfter = "30s" // 故障检查失败30s后 consul自动将注册服务删除
registration.Check = check
// 注册服务到consul
err = client.Agent().ServiceRegister(registration)
if err != nil {
return err
}
return nil
}
四、案例(gin-grpc-consul)
(1)整体流程和目录结构
[](http://photo.liuqingzheng.top/20220528-image-20220528012249481 .png)
// 0 grpc服务注册到consul中
// 1 浏览器访问gin服务的 /index
// 2 gin服务到consul中发现grpc服务
// 3 gin服务向grpc发送请求获得数据
// 4 返回给浏览器
// 目录结构
grpc_gin_consul
-gin_web
-main.go
-grpc_srv
-proto
-hello.proto
-server
-main.go
(2)gin_web/main.go
package main
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
consulapi "github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"grpc_proto_demo/grpc_gin_consul/grpc_srv/proto"
)
// 可能有多个grpc服务,我们只返回一个
func getFirstGrpcRegister()(host string,port int,err error) {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = "10.0.0.102:8500"
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
//res, err := client.Agent().Services()
res, err := client.Agent().ServicesWithFilter(`Service=="grpc_test"`)
if err != nil {
return "", 0,err
}
fmt.Println(res)
for _,value:=range res{
host=value.Address
port=value.Port
}
return // 命名返回值
}
func main() {
r:=gin.Default()
r.GET("/index", func(c *gin.Context) {
host,port,_:=getFirstGrpcRegister()
fmt.Println(host,port)
// 第一步:连接服务端
conn, err := grpc.Dial(fmt.Sprintf("%s:%d",host,port), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
fmt.Println(err)
c.JSON(200,"连接grpc服务异常")
}
//defer 关闭
defer conn.Close()
// 第二步:创建客户端调用
client := proto.NewGreeterClient(conn)
// 测试默认值
resp,err:=client.SayHello(context.Background(),&proto.HelloRequest{
Name: "lqz",
Age: 19,
})
if err != nil {
c.JSON(200,"服务器错误")
}
c.JSON(200,resp.Reply)
})
r.Run()
}
(3)grpc_srv/proto/hello.proto
syntax = "proto3";
option go_package = ".;proto";
service Greeter{
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
// 类似于go的结构体,可以定义属性
message HelloRequest {
string name = 1; // 1 是编号,不是值
int32 age = 2;
}
// 定义一个响应的类型
message HelloResponse {
string reply =1;
}
(4)生成go文件
protoc --go_out=. ./hello.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false ./hello.proto
(5)grpc_srv/server/main.go
package main
import (
"context"
"fmt"
consulapi "github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"grpc_proto_demo/grpc_gin_consul/grpc_srv/proto"
"net"
)
type GreeterServer struct {
}
// proto 的service中只写了一个方法,现在只写一个,如果写了多个,都要实现
func (h GreeterServer) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
// 接收客户端发送过来的数据,打印出来
fmt.Println("客户端传入的名字是:", in.Name)
fmt.Println("客户端传入的年龄是:", in.Age)
return &proto.HelloResponse{
Reply: "gin-调用grpc,grpc给的回复",
}, nil
}
// 服务端代码
func main() {
// 第一步:new一个server
g := grpc.NewServer()
// 第二步:生成一个结构体对象
s := GreeterServer{}
// 第三步: 把s注册到g对象中
proto.RegisterGreeterServer(g, &s)
// 第四步:启动服务,监听端口
lis, error := net.Listen("tcp", "192.168.31.226:50052")
if error != nil {
panic("启动服务异常")
}
//******** 注册grpc服务和设置健康检查********
// 1 设置健康检查
//health.NewServer()具体实现grpc已经帮我们写好了
grpc_health_v1.RegisterHealthServer(g,health.NewServer())
// 2 注册grpc服务
RegisterConsul("192.168.31.226",50052,"grpc_test","grpc_test001",[]string{"grpc","lqz"})
g.Serve(lis)
}
func RegisterConsul(localIP string, localPort int, name string,id string, tags []string) error {
// 创建连接consul服务配置
config := consulapi.DefaultConfig()
config.Address = "10.0.0.102:8500"
client, err := consulapi.NewClient(config)
if err != nil {
fmt.Println("consul client error : ", err)
}
// 创建注册到consul的服务到
registration := new(consulapi.AgentServiceRegistration)
registration.ID = id
registration.Name = name //根据这个名称来找这个服务
registration.Port = localPort
//registration.Tags = []string{"lqz", "gin_web"} //这个就是一个标签,可以根据这个来找这个服务,相当于V1.1这种
registration.Tags = tags //这个就是一个标签,可以根据这个来找这个服务,相当于V1.1这种
registration.Address = localIP
// 增加consul健康检查回调函数
check := new(consulapi.AgentServiceCheck)
check.GRPC = "192.168.31.226:50052" // 健康检查地址只需要写grpc服务地址端口即可,会自动检查
check.Timeout = "5s" //超时
check.Interval = "5s" //健康检查频率
check.DeregisterCriticalServiceAfter = "30s" // 故障检查失败30s后 consul自动将注册服务删除
registration.Check = check
// 注册服务到consul
err = client.Agent().ServiceRegister(registration)
if err != nil {
return err
}
return nil
}
标签:服务,中心,err,proto,grpc,consul,注册
From: https://www.cnblogs.com/Mcoming/p/18080577