【Etcd 】Etcd 详解以及安装教程
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、Etcd 是什么?分布式系统的 “大脑”
Etcd 是一个分布式、高可用、强一致性的键值存储系统,由 CoreOS 公司开发(后被 CNCF 托管)。它的核心价值在于解决分布式系统中的配置共享、服务发现、分布式锁、选主等场景,堪称分布式系统的 “神经中枢”。
和同类产品(如 ZooKeeper)相比,Etcd 有几个显著优势:
- 轻量级:部署简单,资源占用少,适合云原生微服务架构。
- 强一致性:基于 Raft 算法保证分布式数据的强一致性,这在金融、电商等对数据一致性要求高的领域至关重要。
- 实时感知:客户端通过 “长连接 + Watch 机制”,能实时感知数据变化,让服务发现、配置更新更及时。
- 生态完善:与 Kubernetes、Docker Swarm 等云原生项目深度集成,是云原生技术栈的核心组件。
二、Etcd 核心概念:理解分布式存储的 “语言”
在动手用 Etcd 之前,先理清几个核心概念,能让你用起来更得心应手。
1. 键值对(Key-Value)
Etcd 最基础的存储单元是键值对,键(Key)是唯一的,值(Value)可以是任意二进制数据(通常存 JSON、字符串等)。比如可以用/config/db/host作为键,存储数据库主机地址。
2. Raft 算法
Etcd 基于Raft 一致性算法实现分布式数据同步。简单来说,Raft 通过 “领导者(Leader)选举、日志复制、安全约束” 三个阶段,保证集群中所有节点的数据最终一致。这也是 Etcd 能在分布式场景下保证数据可靠的核心。
3. Watch 机制
Etcd 支持客户端长连接监听键的变化(Watch)。当某个键的值被修改时,所有 Watch 该键的客户端会立即收到通知,这让 “服务发现自动更新”“配置实时推送” 成为可能。
4. 租约(Lease)
租约是 Etcd 实现 ** 临时节点(如服务注册实例)** 的关键。客户端可以为某个键申请租约,若租约过期(客户端心跳中断),对应的键会被自动删除,这在服务发现中用于 “自动剔除故障节点” 非常有用。
三、Etcd 安装:从单节点到集群
Etcd 的安装方式很灵活,我们先从单节点安装入手,再扩展到集群部署。
方式 1:系统包管理器安装(以 Ubuntu 为例)
这种方式最适合快速体验 Etcd:
# 安装Etcd
sudo apt-get update
sudo apt-get install etcd -y# 启动Etcd服务
sudo systemctl start etcd# 设置开机自启
sudo systemctl enable etcd
安装完成后,Etcd 默认会在localhost:2379提供 HTTP API 服务,localhost:2380用于集群内部通信。
方式 2:二进制包安装(适合生产环境定制)
如果需要指定版本或更灵活的配置,二进制包安装是更好的选择:
步骤 1:下载二进制包
从Etcd 官方 Release 页面下载对应系统的二进制包,比如:
# 下载并解压(以v3.5.9为例)
wget https://github.com/etcd-io/etcd/releases/download/v3.5.9/etcd-v3.5.9-linux-amd64.tar.gz
tar -zxvf etcd-v3.5.9-linux-amd64.tar.gz
cd etcd-v3.5.9-linux-amd64/
步骤 2:启动单节点 Etcd
# 前台启动(方便查看日志)
./etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://localhost:2379
步骤 3:配置为系统服务(可选)
为了让 Etcd 能后台运行并开机自启,我们可以创建 systemd 服务文件:
sudo vim /etc/systemd/system/etcd.service
在文件中写入以下内容:
[Unit]
Description=Etcd Distributed Key-Value Store
After=network.target[Service]
Type=simple
ExecStart=/path/to/etcd/etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://localhost:2379
Restart=on-failure
RestartSec=5[Install]
WantedBy=multi-user.target
然后启动并设置开机自启:
sudo systemctl daemon-reload
sudo systemctl start etcd
sudo systemctl enable etcd
方式 3:Docker 安装(容器化部署)
在容器化环境中,用 Docker 运行 Etcd 也很方便:
docker run -d \--name etcd \-p 2379:2379 \-p 2380:2380 \--env ETCD_ADVERTISE_CLIENT_URLS=http://localhost:2379 \--env ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \quay.io/coreos/etcd:v3.5.9
验证安装是否成功
安装完成后,我们可以用 Etcd 的命令行工具etcdctl验证:
# 查看Etcd版本
etcdctl version# 写入一个键值对
etcdctl put /hello "world"# 读取键值对
etcdctl get /hello# 监听键的变化(另开一个终端执行put操作,这里会实时收到通知)
etcdctl watch /hello
如果能正常写入、读取和监听,说明 Etcd 安装成功。
四、Etcd 实战:从基础操作到场景化应用
光装好了还不够,我们通过几个场景,看看 Etcd 在实际开发中怎么用。
示例 1:基础键值操作(命令行 + API)
Etcd 的命令行工具etcdctl提供了丰富的操作命令:
# 写入键值
etcdctl put /config/app/name "my-service"
etcdctl put /config/app/port "8080"# 读取键值(单个键)
etcdctl get /config/app/name# 读取键值(前缀查询)
etcdctl get /config/app --prefix# 删除键值
etcdctl del /config/app/port# 批量删除(前缀删除)
etcdctl del /config/app --prefix
如果是编程场景,我们可以用 Etcd 的客户端 SDK(如 Go、Python、Java 等)操作。以 Go 语言为例:
package mainimport ("context""fmt""go.etcd.io/etcd/client/v3"
)func main() {// 创建Etcd客户端cli, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"},DialTimeout: 5 * time.Second,})if err != nil {panic(err)}defer cli.Close()// 写入键值_, err = cli.Put(context.TODO(), "/config/db/host", "127.0.0.1")if err != nil {panic(err)}// 读取键值resp, err := cli.Get(context.TODO(), "/config/db/host")if err != nil {panic(err)}for _, kv := range resp.Kvs {fmt.Printf("键:%s,值:%s\n", kv.Key, kv.Value)}// 监听键变化rch := cli.Watch(context.TODO(), "/config/db/host")for wresp := range rch {for _, ev := range wresp.Events {fmt.Printf("事件类型:%s,键:%s,值:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)}}
}
示例 2:服务发现场景(临时节点 + Watch)
在微服务架构中,服务实例需要 “自动注册” 和 “自动发现”,Etcd 的租约和 Watch 机制可以完美实现:
package mainimport ("context""flag""go.etcd.io/etcd/client/v3""go.etcd.io/etcd/client/v3/concurrency""log""time"
)var (etcdEndpoints = flag.String("etcd-endpoints", "localhost:2379", "Etcd endpoints")svcName = flag.String("service-name", "user-service", "服务名称")svcAddr = flag.String("service-addr", "192.168.1.100:8080", "服务地址")
)func main() {flag.Parse()// 创建Etcd客户端cli, err := clientv3.New(clientv3.Config{Endpoints: []string{*etcdEndpoints},DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}defer cli.Close()// 创建租约(TTL为10秒,客户端需每10秒内发心跳续约)lease, err := concurrency.NewLease(cli, 10)if err != nil {log.Fatal(err)}defer lease.Revoke(context.TODO())// 基于租约创建临时节点(服务注册)kv := clientv3.NewKV(cli)key := fmt.Sprintf("/services/%s/%s", *svcName, *svcAddr)_, err = kv.Put(context.TODO(), key, "online", clientv3.WithLease(lease.ID()))if err != nil {log.Fatal(err)}log.Printf("服务 %s 注册成功,地址:%s\n", *svcName, *svcAddr)// 模拟服务运行,定期续约go func() {for {time.Sleep(5 * time.Second) // 每5秒续约一次if _, err := lease.KeepAliveOnce(context.TODO()); err != nil {log.Printf("续约失败:%v\n", err)}}}()// 另一个goroutine:监听服务列表变化(服务发现)go func() {rch := cli.Watch(context.TODO(), fmt.Sprintf("/services/%s", *svcName), clientv3.WithPrefix())for wresp := range rch {for _, ev := range wresp.Events {svcAddr := string(ev.Kv.Key[len(fmt.Sprintf("/services/%s/", *svcName)):])if ev.Type == clientv3.EventTypePut {log.Printf("服务上线:%s\n", svcAddr)} else {log.Printf("服务下线:%s\n", svcAddr)}}}}()// 保持程序运行select {}
}
这个例子中,服务实例启动时会在 Etcd 中注册一个 “临时节点”(基于租约),并通过 Watch 机制实时感知同服务的其他实例上线 / 下线,完美实现了服务发现。
示例 3:分布式锁场景(保证操作原子性)
在分布式任务调度、资源竞争等场景中,分布式锁是必备工具,Etcd 的etcdctl和 SDK 都支持分布式锁:
package mainimport ("context""log""time""go.etcd.io/etcd/client/v3""go.etcd.io/etcd/client/v3/concurrency"
)func main() {cli, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"},DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}defer cli.Close()// 创建分布式锁s, err := concurrency.NewSession(cli)if err != nil {log.Fatal(err)}defer s.Close()mu := concurrency.NewMutex(s, "/locks/task-distribute")// 加锁if err := mu.Lock(context.TODO()); err != nil {log.Fatal(err)}log.Println("获取到分布式锁,开始执行临界区逻辑...")// 模拟临界区操作time.Sleep(10 * time.Second)// 解锁if err := mu.Unlock(context.TODO()); err != nil {log.Fatal(err)}log.Println("释放分布式锁,临界区逻辑执行完毕")
}
通过分布式锁,我们可以保证 “同一时间只有一个节点执行临界区逻辑”,避免分布式场景下的数据竞争。
五、Etcd 工程化实践:那些你该知道的细节
1. 集群部署(保证高可用)
生产环境中,Etcd 通常以集群形式部署(至少 3 个节点)。以下是一个简单的集群启动示例:
# 节点1
./etcd --name node1 --data-dir node1 --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 --initial-advertise-peer-urls http://192.168.1.101:2380 --initial-cluster node1=http://192.168.1.101:2380,node2=http://192.168.1.102:2380,node3=http://192.168.1.103:2380 --initial-cluster-token etcd-cluster-1 --initial-cluster-state new# 节点2(类似节点1,修改name、data-dir、IP)
./etcd --name node2 --data-dir node2 --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 --initial-advertise-peer-urls http://192.168.1.102:2380 --initial-cluster node1=http://192.168.1.101:2380,node2=http://192.168.1.102:2380,node3=http://192.168.1.103:2380 --initial-cluster-token etcd-cluster-1 --initial-cluster-state new# 节点3(类似节点1,修改name、data-dir、IP)
./etcd --name node3 --data-dir node3 --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 --initial-advertise-peer-urls http://192.168.1.103:2380 --initial-cluster node1=http://192.168.1.101:2380,node2=http://192.168.1.102:2380,node3=http://192.168.1.103:2380 --initial-cluster-token etcd-cluster-1 --initial-cluster-state new
2. 性能与监控
- 性能调优:Etcd 的性能受磁盘 IO、网络延迟影响较大,生产环境建议使用 SSD 磁盘,并合理配置
--snapshot-count(快照触发的键数量)、--auto-compaction-retention(自动压缩时间)等参数。 - 监控集成:Etcd 暴露了丰富的 metrics(通过
--metrics-addr配置),可以接入 Prometheus+Grafana 进行监控,及时发现集群异常。
