分布式之ETCD 篇

前言

此篇记录 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 特性以及特点

特性:

  1. 简单:etcd的安装简单,为用户提供了HTTP的接口,使用也很简单
  2. 存储:etcd的基本功能,基本信息存储在文件中
  3. Watch机制:watch指定的键的更改会通知
  4. 安全通信:SSL证书验证
  5. 高性能:2K/s读操作,提供了基准测试的工具
  6. 一致可靠:raft一致性算法

四、ETCD 的常见分布式场景

  1. KV数据库
  2. 服务注册发现
  3. 共享配置
  4. 协调分布式
  5. 分布式锁

五、快速开始

官方下载地址:

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
}

总结:

这一块,自己可能了解不够细致,先打个草稿,之后再来完善,祝大家生活愉快~

查阅文献:

是什么有什么用 · Etcd 中文文档

golang入门笔记—etcd_golang etcd-CSDN博客

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值