etcd:高可用,分布式的key-value存储系统

引言

etcd是基于go语言开发的一款kv存储引擎,基于raft一致性算法实现的一种存储

一.etcd的底层原理

1.etcd的特点

高可用性与一致性:etcd 使用 Raft 算法保证集群中数据的强一致性,即使在节点故障的情况下也能保持数据完整性。

分布式存储:数据以键值对的形式存储,支持分布式读写,适用于大规模服务发现和配置共享的场景。

易于集成:etcd 提供了 HTTP/gRPC 接口,方便各种编程语言和平台集成。

应用场景:Kubernetes 等容器编排平台使用 etcd 作为核心组件,实现状态管理和服务协调。

2.什么是raft一致性算法

领导者选举(Leader Election)

集群中的所有节点通过选举过程选出一个领导者,负责处理客户端请求以及日志复制工作;如果当前领导者发生故障,其余节点会重新选举出新的领导者。

日志复制(Log Replication)

领导者接收到客户端请求后,会将这条命令追加到自己的日志中,然后通过日志复制机制将该命令复制到所有追随者节点,确保所有节点都以相同的顺序应用这些命令,从而保持一致的状态机。

安全性(Safety)

为防止不同节点之间的日志产生分歧,Raft设计了一套严格的规则。例如,只有领导者拥有提交日志的权利,并且只有当多数节点确认日志条目之后才允许将其提交,保证了在大多数节点正常工作的情况下,系统始终保持一致。
raft一致性算法可视化图形 链接Raft

3.etcd****架构(体系结构)

boltdb 是一个单机的支持事务的 kv 存储, etcd 的事务是基于 boltdb 的事务实现的; boltdb 为每
一个 key 都创建一个索引( B+ 树);该 B+ 树存储了 key 所对应的版本数据;
wal(write ahead log) 预写式日志实现事务日志的标准方法;执行写操作前先写日志,跟 mysql 中 redo 类似, wal 实现的是顺序写,而若按照 B+ 树写,则涉及到多次 io 以及 随机写;
snapshot 快照数据 ,用于其他节点同步主节点数据从而达到一致性地状态;类似 redis 中主从复 制中 rdb 数据恢复;流程: 1. leader 生成 snapshot ; 2. leader 向 follower 发 snapshot ; 3. follower接收并应用 snapshot ;
gRPC server ectd 集群 间以及 client 与 etcd 节点间都是通过 gRPC 进行通讯;

4.etcd的事务 (举一个简单的例子)

key的value是不是lion

是key的value改为tiger

不是的话获取key的value

二.etcd的常用命令 和 go对etcd操作代码案例

启动etc

1.常用命令

put get del

复制代码
etcdctl put /foo bar //将键 "/foo" 对应的值设置为 "bar"

etcdctl get /foo  //查询键 "/foo" 的值

etcdctl del /foo   //删除指定键 "/foo"

watch

复制代码
etcdctl watch /foo  //监视键 "/foo" 及其后续变化(实时输出修改日志)

lease

复制代码
etcdctl lease grant 10 //为键设置租约,有效期 10 秒

etcdctl lease keep-alive 1234567890 //保持已有租约(例如 lease id 为 1234567890)

etcdctl lease revoke 1234567890 //撤销租约以自动删除其绑定的键
2.go对etcd操作代码

①basic

Go 复制代码
package main

import (
	"context" // 上下文包用于控制请求超时、取消等
	"fmt"     // 格式化输出
	"time"    // 时间处理

	clientv3 "go.etcd.io/etcd/client/v3" // etcd 客户端 v3 包
)

func main() {
	// 通过指定 etcd 服务地址和拨号超时时间,创建 etcd 客户端
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"}, // etcd 服务地址
		DialTimeout: 5 * time.Second,            // 连接超时时间为 5 秒
	})
	if err != nil {
		panic(err) // 连接失败则终止程序
	}
	defer cli.Close() // 程序结束前关闭客户端连接

	// 使用 context 限定操作超时时间,防止一直等待
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	// 向 etcd 中写入一个 key/value 对
	_, err = cli.Put(ctx, "key", "lion")
	cancel() // 取消 context 的使用
	if err != nil {
		panic(err) // Put 操作失败则终止程序
	}

	// 创建新的 context 来获取 key 值
	ctx, cancel = context.WithTimeout(context.Background(), time.Second)
	// 从 etcd 中获取指定 key 值
	resp, err := cli.Get(ctx, "key")
	cancel()
	if err != nil {
		panic(err) // Get 操作失败则终止程序
	}

	// 遍历响应中所有返回的 key/value 对,并打印出来
	for _, ev := range resp.Kvs {
		fmt.Printf("%s:%s\n", ev.Key, ev.Value)
	}
}

② watch

Go 复制代码
package main

import (
	"context" // 上下文包,用于传递取消信号和超时控制
	"fmt"     // 格式化输出,用于打印日志信息

	clientv3 "go.etcd.io/etcd/client/v3" // 导入 etcd 客户端 v3 包
)

