前言
此篇记录 etcd 的功能包括简单使用,它提供了许多功能,主要用于配置管理和服务发现。
ETCD 的核心架构:
etcd Server:用于对外接收和处理客户端的请求
gRPC Server:etcd与其他etcd节点之间的通信和信息同步
MVCC:多版本控制,etcd的存储模块。键值对的每一次操作行为都会被记录存储。这些数据底层存储在BoltDB数据库中。
一、ETCD 是什么?
etcd 是一个分布式键值对存储,设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁,leader选举和写屏障(write barriers)来实现可靠的分布式协作。etcd集群是为高可用,持久性数据存储和检索而准备。
Etcd 是 CoreOS 基于 Raft 协议开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。
二、为什么需要 ETCD ?
所有的分布式系统,都面临的一个问题是多个节点之间的数据共享问题,这个和团队协作的道理是一样的,成员可以分头干活,但总是需要共享一些必须的信息,比如谁是 leader, 都有哪些成员,依赖任务之间的顺序协调等。所以分布式系统要么自己实现一个可靠的共享存储来同步信息(比如 Elasticsearch ),要么依赖一个可靠的共享存储服务,而 Etcd 就是这样一个服务。
etcd 以一致和容错的方式存储元数据。分布式系统使用 etcd 作为一致性键值存储,用于配置管理,服务发现和协调分布式工作。使用 etcd 的通用分布式模式包括领导选举,[分布式锁][etcd-concurrency]和监控机器活动。
三、ETCD 特性以及特点
特性:
- 简单:etcd的安装简单,为用户提供了HTTP的接口,使用也很简单
- 存储:etcd的基本功能,基本信息存储在文件中
- Watch机制:watch指定的键的更改会通知
- 安全通信:SSL证书验证
- 高性能:2K/s读操作,提供了基准测试的工具
- 一致可靠:raft一致性算法
四、ETCD 的常见分布式场景
- KV数据库
- 服务注册发现
- 共享配置
- 协调分布式
- 分布式锁
五、快速开始
官方下载地址:
https://github.com/etcd-io/etcd/releases
https://github.com/etcd-io/etcd/releases/download/v3.4.14/etcd-v3.4.14-linux-amd64.tar.gz
单机测试安装使用:
如果在测试开发环境,启动一个单点的 etcd 服务,只需要运行 etcd 执行即可。
直接在终端前台启动,如果不想放到前台,可以在结尾添加 & 放置到后台运行。
/usr/local/bin/etcd
启动的 etcd 成员在 localhost:2379 监听客户端请求。
官方文档会更详细,这块有疑问可以直接跳转--》etcd 快速开始 · Etcd 中文文档
六、基于 go 后端开发的简单实现
首先,你需要安装etcd的Go客户端库:
go get go.etcd.io/etcd/client/v3
然后,写一个etcd 的分布式服务注册的代码:
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/concurrency"
)
const (
etcdAddr = "localhost:2379" // etcd服务器地址
serviceTTL = 10 * time.Second // 服务TTL时间
)
func main() {
// 创建一个etcd客户端
cfg := clientv3.Config{
Endpoints: []string{etcdAddr},
DialTimeout: 5 * time.Second,
}
cli, err := clientv3.New(cfg)
if err != nil {
log.Fatalf("Error connecting to etcd: %v", err)
}
defer cli.Close()
// 使用etcd进行服务注册
go serviceRegister(cli)
// 使用etcd进行服务发现
serviceDiscover(cli)
log.Println("Service registry and discovery done.")
}
func serviceRegister(cli *clientv3.Client) {
// 创建一个会话,用于设置键的TTL
s := concurrency.NewSession(cli)
// 创建一个唯一标识,例如使用服务名和实例ID
id := "service-instance-1"
// 在etcd中注册服务
if _, err := s.Do(context.TODO(), "PUT", "/services/my-service", id); err != nil {
log.Fatalf("Error registering service: %v", err)
}
// 定时发送心跳以续约TTL
ticker := time.NewTicker(serviceTTL / 2)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// 续约TTL
if _, err := s.Do(context.TODO(), "PUT", "/services/my-service", id); err != nil {
log.Printf("Error renewing lease: %v", err)
}
case <-s.Done():
log.Println("Service session done or failed.")
return
}
}
}
func serviceDiscover(cli *clientv3.Client) {
getResp, err := cli.Get(context.TODO(), "/services/my-service")
if err != nil {
log.Fatalf("Error getting service: %v", err)
}
if len(getResp.Kvs) == 0 {
log.Println("No service found.")
return
}
// 假设返回的服务列表只有一个
serviceKey := string(getResp.Kvs[0].Key)
log.Printf("Discovered service: %s", serviceKey)
// 可以在这里添加逻辑,比如连接到发现的服务
// ...
}
func init() {
if len(os.Args) < 2 {
log.Fatal("Usage: ", os.Args[0], "ETCD_ADDR")
}
etcdAddr = os.Args[1]
}
服务注册
服务注册通常涉及到在 etcd 中创建一个带有租约(TTL)的键值对,以确保服务实例的可用性。
func registerService(cli *clientv3.Client, serviceKey, instanceId string, ttl time.Duration) {
// 创建一个 etcd 会话
s := concurrency.NewSession(cli, concurrency.WithTTL(int(ttl.Seconds())))
// 创建一个 etcd 锁,用于注册服务
m := concurrency.NewMutex(s, serviceKey)
// 尝试加锁,注册服务
if err := m.Lock(context.Background()); err != nil {
log.Fatalf("Error locking etcd key: %v", err)
}
// 在 etcd 中设置服务实例的ID
if _, err := cli.Put(context.Background(), serviceKey, instanceId, clientv3.WithLease(s.Lease())); err != nil {
log.Fatalf("Error setting etcd key: %v", err)
}
// 服务注册成功后,可以在这里添加逻辑,比如发送心跳续约
// ...
}
配置共享
配置共享可以通过在 etcd 中存储配置信息,并让服务实例从 etcd 中读取这些信息来实现。
func getSharedConfig(cli *clientv3.Client, configKey string) (string, error) {
// 从 etcd 获取配置
resp, err := cli.Get(context.Background(), configKey)
if err != nil {
return "", err
}
if len(resp.Kvs) == 0 {
return "", fmt.Errorf("config not found")
}
return string(resp.Kvs[0].Value), nil
}
总结:
这一块,自己可能了解不够细致,先打个草稿,之后再来完善,祝大家生活愉快~
查阅文献: