从零到1了解etcd

一:什么是etcd

一句话定义 :etcd 是一个分布式、强一致性 的键值(Key-Value)存储系统,主要用于共享配置和服务发现,是云原生体系中的"基石型"基础设施。

深入理解

  • 分布式:数据在多台机器(节点)上复制和存储,提供高可用性。即使部分节点故障,集群整体仍能正常工作。

  • 强一致性 :基于 Raft 共识算法,确保集群中所有节点看到的数据顺序和内容完全一致。这是 etcd 最核心的特性,也是 Kubernetes 等系统依赖它的根本原因。

  • 键值存储 :数据模型非常简单,就是 key -> valuekey 以目录结构组织(如 /app/database/config),支持前缀查询。

  • 核心用途

    • 服务发现:微服务或容器可以将自己的地址注册到 etcd,其他服务通过查询 etcd 来发现它们。

    • 配置中心:将分布式系统的配置信息集中存储在 etcd 中,配置变更可实时、一致地通知到所有节点。

    • 分布式锁:利用 etcd 的强一致性,可以实现安全的分布式锁(通过 Lease 和事务)。

    • 领导者选举:在分布式主从系统中,多个节点可以通过竞争同一个 Key 来选举主节点。

  • 关键地位 :etcd 是 Kubernetes 的"大脑",存储了集群的所有元数据(如 Pod、Node、ConfigMap 等)和状态。Kubernetes API Server 是唯一直接与 etcd 交互的组件。

一个比喻 :etcd 就像一个高度可靠、实时同步的分布式"电话簿"。任何服务都可以去查询(获取配置)、登记自己(服务注册),并且任何变更都会立刻、准确地通知到所有关心这个变更的服务

二 etcd架构

2.1客户端层

官方提供了 clientv3 库(Go语言),以及其他语言的客户端 SDK。

客户端负责与 etcd 集群建立连接、处理请求重试、负载均衡等

2.2API 层

定义了 etcd 的核心操作接口,如键值空间操作、Watch 监听、租约管理

对外暴露 gRPC API (核心)和 HTTP/JSON API(兼容)

节点内部之间使用Raft API

2.3Raft 共识层

这是 etcd 实现强一致性的心脏

* Raft 算法 :将集群中的节点分为三种角色:

* Leader :唯一处理所有客户端写请求的节点,并将写操作复制到其他节点。

* Follower :被动接收来自 Leader 的日志复制请求,并投票选举新的 Leader。

* Candidate :在选举过程中产生的临时角色。

* 所有写请求都必须经过 Leader,由 Leader 将其作为日志条目复制给大多数 Follower 节点,在确认"日志提交"后才应用到状态机,并返回给客户端。这个过程保证了数据的强一致性。

2.4存储层

WAL :预写式日志。所有对数据的修改在应用到内存状态机之前,都会顺序、持久化 地写入 WAL 文件。这是保证数据不丢失和崩溃恢复的关键。
快照 :随着 WAL 日志增长,为了快速恢复和压缩日志,etcd 会定期将内存中的完整数据状态持久化为一个快照文件。
BoltDB :实际的键值存储引擎。etcd 将持久化的键值数据存储在一个单机的、嵌入式的 BoltDB 数据库中。注意:从 etcd v3 开始,所有数据都存储在 BoltDB 中,内存中只维护了键的索引

2.5 数据模型(v3)

多版本并发控制:每个键(Key)都保留历史版本。当更新一个 Key 时,旧值不会被覆盖,而是生成一个新版本。这为 Watch、事务等提供了基础。

租约:可以为 Key 绑定一个具有 TTL(生存时间)的租约。租约到期后,所有绑定它的 Key 会被自动删除。这是实现服务健康检查和分布式锁的核心机制。

2.6 etcd和zookeeper对比

