etcd

是配置共享和服務(wù)發(fā)現(xiàn)的鍵值存儲系統(tǒng)
  • 1 應(yīng)用程序可以在集群中共享信息哨坪、配置或作服務(wù)發(fā)現(xiàn)握截,etcd 會在集群的各個節(jié)點中復(fù)制這些數(shù)據(jù)并保證這些數(shù)據(jù)始終正確崖媚。

  • 2 服務(wù)注冊的場景在微服務(wù)架構(gòu)設(shè)計中告抄,被用來統(tǒng)一管理各個服務(wù)的服務(wù)地址,客戶端和網(wǎng)關(guān)可以通過注冊中心查詢要訪問的目標服務(wù)地址淳附,實現(xiàn)動態(tài)服務(wù)訪問,并在此基礎(chǔ)上實現(xiàn)服務(wù)負 載均衡蠢古。

  • 3 有點類似Redis的操作奴曙,首先會啟動一個服務(wù),然后利用命令行來操作草讶,它是一個KV存儲的格式

1 get  put watch del 等命令!
  • 4 watch 是來監(jiān)控相關(guān)的操作的
  • 5 etcd 存儲了歷史信息洽糟,也就是有個機制緩存了,這是基于B+樹來執(zhí)行的堕战。(B+是隨機順序的@だ!)
  • 6 etcd 里的Leaded 會為每個Key創(chuàng)建一個索引,每個索引對應(yīng)著一個B+樹
  • 7 每個B+樹里存儲了Key的歷史版本信息践啄。
  • 8
etcd 實現(xiàn)強一致性的核心在于 Raft算法
  • 1 服務(wù)器下載地址:https://github.com/etcd-io/etcd/releases/tag/v3.5.5 下載完畢直接啟動etcd.exe 服務(wù)器就行浇雹。
  • 2 etcd 是一個k,v 存儲系統(tǒng)
  • 3 put 命令是寫數(shù)據(jù):.\etcdctl.exe put key
  • 4 .\etcdctl.exe get key 是獲取key的值
  • 5 .\etcdctl.exe watch key 是監(jiān)聽這個key
  • 6 .\etcdctl.exe txn -i 可以進入事務(wù)操作
{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":8,"raft_term":4},"kvs":[{"key":"a2V5","create_revision":5,"mod_revision":8,"version":4,"value":"eXV5dQ=="}],"count":1}
  • 7 revision 字段是Key的全局版本號,在內(nèi)存中B樹會維護一個Key-->對應(yīng)的revison屿讽,而在磁盤中B+樹會針對revison版本號來修改對應(yīng)的value (映射關(guān)系!)
  • 8 .\etcdctl.exe lease grant xxx 設(shè)置一個過期時間
.\etcdctl.exe lease grant 50
.\etcdctl.exe lease put key sos
.\etcdctl.exe lease put key sos2 --lease=xxxxx字符串
.\etcdctl.exe get key   //等過期時間一過昭灵,在獲取的時候就會失敗!
etcd一致性
  • 1 先寫日志在寫磁盤,涉及到leader的選舉機制伐谈,利用Raft共識算法達成數(shù)據(jù)同步烂完。
demo
  • 1 wins本地啟動服務(wù)器: etcd.exe

import (
    "context"
    "fmt"
    clientv3 "go.etcd.io/etcd/client/v3"
    "time"
)

func main() {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        fmt.Println("connect to etcd is failed")
        return
    }
    defer client.Close()
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    res, _ := client.Get(ctx, "name")
    cancel()

    for index, v := range res.Kvs {
        fmt.Printf("下標為:%v 鍵是:%s 值是:%s \n", index, v.Key, v.Value)
    }
}
Raft.go
import ( 
 "fmt" 
 "math/rand" 
 "sync" 
 "time" 
) 

type NodeInfo struct { 
    ID string 
    Port string
} 
type Message struct {
    MsgBody string
    MsgID   int
}
type Raft struct {
    //本節(jié)點信息
    node *NodeInfo
    //本節(jié)點獲得的投票數(shù)
    vote int
    //互斥鎖
    lock sync.Mutex
    //本節(jié)點編號
    me string
    //當前任期
    currentTerm int
    //為哪個節(jié)點投票
    votedFor string
    //當前節(jié)點狀態(tài)0 follower  1 candidate  2 leader
    state int
    //發(fā)送最后一條消息的時間
    lastSendMessageTime int64
    //發(fā)送最后一次心跳的時間
    lastSendHeartBeatTime int64
    //當前節(jié)點的領(lǐng)導(dǎo)
    currentLeader string
    //心跳超時時間(秒)
    heartBeatTimeout int
    //接收投票成功通道
    voteChan chan bool
    //心跳信號
    heartChan chan bool
}

