Skip to content

Etcd

Etcd是CoreOS开发的分布式键值存储系统,基于Raft一致性协议,为分布式系统提供可靠的数据存储服务,是Kubernetes集群的默认后端存储。

核心特性

强一致性

  • Raft协议 - 基于Raft算法保证数据强一致性
  • ACID事务 - 支持事务操作,保证数据完整性
  • 线性化读写 - 提供严格的线性化一致性保证
  • 多版本并发控制 - MVCC机制支持并发访问

高可用性

  • 集群部署 - 支持多节点集群部署
  • 自动故障转移 - Leader选举和自动切换
  • 数据复制 - 自动在集群节点间复制数据
  • 健康检查 - 节点健康状态监控

监听机制

  • Watch机制 - 监听键值变化事件
  • Prefix Watch - 监听指定前缀的所有键变化
  • 历史事件 - 支持从指定版本开始监听
  • 事件过滤 - 支持事件类型过滤

架构设计

集群架构

Etcd集群(推荐奇数节点):
├── Leader节点(1个)
│   ├── 处理写请求
│   ├── 日志复制
│   └── 心跳维持
├── Follower节点(2或4个)
│   ├── 处理读请求
│   ├── 接收日志复制
│   └── 参与选举
└── Raft一致性协议
    ├── 日志复制
    ├── Leader选举
    └── 安全性保证

存储模型

存储结构:
├── 内存索引(BTree)
├── WAL日志(预写日志)
├── 快照文件(定期创建)
└── 后端存储(BoltDB)

基础使用

安装部署

二进制安装

bash
# 下载etcd
ETCD_VER=v3.5.9
wget https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz

# 解压并安装
tar xzf etcd-${ETCD_VER}-linux-amd64.tar.gz
sudo mv etcd-${ETCD_VER}-linux-amd64/etcd* /usr/local/bin/

单节点启动

bash
etcd --name=etcd-1 \
  --data-dir=/var/lib/etcd \
  --listen-client-urls=http://0.0.0.0:2379 \
  --advertise-client-urls=http://localhost:2379 \
  --listen-peer-urls=http://0.0.0.0:2380 \
  --advertise-peer-urls=http://localhost:2380 \
  --initial-cluster=etcd-1=http://localhost:2380 \
  --initial-cluster-state=new

基本操作

命令行客户端

点击查看完整代码实现
bash
# 设置键值
etcdctl put name "john"
etcdctl put age "30"

# 获取值
etcdctl get name
etcdctl get age

# 获取范围
etcdctl get --prefix user/

# 删除键
etcdctl del name

# 监听变化
etcdctl watch name

# 事务操作
etcdctl txn <<< '
compare:
value("name") = "john"

success:
put result "success"

failure:
put result "failure"
'

HTTP API

bash
# 设置键值
curl -X PUT http://localhost:2379/v3/kv/put \
  -d '{"key":"bmFtZQ==","value":"am9obg=="}'

# 获取值(key和value需要base64编码)
curl -X POST http://localhost:2379/v3/kv/range \
  -d '{"key":"bmFtZQ=="}'

# 监听变化
curl -X POST http://localhost:2379/v3/watch \
  -d '{"create_request":{"key":"bmFtZQ=="}}'

客户端集成

Go客户端

点击查看完整代码实现
go
package main

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

func main() {
    // 创建客户端
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    // 设置键值
    _, err = client.Put(context.Background(), "name", "john")
    if err != nil {
        log.Fatal(err)
    }
    
    // 获取值
    resp, err := client.Get(context.Background(), "name")
    if err != nil {
        log.Fatal(err)
    }
    
    for _, kv := range resp.Kvs {
        fmt.Printf("Key: %s, Value: %s\n", kv.Key, kv.Value)
    }
    
    // 监听变化
    watchChan := client.Watch(context.Background(), "name")
    for watchResp := range watchChan {
        for _, event := range watchResp.Events {
            fmt.Printf("Event: %s, Key: %s, Value: %s\n", 
                event.Type, event.Kv.Key, event.Kv.Value)
        }
    }
}