对比维度 etcd ZooKeeper
核心定位 分布式、强一致的键值存储,专注于配置共享与服务发现。 分布式协调服务,提供构建分布式应用的基础原语。
数据模型 扁平化的键值对(key-value)模型,以目录形式组织键,支持范围查询。 树形的分层命名空间(类似文件系统),每个节点(znode)可存储少量数据并拥有子节点。
一致性协议 Raft协议。设计更易理解和实现,在开源社区应用广泛。 Zab协议(Zookeeper Atomic Broadcast)。经过大量生产环境长期验证。
性能特点 读写性能均衡,写入性能通常被认为更优。 以读为主(读写比约10:1)的场景下表现出色。
API与客户端 提供简洁的gRPC API 和HTTP/JSON接口,官方维护clientv3库。 提供原生Java和C客户端,API围绕其树形模型设计(如创建、删除节点)。
生态与社区 云原生(Cloud Native)生态的基石 ,是Kubernetes唯一默认的元数据存储,社区活力强。 大数据生态的标准配置 ,被Hadoop、Kafka、HBase等广泛集成,成熟稳定。
典型场景 K8s等容器编排平台的配置存储、服务发现、集群状态管理 分布式锁、领导人选举、配置管理、服务命名
容错与选举 基于Raft选举,选举过程较快(毫秒级),集群重新收敛迅速。 同样基于多数派选举,但选举过程可能较长(秒级),期间可能影响服务可用性

三部署

3.1 单节点部署

3.1.1 下载包

复制代码
ETCD_VERSION=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest | grep tag_name | cut -d '"' -f 4)
wget https://github.com/etcd-io/etcd/releases/download/${ETCD_VERSION}/etcd-${ETCD_VERSION}-linux-amd64.tar.gz
# 解压并移动至系统路径
tar -xzf etcd-${ETCD_VERSION}-linux-amd64.tar.gz
cd etcd-${ETCD_VERSION}-linux-amd64

mv etcd etcdctl /mnt/d/ubuntu_dir/etcd/bin/

3.1.2运行

复制代码
 nohup /mnt/d/ubuntu_dir/etcd/bin/etcd &
 /mnt/d/ubuntu_dir/etcd/bin/etcdctl put name xiaodong
/mnt/d/ubuntu_dir/etcd/bin/etcdctl get name

执行结果

3.2 集群部署

准备三台机器

3.2.1所有节点均需要安装etcd。

复制代码
1 #创建etcd日志保存目录
2 mkdir ‐p /var/log/etcd/
3 #创建单独的etcd数据目录
4 mkdir ‐p /data/etcd

3.2.2创建集群发现

复制代码
1 #使用公共etcd发现服务
2 [root@192‐168‐65‐206 ~]# curl https://discovery.etcd.io/new?size=3
3 #生成的url
4 https://discovery.etcd.io/ef89922e093e9d5ff5ae5c8ec481f4e3
size为集群节点数量,若未指定数量,则默认位3

3.2.3启动集群

每个成员必须指定不同的名称标志,否则发现将因重复的名称而失败

复制代码
1 # etcd1
2 etcd ‐‐name etcd1 ‐‐data‐dir /data/etcd \
3 ‐‐initial‐advertise‐peer‐urls http://192.168.65.206:2380 \
4 ‐‐listen‐peer‐urls http://192.168.65.206:2380 \
5 ‐‐listen‐client‐urls http://192.168.65.206:2379,http://127.0.0.1:2379 \
6 ‐‐advertise‐client‐urls http://192.168.65.206:2379 \
7 ‐‐discovery https://discovery.etcd.io/ef89922e093e9d5ff5ae5c8ec481f4e3
8
9 # etcd2
10 etcd ‐‐name etcd2 ‐‐data‐dir /data/etcd \
11 ‐‐initial‐advertise‐peer‐urls http://192.168.65.209:2380 \
12 ‐‐listen‐peer‐urls http://192.168.65.209:2380 \
13 ‐‐listen‐client‐urls http://192.168.65.209:2379,http://127.0.0.1:2379 \
14 ‐‐advertise‐client‐urls http://192.168.65.209:2379 \
15 ‐‐discovery https://discovery.etcd.io/ef89922e093e9d5ff5ae5c8ec481f4e3
16
17 # etcd3
18 etcd ‐‐name etcd3 ‐‐data‐dir /data/etcd \19 ‐‐initial‐advertise‐peer‐urls http://192.168.65.210:2380 \
20 ‐‐listen‐peer‐urls http://192.168.65.210:2380 \
21 ‐‐listen‐client‐urls http://192.168.65.210:2379,http://127.0.0.1:2379 \
22 ‐‐advertise‐client‐urls http://192.168.65.210:2379 \
23 ‐‐discovery https://discovery.etcd.io/ef89922e093e9d5ff5ae5c8ec481f4e3