func NewRaft(id, port string) *Raft {
    rf := new(Raft)
    rf.node = &NodeInfo{
        ID:   id,
        Port: port,
    }
    //當前節(jié)點獲得票數(shù)
    rf.setVote(0)
    //編號
    rf.me = id
    //給0  1  2三個節(jié)點投票,給誰都不投
    rf.setVoteFor("-1")
    //設(shè)置節(jié)點狀態(tài) 0 follower
    rf.setStatus(0)
    //最后一次心跳檢測時間
    rf.lastSendHeartBeatTime = 0
    //心跳超時時間
    rf.heartBeatTimeout = heartBeatTimeout
    //最初沒有領(lǐng)導(dǎo)
    rf.setCurrentLeader("-1")
    //設(shè)置任期
    rf.setTerm(0)
    //投票通道
    rf.voteChan = make(chan bool)
    //心跳通道
    rf.heartChan = make(chan bool)
    return rf
}
//設(shè)置投票數(shù)量
func (rf *Raft) setVote(num int) {
    rf.lock.Lock()
    rf.vote = num
    rf.lock.Unlock()
}

//設(shè)置為誰投票
func (rf *Raft) setVoteFor(id string) {
    rf.lock.Lock()
    rf.votedFor = id
    rf.lock.Unlock()
}

//設(shè)置當前節(jié)點狀態(tài)
func (rf *Raft) setStatus(state int) {
    rf.lock.Lock()
    rf.state = state
    rf.lock.Unlock()
}

//設(shè)置當前領(lǐng)導(dǎo)者
func (rf *Raft) setCurrentLeader(leader string) {
    rf.lock.Lock()
    rf.currentLeader = leader
    rf.lock.Unlock()
}

//設(shè)置任期
func (rf *Raft) setTerm(term int) {
    rf.lock.Lock()
    rf.currentTerm = term
    rf.lock.Unlock()
}

//投票累加
func (rf *Raft) voteAdd() {
    rf.lock.Lock()
    rf.vote++
    rf.lock.Unlock()
}

//任期累加
func (rf *Raft) termAdd() {
    rf.lock.Lock()
    rf.currentTerm++
    rf.lock.Unlock()
}

//獲取當前時間的毫秒數(shù)
func millisecond() int64 {
    return time.Now().UnixNano() / int64(time.Millisecond)
}

//產(chǎn)生隨機值
func randRange(min, max int64) int64 {
    //用于心跳信號的時間
    rand.Seed(time.Now().UnixNano())
    return rand.Int63n(max-min) + min
}

//恢復(fù)默認設(shè)置
func (rf *Raft) reDefault() {
    rf.setVote(0)
    rf.setVoteFor("-1")
    rf.setStatus(0)
}

//給跟隨者節(jié)點發(fā)送心跳包
func (rf *Raft) sendHeartPacket() {
    //如果收到通道開啟的消息诵棵,將會向其他節(jié)點進行固定頻率的心跳檢測
    <-rf.heartChan //沒有收到channel就會阻塞等待
    for {
        fmt.Println("本節(jié)點開始發(fā)送心跳檢測")
        rf.broadcast("Raft.HeartBeatResponse", rf.node, func(ok bool) {
            fmt.Println("收到心跳檢測", ok)
        })
        //最后一次心跳的時間
        rf.lastSendHeartBeatTime = millisecond()
        //休眠 --》心跳檢測頻率的時間
        time.Sleep(time.Second * time.Duration(heartBeatRate))
    }
}

//修改節(jié)點為候選人狀態(tài)
func (rf *Raft) becomeCandidate() bool {
    r := randRange(1500, 5000)
    //休眠隨機時間后抠蚣,再開始成為候選人
    time.Sleep(time.Duration(r) * time.Millisecond)
    //如果當前節(jié)點是跟隨者,并且沒有領(lǐng)導(dǎo)履澳,也沒有為別人投票
    if rf.state == 0 && rf.currentLeader == "-1" && rf.votedFor == "-1" {
        //將節(jié)點狀態(tài)變成候選者
        rf.setStatus(1)
        //設(shè)置為自己投了票
        rf.setVoteFor(rf.me)
        //自己的投票數(shù)量增加
        rf.voteAdd()
        //節(jié)點任期加1
        rf.termAdd()

        fmt.Println("本節(jié)點已經(jīng)變成候選人狀態(tài)")
        fmt.Printf("當前獲得的票數(shù):%d\n", rf.vote)
        //開啟選舉通道
        return true
    }
    return false
}