Java客户端

点击查看完整代码实现
java
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.kv.GetResponse;

public class EtcdExample {
    public static void main(String[] args) throws Exception {
        // 创建客户端
        Client client = Client.builder()
            .endpoints("http://localhost:2379")
            .build();
            
        KV kvClient = client.getKVClient();
        
        // 设置键值
        ByteSequence key = ByteSequence.from("name", "UTF-8");
        ByteSequence value = ByteSequence.from("john", "UTF-8");
        kvClient.put(key, value).get();
        
        // 获取值
        GetResponse response = kvClient.get(key).get();
        response.getKvs().forEach(kv -> {
            System.out.println("Key: " + kv.getKey().toString("UTF-8"));
            System.out.println("Value: " + kv.getValue().toString("UTF-8"));
        });
        
        client.close();
    }
}

集群部署

三节点集群配置

节点1配置

bash
etcd --name=etcd-1 \
  --data-dir=/var/lib/etcd/etcd-1 \
  --listen-client-urls=http://192.168.1.101:2379 \
  --advertise-client-urls=http://192.168.1.101:2379 \
  --listen-peer-urls=http://192.168.1.101:2380 \
  --advertise-peer-urls=http://192.168.1.101:2380 \
  --initial-cluster=etcd-1=http://192.168.1.101:2380,etcd-2=http://192.168.1.102:2380,etcd-3=http://192.168.1.103:2380 \
  --initial-cluster-state=new \
  --initial-cluster-token=etcd-cluster

节点2配置

bash
etcd --name=etcd-2 \
  --data-dir=/var/lib/etcd/etcd-2 \
  --listen-client-urls=http://192.168.1.102:2379 \
  --advertise-client-urls=http://192.168.1.102:2379 \
  --listen-peer-urls=http://192.168.1.102:2380 \
  --advertise-peer-urls=http://192.168.1.102:2380 \
  --initial-cluster=etcd-1=http://192.168.1.101:2380,etcd-2=http://192.168.1.102:2380,etcd-3=http://192.168.1.103:2380 \
  --initial-cluster-state=new \
  --initial-cluster-token=etcd-cluster

节点3配置

bash
etcd --name=etcd-3 \
  --data-dir=/var/lib/etcd/etcd-3 \
  --listen-client-urls=http://192.168.1.103:2379 \
  --advertise-client-urls=http://192.168.1.103:2379 \
  --listen-peer-urls=http://192.168.1.103:2380 \
  --advertise-peer-urls=http://192.168.1.103:2380 \
  --initial-cluster=etcd-1=http://192.168.1.101:2380,etcd-2=http://192.168.1.102:2380,etcd-3=http://192.168.1.103:2380 \
  --initial-cluster-state=new \
  --initial-cluster-token=etcd-cluster

Docker部署

