当前位置: 首页 > news >正文

Etcd学习笔记

etcd的介绍与安装

  • 主要用于微服务的配置中心和服务发现,数据可靠性比redis更强

在对外api的应用中,如何知道order服务的rpc地址?

如果服务的ip地址变化了怎么办?在传统的配置文件模式,修改配置文件,应用程序是需要重启才能解决的,所以引入etcd

  • windows安装

    • etcd-v3.5.16-windows-amd64.zip

  • docker安装

    docker run --name etcd -d -p 2379:2379 -p 2380:2380 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:3.3.11 etcd

安装完成后在下载路径启用cmd(因为未加环境变量)


  • etcd 链接:百度网盘 请输入提取码 提取码:7777 etcdkeeper 链接:百度网盘 请输入提取码 提取码:7777

  • etcd文档

  • etcd不同系统安装


基本命令

// 设置或更新值
etcdctl put name 张三
// 获取值
etcdctl get name
// 只要value
etcdctl get name --print-value-only
// 获取name前缀的键值对
etcdctl get name --prefix
// 删除键值对
etcdctl del name
// 监听键的变化
etcdctl watch name
// 例如
etcdctl put rpc.order 127.0.0.1:7001
etcdctl put rpc.user 127.0.0.1:7002
etcdctl get rpc --prefix
// ------------
etcdctl watch rpc.user 
// 另起一个cmd
etcdctl put rpc.user 127.0.0.1:7003

Go 操作 etcd

驱动包安装

不能直接 go get go.etcd.io/etcd/clientv3(官方提供驱动包)不然会报错的;因为 gRPC 版本过新的缘故;

这里我们需要指定 gRPC 的版本信息;

# 指定 gRPC 版本为 v1.26.0
go mod edit --require=google.golang.org/grpc@v1.26.0
# 下载安装 gRPC 驱动包
go get -u -x google.golang.org/grpc@v1.26.0
# 下载安装 etcd 驱动包
go get go.etcd.io/etcd/clientv3
go mod tidy

启动 etcd

  • 方式一:(etcd 的端口号只能在本地连)

    start /B etcd > start.log 2>&1  
    // /B表示在后台运行程序 >将输出重定向到文件 将标准错误重定向到标准输出
    netstat -an | findstr :2379 
    // 来验证端口是否被监听
    // 用于 Linux 或类 Unix 系统:etcd --listen-client-transport-port 2379
    nohup ./etcd > ./start.log 2>&1 &
    // 查看端口对外开放情况(etcd 默认端口为 2379)
    lsof -i:2379
  • 方式二:(如果 etcd 所在机器是公司内部机器,需要把安全组对应端口号放开,即需要放开 2379)

    start /B etcd.exe --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379' > start.log 2>&1
     
    // 查看端口对外开放情况(etcd 默认端口为 2379)
    netstat -ano | findstr :2379
    nohup ./etcd --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379' > ./start.log 2>&1 &
    ​
    // 查看端口对外开放情况(etcd 默认端口为 2379)
    lsof -i:2379

关闭etcd

// windows
tasklist | findstr etcd
taskkill /F /IM etcd.exe

putget 使用

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/coreos/etcd/clientv3"
)

func main() {
	// 创建连接
	cli, err := clientv3.New(clientv3.Config{ // 创建etcd客户端实例
		// Endpoints 是一个切片,可同时连接多个服务器
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second, // 连接超时时间
	})
	if err != nil {
		panic(err)
	}

	// 程序执行结束前释放连接资源
	defer cli.Close()

	// 这里创建了一个带有超时的上下文ctx,用于控制Put操作的超时
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	// 向etcd中写入数据,如果操作成功,cancel函数会被调用,取消上下文的超时控制
	_, err = cli.Put(ctx, "key", "mark")
	cancel()
	if err != nil { // 操作失败,服务终止
		panic(err)
	}

	// Get操作,有超时
	ctx, cancel = context.WithTimeout(context.Background(), time.Second)
	/*
	   此处的 get 等同于在终端执行 ./etcdctl get key -w json
	   输出结果:
	   {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":17,"raft_term":7},
	    "kvs":[{"key":"a2V5","create_revision":2,"mod_revision":15,"version":8,"value":"dmFsMjAyMw=="}],"count":1}
	*/
	resp, err := cli.Get(ctx, "key")
	cancel()
	if err != nil {
		panic(err)
	}

	for _, ev := range resp.Kvs { // 遍历etcd返回的结果
		fmt.Printf("%s:%s\n", ev.Key, ev.Value)
	}
}

