这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天
etcd名字的由来,“distributed etc directory.”,意思是“分布式etc目录”,说明它存的是大型分布式系统的配置信息。一个用于存储分布式系统中最关键的数据的仓库,它是分布式的、可靠的键值对仓库。首先它是个数据存储仓库,它的特性是分布式的、可靠性的,数据存储格式是键值对存储,它主要用于存储分布式系统中的关键数据。
1.etcd的应用场景
etcd的应用场景:服务发现和服务注册、配置中心、分布式锁。
-
服务发现
服务发现要解决的也是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说,服务发现就是想要了解集群中是否有进程在监听 udp 或 tcp 端口,并且通过名字就可以查找和连接。 控制时序,即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。
-
配置中心
将一些配置信息放到 etcd 上进行集中管理。这类场景的使用方式通常是这样:应用在启动的时候主动从 etcd 获取一次配置信息,同时,在 etcd 节点上注册一个 Watcher 并等待,以后每次配置有更新的时候,etcd 都会实时通知订阅者,以此达到获取最新配置信息的目的。
-
分布式锁
因为 etcd 使用 Raft 算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。
- 保持独占即所有获取锁的用户最终只有一个可以得到。etcd 为此提供了一套实现分布式锁原子操作 CAS(CompareAndSwap)的API。通过设置prevExist值,可以保证在多个节点同时去创建某个目录时,只有一个成功。而创建成功的用户就可以认为是获得了锁。
- 控制时序,即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。etcd 为此也提供了一套API(自动创建有序键),对一个目录建值时指定为POST动作,这样 etcd会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用 API按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。
2.etcd的安装
- 下载
wget https:``//github.com/etcd-io/etcd/releases/download/v3.4.15/etcd-v3.4.15-linux-amd64.tar - 解压
tar xf etcd-v3.4.15-linux-amd64.tar - 进入解压目录
cd etcd-v3.4.15-linux-amd64 - 把etcd的服务端和客服端放入环境变量
cp etcd /usr/sbin/ cp etcdctl /usr/sbin/ - 运行etcd服务
etcd
3.go简单操作
安装etcd/clientv3
go get go.etcd.io/etcd/clientv3
main.go
package main
import (
"context"
"fmt"
"go.etcd.io/etcd/clientv3"
"time"
)
var cli *clientv3.Client
func Init(addr string, timeout time.Duration) (err error){
cli,err = clientv3.New(clientv3.Config{
Endpoints: []string{addr},
DialTimeout: timeout,
})
if err != nil{
fmt.Println("connect to etcd failed,err:",err)
return
}
return
}
func main() {
addr := "127.0.0.1:2379"
timeout := 5 * time.Second
err := Init(addr,timeout)
if err != nil {
fmt.Printf("etcd.Init err:%vn",err)
return
}
fmt.Println("etcd Init success")
// put
ctx,cancel := context.WithTimeout(context.Background(),time.Second)
_, err = cli.Put(ctx,"key","sanqing")
cancel()
if err != nil{
fmt.Printf("cli.Put err:%vn",err)
return
}
ctx,cancel = context.WithTimeout(context.Background(),time.Second)
_,err = cli.Delete(ctx,"key2")
cancel()
if err != nil{
fmt.Printf("cli.Delete err:%vn",err)
return
}
// get
ctx,cancel = context.WithTimeout(context.Background(),time.Second)
resp,err := cli.Get(ctx,"key")
cancel()
if err != nil{
fmt.Printf("cli.Get err:%vn",err)
return
}
fmt.Println(resp)
for _,ev :=range resp.Kvs{
fmt.Printf("%s:%sn",ev.Key,ev.Value)
}
// 派一个哨兵 一直监视 key 的变化(新增,修改,删除)
ch := cli.Watch(context.Background(),"key")
// 尝试重通道取值(监视信息)
for wresp := range ch{
for _,evt := range wresp.Events{
fmt.Printf("type:%v; key:%v; value:%v n",evt.Type,string(evt.Kv.Key),string(evt.Kv.Value))
}
}
}