上面是通过命令行方式, 也可以通过配置文件的形式,准备一个配置文件etcd.conf

复制代码
name: 'etcd-01'
data-dir: '/mnt/d/ubuntu_dir/etcd/data'
listen-client-urls: 'http://192.168.65.206:2379'
advertise-client-urls: 'http://192.168.65.206:2379'
listen-peer-urls: 'http://192.168.65.206:2380'
initial-advertise-peer-urls: 'http://192.168.65.206:2380'
# 注意:所有三个节点的 initial-cluster 字符串必须完全一致
initial-cluster: 'etcd-01=http://192.168.65.206:2380,etcd-02=http://192.168.65.209:2380,etcd-03=http://192.168.65.210:2380'
initial-cluster-state: 'new'

启动:

复制代码
nohup /mnt/d/ubuntu_dir/etcd/bin/etcd --config-file /mnt/d/ubuntu_dir/etcd/conf/etcd.conf &

其他节点也按如上操作

3.2.4集群检测

复制代码
etcdctl member list

执行结果

四 基本命令

4.1 增删改查

复制代码
# 默认走 v2  API,显式切到 v3
export ETCDCTL_API=3

# 增/改(覆盖)
etcdctl put /app/config/cache_ttl 60

# 查(单 key)
etcdctl get /app/config/cache_ttl
# 查(前缀)
etcdctl get /app/config --prefix

# 删(单 key)
etcdctl del /app/config/cache_ttl
# 删(前缀)
etcdctl del /app/config --prefix

# 监听
etcdctl watch /app/config --prefix

# 带租约(TTL = 10s)
lease=`etcdctl lease grant 10 | awk '{print $2}'`
etcdctl put /app/lock/leader "node1" --lease=$lease
# 续租
etcdctl lease keep-alive $lease
# 释放
etcdctl lease revoke $lease

4.2 事务(CAS)

复制代码
# 仅当 key 版本号 = 2 时才更新
etcdctl txn <<EOF
version("/app/config/cache_ttl") = "2"
put /app/config/cache_ttl 120
EOF

4.3 Go 代码(官方 clientv3)

复制代码
package main

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

func main() {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	})
	if err != nil { panic(err) }
	defer cli.Close()

	ctx := context.Background()

	// 增/改
	_, err = cli.Put(ctx, "/demo/key", "hello etcd")
	if err != nil { panic(err) }

	// 查
	resp, err := cli.Get(ctx, "/demo/key")
	if err != nil { panic(err) }
	fmt.Printf("Value=%s\n", resp.Kvs[0].Value)

	// 监听
	watchCh := cli.Watch(ctx, "/demo/key", clientv3.WithPrefix())
	for wresp := range watchCh {
		for _, ev := range wresp.Events {
			fmt.Printf("Type=%s Key=%s Value=%s\n",
				ev.Type, ev.Kv.Key, ev.Kv.Value)
		}
	}
}
相关推荐
云老大TG:@yunlaoda3607 小时前
如何确保数据在腾讯云国际站代理商的归档存储服务中的安全性?
数据库·云计算·腾讯云
wuletaotao7 小时前
Redis 主从搭建笔记
数据库·redis·笔记
NocoBase7 小时前
如何快速搭建一个替换 Excel 的系统?(完整指南)
数据库·低代码·开源·excel·个人开发·零代码·无代码
7 小时前
TIDB——TIDB Server
数据库·分布式·tidb
Mr_wilson_liu7 小时前
通过DBeaver22.0.5 连接数据库ck(clickhouse)、pg(postgres)
数据库·clickhouse
Ashley_Amanda7 小时前
FICO 校验与替代技术点
数据库
黑白极客7 小时前
项目启动时报错找不到UserDetailsService
数据库·oracle
徐子元竟然被占了!!15 小时前
Linux-systemctl
linux·数据库·oracle