ETCD 的理解和簡單使用(一)

ETCD 的理解和簡單使用

1. etcd介紹

etcd是使用Go語言開發的一個開源、高可用的分布式key-value存儲系統,可以用于配置共享服務注冊和發現

特點:

完全復制:集群中的每個節點都可以使用完整的存檔。

高可用性:etcd可用于避免硬件的單點故障和網絡問題。

一致性:每次讀取都會返回跨多主機的最新寫入。

簡單:保羅一個定義良好、面向用戶的API。

安全:實現了帶有可選的客戶端證書身份驗證的自動化TLS。

快速:每秒1W次寫入的基準速度。

可靠: RAFT算法實現了強一致、高可用的服務存儲目錄。

2. etcd的應用場景

2.1 服務發現

服務發現:即在同一個分布式集群中的進程或服務,要如何才能找到對方并建立了解?本質上來說,服務發現就是想要了解進群眾是否有進程監聽UDP和TCP端口,并且通過域名就可以查找和連接。


etcd.png

配置中心
將一些配置信息放到etcd上進行集中管理。
應用在啟動的時候主動從etcd獲取一次配置信息,同事,在etcd節點上注冊一個WATCHER并等待,以后每次配置有更新的時候,etcd都會實時通知訂閱者,一次達到獲取配置信息的目的。

分布式鎖

詳細請參考:

  1. http://www.lxweimin.com/p/3be460bf9e0f

https://www.cnblogs.com/jiujuan/p/12147809.html

https://segmentfault.com/a/1190000021603215?utm_source=tag-newest

因為etcd使用Raft算法保持了數據的強一致性,其次操作存儲到集群中的值必然是全局一直的,所以很容易實現分布式鎖。鎖服務有兩種使用方式。

  • 保持獨占即所有獲取鎖的用戶最終之后一個用戶可以得到。etcd為此提供了一套實現分布式鎖算子操作CAS(CompareAndswap)的API. 通過設置prevExist值,可以保證在多個節點同事創建某個目錄時,只有一個成功。而創建成功的用戶就可以認為是獲得了鎖。
  • 控制時序: 即所有想要獲得所的用戶都會被安排執行,但是獲得所的順序也是全局唯一的,同事決定了執行順序。etcd也提供了一套API(自動創建有序鍵),對一個目錄建值同時指定為POST動作,這樣etcd會自動在目錄下生一個當前最大值為鍵,存儲這個新的值(客戶端編號)。同時還可以使用API按順序列出所有當前目錄下的鍵值。此時這些建的值就是客戶端的時序,而這些鍵中存儲的值可以代表客戶端的編號。

etcd集群

etcd作為一個高可用鍵值存儲系統,天生就是為集群哈而設計的。由于Raft算法在做決策時需要多數節點的投票,所以etcd一般部署集群推薦奇數個節點,推薦數量為3、5或者7個節點構成一個集群。

為什么用etcd而不用zookeeper?

  • etcd簡單,使用Go語言編寫部署簡單,支持HTTP/JSON API,使用簡單:使用Raft算法保證強一致性,讓用戶易于理解。
  • etcd默認數據一更新就進行持久化。
  • etcd支持SSL客戶端安全認證。
  • zookeeper部署維護復雜,其使用的PAXOS強一致性算法難懂。官方只提供了JAVA和C兩種語言的接口。
  • zookeeper 使用JAVA編寫引入大量依賴。運維人員維護起來比較麻煩。

拓展:Raft

etcd下載與安裝

  • 源碼碼下載地址https://github.com/etcd-io/etcd/releases

3. etcd命令簡單使用

1. 啟動etcd

`./etcd`

2. etcdclt 交互

2.1 put

通過put將key和value存儲到etcd集群中。 每個存儲的key都通過Raft協議復制到所有etcd集群成員,以實現一致性和可靠性。

# etcdctl put name dong    
OK
2.2 get

通過get可以從一個etcd集群中讀取key的值。
現有K-V對:

age = 18
age2 = 19
name = dong
name2 = zhang
name3 = zhao
name4 = gan
- 1. 通過key來直接讀取valu
$ etcdctl get name        
name # key
dong # value

- 2. 通過key來直接讀取value,只顯示value
$ etcdctl get name --print-value-only
dong # value

- 3. 通過key來直接讀取value,只顯示key
$ etcdctl get name --keys-only
name # key

- 4. 讀取指定范圍的key,從name~name3 左閉右開區間
$ etcdctl get name name3
name # k1
dong # v1
name2 # k2
zhang # v2

- 5. 按前綴讀取
$ etcdctl get n --prefix --keys-only
name
name2
name3
name4

- 6. 讀取數量限制
$ etcdctl get n --prefix --keys-only --limit 3
name
name2
name3

- 7. 讀取大于或等于指定鍵的字節值的鍵(從字母b開始,包含b)
$ etcdctl get --from-key b --keys-only
name
name2
name3
name4

- 8.

2.3 del
- 1. 刪除指定的 key
$ etcdctl del name
1 # 返回值,影響的個數

- 2. 刪除指定的鍵值對
$ etcdctl del --prev-kv name
1 # 返回值,影響的個數
name # k
dong # v

- 3. 刪除指定范圍的key
$ etcdctl del name name3
2 # 返回受影響的個數

- 4. 刪除具有前綴的key
$ etcdctl del --prefix a
2

-5. 刪除大于或等于鍵的字節值的鍵的命令
$ etcdctl del --from-key b
4
2.4 watch

Watch 用于監測一個 key-value 的變化,一旦 key-value 發生更新,就會輸出最新的值。

  1. 在新的終端輸入etcdctl watch key監聽對應的key。
  1. $ etcdctl put key 001
    OK
    $ etcdctl put key 002
    OK
    $ etcdctl del key
    1
    
    ###### 以下是etcdctl watch key的輸出
    $ etcdctl watch key
    PUT # TYPE
    key # K
    001 # V
    PUT
    key
    002
    DELETE
    key
    
    
2.5 lock(分布式鎖)具體可參考:

etcd 的 lock 指令對指定的 key 進行加鎖。注意,只有當正常退出且釋放鎖后,lock 命令的退出碼是 0,否則這個鎖會一直被占用直到過期(默認 60 秒)。

  • 在第一個終端輸入如下命令:
$ etcdctl lock mutex1
mutex1/694d7a4c4cf36947
  • 在第二個終端輸入同樣的命令:
$ etcdctl lock mutex1

在此可以發現第二個終端發生了阻塞,并未返回類似 mutex1/694d7a4c4cf36947的輸出。此時,如果我們使用 Ctrl+C 結束了第一個終端的 lock,然后第二個終端的顯示如下:

mutex1/694d7a4c4cf3694b

可見,這就是一個分布式鎖的實現。

2.6 transactions(事務)

txn支持從標準輸入中讀取多個請求,并將他們看作一個原子性的事務執行。 事務是由條件列表,條件判斷成功時的執行列表(條件列表中全部條件為真表示成功)和條件判斷失敗時的執行列表(條件列表中有一個為假即為失敗)組成的。

$ etcdctl txn -i # 交互
compares:  
value("name") = "dong" # 條件,可以寫多個

success requests (get, put, del): # 條件為true,執行的命令
put result ok

failure requests (get, put, del): # 條件為fales,執行的命令
put result failed

SUCCESS

OK


####### 因為value("name") = "dong" 為TRUE,所以命令put result ok 執行
$ etcdctl get result
result
ok


2.7 compact(壓縮)

etcd 會保存數據的修訂版本,以便用戶可以讀取舊版本的 key。但是為了避免累積無盡頭的版本歷史,就需要壓縮過去的修訂版本。壓縮后,etcd 會刪除歷史版本并釋放資源。

$ etcdctl compact 5
compacted revision 5

$ etcdctl get --rev=4 foo
Error: etcdserver: mvcc: required revision has been compacted
2.8 lease(租約)

KEY的TTL(time to live 生存時間)是etcd的重要特征之一,即設置KEY的超時時間。 與Redis不同,etcd需要先闖進lease(租約),通過put --lease=設置。 而lease又有TTL管理,以此來實現key的超時時間。

  1. 創建lease

    $ etcdctl lease grant 60 # 創建lease,注意時間是60s
    lease 694d7a4c4cf36969 granted with TTL(60s)
    $ etcdctl put --lease=694d7a4c4cf36969 name soo # 將lease設置到指定的key中
    OK
    $ etcdctl get name # 獲取對應的key,以及返回值
    name
    soo
    $ etcdctl get name
    name
    soo
    $ etcdctl get name # 獲取對應的key,超過存活時間,沒有返回值
    
  2. 廢除指定的lease

    $ etcdctl lease grant 60 # 創建lease
    lease 694d7a4c4cf36970 granted with TTL(60s)
    $ etcdctl put --lease=694d7a4c4cf36970 name soo # 將lease設置到指定的key中
    OK
    $ etcdctl lease revoke 694d7a4c4cf36970 #  撤銷對應的lease
    lease 694d7a4c4cf36970 revoked
    $ etcdctl get name # 無法獲取key對應的value
    
  3. 查看租約期內對應的key,以及租約的狀態

    $ etcdctl lease grant 240 # 創建lease
    lease 694d7a4c4cf3697b granted with TTL(240s)
    
    $ etcdctl put --lease=694d7a4c4cf3697b name liu # 設置key
    OK
    
    $ etcdctl put --lease=694d7a4c4cf3697b name2 song # 設置key 
    OK
    
    $ etcdctl lease timetolive 694d7a4c4cf3697b # 獲取租約信息
    lease 694d7a4c4cf3697b granted with TTL(240s), remaining(184s)
    
    $ etcdctl lease timetolive --keys 694d7a4c4cf3697b # 獲取租約信息以及對應的key
    lease 694d7a4c4cf3697b granted with TTL(240s), remaining(165s), attached keys([name name2])
    
    $ etcdctl lease timetolive --keys 694d7a4c4cf3697b #租約已經過期所對應的返回值
    lease 694d7a4c4cf3697b already expired
    
    
  4. 續約

    $ etcdctl lease grant 30
    lease 694d7a4c4cf36984 granted with TTL(30s)
    
    $ etcdctl put --lease=694d7a4c4cf36984 name aaa
    OK
    
    $ etcdctl lease keep-alive 694d7a4c4cf36984 # 該命令不會退出,一直會續約
    lease 694d7a4c4cf36984 keepalived with TTL(30)
    lease 694d7a4c4cf36984 keepalived with TTL(30)
    lease 694d7a4c4cf36984 keepalived with TTL(30)
    lease 694d7a4c4cf36984 keepalived with TTL(30)
    

4. GO Client SDK 交互

下載對應的庫:go get go get go.etcd.io/etcd/clientv3

