註冊中心-consul

BigSun丶發表於2024-03-19

目錄
  • 一、註冊中心
    • 1.1 服務註冊與發現
    • 1.2 分散式一致性演算法
    • 1.3 註冊中心選型
  • 二、Consul
    • 2.1 介紹
    • 2.2 consul特點
    • 2.3 安裝
      • (1)普通安裝
        • i. win
        • ii. mac
      • (2)docker安裝
  • 三、常用API
    • 3.1 服務註冊
      • (1)使用postman註冊演示
      • (2)Go 語言註冊
    • 3.2 服務刪除
      • (1)使用postman註冊演示
      • (2)go程式碼
    • 3.3 設定健康檢查
    • 3.4 獲取服務
      • (1)獲取所有
      • (2)過濾服務
    • 3.5 同一個服務註冊多次
    • 3.6 gRPC註冊服務和健康檢查
  • 四、案例(gin-grpc-consul)
      • (1)整體流程和目錄結構
      • (2)gin_web/main.go
      • (3)grpc_srv/proto/hello.proto
      • (4)生成go檔案
      • (5)grpc_srv/server/main.go

一、註冊中心

1.1 服務註冊與發現

  • 在使用微服務後,呼叫都變成了服務間的呼叫。

  • 服務間呼叫需要知道IP、埠等資訊。

  • 在沒有微服務之前,我們的呼叫資訊一般都是寫死在呼叫方的配置檔案裡(有的公司把這些資訊寫到資料庫等公共的地方,以方便維護)。

  • 由於業務的複雜,每個服務可能依賴N個其他服務,如果某個服務的IP,埠等資訊發生變更,那麼所有依賴該服務的服務的配置檔案都要去修改,這樣顯然太麻煩了。

  • 有些服務為了負載是有個多個例項的,而且可能是隨時會調整例項的數量。如果每次調整例項數量都要去修改其他服務的配置並重啟那太麻煩了。

  • 為了解決這個問題,就有了註冊中心

  • 假設我們有服務A需要呼叫服務B,並且有服務註冊發現元件R。整個大致流程將變成3步:

  1. 服務B啟動時,向註冊中心註冊資金
  2. 服務A從從註冊中心拉取服務B的資訊
  3. 服務A呼叫服務B
  • 有了服務註冊發現元件之後,當修改A服務資訊的時候再也不用去修改其他相關服務了

image-20220521004920992

1.2 分散式一致性演算法

  • 一致性就是資料保持一致,在分散式系統中,可以理解為多個節點中資料的值是一致

  • 一致性協議演算法主要有:Paxos、Raft、ZAB、Gossip

  • 具體參照:https://zhuanlan.zhihu.com/p/130332285

  • 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

image-20220521010452074

二、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)普通安裝

  • 下載地址:https://www.consul.io/downloads

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

image-20220521012903958

三、常用API

3.1 服務註冊

  • 參考文件:https://www.consul.io/commands/services/register
// 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

}

image-20220521020045128

(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 服務刪除

  • 參考:https://www.consul.io/commands/services/deregister

(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 設定健康檢查

  • 參考:https://www.consul.io/api-docs/agent/check#register-check

3.4 獲取服務

  • 參考:https://www.consul.io/api-docs/agent/service#sample-request

(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)整體流程和目錄結構

image-20220528012249481

// 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

}

相關文章