# etcd

:一个分布式键值对数据库,与 Zookeeper 类似。

  • 官方文档 (opens new window)
  • etcd 是 etc directory 的简称,表示存储配置文件的目录。
  • 2013 年,由 CoreOS 公司开源,采用 Golang 语言开发。
  • 与 Zookeeper 相比,etcd 更轻量级。
  • 与 Consul 相比,etcd 擅长存储键值对数据,但缺少服务发现等功能。

# 原理

  • etcd 允许客户端读写 key-value 形式的数据,采用 B+ tree 结构存储数据。

    • etcd 默认会保存每个 key 所有历史版本的 value ,可通过 compaction 操作只保留最近的历史版本。
  • etcd 会将执行的事务记录到日志文件。

    • 为了避免日志文件无限增长,etcd 会定期生成快照,保留当前时刻的全部数据,并删除旧日志。
  • etcd 以 gRPC 方式提供了多种 API :

    • KV
      • :读写键值对。
    • Watch
      • :监控键值对的变化。
    • Lease
      • :租约,用于自动删除过期的 key 。
      • 客户端可以向服务器申请一个租约,有 ID、TTL 。然后用租约创建并绑定某些 key 。
      • 客户端需要定期向服务器发送 keep-alive 消息,如果超过 TTL 未发送,服务器会撤销租约,并删除其绑定的 key 。
  • etcd 集群采用 Raft 算法实现分布式一致性,建议部署 3 或 5 个节点,实现高可用。

    • 首次启动 etcd 集群时,会生成一个随机的 cluster UUID ,避免与其它 etcd 集群混合通信。
    • 因为 Raft 算法,etcd 希望每个节点都能很快将事务写入磁盘。
      • 如果 etcd 网络延迟较大、写磁盘较慢,建议增加 election-timeout 。
      • 给 etcd 使用 SSD 磁盘可以显著提高速度。相反,使用远程存储会拖慢速度。

# 部署

  • 用 docker-compose 部署:
    version: '3'
    
    services:
      etcd:
        container_name: etcd
        image: bitnami/etcd:3.5
        restart: unless-stopped
        environment:
          ETCD_ROOT_PASSWORD: ***
        ports:
          - 2379:2379     # grpc 端口
          - 2380:2380     # HTTP 端口
        volumes:
          - ./conf:/opt/bitnami/etcd/conf
          - ./data:/bitnami/etcd/data
    
    • etcd 服务器的推荐资源配额为 2 核 CPU 、2GB 内存。

# 配置

  • 配置文件 etcd.yml 示例:
    name: node-1                  # 该 etcd 节点的名称,默认为 default
    # log-level: info
    # data-dir: ${name}.etcd      # 数据目录
    # wal-dir: ''                 # wal 日志文件目录
    # snapshot-count: 10000       # 每记录多少个事务,就生成一次快照
    # max-snapshots: 5            # 最多保留多少个快照,取值为 0 则不限制
    # max-wals: 5                 # 最多保留的 wal 日志文件数量
    # max-txn-ops: 128            # 单个事务中最多包含多少个操作
    # max-request-bytes: 1572864  # 客户端单个请求的最大体积,超过则不接受。默认为 1.5MB
    # heartbeat-interval: 100     # 心跳间隔,单位为 ms 。Leader 会定期发送心跳包给其它 Follower 节点。建议通过 ping 命令测得网络 RTT ,用作心跳间隔
    # election-timeout: 1000      # 选举超时,单位为 ms 。如果 Follower 节点超过一定时长没有收到心跳包,则选举一个节点担任新 Leader
    # grpc-keepalive-min-time: 5s
    # grpc-keepalive-interval: 2h
    # grpc-keepalive-timeout: 20s
    
    auto-compaction-retention: 1    # 保留每个 key 多少历史版本的 value 。默认为 0 ,即不限制
    auto-compaction-mode: periodic  # compaction 的模式。默认为周期性(periodic),即保留最近 n 个小时的历史版本。可选为 revision ,即保留最近 n 个历史版本
    
    listen-client-urls: http://0.0.0.0:2379               # 监听的地址,供客户端连接。默认为 localhost
    listen-peer-urls: http://0.0.0.0:2380                 # 监听的地址,供集群其它 etcd 节点连接
    advertise-client-urls: http://10.0.0.1:2379           # 建议客户端通过该地址访问该节点
    initial-advertise-peer-urls: http://10.0.0.1:2380     # 建议其它 etcd 节点通过该地址访问该节点
    
    # 启动 etcd 节点时,如果集群尚未初始化,则会引导集群
    # initial-cluster: default=http://localhost:2380      # 引导集群时,要连接的 etcd 节点地址。例如 node-1=https://10.0.0.1:2380,node-2=https://10.0.0.2:2380
    # initial-cluster-token: etcd-cluster                 # 引导集群时的 token 。设置一个独特的字符串,可避免与其它集群混合通信
    # initial-cluster-state: new                          # 引导集群时,集群的状态。取值为 new 表示初次启动集群,取值为 existing 表示已有集群
    # discovery: ''                                       # 指定一个 URL ,通过它自动发现其它 etcd 节点,适合节点经常变化的情况
    # discovery-srv: ''                                   # 通过 DNS srv domain 来自动发现其它 etcd 节点
    
    # etcd 通信时默认不启用 SSL 认证
    # client-cert-auth: false
    # peer-client-cert-auth: false
    # cert-file: ...
    # key-file: ...
    

