很久之前写过一篇 etcd 的介绍文章,主要是讲 etcd 的概念和使用方式,这篇文章介绍如何使用 go 语言进行 etcd 的开发工作。
etcd 目前最新版 API 是 v3 版本,之前被广泛使用的 API 为 v2 版本。这篇文章会介绍 v2 版本 go 语言客户端的使用,涉及了最常见的增删查改和监听操作。
之前的文章 已经对 etcd v2 版本的概念和 API 介绍得很详细了,这里再总结一下。
概念讲完了,下面看看如何用 go 语言进行 etcd 的开发工作。etcd 官方就维护了一个 etcd go 语言客户端,在
,我们可以用下面的语句把它导入到 go 程序中:
- github.com/coreos/etcd/client
- import (
- ...
- etcd "github.com/coreos/etcd/client"
- "github.com/coreos/etcd/pkg/transport"
- )
我们给它取了个别名
,之后后面的程序中我们可以用
- etcd
而不是
- etcd
来使用它提供的功能(这样程序的可读性更好)。
- client
首先,我们要创建一个 etcd 的 Client,可以使用
函数,它接受
- etcd.New()
作为参数,后者主要的参数包括:
- etcd.Config
:etcd server 集群的地址列表,是一个 string 列表,每个值为一个 etcd 的监听地址
- Endpoints
:底层用于进行 HTTP 请求传输的结构体
- Transport
和
- Username
:进行简单认证的用户名和密码
- Password
其他还有一些参数这里就不说了,使用默认值就行。为了完整性,我们会处理 etcd server 开启了简单认证和 TLS 的情况,首先创建一个自定义的结构体保存所有需要的参数:
- type EtcdConfig struct {
- Endpoints []string
- KeyFile string
- CertFile string
- CAFile string
- Username string
- Password string
- }
注意这是我们自己定义的结构体,不是客户端程序提供的。
然后构建能进行 TLS 处理的 transport,
是 etcd 提供的库,它内部会处理证书的读取和验证工作:
- transport
- tlsInfo := transport.TLSInfo{
- CertFile: c.CertFile,
- KeyFile: c.KeyFile,
- CAFile: c.CAFile,
- }
- t, err := transport.NewTransport(tlsInfo, time.Second)
然后就能构建 client:
- client, err := etcd.New(etcd.Config{
- Endpoints: c.Endpoints,
- Transport: t,
- Username: c.Username,
- Password: c.Password,
- })
这个 client 主要负责 HTTP API 交互的逻辑,同时也负责从多个 endpoints 中选择一个可用的来调用。但是和 keys 交互的逻辑并没有直接在这个 client 中,而是需要另外一个对象
:
- KeysAPI
- kapi: =etcd.NewKeysAPI(client)
所有键值对有关的操作都是这个
对象的方法提供的,它会实现接口的如下方法:
- kapi
- type KeysAPI interface {
- // Get 从 etcd 中获取 Node 的信息
- Get(ctx context.Context, key string, opts * GetOptions)( * Response, error)
- // Set 设置对应 key 的值为 value,这个方法也可以用来创建目录
- Set(ctx context.Context, key, value string, opts * SetOptions)( * Response, error)
- // Delete 删除节点,包括目录和文件节点
- Delete(ctx context.Context, key string, opts * DeleteOptions)( * Response, error)
- // Create 是 set 的一种特殊形式,只有之前节点不存在时才会创建成功
- Create(ctx context.Context, key, value string)( * Response, error)
- // CreateInOrder 在目录下面创建递增的键值对
- CreateInOrder(ctx context.Context, dir, value string, opts * CreateInOrderOptions)( * Response, error)
- // Update 也是 Set 的一种特殊形式,只有对应的节点存在时才会更新成功
- Update(ctx context.Context, key, value string)( * Response, error)
- // Watcher 返回一个 Watcher 对象,监听某个节点下面的变化
- Watcher(key string, opts * WatcherOptions) Watcher
- }
先来看看设置一个 key value 的操作,第一个是 context 对象,后面两个分别是 key 和 value 值,最后是额外的参数,目前并不需要设置为 nil:
- resp, err := client.Set(context.Background(), "/user/name", "cizixs", nil)
如果要为某个值设置超时时间,可以添加选项
:
- TTL
- resp,
- err: =client.Set(context.Background(), "/user/name", "", &etcd.SetOptions {
- TTL: time.Duration(ttl) * time.Second,
- Refresh: true,
- },
- )
可选的字段有:
- SetOptions
:布尔值,表示要创建的是一个目录
- Dir
:如果设置为 true,则表明只是更新某个 key 的 TTL 时间,不会重置它的值
- Refresh
:原子操作,只有节点的值和这个字段指定的值相同时才会执行更新操作
- PrevValue
:原子操作,只有节点的 ModifiedIndex 和这个字段指定的值相同时才会执行更新操作
- PrevIndex
:原子操作,只有节点存在或者不存在时才会执行更新操作,支持的值有
- PrevExist
、
- PrevIgnore
和
- PrevExist
- PrevNoExist
这里要先讲一下返回值
的构成,它包含了返回中节点的信息以及 index 信息:
- Response
- type Response struct {
- // Action 操作的动作名称,可以是 set、delete
- Action string`json: "action"`
- // Node 代表操作的节点
- Node * Node`json: "node"`
- // PrevNode 节点之前的值
- PrevNode * Node`json: "prevNode"`
- // Index:response 生成是 cluster index 的值
- Index uint64`json: "-"`
- }
其中节点的定义如下:
- type Node struct {
- // 节点的位置,目录名称,比如 `/foo/bar`
- Key string`json: "key"`
- // 节点是否为一个目录
- Dir bool`json: "dir,omitempty"`
- // 节点存储的对象值,如果是目录,则忽略这个字段
- Value string`json: "value"`
- // Nodes 子节点,如果是目录,则这个字段保存了目录下面的所有节点内容
- Nodes Nodes`json: "nodes"`
- // 节点创建时候的 etcd index
- CreatedIndex uint64`json: "createdIndex"`
- // 节点更新时候的 etcd index
- ModifiedIndex uint64`json: "modifiedIndex"`
- // 节点的过期时间
- Expiration * time.Time`json: "expiration,omitempty"`
- // TTL 节点设置的 time to live,单位是秒
- TTL int64`json: "ttl,omitempty"`
- }
获取值是通过
方法,和
- Get
一样,它也支持选项。如果只是简单获取某个键的值,直接设置选项为空就行:
- Set
- resp, err := client.Get(context.Background(), key, nil)
如果要递归地获取某个目录下面所有的内容,可以使用
参数:
- Recursive
- resp, err := client.Get(context.Background(), key, &etcd.GetOptions{
- Recursive: true,
- })
然后通过 resp 就能遍历所有的子节点的值。
删除操作通过
方法完成,简单删除一个文件节点,可以忽略参数:
- Delete
- _, err := client.Delete(context.Background(), key, nil)
如果要删除某个非空的目录,需要
参数:
- Recursive
- _, err := client.Delete(context.Background(), key, &etcd.DeleteOptions{
- Dir: true,
- Recursive: true,
- })
其他字段包括:
:只有节点值和给定的值相同时才执行删除操作
- PrevValue
:只有节点的 index 和给定的 index 相同时,才执行删除操作
- PrevIndex
etcd 另外一个重要的功能是监听目录或者文件的变化,
方法会返回一个对象,调用它的
- Watcher
方法会阻塞,一直到监听的对象有变化,它才会返回变化节点的情况:
- Next()
- watcher: =client.Watcher(key, &etcd.WatcherOptions {
- Recursive: true,
- })
- for {
- resp,
- err: =watcher.Next(context.Background()) if err != nil {
- return err
- }
- }
其中
接受
- WatcherOptions
和
- AfterIndex
两个参数,分别表示才某个 Index 之后开始监听,以及递归监听某个目录下面所有的节点。
- Recursive
完整的 demo 代码在 github 上 ,请前往阅读。
来源: http://www.tuicool.com/articles/FJJNniI