func main() {
	// 通过指定 etcd 服务器的 URL 创建一个 etcd 客户端
	cli, err := clientv3.NewFromURL("127.0.0.1:2379")
	if err != nil {
		// 如果连接失败,则终止程序并输出错误信息
		panic(err)
	}
	// 在 main 函数退出前关闭 etcd 客户端连接
	defer cli.Close()

	// 调用 Watch 方法,对 key3 及其后续 key 的变化进行监听
	// WithFromKey() 表示从 key3 开始后面的所有 key
	watch := cli.Watch(context.Background(), "key3", clientv3.WithFromKey())

	// 循环遍历监听返回的数据
	for resp := range watch {
		// 对于每次监听返回的响应中的每个事件进行处理
		for _, ev := range resp.Events {
			// 打印事件类型、键和值
			fmt.Printf("Type: %s Key: %s Value: %s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
		}
	}
}

③lease

Go 复制代码
package main

import (
	"context" // 上下文包,用于为请求设置超时和取消操作
	"fmt"     // 格式化输出

	clientv3 "go.etcd.io/etcd/client/v3" // 导入 etcd client v3 包
)

func main() {
	// 通过指定 etcd 服务器的 URL 创建一个 etcd 客户端
	cli, err := clientv3.NewFromURL("127.0.0.1:2379")
	if err != nil {
		// 如果连接失败,则终止程序并输出错误信息
		panic(err)
	}
	// 在函数退出前确保关闭 etcd 客户端
	defer cli.Close()

	// 调用 Grant 方法申请一个租约,设置租约有效期为 5 秒
	lease, err := cli.Grant(context.Background(), 5)
	if err != nil {
		// 如果申请租约失败,则终止程序并输出错误信息
		panic(err)
	}
	// 打印租约 ID
	fmt.Println("lease id", lease.ID)

	// 使用租约将 key 与 value 绑定写入到 etcd 中
	_, err = cli.Put(context.Background(), "key", "lion", clientv3.WithLease(lease.ID))
	if err != nil {
		// 如果写入操作失败,则终止程序并输出错误信息
		panic(err)
	}

	// 如果想保持租约活跃,可以通过 KeepAlive 进行续租
	if true {
		// 续租操作: 使用 KeepAlive 方法进行长期续租
		ch, err := cli.KeepAlive(context.Background(), lease.ID)
		if err != nil {
			// 如果续租失败,则终止程序并输出错误信息
			panic(err)
		}
		// 循环从返回的通道中读取续租信息
		for {
			recv := <-ch
			// 打印续租后剩余的 TTL(生存时间)
			fmt.Println("time to live", recv.TTL)
		}
	}

	// 如果只需要进行一次续租,则可以使用 KeepAliveOnce 方法(此处已被禁用)
	if false {
		// 单次续租操作:仅获取一次续租信息
		res, err := cli.KeepAliveOnce(context.Background(), lease.ID)
		if err != nil {
			// 如果操作失败,则终止程序并输出错误信息
			panic(err)
		}
		// 打印续租后剩余的 TTL(生存时间)
		fmt.Println("time to live", res.TTL)
	}
}

维持心跳包

心跳包断开

3.etcd总结

etcd 是一个高度可靠的分布式键值存储系统,它提供了强一致性、故障恢复和高可用的保证。它主要依靠 Raft 算法来实现集群中数据的复制与一致性,是服务发现、配置管理和分布式协调的理想工具。同时,etcd 提供了简洁的 API(基于 HTTP/gRPC),易于与各种系统和编程语言集成,被 Kubernetes 等众多云原生平台广泛采用。

相关推荐
Elastic 中国社区官方博客15 小时前
在 Elasticsearch 中使用 Mistral Chat completions 进行上下文工程
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
编程爱好者熊浪16 小时前
两次连接池泄露的BUG
java·数据库
TDengine (老段)18 小时前
TDengine 字符串函数 CHAR 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
qq74223498418 小时前
Python操作数据库之pyodbc
开发语言·数据库·python
姚远Oracle ACE18 小时前
Oracle 如何计算 AWR 报告中的 Sessions 数量
数据库·oracle
Dxy123931021619 小时前
MySQL的SUBSTRING函数详解与应用
数据库·mysql
码力引擎19 小时前
【零基础学MySQL】第十二章:DCL详解
数据库·mysql·1024程序员节
杨云龙UP19 小时前
【MySQL迁移】MySQL数据库迁移实战(利用mysqldump从Windows 5.7迁至Linux 8.0)
linux·运维·数据库·mysql·mssql
l1t19 小时前
利用DeepSeek辅助修改luadbi-duckdb读取DuckDB decimal数据类型
c语言·数据库·单元测试·lua·duckdb
安当加密19 小时前
Nacos配置安全治理:把数据库密码从YAML里请出去
数据库·安全