注: 這里開啟mod可能會有error: undefined: balancer.PickOptions, 需要在mod文件中:replace google.golang.org/grpc => google.golang.org/grpc v1.26.0`

1. put、get 和 del
package main

import (
    "context"
    "fmt"
    "time"

    "go.etcd.io/etcd/clientv3"
)

func main() {

    // 創建鏈接
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 10 * time.Second,
    })
    if err != nil {
        // handle error!
        fmt.Printf("connect to etcd failed, err:%v\n", err)
        return
    }
    fmt.Println("connect to etcd success")
    defer cli.Close()

    // put
    ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
    _, err = cli.Put(ctx, "name", "dong")
    cancel()
    if err != nil {
        fmt.Printf("put to etcd failed, err:%v\n", err)
        return
    }


    // get
    ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
    resp, err := cli.Get(ctx, "name")
    cancel()
    if err != nil {
        fmt.Printf("get from etcd failed, err:%v\n", err)
        return
    }
    for _, ev := range resp.Kvs {
        fmt.Printf("%s:%s\n", ev.Key, ev.Value)
    }

    // del
    delresp, err := cli.Delete(context.TODO(), "name")
    if err != nil {
        fmt.Printf("del key failed, err:%v\n", err)
        return
    }

    if delresp.Deleted == 1{
        fmt.Println("del key success!")
    }
}

2. wathc
package main

import (
    "context"
    "fmt"
    "time"

    "go.etcd.io/etcd/clientv3"
)


func main() {
        // 創建連接  
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 10 * time.Second,
    })
    if err != nil {
        fmt.Printf("connect to etcd failed, err:%v\n", err)
        return
    }
    fmt.Println("connect to etcd success")
    defer cli.Close()
  
  
    // watch
    rch := cli.Watch(context.TODO(), "name") // 返回一個channel

  // 一直監聽key的變化
    for wresp := range rch {
        for _, ev := range wresp.Events {
            fmt.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
        }
    }
}
3. lease
package main

import (
    "fmt"
    "time"
)

// etcd lease

import (
    "context"
    "log"

    "go.etcd.io/etcd/clientv3"
)

func main() {
    // 創建連接
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 10 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("connect to etcd success.")
    defer cli.Close()

    // 創建一個5秒的租約
    resp, err := cli.Grant(context.TODO(), 5)
    if err != nil {
        log.Fatal(err)
    }

    // 5秒鐘之后, name 這個key就會被移除
    _, err = cli.Put(context.TODO(), "name", "dong", clientv3.WithLease(resp.ID))
    if err != nil {
        log.Fatal(err)
    }


    // 設置 keep-alive
    ch, err := cli.KeepAlive(context.TODO(), resp.ID)
    if err != nil {
        log.Fatal(err)
    }
    for {
        ka := <-ch
        fmt.Println("ttl:", ka.TTL)
    }

4. 事務
package main

import (
    "fmt"
    "time"
)

// etcd lease

import (
    "context"
    "log"

    "go.etcd.io/etcd/clientv3"
)

func main() {
    // 創建連接
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 10 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("connect to etcd success.")
    defer cli.Close()

  // 先設置了一個key
    _, _ = cli.Put(context.TODO(), "name", "dong")
  
 // 獲取事務對象 
    txn := cli.Txn(context.TODO())
  
  // 這里的if是不成立的,執行else,會將name的值設置為liu
    txnResp, err := txn.If(clientv3.Compare(clientv3.Value("name"), "=", "liu")).
        Else(clientv3.OpPut("name", "liu")).Commit() 
    if err != nil{
        fmt.Printf("commit failed, err:%v\n", err)
        return
    }

    if !txnResp.Succeeded {
        getResp, _ := cli.Get(context.TODO(), "name")
        fmt.Println(string(getResp.Kvs[0].Value))  // 打印出liu
    }
}
5. 分布式鎖(待研究)

參考:

  1. https://blog.csdn.net/wohu1104/article/details/108552649
  2. http://www.topgoer.com/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/go%E6%93%8D%E4%BD%9Cetcd/etcd%E4%BB%8B%E7%BB%8D.html
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 0 專輯概述 etcd 是云原生架構中重要的基礎組件,由 CNCF 孵化托管。etcd 在微服務和 Kuberna...
    aoho閱讀 19,926評論 0 2
  • 官方鏈接: etcd命令行 快速入門單機啟動etcd本地集群啟動使用goreman啟動本地三節點Procfileg...
    撈月亮的阿湯哥閱讀 1,895評論 0 2
  • 概覽 下圖中展示了etcd如何處理一個客戶端請求的涉及到的模塊和流程。圖中淡紫色的矩形表示etcd,它包括如下幾個...
    神奇的考拉閱讀 6,336評論 0 14
  • 部署環境三臺機子:10.42.5.18710.42.5.18810.42.5.189 在10.42.5.187機子...
    SkTj閱讀 805評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,592評論 28 53