点击查看完整代码实现
yaml
# docker-compose.yml
version: '3.8'
services:
  etcd1:
    image: gcr.io/etcd-development/etcd:v3.5.9
    container_name: etcd1
    command: >
      /usr/local/bin/etcd
      --name etcd1
      --data-dir /etcd-data
      --advertise-client-urls http://etcd1:2379
      --listen-client-urls http://0.0.0.0:2379
      --advertise-peer-urls http://etcd1:2380
      --listen-peer-urls http://0.0.0.0:2380
      --initial-cluster etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      --initial-cluster-state new
    ports:
      - "2379:2379"
    volumes:
      - etcd1-data:/etcd-data
    networks:
      - etcd-net

  etcd2:
    image: gcr.io/etcd-development/etcd:v3.5.9
    container_name: etcd2
    command: >
      /usr/local/bin/etcd
      --name etcd2
      --data-dir /etcd-data
      --advertise-client-urls http://etcd2:2379
      --listen-client-urls http://0.0.0.0:2379
      --advertise-peer-urls http://etcd2:2380
      --listen-peer-urls http://0.0.0.0:2380
      --initial-cluster etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      --initial-cluster-state new
    ports:
      - "22379:2379"
    volumes:
      - etcd2-data:/etcd-data
    networks:
      - etcd-net

  etcd3:
    image: gcr.io/etcd-development/etcd:v3.5.9
    container_name: etcd3
    command: >
      /usr/local/bin/etcd
      --name etcd3
      --data-dir /etcd-data
      --advertise-client-urls http://etcd3:2379
      --listen-client-urls http://0.0.0.0:2379
      --advertise-peer-urls http://etcd3:2380
      --listen-peer-urls http://0.0.0.0:2380
      --initial-cluster etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
      --initial-cluster-state new
    ports:
      - "32379:2379"
    volumes:
      - etcd3-data:/etcd-data
    networks:
      - etcd-net

volumes:
  etcd1-data:
  etcd2-data:
  etcd3-data:

networks:
  etcd-net:
    driver: bridge

高级功能

分布式锁

点击查看完整代码实现
go
import (
    "context"
    "fmt"
    "time"
    
    clientv3 "go.etcd.io/etcd/client/v3"
    "go.etcd.io/etcd/client/v3/concurrency"
)

func distributedLock() {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        panic(err)
    }
    defer client.Close()
    
    // 创建会话
    session, err := concurrency.NewSession(client, concurrency.WithTTL(10))
    if err != nil {
        panic(err)
    }
    defer session.Close()
    
    // 创建分布式锁
    mutex := concurrency.NewMutex(session, "/my-lock")
    
    // 获取锁
    if err := mutex.Lock(context.Background()); err != nil {
        panic(err)
    }
    fmt.Println("获取锁成功")
    
    // 执行业务逻辑
    time.Sleep(5 * time.Second)
    
    // 释放锁
    if err := mutex.Unlock(context.Background()); err != nil {
        panic(err)
    }
    fmt.Println("释放锁成功")
}

领导者选举

点击查看完整代码实现
go
func leaderElection() {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        panic(err)
    }
    defer client.Close()
    
    session, err := concurrency.NewSession(client)
    if err != nil {
        panic(err)
    }
    defer session.Close()
    
    // 创建选举
    election := concurrency.NewElection(session, "/my-election")
    
    // 参与选举
    if err := election.Campaign(context.Background(), "node-1"); err != nil {
        panic(err)
    }
    
    fmt.Println("成为Leader")
    
    // 作为Leader执行任务
    select {
    case <-session.Done():
        fmt.Println("会话结束,失去Leader地位")
    }
}

租约管理

点击查看完整代码实现
go
func leaseExample() {
    client, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        panic(err)
    }
    defer client.Close()
    
    // 创建租约
    lease, err := client.Grant(context.Background(), 30)
    if err != nil {
        panic(err)
    }
    
    // 将键值与租约关联
    _, err = client.Put(context.Background(), "temp-key", "temp-value", clientv3.WithLease(lease.ID))
    if err != nil {
        panic(err)
    }
    
    // 续约
    keepAlive, err := client.KeepAlive(context.Background(), lease.ID)
    if err != nil {
        panic(err)
    }
    
    // 处理续约响应
    go func() {
        for ka := range keepAlive {
            fmt.Printf("续约成功,TTL: %d\n", ka.TTL)
        }
    }()
    
    time.Sleep(60 * time.Second)
    
    // 撤销租约
    _, err = client.Revoke(context.Background(), lease.ID)
    if err != nil {
        panic(err)
    }
}

监控管理

集群状态检查

bash
# 检查集群健康状态
etcdctl endpoint health

# 检查集群成员
etcdctl member list

# 查看Leader
etcdctl endpoint status --write-out=table

# 查看集群指标
curl http://localhost:2379/metrics

