利用etcd選舉sdk實(shí)踐master/slave故障轉(zhuǎn)移

本次記錄[利用etcd選主sdk實(shí)踐master/slave故障轉(zhuǎn)移], 并利用etcdctl客戶端驗(yàn)證選主sdk的工作原理。

master/slave高可用集群
本文目標(biāo)
在異地多機(jī)房部署節(jié)點(diǎn),slave作為備用實(shí)例啟動(dòng),但不接受業(yè)務(wù)流量, 監(jiān)測(cè)到master宕機(jī),slave節(jié)點(diǎn)自動(dòng)提升為master并接管業(yè)務(wù)流量。
基本思路
各節(jié)點(diǎn)向etcd注冊(cè)帶租約的節(jié)點(diǎn)信息, 并各自維持心跳保活,選主sdk根據(jù)目前存活的、最早創(chuàng)建的節(jié)點(diǎn)信息鍵值對(duì)?來(lái)確立leader,?并通過(guò)watch機(jī)制通知業(yè)務(wù)代碼leader變更。

講道理,每個(gè)節(jié)點(diǎn)只需要知道兩個(gè)信息就能各司其職
??誰(shuí)是leader ==> 當(dāng)前節(jié)點(diǎn)是什么角色===> 當(dāng)前節(jié)點(diǎn)該做什么事情
??感知集群leader變更的能力 ===》當(dāng)前節(jié)點(diǎn)現(xiàn)在要不要改變行為
除了官方etcd客戶端go.etcd.io/etcd/client/v3, 還依賴go.etcd.io/etcd/client/v3/concurrency?package:實(shí)現(xiàn)了基于etcd的分布式鎖、屏障、選舉
| 選主過(guò)程 | 實(shí)質(zhì) | api |
| 競(jìng)選前先查詢leader了解現(xiàn)場(chǎng) | 查詢當(dāng)前存活的,最早創(chuàng)建的kv值 | *concurrency.Election.Leader() |
| 初始化時(shí),各節(jié)點(diǎn)向etcd阻塞式競(jìng)選 | 各節(jié)點(diǎn)向etcd注冊(cè)帶租約的鍵值對(duì) | *concurrency.Election.compaign |
| 建立master/slave集群,還能及時(shí)收到變更通知 | 通過(guò)chan傳遞最新的leader value | *concurrency.Election.Observe() |
重點(diǎn)解讀
1.初始化etcd go客戶端
注意:etcd客戶端和服務(wù)端是通過(guò)grpc來(lái)通信,目前新版本的etcd客戶端默認(rèn)使用非阻塞式連接, 也就是說(shuō)v3.New函數(shù)僅表示從指定配置創(chuàng)建etcd客戶端。

為快速確定etcd選舉的可用性,本實(shí)踐使用阻塞式創(chuàng)建客戶端:
cli,?err?:=?v3.New(v3.Config{
??Endpoints:???addr,
??DialTimeout:?time.Second?*?5,
??DialOptions:?[]grpc.DialOption{grpc.WithBlock()},
?})
?if?err?!=?nil?{
??log.WithField("instance",?Id).Errorln(err)
??return?nil,?err
?}
2. 競(jìng)選
使用阻塞式命令compaign競(jìng)選之前,應(yīng)先查詢當(dāng)前l(fā)eader:
//?將id:ip:port作為競(jìng)選時(shí)寫入etcd的value
func?(c?*Client)?Election(id?string,?notify?chan<-?bool)?error?{
?//競(jìng)選前先試圖去了解情況
?ctx,?cancel?:=?context.WithTimeout(context.Background(),?time.Second*3)
?defer?cancel()
?resp,?err?:=?c.election.Leader(ctx)
?if?err?!=?nil?{
??if?err?!=?concurrency.ErrElectionNoLeader?{
???return?err
??}
?}?else?{?//?已經(jīng)有l(wèi)eader了
??c.Leader?=?string(resp.Kvs[0].Value)
??notify?<-?(c.Leader?==?id)
?}
?if?err?=?c.election.Campaign(context.TODO(),?id);?err?!=?nil?{
??log.WithError(err).WithField("id",?id).Error("Campaign?error")
??return?err
?}?else?{
??log.Infoln("Campaign?success!!!")
??c.Leader?=?id
??notify?<-?true
?}
?c.election.Key()
?return?nil
}
參選:將持續(xù)刷新的leaseID作為key,將特定的客戶端標(biāo)記(這里使用ip:port)作為value,寫到etcd.
當(dāng)選:當(dāng)前存活的、最早創(chuàng)建的key是leader?,?也就是說(shuō)leader故障轉(zhuǎn)移并不是隨機(jī)的。

3. watch leader變更
golang使用信道完成goroutine通信,
本例聲明信道:?notify = make(chan bool, 1)
一石二鳥(niǎo):監(jiān)聽(tīng)該信道能夠知道集群leader是否發(fā)生變化;信道內(nèi)傳的值表示當(dāng)前節(jié)點(diǎn)是否是leader
func?(c?*Client)?Watchloop(id?string,?notify?chan<-?bool)?error?{
?ch?:=?c.election.Observe(context.TODO())?//?觀察leader變更
?tick?:=?time.NewTicker(c.askTime)
?defer?tick.Stop()
?for?{
??var?leader?string
??select?{
??case?_?=?<-c.sessionCh:
???log.Warning("Recv?session?event")
???return?fmt.Errorf("session?Done")?//?一次續(xù)約不穩(wěn),立馬退出程序
??case?e?:=?<-ch:
???log.WithField("event",?e).Info("watch?leader?event")
???leader?=?string(e.Kvs[0].Value)
???ctx,?cancel?:=?context.WithTimeout(context.Background(),?time.Second*3)
???defer?cancel()
???resp,?err?:=?c.election.Leader(ctx)
???if?err?!=?nil?{
????if?err?!=?concurrency.ErrElectionNoLeader?{
?????return?err
????}?else?{?//?目前沒(méi)leader,開(kāi)始競(jìng)選了
?????if?err?=?c.election.Campaign(context.TODO(),?id);?err?!=?nil?{
??????log.WithError(err).WithField("id",?id).Error("Campaign?error")
??????return?err
?????}?else?{?//?競(jìng)選成功
??????leader?=?id
?????}
????}
???}?else?{
????leader?=?string(resp.Kvs[0].Value)
???}
??}
??if?leader?!=?c.Leader?{
???log.WithField("before",?c.Leader).WithField("after",?leader?==?id).Info("leader?changed")
???notify?<-?(leader?==?id)
??}
??c.Leader?=?leader
?}
}
c.election.Observe(context.TODO()) 返回最新的leader信息,配合select case控制結(jié)構(gòu)能夠及時(shí)拿到leader變更信息。
如題:通過(guò)Leader字段和chan <- bool信道, 掌控了整個(gè)選舉集群的狀態(tài), 可根據(jù)這兩個(gè)信息去完成業(yè)務(wù)上的master/slave故障轉(zhuǎn)移。
使用etcdctl確定leader
election.Leader的源碼證明了[當(dāng)前存活的,最早創(chuàng)建的kv為leader]
//?Leader?returns?the?leader?value?for?the?current?election.
func?(e?*Election)?Leader(ctx?context.Context)?(*v3.GetResponse,?error)?{
?client?:=?e.session.Client()
?resp,?err?:=?client.Get(ctx,?e.keyPrefix,?v3.WithFirstCreate()...)
?if?err?!=?nil?{
??return?nil,?err
?}?else?if?len(resp.Kvs)?==?0?{
??//?no?leader?currently?elected
??return?nil,?ErrElectionNoLeader
?}
?return?resp,?nil
}
等價(jià)于
./etcdctl get /merc --prefix --sort-by=CREATE --order=ASCEND --limit=1
--?sort-by?:以某標(biāo)準(zhǔn)(創(chuàng)建時(shí)間)檢索數(shù)據(jù)
-- order :以升/降序給已檢出的數(shù)據(jù)排序
-- limit:從以檢出的數(shù)據(jù)中取x條數(shù)據(jù)顯示
有態(tài)度的馬甲建立了真●?高質(zhì)量交流群:大佬匯聚、無(wú)事靜默、有事激活、深度思考。
[長(zhǎng)按圖片加我好友]

年終總結(jié):2021技術(shù)文大盤點(diǎn) ?| ?打包過(guò)去,面向未來(lái)
項(xiàng)目總結(jié):麻雀雖小,五臟俱全
理念總結(jié):實(shí)話實(shí)說(shuō):只會(huì).NET,會(huì)讓我們一直處于鄙視鏈、食物鏈的下游
云原生系列:?什么是云原生?
點(diǎn)“贊”
戳“在看”
體現(xiàn)態(tài)度很有必要!