watch 使用

package main

import (
	"context"
	"fmt"

	"github.com/coreos/etcd/clientv3"
)

func main() {
	// 创建etcd客户端实例
	cli, err := clientv3.NewFromURL("127.0.0.1:2379")
	if err != nil {
		panic(err)
	}
	defer cli.Close()

	// 监听key 的操作
	//watch := cli.Watch(context.Background(), "key")

	// 创建了一个监听器来监听名为"key"的键或大于"key"的所有键的变化
	// clientv3.WithFromKey()选项表示监听的范围从"key"开始
	watch := cli.Watch(context.Background(), "key", 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)
		}
	}
}

执行上述代码后会阻塞等待其它用户操作 etcd,如下所示:

1)在终端执行 etcd 操作

img

2)在 go 客户端查看监听情况

img

lease 使用

package main

import (
	"context"
	"fmt"

	"github.com/coreos/etcd/clientv3"
)

func main() {
	// 创建连接
	cli, err := clientv3.NewFromURL("127.0.0.1:2379")
	if err != nil {
		panic(err)
	}
	defer cli.Close() // 关闭连接

	// 创建租约
	lease, err := cli.Grant(context.Background(), 5) // 5秒的租约
	if err != nil {
		panic(err)
	}
	fmt.Println("lease id", lease.ID) // 打印租约ID

	// 把 key-val 绑定到租约
	_, err = cli.Put(context.Background(), "key", "mark", clientv3.WithLease(lease.ID))
	if err != nil {
		panic(err)
	}

	// 续租:长期续租、短期续租, 续约到期删除键值对
	// 长期续租:不停的续租
	if true {
		ch, err := cli.KeepAlive(context.Background(), lease.ID)
		if err != nil {
			panic(err)
		}
		for {
			recv := <-ch                          // 接收续租结果
			fmt.Println("time to live", recv.TTL) // 打印剩余租期
		}
	}
	// 短期续租:只续租一次
	if false {
		res, err := cli.KeepAliveOnce(context.Background(), lease.ID)
		if err != nil {
			panic(err)
		}
		fmt.Println("time to live", res.TTL) // 打印剩余租期
	}
}

lock 使用

package main

import (
	"context"
	"fmt"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
)

func main() {
	// 创建连接
	cli, err := clientv3.New(clientv3.Config{
		Endpoints: []string{"127.0.0.1:2379"},
	})
	if err != nil {
		panic(err)
	}
	defer cli.Close()

	// 创建了第一个会话 s1,并设置了一个 TTL为 10 秒
	s1, err := concurrency.NewSession(cli, concurrency.WithContext(context.Background()), concurrency.WithTTL(10))
	if err != nil {
		panic(err)
	}
	defer s1.Close()

	// 使用会话 s1 创建了一个名为 "lock" 的互斥锁
	m1 := concurrency.NewMutex(s1, "lock")

	// 创建了第二个会话 s2,没有设置 TTL,将一直保持活跃,直到显式关闭
	s2, err := concurrency.NewSession(cli, concurrency.WithContext(context.Background()))
	if err != nil {
		panic(err)
	}
	defer s2.Close()

	// 为 session2 创建锁
	m2 := concurrency.NewMutex(s2, "lock")

	// 对 session1 加锁
	if err := m1.Lock(context.Background()); err != nil {
		panic(err)
	}
	fmt.Println("s1 acquired lock")

	// 创建管道
	m2ch := make(chan struct{})

	// 开启协程,对 session2 加锁,但由于已经被 session1 锁住,所以 session2 的加锁操作,阻塞等待
	go func() {
		defer close(m2ch)
		if err := m2.Lock(context.Background()); err != nil {
			panic(err)
		}
	}()

	// session1 释放锁
	if err := m1.Unlock(context.Background()); err != nil {
		panic(err)
	}
	fmt.Println("s1 released lock")

	// 通知 session2 session1 已经释放锁,此时 session2 可执行加锁操作
	<-m2ch
	fmt.Println("s2 acquired lock")
}

etcd 存储原理及读写机制

etcd 为每个 key 创建一个索引;一个索引对应着一个 B+ 树;B+ 树 key 为 revision,B+ 树节点存储的值为 value;B+ 树存储着 key 的版本信息从而实现了 etcdmvccetcd 不会任由版本信息膨胀,通过定期的 compaction 来清理历史数据;

