1. 服務(wù)發(fā)現(xiàn)是什么?
我們在做微服務(wù)開發(fā)的時候,客戶端的一個接口可能需要調(diào)用N個服務(wù),客戶端必須知道所有服務(wù)的網(wǎng)絡(luò)位置(ip+port),如下圖所示
以往的做法是把服務(wù)的地址放在配置文件活數(shù)據(jù)庫中,這樣就有以下幾個問題:
- 需要配置N個服務(wù)的網(wǎng)絡(luò)位置,加大配置的復(fù)雜性
- 服務(wù)的網(wǎng)絡(luò)位置變化,需要改變每個調(diào)用者的配置
- 集群的情況下,難以做負(fù)載(反向代理的方式除外)
總結(jié)起來一句話:服務(wù)多了,配置很麻煩,問題一大堆
所以現(xiàn)在就選擇服務(wù)發(fā)現(xiàn)來解決這些問題。我們來看一下,服務(wù)發(fā)現(xiàn)如何解決這個問題,具體設(shè)計如下:
與之前解決方法不同的是,加了個服務(wù)發(fā)現(xiàn)模塊。服務(wù)端把當(dāng)前自己的網(wǎng)絡(luò)位置注冊到服務(wù)發(fā)現(xiàn)模塊(這里注冊的意思就是告訴),服務(wù)發(fā)現(xiàn)就以K-V的方式記錄下,K一般是服務(wù)名,V就是IP:PORT。服務(wù)發(fā)現(xiàn)模塊定時的輪詢查看這些服務(wù)能不能訪問的了(這就是健康檢查)。客戶端在調(diào)用服務(wù)A-N的時候,就跑去服務(wù)發(fā)現(xiàn)模塊問下它們的網(wǎng)絡(luò)位置,然后再調(diào)用它們的服務(wù)。這樣的方式是不是就可以解決上面的問題了呢?客戶端完全不需要記錄這些服務(wù)的網(wǎng)絡(luò)位置,客戶端和服務(wù)端完全解耦!
常見的服務(wù)發(fā)現(xiàn)框架有:Etcd、Eureka、Consul、Zookeeper
這里我們選擇go-micro默認(rèn)的服務(wù)發(fā)現(xiàn)框架consul來做一個詳細(xì)介紹。
2. consul相關(guān)
consul架構(gòu)圖:
Consul集群是有N個SERVER,加上M個CLIENT組成的。 而不管是SERVER還是CLIENT,都是CONSUL的一個節(jié)點,所有的服務(wù)都可以注冊到這些節(jié)點上,正是通過這些節(jié)點實現(xiàn)服務(wù)注冊信息的共享。
CLIENT:client表示consul的client模式,就是客戶端模式。這種模式下,所有注冊到當(dāng)前節(jié)點的服務(wù)會被轉(zhuǎn)發(fā)到SERVER【通過HTTP和DNS接口請求SERVER】,本身是不持久化這些信息,
SERVER: server表示consul的server模式,表明此consul是個server,這種模式下,功能和client都一樣,唯一不同的是,它會把所有的信息持久化到本地。遇到故障,信息是可以被保留并溯源。
SERVER-LEADER: 表明這個server是leader,它和其他server不一樣的一點是,它需要負(fù)責(zé)同步注冊的信息給其他的server,同時也要負(fù)責(zé)各個節(jié)點的健康監(jiān)測。
2.1 consul關(guān)鍵特性:
服務(wù)發(fā)現(xiàn):consul通過DNS或者HTTP接口使服務(wù)注冊和服務(wù)發(fā)現(xiàn)變的很容易,一些外部服務(wù),例如saas提供的也可以一樣注冊。
健康檢查:Cousul的client可以提供任意數(shù)量的健康檢查,既可以給定的服務(wù)相關(guān)聯(lián)(webserver是否返回200),也可以與本地節(jié)點相關(guān)聯(lián)(內(nèi)存利用率是都低于90%)。 操作員可以使用這些信息來檢測集群的健康狀況,服務(wù)發(fā)現(xiàn)組件可以使用這些信息將流量從不健康的主路由出去。健康檢測使consul可以快速的告警在集群中的操作。和服務(wù)發(fā)現(xiàn)的集成,可以防止服務(wù)轉(zhuǎn)發(fā)到故障的服務(wù)上面。(心跳機制)
鍵/值存儲:一個用來存儲動態(tài)配置的系統(tǒng)。提供簡單的HTTP接口,可以在任何地方操作。
安全服務(wù)通信:Consul可以為服務(wù)生成和分發(fā)TLS證書,以建立項目的TLS連接。 意圖可用于定義允許哪些服務(wù)通信。 服務(wù)分割可以很容易地進(jìn)行管理,其目的是可以實時更改的,而不是使用復(fù)雜的網(wǎng)絡(luò)拓?fù)浜挽o態(tài)防火墻規(guī)則。
多數(shù)據(jù)中心:無需復(fù)雜的配置,即可支持任意數(shù)量的區(qū)域。
官方建議:最好是三臺或者三臺以上的consul在運行,同名服務(wù)最好是三臺或三臺以上,默認(rèn)可以搭建集群
.2 下載:
https://www.consul.io/downloads.html
2.3 consul簡單使用
首先我們要運行consul,運行有兩種模式,分別是==server==和==client==,通過下面的命令開啟:
consul agent -server # server運行模式
consul agent # client 運行模式
每個數(shù)據(jù)中心至少必須擁有一個server。
一個client是一個非常輕量級的進(jìn)程,用于注冊服務(wù),運行健康檢查和轉(zhuǎn)發(fā)對server的查詢.
agent必須在集群中的每個主機上運行.
server模式啟動
$ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=n1 -bind=192.168.6.108 -ui -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0
需要先在/etc/下面創(chuàng)建consul.d目錄
-server
: 定義agent運行在server模式
-bootstrap-expect
:在一個datacenter中期望提供的server節(jié)點數(shù)目,當(dāng)該值提供的時候,<u>consul一直等到達(dá)到指定sever數(shù)目的時候才會引導(dǎo)整個集群,</u>該標(biāo)記不能和bootstrap共用
-bind
:該地址用來在集群內(nèi)部的通訊,集群內(nèi)的所有節(jié)點到地址都必須是可達(dá)的,默認(rèn)是0.0.0.0
-node
:節(jié)點在集群中的名稱,在一個集群中必須是唯一的,默認(rèn)是該節(jié)點的主機名
-ui
: 啟動web界面 :8500
-rejoin
:使consul忽略先前的離開,在再次啟動后仍舊嘗試加入集群中。
-config-dir
:配置文件目錄,里面所有以.json結(jié)尾的文件都會被加載(需要先創(chuàng)建/etc/consul.d
這個目錄)
-client
:consul服務(wù)監(jiān)聽地址,這個地址提供HTTP、DNS、RPC等服務(wù),默認(rèn)是127.0.0.1所以不對外提供服務(wù),如果你要對外提供服務(wù)改成0.0.0.0
--data-dir
:提供一個目錄用來存放agent的狀態(tài),所有的agent允許都需要該目錄,該目錄必須是穩(wěn)定的,系統(tǒng)重啟后都繼續(xù)存在
client模式啟動
consul agent -data-dir /tmp/consul -node=n2 -bind=192.168.137.82 -config-dir /etc/consul.d -rejoin -join 192.168.137.81
運行cosnul agent以client模式,-join 加入到已有的集群中去。
停止Agent
可以使用 Ctrl-C
來關(guān)閉Agent,中斷Agent之后你可以看到他離開了集群應(yīng)關(guān)閉。
在退出中,Consul提醒其他集群成員,這個節(jié)點離開了。如果強行殺掉進(jìn)程,集群的其他成員應(yīng)該能檢測到這個節(jié)點失效了。 當(dāng)一個成員離開,他的服務(wù)和檢測也會從目錄中移除。當(dāng)一個成員失效了,他的健康狀況被簡單的標(biāo)記為危險。但是不會從目錄中移除,Consul會自動對失效的節(jié)點進(jìn)行重連。允許他從某些網(wǎng)絡(luò)條件下恢復(fù)過來,離開的節(jié)點則不再繼續(xù)聯(lián)系。
此外,如果一個agent作為一個服務(wù)器,一個優(yōu)雅的離開是很重要的,可以避免潛在的可用性故障,影響達(dá)成一致性協(xié)議。
consul leave # consul優(yōu)雅退出命令
注冊服務(wù)
搭建好consul集群后,用戶或者程序就能到consul中查詢或注冊服務(wù)。可以通過提供服務(wù)定義文件或者調(diào)用HTTP API來注冊一個服務(wù)。
需要注意的是,要先為consul配置創(chuàng)建一個目錄 /etc/consul.d
, consul會載入配置文件件里的所有以.json
為后綴的配置文件。
mkdir /etc/consul.d
web.json
{
"service": {
"ID":"S1",
"name": "test",
"tags": ["001", "002"], // 標(biāo)簽
"port": 8989,
"address":"127.0.0.1",
"checks":[
{
"http":"http://127.0.0.1:8989/consul", // 健康檢查的請求
"timeout":"3s", //timeout設(shè)置
"interval":"1s" // 心跳時間
}
]
}
}
保存文件并重啟服務(wù)即可。
然后啟動一個簡易的go服務(wù):
package main
import (
"fmt"
"net/http"
)
func main(){
http.HandleFunc("/consul", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println("來自consul的健康問候...")
})
http.ListenAndServe(":8989", nil)
}
會看到不停的打印 來自consul的健康問候...
,consul 進(jìn)行健康檢查,根據(jù)配置的心跳時間,不停的訪問http://127.0.0.1:8989/consul
查詢服務(wù)
- 此時可以到ui界面
localhost:8500
查看服務(wù)的狀態(tài) - 使用命令:
dig @127.0.0.1 -p 8600 test.service.consul srv
請思考一個問題: consul中的client模式把請求轉(zhuǎn)向server,那么client的作用是什么?
首先server端的網(wǎng)絡(luò)連接資源有限。對于一個分布式系統(tǒng),一般情況下的訪問量還是很大的。如果用用戶不能通過client直接訪問數(shù)據(jù)中心,那么數(shù)據(jù)中心必然要為每個用戶提供一個單獨的連接資源,那么server端的負(fù)擔(dān)會非常大。隨意很有必要用大量的client端來分散連接請求,在client端先統(tǒng)一整合用戶的服務(wù)請求,然后一次性的通過單一的鏈接發(fā)送大量的的請求給server端,能夠大量減少server端的網(wǎng)絡(luò)負(fù)擔(dān)。
其次,在client端可以對用戶的請求進(jìn)行一些處理來提高服務(wù)的效率,比如將相同的請求合并成同一個查詢,再比如將之前的查詢通過cookie的形式緩存下來。但是這些功能都需要消耗不少的計算和存儲資源。 如果在server端提供這些功能,必然加重server端的負(fù)擔(dān),使得server端更加不穩(wěn)定。而通過client端來進(jìn)行這些服務(wù)就沒有這些問題了,移位client端不提供實際服務(wù),有充足的計算資源來處理這些工作。
最后一點,consul規(guī)定只要介入一個client就將自己注冊到一個網(wǎng)絡(luò)服務(wù)當(dāng)中。這些結(jié)構(gòu)使得系統(tǒng)的拓展性非常強,網(wǎng)絡(luò)的拓?fù)渥兓梢蕴貏e靈活。這也依賴于client-server結(jié)構(gòu)的。如果系統(tǒng)中只有幾個數(shù)據(jù)中心存在,那網(wǎng)絡(luò)的擴張也無從談起。
參考文檔:http://www.liangxiansen.cn/tags/Consul/
3. consul+grpc
參考文檔:https://www.consul.io/api/agent/check
健康檢查參考文檔:https://www.consul.io/docs/discovery/checks
1. 安裝包
go get -u -v github.com/hashicorp/consul
2. 代碼相關(guān)
2.1 proto文件:
health.pb.proto 參考文檔:https://github.com/grpc/grpc/blob/master/doc/health-checking.md
syntax = "proto3";
package grpc.health.v1;
option go_package="./grpc.health.v1/grpc.health.v1";
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
SERVICE_UNKNOWN = 3; // Used only by the Watch method.
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
people.proto
syntax = "proto3";
package pb;
option go_package="./pb;pb";
message people {
string name = 1;
}
service SayHi {
rpc sayHi (people) returns (people);
}
通過命令,生成對應(yīng)的.proto.go
文件后,開始編寫client.go
和sever.go
server.go
package main
import (
"ConsulDemo/grpc.health.v1"
"ConsulDemo/pb"
"context"
"fmt"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"net"
)
type people struct {}
func (c *people) SayHi(ctx context.Context, p *pb.People) (*pb.People, error){
fmt.Printf("hi %v", p.Name)
return p, nil
}
// HealthImpl 健康檢查實現(xiàn)
type HealthImpl struct{}
// Check 實現(xiàn)健康檢查接口,這里直接返回健康狀態(tài),這里也可以有更復(fù)雜的健康檢查策略,比如根據(jù)服務(wù)器負(fù)載來返回
func (h *HealthImpl) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
func (*HealthImpl) Watch(*grpc_health_v1.HealthCheckRequest, grpc_health_v1.Health_WatchServer) error {
return nil
}
func main(){
//初始化consul配置
consulConfig := api.DefaultConfig()
//consulConfig.Address= "localhost:8900"
//獲取consul操作對象
registry,err := api.NewClient(consulConfig)
if err != nil{
panic(err)
}
//注冊服務(wù),服務(wù)的常規(guī)配置
registerService := api.AgentServiceRegistration{
ID:"1",
Tags:[]string{"sayHi"},
Name:"sayHi",
Port:1234,
Address:"localhost",
Check:&api.AgentServiceCheck{
GRPC: fmt.Sprintf("%v:%v/%v", "localhost", "1234", "HealthImpl"),
Timeout:"5s",
Interval:"5s",
},
}
//注冊服務(wù)到consul上
err = registry.Agent().ServiceRegister(®isterService)
if err != nil{
panic(err)
}
//獲取grpc服務(wù)端對象
grpcServer := grpc.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, &HealthImpl{})
//注冊grpc服務(wù)
pb.RegisterSayHiServer(grpcServer,new(people))
//設(shè)置服務(wù)端監(jiān)聽
lis,err := net.Listen("tcp",":1234")
if err != nil {
panic(err)
}
//在指定端口上提供grpc服務(wù)
grpcServer.Serve(lis)
}
注意:此時需要開啟一個consul agent,consul agent -dev
client.go
package main
import (
"fmt"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"strconv"
"ConsulDemo/pb"
"context"
)
func main(){
//初始化consul配置, 客戶端服務(wù)器需要一致
consulConfig := api.DefaultConfig()
//獲取consul操作對象
registerClient,_ := api.NewClient(consulConfig)
//獲取地址
serviceEntry,_,_ :=registerClient.Health().Service("sayHi","sayHi",true,nil)
fmt.Println(fmt.Sprintf("%+v",serviceEntry ))
//和grpc服務(wù)建立連接
conn,err := grpc.Dial(serviceEntry[0].Service.Address+":"+strconv.Itoa(serviceEntry[0].Service.Port),grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewSayHiClient(conn)
reply, err := client.SayHi(context.Background(),&pb.People{Name:"dongxiaojian"})
if err != nil {
panic(err)
}
fmt.Println("reply:",reply)
}