性能基准测试

bash
# 写入性能测试
etcdctl check perf

# 自定义基准测试
benchmark --endpoints=localhost:2379 put --total=10000 --val-size=256

# 读取性能测试
benchmark --endpoints=localhost:2379 range foo --total=10000 --consistency=l

数据备份恢复

bash
# 创建快照备份
etcdctl snapshot save backup.db

# 查看快照状态
etcdctl snapshot status backup.db

# 从快照恢复
etcdctl snapshot restore backup.db \
  --name etcd-1 \
  --data-dir /var/lib/etcd/restored \
  --initial-cluster etcd-1=http://localhost:2380 \
  --initial-advertise-peer-urls http://localhost:2380

安全配置

TLS加密

点击查看完整代码实现
bash
# 生成CA证书
openssl genrsa -out ca-key.pem 2048
openssl req -new -x509 -key ca-key.pem -out ca.pem -days 365

# 生成服务器证书
openssl genrsa -out server-key.pem 2048
openssl req -new -key server-key.pem -out server.csr
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem -out server.pem

# 启用TLS的etcd
etcd --name=etcd-secure \
  --cert-file=server.pem \
  --key-file=server-key.pem \
  --trusted-ca-file=ca.pem \
  --client-cert-auth \
  --listen-client-urls=https://localhost:2379 \
  --advertise-client-urls=https://localhost:2379

基于角色的访问控制

点击查看完整代码实现
bash
# 启用认证
etcdctl auth enable

# 创建用户
etcdctl user add myuser
etcdctl user add root

# 创建角色
etcdctl role add myrole

# 授予权限
etcdctl role grant-permission myrole read /foo
etcdctl role grant-permission myrole write /foo

# 将角色分配给用户
etcdctl user grant-role myuser myrole

# 使用用户认证
etcdctl --user=myuser:password get /foo

应用场景

服务发现

  • Kubernetes Pod和Service信息存储
  • 微服务实例注册和发现
  • 负载均衡器后端列表维护
  • 健康检查状态存储

配置管理

  • 应用配置中心化存储
  • 配置热更新和分发
  • 环境变量管理
  • 特性开关控制

分布式协调

  • 分布式锁实现
  • 领导者选举
  • 分布式队列
  • 集群成员管理

Kubernetes集成

  • 集群状态存储
  • API对象持久化
  • 事件和审计日志
  • 资源配额和限制

性能优化

硬件配置建议

  • CPU: 多核心,支持并发处理
  • 内存: 至少4GB,建议8GB以上
  • 存储: SSD固态硬盘,低延迟
  • 网络: 千兆以太网,低延迟

配置优化

bash
# 优化配置参数
--heartbeat-interval=100          # 心跳间隔
--election-timeout=1000           # 选举超时
--snapshot-count=100000           # 快照触发条件
--max-snapshots=5                 # 保留快照数
--max-wals=5                      # 保留WAL数
--quota-backend-bytes=8589934592  # 后端存储配额

监控指标

  • 延迟指标: 读写延迟、网络延迟
  • 吞吐量: QPS、带宽使用
  • 存储: 数据库大小、磁盘使用
  • 集群: 节点状态、Leader选举

最佳实践

集群规划

  • 部署奇数个节点(3、5、7)
  • 跨可用区部署提高可用性
  • 合理规划网络拓扑
  • 使用专用的硬件资源

数据设计

  • 合理设计键值结构
  • 使用前缀组织相关数据
  • 避免存储大值数据
  • 定期清理过期数据

运维管理

  • 定期备份数据
  • 监控集群健康状态
  • 建立告警机制
  • 制定故障恢复计划

Etcd作为云原生时代的关键基础设施,凭借其强一致性保证、简洁的API设计和卓越的可靠性,成为Kubernetes等容器编排平台的首选后端存储,为现代分布式系统提供了坚实的数据存储基础。

正在精进