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-clusterDocker部署
点击查看完整代码实现
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等容器编排平台的首选后端存储,为现代分布式系统提供了坚实的数据存储基础。