etcd 为了加速索引数据,在内存中维持着一个 B 树;B 树 key 为 key-val 中的 key,value 为该 key 的 revision;示意图如下:

img

etcd不同命令执行流程:

  • etcd get 命令执行流程:etcd 在执行 get 获取数据时,先从内存中的 B 树中寻找,如果找不到,再从 B+ 树中寻找,从 B+ 树中找到数据后,将其缓存到 B 树并输出到客户端;

  • etcd put 命令执行流程:etcd 在执行 put 插入或修改数据时,先从内存中的 B 树中寻找,如果找到了,则对其进行修改并将其写入到 B+ 树;

问题:mysqlmvcc 是通过什么实现的?

答:undolog

问题:mysql B+ 树存储什么内容?

答:具体分为聚簇索引和二级索引;

问题:mysql 为了加快索引数据,采用什么数据结构?

答:MySQL采用自适应 hash 来加速索引;

扩展:B-树和 B+ 树区别?

  • B-树和 B+ 树都是多路平衡搜索树;采用中序遍历的方式会得到一个有序的结构;都是通过 key 的方式来维持树的有序性;

  • B-树一个节点中 n 个元素对应着 n+1 个指针;而 B+ 树一个节点中 n 个元素对应着 n 个指针;

  • B-树每个节点都存储节点信息,B+ 树只有叶子节点存储节点信息,非叶子节点只存储索引信息;

  • B+ 树叶子节点之间通过双向链表连接,对于范围查询速度更快,这样减少了磁盘 io

读写机制


etcd 是串行写(避免不必要的加锁),并发读;

并发读写时(读写同时进行),读操作是通过 B+ 树 mmap 访问磁盘数据;写操作走日志复制流程;可以得知如果此时读操作走 B 树出现脏读幻读问题;通过 B+ 树访问磁盘数据其实访问的事务开始前的数据,由 mysql 可重复读隔离级别下MVCC 读取规则可智能避免脏读和幻读问题;

并发读时,可走内存 B 树;

注:由于 etcd 写的时候是先写到内存中的 B 树,然后再写到磁盘上的 B+ 树,因此并发读写时需要读 B+ 树数据,否则容易出现脏读幻读问题;

关闭etcd监听

# 使用 tasklist 命令来查找 etcd 相关的进程
tasklist | findstr etcd
# 使用 taskkill 命令结束 /F 表示强制结束进程,/IM 后面跟上进程的名称
taskkill /F /IM etcd.exe
# 通过服务的方式来启动 etcd (如:etcd.exe --service)
net stop etcd
# 使用 netstat 命令检查端口是否还在监听
netstat -an | findstr :2379

相关文章:

  • 通过一篇文章让你稳过计算机二级(C语言)
  • 为wordpress自定义一个留言表单并可以在后台进行管理的实现方法
  • 火语言RPA--指定PDF页另存为图片
  • Redis客户端
  • swift-5-汇编分析闭包本质
  • 2025年基于Zabbix主动发现机制实现Nacos服务动态监控
  • vue3中使用h()函数加载elementPlus 组件
  • 3个 Vue Scoped 的核心原理
  • Python中的*args和**kwargs:灵活参数处理的完全指南
  • Spring Boot 本地缓存工具类设计与实现
  • 【数据结构与算法】Java描述:第三节:栈与队列
  • 《苍穹外卖》SpringBoot后端开发项目重点知识整理(DAY1 to DAY3)
  • DeepSeek:中国AGI先锋,用技术重塑通用人工智能的未来
  • WPF基础知识61-80
  • 【python】模块和包相关知识
  • cpu 多级缓存L1、L2、L3 与主存关系
  • Easysearch 使用 AWS S3 进行快照备份与还原:完整指南及常见错误排查
  • 渗透测试之利用sql拿shell(附完整流程+防御方案)【下】
  • 本地搭建DeepSeek R1模型 + 前端
  • docker compose 以redis为例
  • 国寿资产获批参与第三批保险资金长期投资改革试点
  • 中方是否计划解除或调整稀土出口管制?外交部回应
  • 英德宣布开发射程超2000公里导弹,以防务合作加强安全、促进经济
  • 国税总局上海市税务局通报:收到王某对刘某某及相关企业涉税问题举报,正依法依规办理
  • 申论|空间更新结合“青银共生”,助力青年发展型城区建设
  • 美国调整对华加征关税