# 命令

  • etcdctl 是 etcd 的命令行客户端,用法如下:

    etcdctl
        # 通用选项
        --endpoints localhost:2379    # 指定 etcd 服务器的地址
        --user ""                     # 指定账号密码,默认为空
        --password ""
        --dial-timeout 2s             # 连接到服务器的超时时间
        --command-timeout 5s          # 连接到服务器之后,执行命令的超时时间
        --write-out simple            # 命令的输出格式。默认为 simple ,还可选 fields、json、protobuf、table
    
        # 关于服务器
        member list         # 列出所有 etcd 节点。该操作不需要登录
        endpoint health     # 检查 --endpoints 所指的服务器是否健康
        check perf          # 执行一次性能测试
        compaction <int>    # 执行一次压缩,只保留最近 n 个历史版本
        defrag              # 清理 compaction 之后产生的磁盘碎片空间,这会导致服务器暂停读写数据
            --cluster       # 作用于集群所有节点。默认只作用于 --endpoints 所指的服务器
    
        # 关于键值对
        get <key>           # 读取键名等于 key 的键值对
            --prefix        # 读取键名以 key 开头的键值对          
            --limit 0       # 限制读取的键值对数量
            --count-only    # 只是返回键值对的数量
            --keys-only     # 只读取 key
            --print-value-only  # 只读取 value
            --rev 0         # 查看距离现在第 n 个历史版本的 value 。如果不存在,则报错:required revision is a future revision
        put <key> <value>   # 写入一个键值对
            --lease ''      # 绑定租约
            --prev-kv       # 返回前一个版本的 value
        del <key>           # 删除键值对,并返回删除的数量
            --prefix
        compaction
        watch <key>         # 监听 key ,返回与之相关的事务的内容
            --prefix
        txn                 # 创建一条事务,可在其中编写多个操作
    
        lease
            list            # 列出所有租约
            grant 60        # 申请一个 TTL 为 60s 的租约,返回其 ID
            keep-alive <id> # 续订一个租约。如果不存在,则报错:lease expired or revoked
            revoke <id>     # 立即撤销一个租约
    
        snapshot save    <filename>   # 生成快照,保存为指定文件
        snapshot status  <filename>   # 查看快照文件的状态
        snapshot restore <filename>   # 导入快照文件中的数据
    
    • etcd 不支持按目录划分 key 。比如 put key1/key2 Hello 命令会创建一个名为 key1/key2 的键值对。
    • etcd v3 版本没有 ls 命令,可用 get '' --prefix --keys-only 命令列出所有 key 。
  • 例:

    [root@CentOS ~]# etcdctl put key1 Hello
    OK
    [root@CentOS ~]# etcdctl get key1
    key1
    Hello
    
    etcdctl get / --prefix --keys-only    # 查看所有 key