etcd

etcd

etcd 是一个分布式键值对存储,设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁,leader选举和写屏障(write barriers)来实现可靠的分布式协作。etcd集群是为高可用,持久性数据存储和检索而准备。

Etcd 是 CoreOS 基于 Raft 协议开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。

单机安装

下载地址 https://github.com/etcd-io/etcd/releases,解压后查看版本

etcd --version
etcdctl version

创建配置文件:conf.yaml

name: "hezebin-etcd"
data-dir: /usr/local/etcd/data
listen-client-urls: "http://127.0.0.1:2379"
advertise-client-urls: "http://127.0.0.1:2379"
initial-cluster-token: "hezebin-etcd-token"
initial-cluster: "hezebin-etcd=http://127.0.0.1:2380"
initial-advertise-peer-urls: "http://127.0.0.1:2380"
initial-cluster-state: "new"

启动 etcd 服务:

etcd --config-file=/usr/local/etcd/conf.yaml

打开终端,执行以下命令来检查 etcd 服务的健康状态:

etcdctl endpoint health

如果一切正常,您将看到类似以下的输出:

127.0.0.1:2379 is healthy: successfully committed proposal: took = 11.667µs

如果 etcd 服务正在运行且健康,您会收到健康状态的确认。

您还可以通过执行以下命令来查看 etcd 集群的成员状态:

etcdctl member list

这将列出 etcd 集群中的成员信息,包括成员的 ID、名称和状态。

设置键值对

etcdctl put name "hezebin"
etcdctl get name# 查询Etcd所有的key
etcdctl get --prefix ""# 只读取键 name 的值的命令
etcdctl get name --print-value-only# 以 name 为前缀的所有键的命令,结果数量限制为2:
etcdctl get --prefix --limit=2 name# 访问修订版本为 4 时的键的版本
etcdctl get --prefix --rev=4 name# 假设 etcd 集群已经有下列键:
a = 123
b = 456
z = 789
# 读取大于等于键 b 的 byte 值的键的命令:
etcdctl get --from-key b
# 清空数据
etcdctl del name
# 删除所有 n 前缀的节点
etcdctl del n -- prefix
# 删除键 zoo 并返回被删除的键值对的命令:
etcdctl del --prev-kv zoo

watch操作

watch 监测一个键值的变化,一旦键值发生更新,就会输出最新的值并退出

etcdctl watch name#更新键值
etcdctl put name "korbin"# watch 阻塞处返回结果
PUT 
name
korbin

读取键过往版本的值

应用可能想读取键的被替代的值。例如,应用可能想通过访问键的过往版本来回滚到旧的配置。或者,应用可能想通过多个请求来得到一个覆盖多个键的统一视图,而这些请求可以通过访问键历史记录而来。因为 etcd 集群上键值存储的每个修改都会增加 etcd 集群的全局修订版本,应用可以通过提供旧有的 etcd 修改版本来读取被替代的键。

假设 etcd 集群已经有下列键:

foo = bar         # revision = 2
foo1 = bar1       # revision = 3
foo = bar_new     # revision = 4
foo1 = bar1_new   # revision = 5

这里是访问键的过往版本的例子:

$ etcdctl get --prefix foo # 访问键的最新版本
foo
bar_new
foo1
bar1_new
$ etcdctl get --prefix --rev=4 foo # 访问修订版本为 4 时的键的版本
foo
bar_new
foo1
bar1
$ etcdctl get --prefix --rev=3 foo # 访问修订版本为 3 时的键的版本
foo
bar
foo1
bar1
$ etcdctl get --prefix --rev=2 foo # 访问修订版本为 2 时的键的版本
foo
bar
$ etcdctl get --prefix --rev=1 foo # 访问修订版本为 1 时的键的版本

压缩修订版本

如我们提到的,etcd 保存修订版本以便应用可以读取键的过往版本。但是,为了避免积累无限数量的历史数据,压缩过往的修订版本就变得很重要。压缩之后,etcd 删除历史修订版本,释放资源来提供未来使用。所有修订版本在压缩修订版本之前的被替代的数据将不可访问。

这是压缩修订版本的命令:

$ etcdctl compact 5
compacted revision 5
# 在压缩修订版本之前的任何修订版本都不可访问
$ etcdctl get --rev=4 foo
Error:  rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted

注意: etcd 服务器的当前修订版本可以在任何键(存在或者不存在)以json格式使用get命令来找到。下面展示的例子中 mykey 是在 etcd 服务器中不存在的:

$ etcdctl get mykey -w=json
{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":15,"raft_term":4}}

lease租约(过期机制)

应用可以为 etcd 集群里面的键授予租约。当键被附加到租约时,它的存活时间被绑定到租约的存活时间,而租约的存活时间相应的被 time-to-live (TTL)管理。在租约授予时每个租约的最小TTL值由应用指定。租约的实际 TTL 值是不低于最小 TTL,由 etcd 集群选择。一旦租约的 TTL 到期,租约就过期并且所有附带的键都将被删除。

授予租约

# 授予租约,TTL为10秒
$ etcdctl lease grant 10
lease 32695410dcc0ca06 granted with TTL(10s)
# 附加键 foo 到租约32695410dcc0ca06
$ etcdctl put --lease=32695410dcc0ca06 foo barOK

应用通过租约 id 可以撤销租约。撤销租约将删除所有它附带的 key。

撤销租约

$ etcdctl lease revoke 32695410dcc0ca06
lease 32695410dcc0ca06 revoked
$ etcdctl get foo
# 空应答,因为租约撤销导致foo被删除

keepAlive续约

应用可以通过刷新键的 TTL 来维持租约,以便租约不过期。维持同一个租约的命令:

$ etcdctl lease keep-alive 32695410dcc0ca06
lease 32695410dcc0ca06 keepalived with TTL(10)
lease 32695410dcc0ca06 keepalived with TTL(10)
lease 32695410dcc0ca06 keepalived with TTL(10)
...

获取租约信息

应用程序可能想知道租约信息,以便可以更新或检查租约是否仍然存在或已过期。应用程序也可能想知道有那些键附加到了特定租约。

获取租约信息的命令:

$ etcdctl lease timetolive 694d5765fc71500b
lease 694d5765fc71500b granted with TTL(500s), remaining(258s)

获取租约信息和租约附带的键的命令:

$ etcdctl lease timetolive --keys 694d5765fc71500b
lease 694d5765fc71500b granted with TTL(500s), remaining(132s), attached keys([zoo2 zoo1])
# 如果租约已经过期或者不存在,它将给出下面的应答:
Error:  etcdserver: requested lease not found

事务

etcd 的事务(Transaction)机制允许你在一个原子操作中执行一系列操作,这些操作要么全部成功,要么全部失败,确保数据的一致性和完整性。

etcdctl txn 并没有对事务提供过多的支持,执行事务最好通过 go 来实现:

go get go.etcd.io/etcd/client/v3
package mainimport ("context""fmt""log""time""go.etcd.io/etcd/clientv3"
)func main() {// 创建 etcd 客户端cli, err := clientv3.New(clientv3.Config{Endpoints:   []string{"http://localhost:2379"}, // 替换为你的 etcd 地址DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}defer cli.Close()// 创建一个 etcd 事务etcdTxn := clientv3.NewKV(cli).Txn(context.Background())// 定义事务的比较和操作etcdTxn.If(clientv3.Compare(clientv3.Value("my_key"), "=", "10"),).Then(clientv3.OpPut("my_key", "20"),).Else(clientv3.OpPut("my_key", "30"),)// 提交事务txnResp, err := etcdTxn.Commit()if err != nil {log.Fatal(err)}// 检查事务是否成功if !txnResp.Succeeded {fmt.Println("Transaction failed")} else {fmt.Println("Transaction succeeded")}
}

注意:etcdTxn.If 中指定的条件是 clientv3.Compare(clientv3.Value("my_key"), "=", "10"),也就是检查键 “my_key” 的值是否等于 “10”。如果这个条件不满足(例如,键 “my_key” 的值为空),那么事务的 Else 分支会执行,而且整个事务会被标记为失败。

基于etcd实现分布式锁

原生实现

要在 etcd 中实现分布式锁,你可以利用 etcd 的事务特性和租约(Lease)机制。以下是一个使用 etcd 实现分布式锁的示例:

# 伪代码,etcdctl 并不支持下述命令,需要以 go 方式实现,api 更丰富
etcdctl txn -- \put my_lock some_value  --lease=0d7689bc575d6611  \if_not_exists# if_not_exists: 这是一个条件,表示只有在键不存在时才执行上述的 put 操作。这个条件确保只有第一次创建节点的时候才会成功,从而实现锁的获取。

这个命令的目的是在一个 etcd 事务中,尝试创建一个指定键名的键值对,如果该键名在 etcd 中尚不存在(即尝试获取锁),则创建成功,否则操作失败。这个操作模式可以帮助实现分布式锁的基本机制。

Go 代码实现:

package mainimport ("context""fmt""log""time""go.etcd.io/etcd/clientv3"
)func main() {// 创建 etcd 客户端cli, err := clientv3.New(clientv3.Config{Endpoints:   []string{"http://localhost:2379"}, // 替换为你的 etcd 地址DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}defer cli.Close()// 创建租约leaseResp, err := cli.Grant(context.Background(), 10) // 10 秒的租约时间if err != nil {log.Fatal(err)}// 锁的键名lockKey := "my_lock"// 尝试获取锁txnResp, err := cli.Txn(context.Background()).If(clientv3.Compare(clientv3.CreateRevision(lockKey), "=", 0)).Then(clientv3.OpPut(lockKey, "lock_holder", clientv3.WithLease(leaseResp.ID))).Commit()if err != nil {log.Fatal(err)}// 检查是否成功获取锁if !txnResp.Succeeded {log.Println("Failed to acquire lock")return}log.Println("Lock acquired")// 续约循环(保持锁)keepAliveCh, err := cli.KeepAlive(context.Background(), leaseResp.ID)if err != nil {log.Fatal(err)}go func() {for range keepAliveCh {// 续约成功,执行你的逻辑,或者也可以不做任何处理}}()// 在这里执行需要保护的临界区代码// 释放锁(取消续约)_, err = cli.Revoke(context.Background(), leaseResp.ID)if err != nil {log.Fatal(err)}log.Println("Lock released")
}

官方 concurrency 包实现

cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
if err != nil {log.Fatal(err)
}
defer cli.Close()// 创建两个单独的会话用来演示锁竞争
s1, err := concurrency.NewSession(cli)
if err != nil {log.Fatal(err)
}
defer s1.Close()
m1 := concurrency.NewMutex(s1, "/my-lock/")s2, err := concurrency.NewSession(cli)
if err != nil {log.Fatal(err)
}
defer s2.Close()
m2 := concurrency.NewMutex(s2, "/my-lock/")// 会话s1获取锁
if err := m1.Lock(context.TODO()); err != nil {log.Fatal(err)
}
fmt.Println("acquired lock for s1")m2Locked := make(chan struct{})
go func() {defer close(m2Locked)// 等待直到会话s1释放了/my-lock/的锁if err := m2.Lock(context.TODO()); err != nil {log.Fatal(err)}
}()if err := m1.Unlock(context.TODO()); err != nil {log.Fatal(err)
}
fmt.Println("released lock for s1")<-m2Locked
fmt.Println("acquired lock for s2")

通过源码可以发现concurrency包的实现原理为执行一个 key 的前缀,并在最终设置到 etcd key 的尾部拼接上 /lease_id,租约为 60 秒,且每隔 20 秒续租一次。

在 tryAcquire 函数中通过 put 上述的键值,判断版本号,若设置成功则拿到锁,否则阻塞等待锁:
在这里插入图片描述

阻塞期间先通过查询一次 key 是否存在判断是否阻塞,若不存在表示拿到锁,结束阻塞;存在则通过 watch 监听 key 的变更,仅允许DELETE类型,其他变更操作会报错,并做强制解锁。
在这里插入图片描述
在这里插入图片描述

服务注册与发现

当使用 etcd 实现服务注册与发现时,通常需要以下步骤:

  1. 引入 etcd 的 Go 客户端库
  2. 连接到 etcd 服务器
  3. 注册服务:将服务的信息写入 etcd 中
  4. 发现服务:从 etcd 中获取已注册的服务信息

以下是一个简单示例,演示如何使用 Go 语言操作 etcd 实现基本的服务注册与发现功能。请确保已经安装了 etcd 并启动了 etcd 服务器。

package mainimport ("context""fmt""log""time""go.etcd.io/etcd/clientv3"
)func main() {// 创建 etcd 客户端cli, err := clientv3.New(clientv3.Config{Endpoints:   []string{"localhost:2379"}, // etcd 服务器地址DialTimeout: 5 * time.Second,})if err != nil {log.Fatal(err)}defer cli.Close()// 注册服务serviceName := "my-service"serviceIP := "192.168.1.100"servicePort := "8080"serviceKey := fmt.Sprintf("/services/%s/%s:%s", serviceName, serviceIP, servicePort)serviceValue := "some-metadata-about-the-service"ctx := context.Background()_, err = cli.Put(ctx, serviceKey, serviceValue)if err != nil {log.Fatal(err)}fmt.Printf("Service registered: %s\n", serviceKey)// 发现服务discoveryKey := fmt.Sprintf("/services/%s", serviceName)resp, err := cli.Get(ctx, discoveryKey, clientv3.WithPrefix())if err != nil {log.Fatal(err)}fmt.Println("Discovered services:")for _, kv := range resp.Kvs {fmt.Printf("Key: %s, Value: %s\n", kv.Key, kv.Value)}
}

可以结合 watch 机制优化更新服务变更。

Go 操作 Etcd 参考

go get go.etcd.io/etcd/client/v3
  • 民间文档:http://www.topgoer.com/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/go%E6%93%8D%E4%BD%9Cetcd/%E6%93%8D%E4%BD%9Cetcd.html

  • 官方文档:https://github.com/etcd-io/etcd/blob/main/client/v3/README.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/23853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

02|Oracle学习(数据类型、DDL)

1. 数据类型&#xff1a; 通常为&#xff1a;字符型、数值型、日期型以及大字段型大字段型&#xff1a;存放大数据及文件。 存储大数据时&#xff0c;基本上blob就能满足。 2. DDL&#xff08;数据库定义语言&#xff09; 主要包括对数据库对象的创建、删除及修改的操作。…

2.文件的逻辑结构

第四章 文件管理 2.文件的逻辑结构 顺序文件采用顺序存储则意味着各个逻辑上相邻的记录在物理上也是相邻的存储的。所以如果第0号记录的逻辑地址为0的话&#xff0c;则i号记录的逻辑为i *L。 特别的如果这个定长记录的顺序文件采用串结构&#xff0c;也就是这些记录的顺序和他…

go 结构体 - 值类型、引用类型 - 结构体转json类型 - 指针类型的种类 - 结构体方法 - 继承 - 多态(interface接口) - 练习

目录 一、结构体 1、python 与 go面向对象的实现&#xff1a; 2、初用GO中的结构体&#xff1a;&#xff08;实例化一个值类型的数据&#xff08;结构体&#xff09;&#xff09; 输出结果不同的三种方式 3、实例化一个引用类型的数据&#xff08;结构体&#xff09; 4、…

Tomcat添加第三方jar包、如何在IDEA中启动部署Web模板

前言:公司最近维护老项目,是最原始的web项目,servlet和jsp结合的web项目,启动的时候配置了好几遍, 都起不来,很折磨人,这个文档比较全配置一遍准备工作 首先 拉取代码: git clone xxx.git ,如需要别的操作,自行baidu 也可以在idea中拉取第一步File ->Project Structure->…

Redis两种持久化方案RDB持久化和AOF持久化

Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启…

VSCode插件Todo Tree的使用

在VSCode中安装插件Todo Tree。按下快捷键ctrlshiftP&#xff0c;输入setting.jspn&#xff0c;选择相应的配置范围&#xff0c;我们选择的是用户配置 Open User Settings(JSON)&#xff0c;将以下代码插入其中。 //todo-tree 标签配置从这里开始 标签兼容大小写字母(很好的功…

Spring Boot 集成Seata

Seata的集成方式有&#xff1a; 1. Seata-All 2. Seata-Spring-Boot-Starter 3. Spring-Cloud-Starter-Seata 本案例使用Seata-Spring-Boot-Starter演示&#xff1a; 第一步&#xff1a;下载Seata 第二步&#xff1a;为了更好看到效果&#xff0c;我们将Seata的数据存储改…

linuxARM裸机学习笔记(2)----汇编LED灯实验

MX6ULL 的 IO IO的复用功能 这里的只使用了低五位&#xff0c;用来配置io口&#xff0c;其中bit0~bit3(MUX_MODE)就是设置 GPIO1_IO00 的复用功能的&#xff0c;GPIO1_IO00 一共可以复用为 9种功能 IO&#xff0c;分别对应 ALT0~ALT8。每种对应了不同的功能 io的属性配置 HY…

SolidWorks 3D Interconnect介绍

目前市面上有的三维设计软件有很多&#xff0c;如UG、Pro/E、CATIA等&#xff0c;而且每个三维设计软件都会生成自己文件格式。由于产品设计的原因&#xff0c;我们避免不了的会需要去使用不同三维设计软件的文件&#xff0c;这对于工程师来说其实是一件比较麻烦的事。 为什么…

[腾讯云Cloud Studio实战训练营]基于Cloud Studio完成图书管理系统

[腾讯云Cloud Studio实战训练营]基于Cloud Studio完成图书管理系统 ⭐前言&#x1f31c;Cloud Studio产品介绍1.登录2.创建工作空间3.工作空间界面简介4.环境的使用 ⭐实验实操&#x1f31c;Cloud Studio实现图书管理系统1.实验目的 2. 实验过程2.实验环境3.源码讲解3.1添加数据…

下载Windows 10光盘镜像(ISO文件)

文章目录 下载Windows 10镜像文件 下载Windows 10镜像文件 打开微软官网下载地址 立即下载工具 找到下载工具&#xff0c;双击运行&#xff0c;等待 接受条款&#xff0c;等待 选择为另一台电脑安装介质 选择Windows10&#xff0c;下一步 选择ISO文件&#xff0c;…

【2023.8】docker一键部署wvp-GB28181-pro和ZLMediaKit过程全记录

安装docker 使用的操作系统是ubuntu20.04 如何在 Ubuntu 20.04 上安装和使用 Docker https://developer.aliyun.com/article/762674 docker拉取配置好的ZLMediaKIt和wvp-GB28181-pro docker pull 648540858/wvp_pro第一次运行 docker一键运行ZLMediaKIt和wvp-GB28181-pro …

spring boot中web容器配置

web容器配置 spring boot 默认的web容器是 tomcat&#xff0c;如果需要换成其他的 web 容器&#xff0c;可以如下配置。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 默…

使用 OpenCV 和 Python 卡通化图像-附源码

介绍 在本文中,我们将构建一个有趣的应用程序,它将卡通化提供给它的图像。为了构建这个卡通化器应用程序,我们将使用 python 和 OpenCV。这是机器学习令人兴奋的应用之一。在构建此应用程序时,我们还将了解如何使用 easygui、Tkinter 等库。在这里,您必须选择图像,然后应…

苍穹外卖day12(完结撒花)——工作台+Spring_Apche_POI+导出运营数据Excel报表

工作台——需求分析与设计 产品原型 接口设计 工作台——代码导入 将提供的代码导入对应的位置。 工作台——功能测试 Apache POI_介绍 应用场景 Apache POI_入门案例 导入坐标 <!-- poi --><dependency><groupId>org.apache.poi</groupId><ar…

《命运》阅读笔记

《命运》阅读笔记 2023年5月17号在杭州的小屋读完&#xff0c;我读完后&#xff0c;脑海里经常把余华的《活着》和这本《命运》的故事情节搞混淆&#xff0c;几乎都是讲着生活的苦难。全文以阿太&#xff08;外婆的妈妈&#xff09;的视角&#xff0c;在她九十九岁的人生里&…

《cuda c编程权威指南》05 - cuda矩阵求和

目录 1. 使用一个二维网格和二维块的矩阵加法 1.1 关键代码 1.2 完整代码 1.3 运行时间 2. 使用一维网格和一维块的矩阵加法 2.1 关键代码 2.2 完整代码 2.3 运行时间 3. 使用二维网格和一维块的矩阵矩阵加法 3.1 关键代码 3.2 完整代码 3.3 运行时间 1. 使用一个二…

记一次ubuntu16误删libc.so.6操作的恢复过程

背景 操作系统&#xff1a;ubuntu16 glibc版本&#xff1a;2.23 修改原因&#xff1a; 经过一系列报错和手工构建之后&#xff0c;vulkansdk成功安装&#xff08;起码运行./vulkansdu成功&#xff09;&#xff0c;在进行./vulkaninfo进行验证时&#xff0c;报错&#xff1a…

拦截器在SpringBoot中使用,HandlerInterceptor,WebMvcConfigurer

拦截器在Controller之前执行。 用于权限校验&#xff0c;日志记录&#xff0c;性能监控 在SpringBoot中使用 创建拦截器类&#xff1a;首先&#xff0c;创建一个Java类来实现拦截器逻辑。拦截器类应该实现Spring提供的HandlerInterceptor接口。实现拦截器方法&#xff1a;拦…

【Ubuntu】Ubuntu 22.04 升级 OpenSSH 9.3p2 修复CVE-2023-38408

升级原因 近日Openssh暴露出一个安全漏洞CVE-2023-38408&#xff0c;以下是相关资讯&#xff1a; 一、漏洞详情 OpenSSH是一个用于安全远程登录和文件传输的开源软件套件。它提供了一系列的客户端和服务器程序&#xff0c;包括 ssh、scp、sftp等&#xff0c;用于在网络上进行…