//進行選舉
func (rf *Raft) election() bool {
    fmt.Println("開始進行領(lǐng)導(dǎo)者選舉嘶窄,向其他節(jié)點進行廣播")
    go rf.broadcast("Raft.Vote", rf.node, func(ok bool) {
        rf.voteChan <- ok
    })
    for {
        select {
        //選舉超時
        case <-time.After(time.Second * time.Duration(electionTimeout)):
            fmt.Println("領(lǐng)導(dǎo)者選舉超時怀跛,節(jié)點變更為追隨者狀態(tài)")
            rf.reDefault()
            return false
        case ok := <-rf.voteChan:
            if ok {
                rf.voteAdd()
                fmt.Printf("獲得來自其他節(jié)點的投票,當前得票數(shù):%d\n", rf.vote)
            }
            if rf.vote >= nodeCount/2+1 && rf.currentLeader == "-1" {
                fmt.Println("獲得大多數(shù)節(jié)點的同意柄冲,本節(jié)點被選舉成為了leader")
                //節(jié)點狀態(tài)變?yōu)?吻谋,代表leader
                rf.setStatus(2)
                //當前領(lǐng)導(dǎo)者為自己
                rf.setCurrentLeader(rf.me)
                fmt.Println("向其他節(jié)點進行廣播本節(jié)點成為了leader...")
                go rf.broadcast("Raft.ConfirmationLeader", rf.node, func(ok bool) {
                    fmt.Println("其他節(jié)點:是否同意[", rf.node.ID, "]為領(lǐng)導(dǎo)者", ok)
                })
                //有l(wèi)eader了,可以發(fā)送心跳包了
                rf.heartChan <- true
                return true
            }
        }
    }
}

//嘗試成為候選人并選舉
func (rf *Raft) tryToBeCandidateWithElection() {
    for {
        //嘗試成為候選人節(jié)點
        if rf.becomeCandidate() {
            //成為后選人節(jié)點后 向其他節(jié)點要選票來進行選舉
            if rf.election() {
                break
            } else {
                continue //領(lǐng)導(dǎo)者選舉超時,重新稱為候選人進行選舉
            }
        } else {
            //沒有變成候選人现横,則退出
            //不是跟隨者漓拾,或者有領(lǐng)導(dǎo),或者為別人投票
            break
        }
    }
}

//心跳超時檢測
func (rf *Raft) heartTimeoutDetection() {
    for {
        //0.5秒檢測一次
        time.Sleep(time.Millisecond * 5000)
        //心跳超時
        if rf.lastSendHeartBeatTime != 0 && (millisecond()-rf.lastSendHeartBeatTime) > int64(rf.heartBeatTimeout*1000) {
            fmt.Printf("心跳檢測超時戒祠,已超過%d秒\n", rf.heartBeatTimeout)
            fmt.Println("即將重新開啟選舉")
            rf.reDefault()
            rf.setCurrentLeader("-1")
            rf.lastSendHeartBeatTime = 0
            go rf.tryToBeCandidateWithElection()
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骇两,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姜盈,更是在濱河造成了極大的恐慌低千,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馏颂,死亡現(xiàn)場離奇詭異栋操,居然都是意外死亡,警方通過查閱死者的電腦和手機饱亮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門矾芙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人近上,你說我怎么就攤上這事剔宪。” “怎么了壹无?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵葱绒,是天一觀的道長。 經(jīng)常有香客問我斗锭,道長地淀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任岖是,我火速辦了婚禮帮毁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豺撑。我一直安慰自己烈疚,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布聪轿。 她就那樣靜靜地躺著爷肝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灯抛,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天金赦,我揣著相機與錄音,去河邊找鬼对嚼。 笑死素邪,一個胖子當著我的面吹牛彼水,可吹牛的內(nèi)容都是我干的氓皱。 我是一名探鬼主播住拭,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磨确!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起声邦,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤乏奥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亥曹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邓了,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年媳瞪,在試婚紗的時候發(fā)現(xiàn)自己被綠了骗炉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛇受,死狀恐怖句葵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兢仰,我是刑警寧澤乍丈,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站把将,受9級特大地震影響轻专,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜察蹲,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一请垛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洽议,春花似錦叼屠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春荚坞,著一層夾襖步出監(jiān)牢的瞬間挑宠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工颓影, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留各淀,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓诡挂,卻偏偏與公主長得像碎浇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子璃俗,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容