etcd的使用
潘忠显 / 2025-03-21
本文将介绍如何部署 etcd、如何使用 SDK、常见使用场景、使用限制等内容。
etcd 是一个分布式键值存储系统,广泛用于配置管理、服务发现和分布式锁等场景。
“etcd” 命名是 etc 加个 d 的结合体,官网上介绍其发音应该是 /ˈɛtsiːdiː/,含义是也就是 “distributed etc
directory.”
/etc
是 类 Unix 操作系统上,存放系统配置的目录,包括用户账户、网络配置、服务管理等配置。
这是个「老掉牙」的组件,最近在回顾容器编排平台 Kubernetes 的一些内容,提到用它来配置数据、状态数据和元数据等作用,这里就顺带回顾一下。
一、如何部署
单机或集群部署环境
etcd 可以在单机上运行,但通常用于生产环境时,建议使用多节点集群部署以提高可靠性和容错能力。
单机部署适合开发和测试环境,但在生产环境中,单节点故障会导致服务不可用。
通常,etcd 集群由多个节点组成,以确保高可用性和数据一致性。推荐的集群规模是 3、5 或 7 个节点。
以下是多节点集群部署的基本要求:
- 网络:节点之间需要稳定的网络连接。
- 存储:每个节点需要持久化存储,以确保数据不会因节点重启而丢失。
- 配置:每个节点需要配置唯一的名称和地址,以便集群成员识别和通信。
部署示例
以下是一个简单的 3 节点 etcd 集群部署中,node1 节点的启动示例:
# Node 1
etcd --name node1 --initial-advertise-peer-urls http://node1:2380 \
--listen-peer-urls http://node1:2380 \
--listen-client-urls http://node1:2379 \
--advertise-client-urls http://node1:2379 \
--initial-cluster node1=http://node1:2380,node2=http://node2:2380,node3=http://node3:2380
上边参数比较多,主要分成4类:
- –name node1: 指定了 etcd 节点的名称。集群中,每个节点必须有一个唯一的名称,用于标识和区分不同的节点
- –initial-advertise-peer-urls 和 –listen-peer-urls 用于指定节点间通信地址(即 peer 通信),前者主要用于集群初始化和节点加入时,而后者是节点运行时的选举、数据同步等交互通信。
- –listen-client-urls 和 –advertise-client-urls 都与客户端访问节点有关。前者可能会绑定到多个网络接口(如
0.0.0.0
),而 –advertise-client-urls 通常是一个具体的可被客户端访问的地址。 - –initial-cluster :指定初始集群的成员列表。每个成员的格式为 name=peer-url,其中 name 是节点名称,peer-url 是该节点的
--initial-advertise-peer-urls
地址。这个列表用于在集群启动时让各个节点知道其他节点的存在和地址。
node2 和 node3 的启动指令跟上边类似,就不在这赘述。
二、什么情况下使用
etcd 在许多分布式系统中扮演着关键角色,以下是三个常见的使用场景:
配置管理
etcd 可以用来存储和管理分布式系统的配置数据。由于 etcd 提供了强一致性和高可用性,配置数据可以在多个服务实例之间保持一致。
在一个微服务架构中,所有服务的配置数据(如数据库连接字符串、API 密钥等)可以存储在 etcd 中。服务启动时从 etcd 获取配置,并在配置更新时自动重新加载。
服务发现
etcd 可以用来实现服务发现机制。服务实例在启动时将自己的地址注册到 etcd 中,其他服务可以查询 etcd 以获取可用服务实例的地址。
在一个动态扩展的集群中,服务实例启动时将自己的地址注册到 etcd 中,负载均衡器或其他服务可以查询 etcd 以获取最新的服务实例列表,从而实现动态服务发现和负载均衡。
分布式锁
etcd 提供了分布式锁的功能,可以用来协调多个进程或服务之间的操作,防止资源竞争和数据不一致。
在一个分布式任务调度系统中,多个调度器实例需要协调执行任务。通过 etcd 实现分布式锁,确保同一时间只有一个调度器实例执行特定任务,避免任务重复执行。
使用限制
虽然 etcd 是一个强大的分布式键值存储系统,但在某些场景下也有其限制和缺点:
- 响应时间长:在高并发和大规模数据写入的场景下,etcd 的响应时间可能会变长。由于 etcd 保证强一致性,写操作需要在集群中的大多数节点上达成一致,这会增加延迟。
- 高频访问场景:etcd 适用于配置管理和服务发现等低频访问场景,但在高频访问场景下(如实时数据存储和查询),性能可能不如专门的高性能数据库。
- 存储限制:etcd 主要用于存储小型配置数据和元数据,不适合存储大规模数据。每个键值对的大小和总数据量都有一定限制。
- 网络依赖:etcd 集群对网络稳定性要求较高,网络分区可能导致服务不可用。
- 运维复杂性:管理和维护 etcd 集群需要一定的运维经验,特别是在处理节点故障和数据恢复时。
使用的etcd的软件
-
Kubernetes 使用 etcd 作为其主要的数据存储,用于存储集群的所有配置信息和状态数据。
-
CoreDNS 是一个灵活的 DNS 服务器,可以使用 etcd 作为后端存储,实现动态 DNS 解析和服务发现。
-
OpenStack 是一个开源的云计算平台,部分组件(如 Kuryr 和 Magnum)使用 etcd 进行配置管理和服务发现。
-
TiDB 是一个分布式 SQL 数据库,使用 etcd 进行元数据管理和分布式协调。
三、和其他组件的对比
和 Zookeeper 的比较
有了解过 Kafka 的同学,应该知道,Kafka最初是使用 Zookeeper 来管理集群元数据、分区领导者选举、消费者组协调等。(目前Kafka 已经不再使用 Zookeeper,改用KRaft 模式,将元数据的集成到 Kafka 本身,简化了 Kafka 的架构。)
Zookeeper 和 etcd 都是分布式协调服务,广泛用于分布式系统中以实现配置管理、服务发现、分布式锁等功能,他们有一定的可比性。
特性 | Zookeeper | etcd |
---|---|---|
协议 | 基于 Zab (Zookeeper Atomic Broadcast) 协议 | 基于 Raft 共识算法 |
实现 | Java | Go |
数据模型 | 层次化的文件系统数据模型,数据节点称为 znode,支持子节点 | 采用扁平的键值对数据模型,数据以键值对形式存储 |
API 接口 | 提供丰富的 API,包括创建、删除、更新、读取节点,设置和获取节点的元数据等 | 提供简单的 RESTful API,支持基本的键值对操作、事务、监听键变化等 |
性能 | 功能和数据模型复杂,可能在某些场景下性能和延迟较高 | 轻量级,通常在性能和延迟上表现更好 |
使用场景 | 适用于需要复杂协调和管理的分布式系统 如 Hadoop、Kafka、HBase 等 |
适用于需要简单、高效的键值存储和服务发现的场景 如 Kubernetes、CoreOS 等 |
社区和生态 | 由 Apache 基金会维护 | 由 CoreOS 开发,CNCF 毕业项目,应用于云原生和容器化环境 |
和Redis的比较
前边提到 etcd 可以用作分布式数据存储、分布式锁等功能,而 Redis 也是有类似的作用。
与 etcd 相比,Redis 支持更广泛的数据类型和结构,并且具有更高的读/写性能,可以用作数据库、缓存或消息代理。
但 etcd 具有卓越的容错能力、更强大的故障转移和持续的数据可用性功能。
etcd 将所有存储的数据持久保存到磁盘上,这本质上牺牲了速度来换取更高的可靠性并保证一致性。
因此,Redis 更适合用作分布式内存缓存系统,而不是存储分布式系统配置信息。
四、SDK如何使用
etcd 提供了多种编程语言的 SDK,包括后边要介绍的 Python 和 Go。
在介绍 SDK 之前,我们先来看一下如何通过 etcdctl
命令行工具直接进行 put
和 get
操作。
使用 etcdctl 操作
如果本机上有运行 etcd 可以直接进行操作:
- put 操作:
etcdctl put mykey "this is a value"
- get 操作:
etcdctl get mykey
如果需要连接远端的 etcd 可以使用环境变量指定:
export ETCDCTL_ENDPOINTS="http://192.168.1.100:2379"
或者在调用 etcdctl
指令的时候,带上 --endpoints=http://192.168.1.100:2379
的参数来指定。
Python SDK 使用示例
使用 etcd3
库来与 etcd 交互:
(如果你机器上没有合适版本的protoc,可以加个 PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
环境变量,使用纯 Python 实现来解析 protobuf,但性能会较慢)
import etcd3
# 连接到 etcd
client = etcd3.client(host='localhost', port=2379)
# 写入键值
client.put('foo', 'bar')
# 读取键值
value, metadata = client.get('foo')
print(value.decode('utf-8')) # 输出: bar
# 删除键值
client.delete('foo')
Go SDK 使用示例
使用 go.etcd.io/etcd/client/v3
库来与 etcd 交互。因为涉及到网络通信,所以 Cli 的一些参数都需要传入上下文:
package main
import (
"context"
"fmt"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
)
func main() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379", "localhost:22379", "localhost:32379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
// handle error!
}
defer cli.Close()
// 写入键值
_, err = cli.Put(context.Background(), "foo", "bar")
if err != nil {
panic(err)
}
// 读取键值
resp, err := cli.Get(context.Background(), "foo")
if err != nil {
panic(err)
}
for _, kv := range resp.Kvs {
fmt.Printf("%s : %s\n", kv.Key, kv.Value) // 输出: foo : bar
}
// 删除键值
_, err = cli.Delete(context.Background(), "foo")
if err != nil {
panic(err)
}
}
总结
etcd 是一个强大且灵活的分布式键值存储系统,广泛应用于配置管理、服务发现和分布式锁等场景。
尽管在高并发和大规模数据写入场景下存在一定限制,但其强一致性和高可用性使其成为许多分布式系统的首选。
通过合理的部署和使用,可以充分发挥 etcd 的优势,为分布式系统提供可靠的基础设施支持。