从2020年到现在,对于etcd的技术恐惧持续了很长时间,偶然发现极客时间有一门课程《etcd实战课》,读了下开篇词,深有感触,是时候踏出舒适区,系统性的学习一下etcd了。本文正是对etcd学习的一个总结,从一个新手的角度回顾一下etcd学习的知识点。
按照官网的描述,etcd是一个分布式的key-value存储系统。分布式和存储这两个关键字哪个都不简单,组合到一起更是让人望而生畏。
如果只是一个简单的key-value存储系统,etcd用不到花这么多年的时间持续的优化。那么在这个key-value的基础上,etcd又扩充了哪些能力,导致etcd给人的感觉这么的复杂呢?


简单来说,raft是共识算法的一种实现,有leader选举、日志复制、日志存储。raft提供了输入、输出的相关接口,比如raft输出的日志同步消息(Ready接口)要经过etcdserver提供的网络功能进行传输,etcdserver处理完请求之后,要驱使raft进行下一个消息的处理。
etcd做为一个基础组件,本身必须具备一定的高可用,需要多副本部署。etcd引入了raft算法,raft算法包括leader选举、日志复制、状态机。这样etcd首先具备了多副本部署的数据协调能力。为了设计上的简单化,写操作只能由leader进行处理,由leader将数据同步到各个follower节点。这样一份数据就在多个节点上都存在,读请求任意节点都可以处理,这就是分布式存储的意义吗?
接触过openstack的知道,openstack社区推荐的一个集群大小建议是小于500台,然而kubernetes社区推荐的一个集群大小建议是小于5000台,10倍的差距一方面得益于kubernetes优良的设计,etcd在性能提升中也扮演了非常重要的角色。
etcd 基于raft 实现了 分布式,基于boltdb实现key-value存储,那么etcd又在此之上扩充了哪些能力呢?
lease
lease是etcd提供的一个附加了ttl(time to live)属性的功能。比如创建了一个过期时期600秒的lease,又将几个key附加到了这个lease上,那么在600s之后,这个lease和这个lease关联的key都会被etcd自动清理掉,根据业务需要,所以需要保持lease,需要client定期为lease续期(keepalive)。
lease相关的接口,包括 创建、撤销(删除)、续期、关联(attach key to lease)操作。相应得,etcd有两个goroutine来管理lease,一是定期更新lease的到期时间,二是删除过期的lease,当集群的lease数非常多时,效率也是个问题,为此etcd使用最小堆这种数据结构来管理lease,最小堆的查询时间复杂度为O(1),这样每次只需要遍历堆顶lease是否过期即可,大大减少了cpu的消耗。
watch
watch是指etcd可以实时将key的变更通知到client。比如client通过watch接口告知etcd自己关注money的变化,假如money有变化的话,etcd会实时的推送给client money的变化。
etcd支持监听key以及范围key,如何高效的根据key查找到对应的client watcher呢?etcd使用了map和区间树两种数据结构来实现高效的查找。
watch是如何监测到key变化并进行通知呢?是在事务结束时,将变更打包成event,通知到etcdserver。
func (tw *watchableStoreTxnWrite) End() {
changes := tw.Changes()
rev := tw.Rev() + 1
evs := make([]mvccpb.Event, len(changes))
for i, change := range changes {
evs[i].Kv = &changes[i]
}
// end write txn under watchable store lock so the updates are visible
// when asynchronous event posting checks the current store revision
tw.s.notify(rev, evs)
}
etcd的存储是让人很容易迷惑的地方,这里首先接受一个etcd写入一个key-value的流程。leader收到一个put hello=world请求,leader将此put操作打包成一个提案(proposal)递交给raft模块,raft模块将此提案同步给各个follower节点,各个follower节点从raft模块获取到这个提案,应用到raft的存储中,并追加到wal中,随后回复给leader此提案已提交。leader收到follower节点的已提交回复后,如果集群中的多数节点都为已提交,那么各个节点的etcdserver 就可以将此提案更新到boltdb持久化存储中。
// MemoryStorage implements the Storage interface backed by an
// in-memory array.
type MemoryStorage struct {
// Protects access to all fields. Most methods of MemoryStorage are
// run on the raft goroutine, but Append() is run on an application
// goroutine.
sync.Mutex
hardState pb.HardState
snapshot pb.Snapshot
// ents[i] has raft log position i+snapshot.Metadata.Index
ents []pb.Entry
}
在etcd中,多个场景下的操作都叫snapshot,这样不加区分的命名,增加了我们理解的难度。
etcd具有保存key的多个版本的能力,keyIndex中存储的是key与revision的关系,boltdb中存储的是revision与key-value的关系,那么随着变更次数的增加,etcd内存和占用磁盘的空间很快就会超限,所以要有机制来定期清理历史的key,这个操作叫做compact,etcd支持周期性或者版本号的压缩策略。etcd中默认配置中是没有配置压缩策略的,但是在kubernetes的环境中,查看etcd的日志,发现每5min中就会有一条压缩的日志,这个日志是kube-apiserver的配置etcd-compaction-interval,默认值就是5min。
要想更深入的学习etcd相关的知识,还是要深入到源码中。etcd已经走过了近10个年头,相关的代码抽象度也是很高的,没有一定的实践,也不太容易厘清etcd的代码结构。幸运的是etcd是golang编写的,也可以在windows下运行,因此通过使用goland 源码 debug etcd,学习起来效率会更高。最简单的可以单节点运行,学习etcd的读写事务操作的流程,后面可以在一台机器上通过多个不同的端口部署多个etcd,调整选举的超时时间,选择其中的一个进程进行调试即可。
debug的方式比较简单,goland的界面也是简单易懂,按照正常的go程序的debug方式操作就可以 了

etcd提供了非常多的metrics用来观测etcd集群,社区也提供了相应的grafana的dashboard简化配置的复杂度。
如果不理解etcd的整个读写流程,相关的metrics也不容易看懂,最好的方式还是到源码中查看metrics在什么流程下更新,才能更好的理解metrics的含义。
一些常用的metrics,比如db文件大小、网络流量大小,节点间的ttl延迟、磁盘延迟、B+树的分裂与重平衡的耗时,提交的提案数等等。下面四张图是从《etcd实战课》中贴过来的。




更多的metrics可以在代码中搜索prometheus.MustRegister。
func init() {
prometheus.MustRegister(rangeCounter)
prometheus.MustRegister(rangeCounterDebug)
prometheus.MustRegister(putCounter)
prometheus.MustRegister(deleteCounter)
prometheus.MustRegister(txnCounter)
prometheus.MustRegister(keysGauge)
prometheus.MustRegister(watchStreamGauge)
prometheus.MustRegister(watcherGauge)
prometheus.MustRegister(slowWatcherGauge)
prometheus.MustRegister(totalEventsCounter)
prometheus.MustRegister(pendingEventsGauge)
prometheus.MustRegister(indexCompactionPauseMs)
prometheus.MustRegister(dbCompactionPauseMs)
prometheus.MustRegister(dbCompactionTotalMs)
prometheus.MustRegister(dbCompactionLast)
prometheus.MustRegister(dbCompactionKeysCounter)
prometheus.MustRegister(dbTotalSize)
prometheus.MustRegister(dbTotalSizeInUse)
prometheus.MustRegister(dbOpenReadTxN)
prometheus.MustRegister(hashSec)
prometheus.MustRegister(hashRevSec)
prometheus.MustRegister(currentRev)
prometheus.MustRegister(compactRev)
prometheus.MustRegister(totalPutSizeGauge)
}
func init() {
prometheus.MustRegister(walFsyncSec)
prometheus.MustRegister(walWriteBytes)
}
func init() {
prometheus.MustRegister(leaseGranted)
prometheus.MustRegister(leaseRevoked)
prometheus.MustRegister(leaseRenewed)
prometheus.MustRegister(leaseTotalTTLs)
}
本文从技术演进的角度概括了etcd的功能点,一些注意事项,以及etcd大概的工作流程。水平有高低,细节深似海,表达有出入,有错误也在所难免,不同的时间,有不同的理解。
大家好,我是辣条。现在短视频可谓是一骑绝尘,吃饭的时候、休息的时候、躺在床上都在刷短视频,今天给大家带来python爬虫进阶:美拍视频地址加密解析。短视频js逆向解析抓取目标工具使用重点学习内容项目思路解析抓取目标目标网址:美拍视频工具使用开发环境:win10、python3.7开发工具:pycharm、Chrome工具包:requests、xpath、base64重点学习内容爬虫采集数据的解析过程js代码调试技巧js逆向解析代码Python代码的转换项目思路解析进入到网站的首页挑选你感兴趣的分类根据首页地址获取到进入详情页面的超链接的跳转地址找到对应加密的视频播放地址数据这个数据是静态的网页
文章目录前言一、Spring是什么?二、什么是容器?三、什么是IoC?3.1初始loC3.2举例解释loC3.3 SpringIoC思想的体现四、什么是DI?4.1DI的概念4.2 Ioc和DI的区别总结前言今天我们将进入到有关spring的认识当中,要使用它的前提就是要认识并熟悉它,上一节我们介绍了有关maven的配置,必须要配置完成后,才能完成我们后面的学习工作,让我们进入到今天的学习当中吧!!!!!!!!!一、Spring是什么?概念:我们通常所说的Spring指的是SpringFramework(Spring框架),它是⼀个开源框架,有着活跃⽽庞⼤的社区,这就是它之所以能⻓久不衰的原因
目录🍊前言🍊:🍈一、宏与函数🍈: 1.宏与函数对比: 2.宏与函数的命名约定:🍓二、预处理操作符🍓: 1.预处理操作符"#": 2.预处理操作符"##":🥝三、条件编译🥝: 1.简述条件编译指令: 2.常见条件编译指令:🍒总结🍒:🛰️博客主页:✈️銮同学的干货分享基地🛰️欢迎关注:👍点赞🙌收藏✍️留言🛰️系列专栏:💐【进阶】C语言学习 🧧 C语言学习🛰️代码仓库:🎉VS2022_C语言仓库 家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真重要,各位路过的友友麻烦多多点赞关注,欢迎你们的私信提问,感谢你们的转发!
我希望在json中有这样的结构:{"a":["b":1,"c":2],"x":["y":3,"z":4]}我可以使用“a”和“x”作为目录并在它们下面有节点来存储数据。我无法在如何完成此操作的文档或示例中找到它。编辑:我刚刚通过为Set调用/a/b、/a/c、/x/y和/x/z将其创建为目录。这创建了必要的结构,但我正在寻找一个简化版本来做同样的事情,而不是4个etcd调用。 最佳答案 创建目录etcdctlmkdir做你想做的,有这个选项:etcdctlsetmyobject'{"a":["b":1,"c":2],"x":["y"
ChatGPT是OpenAI在11月30日推出的聊天机器人,于12月1日起对公众免费开放。自从这东西出来之后,大家对此的讨论热情越发浓烈。ChatGPT具体可以干些什么?帮你写论文、检讨书、情书,甚至情诗也能信手拈来。以上都是网友测试它写出来的内容,但仔细一看,这些虽然有框架在,但基本上都是车轱辘话来回倒腾。如果真的说用来取代人类,还为时过早,而这些AI技术的本意也是为了提高生产率。除了写文案的能力让大家震惊,其中最震惊的还是它的编程能力。是的,它可以帮你写代码。它还可以帮你debug,直接指出你这段代码的问题和优化方式。没有深入尝试,只是确认了下有这个功能。刷算法题啥的,更是不在话下。随便在
我正在尝试编写一段代码,我需要在其中查找某个key是否存在于etcd中。我试过这个:_,err=kapi.Get(context.Background(),key,nil)iferr!=nil{returnerr}else{...但即使键不在集群中,错误也总是nil。知道我在这里做错了什么吗?或者是否有任何其他API调用? 最佳答案 如果你在这里使用goclientv3KV客户端:https://godoc.org/go.etcd.io/etcd/clientv3#KV它返回以下类型:https://godoc.org/go.etc
文章目录前言:fac是什么?“人生苦短,我用Python;Web开发,首选Feffery!”↓↓↓今日笔记↓↓↓五、fac反馈:AntdNotification通知提醒框5.1语法与参数5.1.1语法5.1.2主要参数说明5.2使用示例5.2.1基础使用5.2.2不同的状态5.2.3不同的弹出位置5.2.4持续显示时长的设置前言:fac是什么?feffery-antd-components(简称fac),是国内大佬费弗里(Feffery)老师基于著名的Rea
Unity之可寻址系统--代码加载介绍--进阶(一)一,可寻址系统代码加载1.1回调形式1.2异步等待1.3面板赋值1.4同步加载二,可寻址系统分标签加载2.1场景搭建2.2代码示例2.3效果展示三,代码加载可寻址的解释概述:本片文章为大家介绍可寻址系统使用代码动态加载物体的多种形式。一,可寻址系统代码加载准备工作,创建几个预制体分别为:Cube,Capsule,Sphere,并将预制体设置为可寻址系统的资源,然后将Cube的地址修改为Cube,如下图:1.1回调形式usingUnityEngine;//引用命名空间usingUnityEngine.AddressableAssets;usin
前言: 对于链表,上一篇的单链表解决了顺序表的一部分缺陷,但并没有彻底的解决顺序表的问题,比如在进行单链表尾插尾删的时候还是需要进行遍历找尾,并没有达到全部的O(1),并且在头插的时候还要分情况来考虑,比如传入为空指针和不是空指针时候还要分情况考虑,对于指针的改变还要传二级指针,这对于一部分人来说并不熟悉,所以!!!今天看完这篇文章,掌握带双向循环数据表,让我们不再害怕链表的增删插改😎😎 💞💞 欢迎来到小马学习代码博客!!!! 思维导图:目录一、链表实现前的准备 💜1.1结构图:💜1.2初步的理解:二、带头双向链表功能实现前的准备🤎 2.1链表实现所需要的头文件:
🌇个人主页:_麦麦_📚今日名言:“你若爱,生活哪里都可爱。你若恨,生活哪里都可恨。你若感恩,处处可感恩。你若成长,事事可成长。不是世界选择了你,是你选择了这个世界。既然无处可躲,不如傻乐。既然无处可逃,不如喜悦。既然没有净土,不如静心。既然没有如愿,不如释然。” ——丰子恺《豁然开朗》目录编辑一、前言二、正文 1.内存的分布 2.为什么存在动态内存开辟 3.动态内存函数的介绍 3.1malloc