Kubernetes控制平面组件:etcd(一)
云原生学习路线导航页(持续更新中)
- kubernetes学习系列快捷链接
- Kubernetes架构原则和对象设计(一)
- Kubernetes架构原则和对象设计(二)
- Kubernetes架构原则和对象设计(三)
- kubectl 和 kubeconfig 基本原理
- kubeadm 升级 k8s集群 1.17到1.20
- Kubernetes常见问题解答
- 查看云机器的一些常用配置
本文主要对Kubernetes控制平面组件:etcd进行介绍,深入理解etcd的设计原理,深入分析 Raft 协议如何实现一致性,如何完成leader选举和日志复制等
1.Etcd介绍
1.1.Etcd简介
-
任何一个系统都要有地方存数据,而在Kubernetes中唯一存储数据的就是etcd
-
etcd是CoreOS公司研发的,后来CoreOS被RedHat收购了
-
etcd的字面意思为 etc distribution,在Linux中etc目录一般是用于存储配置文件的,所以etcd可以理解为分布式配置管理中心。又因为etcd中存储的是key-value形式,所以又可以理解为 分布式键值存储数据库
-
本节主要向大家分享etcd的设计理念,遵循的协议是什么,怎么保证数据一致,如何支持我们之前讲的watch机制,进而理解为什么Kubernetes会选它作为唯一的数据存储

-
分布式场景中需要考虑的一些问题
- 保证服务高可用性
- 在没有分布式部署时,单节点部署存在很多问题:数据备份压力(备份周期不好选择、备份数据有时效性、故障时需要人工手动恢复等)、单点故障导致的数据丢失问题。为了保证数据库的高可用,一般要多实例冗余部署。
- 但是 有状态的服务 和 无状态的服务 高可用复杂性完全不可同日而语,无状态可以非常轻松的完成副本的扩缩,死一个无关紧要,但有状态应用,比如etcd数据库多个member组成的一个集群中数据怎么保持一致性 是非常复杂的事情。
- 实现服务注册和发现
- 在微服务架构中,a服务要访问b服务时,需要获取到b服务的地址,此时就需要服务注册和发现,很多时候注册中心使用键值对就可以满足这个需求,而且效率也比较高
- 实现监听变更
- 如果使用key-value存储一些数据,希望在某个key-value发生变化时得到通知,就需要有一个监听-通知机制
- 这是一个典型的消息机制,可以使用消息队列实现
- 保证服务高可用性
-
Etcd介绍
- Etcd是一个 基于raft协议的 分布式键值存储数据库,对上述三个问题的处理比较友好。Etcd有分布式部署保证自身高可用和数据高可用,可以用key-value实现服务注册和发现,自带watch机制实现消息机制
- Etcd中通过API访问数据,且具备可选的SSL客户端认证来保证数据安全性,有一套完备的认证授权体系,包括角色管理、用户管理等。
- Etcd还具备key的过期和续约机制,使用
- 不过Kubernetes没有使用Etcd自带的认证鉴权体系,所以etcd的速度还是不错的
- Etcd 基于Raft协议保证数据一致性,单实例每秒支持1000次的写操作,2000次的读操作,随着Etcd的升级效果可能会有所提升。
1.2.Etcd的主要功能

- Etcd的Key-value存储
- 键值存储是Etcd的核心功能,结构简单,不需要像关系型数据库那样有结构化表格的row和column,key-value的存储保证了查询速度的迅速
- 另外Etcd也支持数据持久化,存储包括内存数据和磁盘数据
- Etcd的监听机制
- Etcd的监听机制,就是利用watch机制把自己打造成一个消息队列,进而避免引入第三方消息组件
- Kubernetes正是利用了Etcd的watch机制将自己变成一个异步组件
- Etcd使用Lease实现过期续约机制
- 比如说你给一个key设个1分钟,在一分钟有效期之内是可以get到这个数据的,超出1分钟这个数据就失效了,就get不到了
- 当然,可以在过期前发起renew,对key进行续约
- Etcd还可以用于实现 Compare And Swap 乐观锁机制
- Compare And Swap 即更改之前先Get一把,看是否被修改过,没有修改过自己再发起修改。
- CAS 可以用于服务发现和服务注册
1.3.Etcd的键值对存储

- 在etcd中,key以B树的形式存储在内存中, value以B+树的形式存储在硬盘中
1.4.Etcd应用于服务注册与发现

有些人可能对服务注册和发现很熟悉,尤其是做过微服务开发的人,但有些人不一定了解。这里我们举一个最简单的例子来说明。
- 在 Spring Cloud 中,它是一个微服务框架。我们先抛开 ETCD,Spring Cloud 本身有一个服务注册中心,叫做 Eureka。
- 服务提供者的注册
- 如果你要写一个应用,作为一个服务提供方的角色,比如提供一个 REST API 给别人用,那么在部署时,通常会进行高可用部署。例如,你部署了 3 个实例。
- 那么,别人如何使用你的服务呢?你部署的 3 个实例,每个实例的 IP 地址和端口你是知道的。你需要通过服务注册机制,将这些 IP 地址和端口注册到服务注册中心。这是第一步:将自己注册上去。
- 保活与健康检查
- 第二个重点是保活。服务提供者需要不断进行健康检查,确保自己的实例是健康的还是异常的。健康检查非常重要,服务提供者会不断刷新注册中心的状态。
- 如果注册中心在一段时间内没有收到某个实例的心跳,就会认为该实例已经失效。这个机制在很多服务注册中心中都是通用的,比如 Kubernetes 中的 Pod 探活机制(通过 Probe 探测 Pod 是否 Ready)。
- 消费者的服务发现
- 通过这种机制,服务提供者始终将健康的实例注册到服务注册中心。
- 当消费者需要访问某个服务时,它会向服务注册中心查询当前可用的实例。
- 例如,如果当前有 3 个实例都健康,服务注册中心会将这 3 个实例的 IP 地址返回给消费者,消费者即可访问这些实例。
- 续约机制
- 服务提供者需要不断续约,向注册中心表明自己仍然存活。ETCD 的续约和过期机制天然满足这种需求。
- 例如,注册一个服务时,服务中包含 3 个实例,每个实例有自己的 IP 和健康状况。服务提供者会定期更新实例的状态,并以固定时间间隔续约。如果某个实例在指定时间内未完成续约,注册中心会认为该实例已失效。
- 当消费者查询时,失效的实例不会被返回。这就是 ETCD 作为服务注册中心的一种用法,其机制与 Consul 等开源产品类似。
1.5.Etcd应用于消息发布和订阅

1.5.1.消息发布与订阅的基本概念
- 消息发布与订阅是消息队列(Message Queue)的核心功能之一。有些同学可能不太理解消息中心是什么,其实消息中心就是这样的:它同样有多种角色,比如生产者和消费者。
1.5.2.生产者与消费者的角色
-
生产者:负责产生消息。
-
消费者:负责订阅消息。
-
那么,生产者这边一旦有消息了,它就会通过消息中心告知消费者说:“哎,有数据了,你可以来处理了。”消费者就可以基于这个事件,去做一些配置管理,或者是做任何你想做的行为。这就是消息发布与订阅的功能。
1.5.3.ETCD 中的消息发布与订阅
那么接下来,我们讲一下 ETCD 中的消息发布与订阅功能。其实,ETCD 的三大核心功能我们已经讲过了,分别是键值存储、服务注册与发现,以及消息发布与订阅。
1.5.3.1.键值存储与 TTL
- 在 ETCD 中,键值存储可以与 TTL(Time to Live)绑定。TTL 就是它的保活周期时长。
- 那么,你一旦在写数据的时候,跟这个 TTL 绑定,它就会去确定说:“哎,多久没有续约了?”如果超过你设置的时间,比如你设置了 1 分钟,那如果 1 分钟它还没续约,你这个 key 就会消失。
1.5.3.2.消息发布与订阅的实现
- 消息发布与订阅其实就是通过 Watch 机制实现的。在 ETCD 中,你可以通过 Watch 监听某个键值对的变化。当键值对发生变化时,ETCD 会通知订阅的客户端。
- 比如,生产者可以将消息写入某个键值对,消费者通过 Watch 监听该键值对的变化。一旦有新消息写入,消费者会收到通知并处理数据。这其实就是我们上次所说的 Watch 机制。
1.5.3.3.是否可以用 ETCD 代替消息队列
- 那么,是否可以用 ETCD 代替消息队列呢?
- 其实,ETCD 的 Watch 机制可以用于实现简单的消息发布与订阅功能,但它的设计初衷并不是作为消息队列使用的。对于高吞吐量或复杂消息处理场景,建议使用专门的消息队列系统,比如 Kafka、RabbitMQ 等。
- ETCD 更适合用于配置管理、服务注册与发现等场景,消息发布与订阅功能可以作为它的附加能力使用。
1.6.Etcd的安装

1.6.1.etcd的安装
- 访问 etcdio 网站 下载 release 版本
- 解压二进制包,将其放在本地目录下
- 也可以参考官网的安装方法:https://etcd.io/docs/v3.5/install/
1.6.2.启动 etcd
- 启动命令示例:
etcd --listen-client-urls=http://localhost:2379 --advertise-client-urls=http://localhost:2379 - 参数说明:
- –listen-client-urls:客户端访问的 URL。
- –advertise-client-urls:集群成员间通信的 URL。
1.6.3.docker安装方式
- 我这里给出使用docker的安装方法
- 这里使用sh脚本部署,
vim docker-etcd.sh,然后把下面的内容写入文件 - 注意,如果你安装了kubernetes(已经携带了etcd),这种方式跑不起来,端口会有冲突,可以把下面内容的所有 2379–>2479,2380–>2480
- 我下面的就是更改为 2479、2480 的。如果执行后容器是关闭了,可以docker start一下,把容器跑起来
ETCD_VER=v3.5.18rm -rf /tmp/etcd-data.tmp && mkdir -p /tmp/etcd-data.tmp && \docker rmi gcr.io/etcd-development/etcd:${ETCD_VER} || true && \docker run \-p 2479:2479 \-p 2480:2480 \--mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data \--name etcd-gcr-${ETCD_VER} \gcr.io/etcd-development/etcd:${ETCD_VER} \/usr/local/bin/etcd \--name s1 \--data-dir /etcd-data \--listen-client-urls http://0.0.0.0:2479 \--advertise-client-urls http://0.0.0.0:2479 \--listen-peer-urls http://0.0.0.0:2480 \--initial-advertise-peer-urls http://0.0.0.0:2480 \--initial-cluster s1=http://0.0.0.0:2480 \--initial-cluster-token tkn \--initial-cluster-state new \--log-level info \--logger zap \--log-outputs stderrdocker exec etcd-gcr-${ETCD_VER} /usr/local/bin/etcd --version docker exec etcd-gcr-${ETCD_VER} /usr/local/bin/etcdctl version docker exec etcd-gcr-${ETCD_VER} /usr/local/bin/etcdutl version docker exec etcd-gcr-${ETCD_VER} /usr/local/bin/etcdctl endpoint health docker exec etcd-gcr-${ETCD_VER} /usr/local/bin/etcdctl put foo bar docker exec etcd-gcr-${ETCD_VER} /usr/local/bin/etcdctl get foo - 这里使用sh脚本部署,
- 不建议在生产环境中使用二进制部署,因为无法确保资源保证,使用容器管理可以更好地进行资源限制和质量隔离
1.7. etcd 的基本使用

- etcdctl是用于与etcd键值存储系统进行交互的命令行工具,会把命令转成rest http请求转发到etcd
- 在使用etcdctl时,指定endpoint是必需的,因为它告诉etcdctl要连接的etcd实例的位置
- Etcd是一个分布式键值存储系统,可以在多个节点上运行。每个节点都有一个唯一的endpoint,用于标识它在网络上的位置。通过指定endpoint,etcdctl知道要连接到哪个etcd实例以执行命令
- 因为我上面安装时将端口从2379改成了2479,所以下面我的endpoints都指定为
--endpoints=127.0.0.1:2479
- 如果不指定endpoint,默认会连接2379,但是我们很多人的机器上都有kubernetes,默认2379给kubernetes的etcd用了,我们连不上的,所以做实验的时候还是都加上
--endpoints吧
1.7.1.使用 etcdctl 命令行工具
- 设置环境变量:
# etcdctl 存在两个主要的 API 版本:API v2 和 API v3。API v3 是较新的版本,引入了一些新的功能和改进 export ETCDCTL_API=3 - 常用命令:
# 列出etcd集群的所有成员 etcdctl --endpoints=127.0.0.1:2479 member list 42ca3f1db112078d, started, vm-226-235-tencentos, https://xxx.xxx.xxx.xxx:2380, https://xxx.xxx.xxx.xxx:2379, false# 列出etcd集群的所有成员,以表格方式输出 etcdctl --endpoints=127.0.0.1:2479 member list --write-out=table [root@VM-226-235-tencentos ~/zgy/yamls]# docker exec -it bd0feb758a0b etcdctl --endpoints=127.0.0.1:2479 member list --write-out=table +------------------+---------+------+---------------------+---------------------+------------+ | ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | +------------------+---------+------+---------------------+---------------------+------------+ | 9b0b0f15059fc529 | started | s1 | http://0.0.0.0:2480 | http://0.0.0.0:2479 | false | +------------------+---------+------+---------------------+---------------------+------------+# 写入键值对: etcdctl --endpoints=127.0.0.1:2479 put key value# 读取键值对: etcdctl --endpoints=127.0.0.1:2479 get key# 监听键值变化: etcdctl --endpoints=127.0.0.1:2479 watch key# 还可以监听某个前缀 etcdctl --endpoints=127.0.0.1:2479 watch --prefix /api/v1/namespaces/default
1.7.2.键值对的规划
-
键值对可以按照目录结构进行规划
# 键值对可以按照目录结构进行规划 sh-5.0# etcdctl --endpoints=127.0.0.1:2479 put /a/b value1 sh-5.0# etcdctl --endpoints=127.0.0.1:2479 put /a/c value2# 通过前缀获取键值对: sh-5.0# etcdctl --endpoints=127.0.0.1:2479 get --prefix /a /a/b value1 /a/c value2 -
Kubernetes正是利用了 目录结构的键值对规划,保证了资源存储的唯一性和正常存取,比如我们执行一下
kubectl get pods -n default -v9,可以看下default下pods的key设计:- 设计为:
/api/v1/namespaces/default/pods - 则apiserver在查询default命名空间下pod的时候,只需要执行
get --prefix /api/v1/namespaces/default/pods即可找到所有前缀为这个的pod信息
[root@VM-226-235-tencentos ~/zgy/yamls]# kubectl get pods -n default -v9 I0210 14:12:23.230538 766 loader.go:375] Config loaded from file: /root/.kube/config ...... I0210 14:12:23.267173 766 round_trippers.go:424] curl -k -v -XGET -H "Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json" -H "User-Agent: kubectl/v1.19.16 (linux/amd64) kubernetes/e37e4ab" 'https://9.135.226.235:6443/api/v1/namespaces/default/pods?limit=500' I0210 14:12:23.269413 766 round_trippers.go:444] GET https://9.135.226.235:6443/api/v1/namespaces/default/pods?limit=500 200 OK in 2 milliseconds I0210 14:12:23.269443 766 round_trippers.go:450] Response Headers: - 设计为:
-
比如查看一个svc在etcd中的存储内容
- kubernetes资源在etcd中存储的都是protobuf的格式,所以直接查看是有些乱码的,不过不影响查看
# 先查看系统中所有的key sh-5.0# etcdctl --endpoints 127.0.0.1:2379 get --prefix / --keys-only# 再获取某个key的值,比如这里获取 kube-system/kube-dns service 的存储内容 sh-5.0# etcdctl --endpoints 127.0.0.1:2379 get /registry/services/specs/kube-system/kube-dns /registry/services/specs/kube-system/kube-dns k8sv1Service� � kube-dnskube-system"*$1aebd608-6aa9-499c-9d3a-9584df9e7cb42����Z k8s-apkube-dnsZ% kubernetes.io/cluster-servicetrueZ kubernetes.io/nameKubeDNSb prometheus.io/port9153b prometheus.io/scrapetruez�dnsUDP55(dns-tcpTCP55(metricsTCP�G�G( k8s-apkube-dns 10.96.0.10" ClusterIP:NoneBRZ`h "
1.7.3.高级操作
- 仅获取键名:
# 获取/前缀的所有key,一般使用--prefix的时候都要加--keys-only,否则对象多时同时把value输出就没法看了 etcdctl --endpoints=127.0.0.1:2479 get --prefix / --keys-only - debug模式:类似kubectl的-v9,–debug会输出debug信息,包含环境变量、超时时间、endpoint等
etcdctl --endpoints=127.0.0.1:2479 --debug get key # 获取/a的数据,并且启动debug模式,输出细节 sh-5.0# etcdctl --debug get /a ETCDCTL_CACERT=ca.crt ETCDCTL_CERT=server.crt ETCDCTL_COMMAND_TIMEOUT=5s ETCDCTL_DEBUG=true ETCDCTL_DIAL_TIMEOUT=2s ETCDCTL_DISCOVERY_SRV= ETCDCTL_DISCOVERY_SRV_NAME= ETCDCTL_ENDPOINTS=[127.0.0.1:2379] ETCDCTL_HEX=false ETCDCTL_INSECURE_DISCOVERY=true ETCDCTL_INSECURE_SKIP_TLS_VERIFY=false ETCDCTL_INSECURE_TRANSPORT=true ETCDCTL_KEEPALIVE_TIME=2s ETCDCTL_KEEPALIVE_TIMEOUT=6s ETCDCTL_KEY=server.key ETCDCTL_PASSWORD= ETCDCTL_USER= ETCDCTL_WRITE_OUT=simple WARNING: 2025/02/10 02:18:39 Adjusting keepalive ping interval to minimum period of 10s WARNING: 2025/02/10 02:18:39 Adjusting keepalive ping interval to minimum period of 10s INFO: 2025/02/10 02:18:39 parsed scheme: "endpoint" INFO: 2025/02/10 02:18:39 ccResolverWrapper: sending new addresses to cc: [{127.0.0.1:2379 <nil> 0 <nil>}]
1.7.4.etcd的数据存储
- ETCD 的数据默认存储在运行目录下的
etcd目录中 - 可以通过指定
--data-dir来更改存储位置,如果不指定,默认会在运行目录下生成一个etcd目录

1.7.5.etcd的安全认证
- ETCD 使用双向 TLS 认证,不仅客户端验证服务器端,服务器端也验证客户端,客户端需要携带
cert、key和cacert文件 访问服务器端 - 这些文件在启动命令中已经指定,只需知道如何获取这些参数,当然有需要时也可以在命令中手动指定
sh-5.0# etcdctl --endpoints 127.0.0.1:2479 --cacert=ca.crt --cert=server.crt --key=server.key put /a/b value1
1.7.6.如何查看k-v的细节
- 使用-wjson,输出内容包括:集群信息、member信息、版本信息、kv信息等,其中kv是base64加密的
etcdctl --endpoints 127.0.0.1:2479 get a -wjson[root@VM-226-235-tencentos ~/zgy/yamls]# docker exec -it bd0feb758a0b etcdctl --endpoints=127.0.0.1:2479 get /a -wjson {"header":{"cluster_id":703969991527618354,"member_id":11172039883585733929,"revision":2,"raft_term":3},"kvs":[{"key":"L2E=","create_revision":2,"mod_revision":2,"version":1,"value":"MQ=="}],"count":1}
1.8. etcd 的核心机制

1.8.1.过期时间 TTL(Time To Live)
- 为键值对设置过期时间,默认单位是秒:
etcdctl --endpoints 127.0.0.1:2379 put key value --lease=1234
1.8.2.乐观锁 CAS(Compare And Swap)
- 条件写入:
- 在写入键值对之前,先检查某个条件是否满足的操作。如果条件满足,执行成功请求;如果条件不满足,执行失败请求
- etcdctl txn 命令执行条件写入操作,并通过 --interactive 参数启用交互模式
# 进入写入操作 etcdctl txn --interactive# 命令格式 compare: value("key") = "old_value"success requests: put key new_valuefailure requests: put key fail_value# 使用示例:每输入一行,要进行回车进入下一条命令 sh-5.0# etcdctl txn --interactive compares: value("/a/b") = "value1"success requests (get, put, del): put /a/b value-newfailure requests (get, put, del): put /a/b value1SUCCESSOK # 查看CAS更新结果 sh-5.0# etcdctl get /a/b /a/b value-new - etcd使用CAS机制目的就是避免锁的引入,不过etcd还是有锁的,它支持类似于数据库的事务,如果你有多条写操作,可以加起一个transaction,最后一起commit
2.Raft 协议
- 学习网站:
- http://thesecretlivesofdata.com/raft
- https://raft.github.io/
2.1 CAP 原则
- 一致性(Consistency)
- 可用性(Availability)
- 分区容错性(Partition Tolerance)
一致性和可用性一定要有个取舍,强一致代表效率一定不会很高,弱一致时一般都会有数据同步等问题
2.2 Raft 确保一致性的核心:quorum多数派机制
- quorum 多数派机制:即当同意当前操作的节点数据 超过半数时,才允许生效,否则不生效
- Raft确保分布式一致性,其实主要做两件事:
- Leader Election:leader选举
- Log Replication:日志复制
- 这两个过程都可以在这个网站看到动态图
- http://thesecretlivesofdata.com/raft
2.3.Raft 协议的 Leader Election(leader选举)
2.3.1.节点的3种身份
- 当使用 Raft 一致性算法时,节点可以扮演三种不同的角色:Leader(领导者)、Follower(跟随者)和Candidate(候选人)。这些角色在 Raft 协议中起着不同的作用和责任。
- Leader(领导者)
- Leader 是 Raft 群集中的一种特殊节点角色
- Leader 负责处理客户端的请求,并将日志条目复制到其他节点(跟随者)。
- Leader 通过发送心跳消息来维持其领导地位,并防止其他节点成为 Leader。
- Follower(跟随者)
- Follower 是 Raft 群集中的普通节点角色
- Follower 跟随 Leader 的指导,并接受 Leader 发送的日志条目
- Follower 可以投票给候选人,以帮助选举新的 Leader
- Candidate(候选人)
- Candidate 是 Raft 群集中的临时节点角色。
- 当一个节点超过一定时间(每个节点都维护了一个选举超时时间Election Timeout)没有收到leader的心跳时,将会成为候选人,开始一次新的选举过程,试图成为新的 Leader。
- 候选人会向其他节点发送选举请求,并等待其他节点的投票。
- 如果候选人获得了大多数节点的投票,它将成为新的 Leader。
- Leader(领导者)
- Raft 协议通过定期的选举过程来维护 Leader 的稳定性。当 Leader 失败或无法与其他节点通信时,会触发新的选举过程,以选择一个新的 Leader。在选举过程中,节点将转换为候选人角色,并尝试获得其他节点的投票
- Leader-Follower-Candidate 的角色切换机制确保了 Raft 群集的一致性和可用性
- Leader 负责处理客户端请求,Follower 跟随 Leader 的指导,而 Candidate 则参与选举过程以确保 Leader 的连续性。
2.3.2.选举过程
-
初始状态:
- 假设我们有一个 Raft 集群,包含 5 个节点:A、B、C、D 和 E。
- 初始状态下,所有节点都是 Follower。
-
选举超时(Election Timeout):
- 每个 Follower 节点都有一个选举超时计时器。
- 当选举超时计时器到期时,节点将转变为候选人(Candidate)角色,并开始新的选举过程。
-
候选人状态:
- 节点 A 的选举超时计时器到期,它成为候选人,给自己投一票,同时向其他节点发送选举请求(Request Vote)。
- 其他节点(B、C、D 和 E)收到选举请求后,会检查自己的状态并决定是否投票给候选人。
-
投票过程:
- 节点 B、C 和 D 向候选人 A 投票,因为它们还没有投票给其他候选人。
- 节点 E 由于已经投票给了另一个候选人,所以不会再投票给候选人 A。
-
选举结果:
- 如果候选人 A 获得了大多数节点的选票(超过半数),它将成为新的 Leader。
- 假设节点 B、C 和 D 都投票给了候选人 A,那么 A 将成为新的 Leader。
-
Leader 宣布:
- 新的 Leader A 会使用心跳机制 宣布自己的地位,并开始处理客户端的请求。
- 其他节点(B、C、D 和 E)将转变为 Follower 角色,并跟随新的 Leader。
- 选举中的一些注意点:
- 只要一个节点没有leader,且有Candidate向他请求投票,它就一定会投 赞成
- 当一个节点投了票之后,就会重置自己的超时时间,保证不会在投票后自行又发起选举
2.3.3.选举问题一:选票冲突(Vote Conflict)
- 当多个候选人同时发起选举时,可能会导致选票冲突。
- 解决方法:
- 集群初始化时,默认每个节点都是Follower,此时可以为每个节点初始化 随机的选举超时时间,比如150-300毫秒之间随机值,保证节点不会同时发起选举
- 集群运行中,如果出现多个Candidate同时选举,Raft 使用递增的任期号(Term)来解决选票冲突。节点会比较候选人的任期号,并投票给任期号更大的候选人。如果发生脑裂也会这样保证老leader会退位让贤

2.3.4.选举问题二:无法达到多数票(Majority Votes)
- 在选举过程中,候选人需要获得多数节点的选票才能成为新的 Leader。
- 解决方法:
- 如果没有候选人获得多数票,选举将失败。此时,节点可以增加选举超时时间,并重新发起选举过程,直到有候选人获得多数票。
- 另外,raft集群一般都要求 奇数个节点,保证投票时不会发生平票
2.3.5.选举问题三:网络分区(Network Partition)导致脑裂
- 当 Raft 群集中的节点由于网络故障或分区而无法通信时,可能会导致选举过程中断或发生脑裂。
- Raft 使用心跳机制来维持 Leader 的地位,发生网络分区时,每个分区都将产生一个leader,称为脑裂。脑裂是一个常见的分布式系统中的挑战
- 解决方法:
- raft集群一般都要求 奇数个节点,比如5个节点ABCDE,其中A为leader,发生分区后产生两个分区:AB、CDE
- 此时CDE会选举出新的leader,比如C,则此时有两个leader A、C 都在接收客户端请求
- 当有请求进入A时,A写入本地log,然后复制到B,B返回ack,但是只有一票成功,无法达到大多数,所以到达A的请求无法commit,也就无法生效
- 当有请求进入C时,C写入本地log,然后复制到DE,DE都返回ack,2票成功,更改commited生效
- 当网络分区恢复后,A认为自己是leader,向C发心跳,C看到自己的Term更大就会无视A。而C向A发心跳,A发现自己的Term小,就会主动成为C的follower,AB回滚自己未commit的更改,重新比较C的log,同步最新数据,从而解决网络分区带来的脑裂

- 但如果一个网络分区,无法达到多数票,就无法选出leader
4. 选举过程过长(Election Process Takes Too Long)
- 在某些情况下,选举过程可能需要较长时间才能完成,导致系统的可用性下降。
- 解决方法:可以调整选举超时时间和心跳间隔,以减少选举过程的时间。另外,使用预投票(Pre-vote)机制可以更快地检测到网络分区并进行选举。
5. 恢复过程中的数据一致性(Data Consistency during Recovery)
- 当 Leader 失败或重新选举时,可能会导致数据一致性问题。
- 解决方法:Raft 使用日志复制机制来确保数据一致性。当新的 Leader 当选后,它会将自己的日志条目复制到其他节点,以恢复数据一致性。
2.4.Raft 协议的 Log Replication(日志复制)

Raft对数据的变更操作,都是通过 日志复制 + 多数派机制 来完成的
- 每个节点都包括:一致性模块、日志模块、状态机
2.4.1.日志复制原理
-
Leader 推送日志:
- Leader 接收客户端的请求,并将请求转化为日志条目(Log Entry)。
- Leader 将日志条目追加到自己的日志中,并并行地将日志条目发送给其他节点(Followers)。
- 发给其他节点时,是通过下一次的心跳携带数据过去的
-
日志复制过程:
- Followers 接收到 Leader 发送的日志条目后,将其追加到自己的日志中。
- Followers 向 Leader 发送确认消息(Append Entries Response)表示已成功复制日志条目。
-
多数派确认:
- Leader 在收到多数派节点的确认消息后,将该日志条目标记为已提交(Committed)。
- Leader 会通知 Followers 将已提交的日志条目应用到状态机,以实现数据一致性。
-
容错性:
- 如果 Leader 失败,新的 Leader 将被选举出来,并继续进行日志复制过程。
- 如果 Followers 失败或无法通信,Leader 会重试发送日志条目,直到多数派节点确认复制成功。
2.4.2.日志复制过程描述
-
假设我们有一个 Raft 集群,包含 Leader(L)和两个 Followers(F1、F2)。
-
Leader 接收客户端请求,生成日志条目:
Log Entry 1。 -
Leader 将
Log Entry 1追加到自己的日志中,并并行发送给 Followers。 -
F1 和 F2 接收到
Log Entry 1,将其追加到各自的日志中,并发送确认消息给 Leader。 -
Leader 收到来自 F1 和 F2 的确认消息,确认
Log Entry 1已成功复制。 -
Leader 将
Log Entry 1标记为已提交,并通知 Followers 将其应用到状态机。 -
F1 和 F2 将
Log Entry 1应用到各自的状态机,实现数据一致性。
-
2.5.Raft协议中的两个超时时间
-
--heartbeat-interval:心跳间隔,leader发送心跳的周期,xxxx ms 向follower发送一次心跳 -
--election-timeout:选举超时,follower能容忍多久没收到心跳的时间,超过后follower就会造反转变成候选人身份,发起新的选举 -
可以在etcd的命令行参数中配置
2.6.节点新角色:Learner 解决增加集群节点问题

-
Raft 协议传统角色构成:Leader、Follower、Candidate,此时想要向 Raft 集群中新增一个节点,存在问题:
- 新节点数据为空,Leader 需要大量数据复制
- 数据同步可能会占用大量网络带宽,导致leader发送心跳失败,进而触发选举重新投票
- 而新节点数据太落后,此时触发选举 可能会产生无效投票,进而影响集群的稳定性
-
为解决上述问题,引入了Learner角色,特性:仅接收数据,不参与投票
-
Learner 引入时间
- Learner的概念最早在 ETCD 3.4 版本 中实现(2019 年)
- 后来逐渐被其他 Raft 实现(如 TiKV、Consul 等)采纳,在 Raft 4.2.1引入
- 因此Learner 是 Raft 协议的扩展功能,并非原始 Raft 论文的一部分,是 Raft 协议在实际工程应用中的一种优化和改进
-
引入Learner后,增加新节点的优势:
- Learner不参与投票,所以票数quorum没有变,原健康节点可快速达成共识
- 避免新节点因数据差异导致无效投票
- 保持集群稳定性
- Learner节点支持异步数据同步,同步完成后将自动转为Follower,参与后续投票
2.7.强一致性和弱一致性
- 弱一致性:
- 主节点 commit 后,会立刻返回客户端确认,不会等待从节点也commit完成。
- 缺点:数据安全性较低,主节点故障时,未 commit 的数据可能丢
- 优点:效率高
- 适用于对性能要求较高,且可以容忍少量数据丢失的场景,如日志系统
- 强一致性:
- 必须所有 follower 二次确认 commit 后,主节点才返回客户端确认。
- 缺点:效率较低
- 优点:数据安全性高
- 适用于对数据一致性要求极高的场景,如金融系统
- 配置参数:
- ETCD 启动时可配置强一致性或弱一致性,
--quorum-read参数,默认false弱一致性 - 默认一般为弱一致性,因为大多数场景下够用了,如果你的场景里对数据安全性要求非常高,可以配置为强一致性。
- ETCD 启动时可配置强一致性或弱一致性,
2.8.Raft的安全性机制详解
Raft 协议通过选举安全性和Leader 完整性等机制,确保集群中所有节点执行相同的操作序列,避免数据不一致。以下是详细解释:
2.8.1. 选举安全性(Election Safety)
-
定义:在同一个任期内(Term),只能选举出一个 Leader。
-
作用:避免多个 Leader 同时存在,导致集群分裂和数据不一致。
-
实现方式:
- 每个节点在同一任期内只能投一次票(给一个 Candidate)。
- 只有获得多数票的 Candidate 才能成为 Leader。
- 如果选举超时,会进入新的任期,重新发起选举。
-
举例:
- 假设集群有 5 个节点,Term 为 1。
- 如果有两个 Candidate 同时发起选举,最多只有一个 Candidate 能获得至少 3 票(多数票),成为 Leader。
2.8.2.Leader 完整性(Leader Completeness)
- 定义:如果一个日志条目在某个任期被提交(Committed),那么后续任期的 Leader 必须包含这个日志条目。
- 作用:确保已经被提交的日志不会被覆盖或丢失。
- 实现方式:
- Raft 通过选举限制来保证 Leader 完整性:
- 只有日志足够新的节点(即包含更多已提交日志的节点)才能成为 Leader。
- 在选举时,Candidate 会携带自己的日志信息(Term 和 Index)。
- 其他节点会比较 Candidate 的日志是否比自己更新:
- 如果 Candidate 的 Term 更大,或者 Term 相同但 Index 更大,则投票给它。
- 否则,拒绝投票。
- Raft 通过选举限制来保证 Leader 完整性:
- 举例:
- 假设 Term 1 的 Leader 提交了一条日志(Log A)。
- 如果 Term 1 的 Leader 崩溃,Term 2 的选举中,只有那些包含 Log A 的节点才有可能成为 Leader。
- 这样,Log A 就不会丢失,新 Leader 会继续复制 Log A 给其他节点。
2.8.3. 为什么需要这些机制?
- 问题场景:
- 假设某个 Follower 在 Leader 提交日志时变得不可用。
- 稍后,这个 Follower 重新加入集群,并被选举为 Leader。
- 如果这个新 Leader 的日志不完整,它可能会用新的日志覆盖已经提交的日志,导致数据不一致。
- 解决方案:
- Raft 通过选举安全性和Leader 完整性,确保只有日志足够新的节点才能成为 Leader。
- 这样,新 Leader 一定包含所有已提交的日志,避免了数据丢失或不一致。
2.8.4. 总结
- 选举安全性:确保同一任期内只有一个 Leader,避免脑裂。
- Leader 完整性:确保新 Leader 包含所有已提交的日志,避免数据丢失。
- 核心思想:通过日志比较和多数投票机制,Raft 保证了集群的一致性和安全性。
2.9.Etcd基于Raft协议实现一致性
2.9.1.Etcd 的 leader选举

2.9.2.Etcd 的 日志复制(默认弱一致性)

2.9.3.安全性

2.9.4.Etcd 的 失效处理

2.9.5.Etcd 的 wal log日志

- Raft任何请求的数据都会先写到本地 wal log日志里,然后同步给follower
- Etcd wal log 的type早期版本主要有两种,现已扩展到五种
- EntryNormal:表示普通的数据变更操作。这是最常见的类型,用于记录键值对的插入、更新和删除等操作。
- EntryConfChange:表示配置变更操作。这种类型的日志记录用于记录集群配置的变更,例如添加或删除节点、更改节点角色等。
- EntryAddNode:表示添加节点操作。这种类型的日志记录用于记录添加新节点到集群的操作。
- EntryRemoveNode:表示移除节点操作。这种类型的日志记录用于记录从集群中移除节点的操作。
- EntryBarrier:表示屏障操作。这种类型的日志记录用于实现线性一致性,确保在屏障操作之前的所有操作都已提交。
- 使用
etcd-prologue工具查看 wal log 信息,该工具可以将二进制格式的 wal log 转换为文本格式,便于分析和调试。 - wal是日志记录技术,在etcd中日志的格式为vlog
2.9.6.Etcd对存储的具体实现


- Etcd对存储的具体实现,可以分为4部分:
- KV store:
- 包含 KV index,基于 betree 数据结构,在内存中对所有的key做了一个索引,用于加速查询
- 用于数据索引,提高读写性能。
- boltDB:
- 数据最终存储到backend,目前backend其实用的是另外一个数据库 boltDB。
- boltDB 是谷歌开发的一个轻量级的键值存储数据库,适用于小型数据集。在设计里也可以使用其他db,目前用的是boltDB
- watchable store:
- 提供监听机制,监听数据变化。
- 通过监听机制,客户端可以实时获取数据变更通知。
- lease:
- 控制数据的 TTL(生命周期),到期自动删除。
- 通过 TTL 机制,可以实现数据的自动清理和过期管理。
- KV store:
3.etcd常见问题
3.1.etcdctl 执行报错
- 进入kubernetes携带安装的etcd中,执行etcdctl命令,可能会报错:
[root@VM-226-235-tencentos ~]# kubectl exec -it etcd-vm-226-235-tencentos -n kube-system -- /bin/sh sh-5.0# sh-5.0# etcdctl get --prefix / --keys-only {"level":"warn","ts":"2025-02-10T02:05:21.349Z","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-cd94a73a-3967-450f-83f0-ec2744418038/127.0.0.1:2379","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = latest balancer error: all SubConns are in TransientFailure, latest connection error: connection closed"} Error: context deadline exceeded - github issues:https://github.com/etcd-io/etcd/issues/12234
- 这种是证书没有读取成功,可以手动指定证书来执行etcdctl命令
# 先ps -ef查看你的etcd启动命令 [root@VM-226-235-tencentos ~]# ps -ef | grep etcd root 24115 24089 2 2024 ? 7-15:58:52 etcd --advertise-client-urls=https://xxx.xxx.xxx.xxx:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/etcd --initial-advertise-peer-urls=https://xxx.xxx.xxx.xxx:2380 --initial-cluster=vm-226-235-tencentos=https://xxx.xxx.xxx.xxx:2380 --key-file=/etc/kubernetes/pki/etcd/server.key --listen-client-urls=https://127.0.0.1:2379,https://xxx.xxx.xxx.xxx:2379 --listen-metrics-urls=http://127.0.0.1:2381 --listen-peer-urls=https://xxx.xxx.xxx.xxx:2380 --name=vm-226-235-tencentos --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt --peer-client-cert-auth=true --peer-key-file=/etc/kubernetes/pki/etcd/peer.key --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt --snapshot-count=10000 --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt# 从启动参数中获取到证书位置,写在etcdctl的命令中 [root@VM-226-235-tencentos ~]# etcdctl --endpoints 127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key put /a/b value1 OK
3.2.etcd和consul如何选择
- consul主要应用场景:服务发现和负载均衡。因此如果你有服务发现的需求,还是优先推荐使用consul,它是专业的
- etcd:也可以做服务发现,且抱着kubernetes的大腿,如果再云原生有一些小的场景需求,使用etcd就足够了,不需要再引入其他组件了,还有学习成本
- 比如spring cloud现在支持native的service discovery,其实就是查询ETCD的
3.3.ETCD 和 Zookeeper 的区别
- zookeeper:使用 Paxis 协议,复杂度较高,性能和一致性维护上存在一些问题。
- etcd:使用Raft协议,是对 Paxos 协议的简化和增强
3.4.ETCD 的一些其他知识点
3.4.1.数据持久化
- 数据存储位置:
- ETCD 的数据存储在
/var/lib/etcd目录下。 - 该目录通过 volume 挂载到 ETCD Pod 中。
- ETCD 的数据存储在
- 数据持久化:
- 即使 Pod 重启或主机重启,只要
/var/lib/etcd目录未被删除,数据不会丢失。 - 建议将数据存储在主机目录或独立的 SSD 盘上,而不是容器内的 Overlay FS
- 即使 Pod 重启或主机重启,只要
3.4.2.ETCD 部署模式
- 独立部署 vs. Pod 部署:
- 独立部署:ETCD 集群与 Kubernetes 控制平面分离,适用于大规模集群。
- Pod 部署:ETCD 与 Kubernetes 控制平面部署在同一节点,适用于小型集群。
- 读请求优化:
- ETCD 的读请求不一定要经过 leader,每个 member 都可以处理读请求。
- 如果 ETCD 和 API Server 部署在同一节点,读请求的效率会更高。
3.4.3.ETCD 扩容
- 扩容操作:
- 通过
etcdctl的add-member命令添加新 member。 - 扩容时需确保集群处于健康状态。
- 通过
3.4.4.ETCD 高可用性
- 3 个 ETCD member 挂掉 1 个:
- 集群仍可正常工作,因为剩余 2 个 member 可以满足多数投票条件。
- 如果挂掉 2 个 member,集群将无法正常工作。
3.4.5.ETCD 的亲和性
- 亲和性:
- ETCD 和 API Server 之间具有亲和性,因为 API Server 是 ETCD 的主要调用方。
- 在调度系统中,亲和性可以提高服务的稳定性和性能。
- 反亲和性:
- 反亲和性是指将服务的多个实例部署到不同节点,以提高容错性。
3.4.6.ETCD 数据恢复
- 数据恢复:
- 只要有集群的备份数据,即使 3 个 master 节点同时宕机,也可以通过备份恢复集群。
- 使用
etcdctl snapshot命令进行数据备份和恢复。
相关文章:
Kubernetes控制平面组件:etcd(一)
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)kubectl 和 …...
2100年芜湖人的一天:张明的生活剪影
2100年芜湖人的一天:张明的生活剪影 破晓 6:30 "沙沙"的微风声轻轻掠过耳畔,杨柳的沙沙声混合着若有若无的鸟鸣,张明的意识从深邃的梦境中缓缓浮现。这并非真实的自然声响,而是他的脑机接口精心编织的唤醒交响曲。量子…...
外贸网站源码 助力企业抢占蛇年市场先机!
在竞争激烈的外贸市场中,蛇年无疑是企业寻求突破与增长的关键一年。外贸网站源码为企业提供了快速搭建专业外贸网站的解决方案,助力企业在新的一年抢占市场先机。 快速上线 时间就是商机,尤其是在蛇年这样充满变数和机遇的年份。外贸网站源码…...
verilog练习:i2c slave 模块设计
文章目录 前言1.结构2.代码2.1 iic_slave.v2.2 sync.v2.3 wr_fsm.v2.3.1 状态机状态解释 2.4 ram.v 3. 波形展示4. 建议5. 资料总结 前言 首先就不啰嗦iic协议了,网上有不少资料都是叙述此协议的。 下面将是我本次设计的一些局部设计汇总,如果对读者有…...
项目6:基于大数据校园一卡通数据分析和可视化
1、项目简介 本项目是基于大数据的清华校园卡数据分析系统,通过Hadoop,spark等技术处理校园卡交易、卡号和商户信息数据。系统实现消费类别、男女消费差异、学院消费排行和年级对比等分析,并通过Web后端和可视化前端展示结果。项目运行便捷&…...
Linux常见系统日志类型
目录 系统日志(/var/log/syslog 或 /var/log/messages) 认证日志(/var/log/auth.log 或 /var/log/secure) Web服务器日志(/var/log/apache2/ 或 /var/log/nginx/) MySQL日志(/var/log/mysql/…...
npm 常用命令大全
npm 常用命令大全 下载包 npm install清理缓存 npm cache clean --force查看当前配置 npm config get registry设置淘宝镜像 npm config set registry https://registry.npmmirror.com查看 npm 版本 npm -vnpm 设置超时时间 npm config set fetch-timeout 600更新依赖 …...
java.io.InvalidClassException
类实现序列问题 如果实现了序列,最好生成序列号,因为类结构发生改变时,会报错 java.io.InvalidClassException 所以实现序列,需要生成序列号 private static final long serialVersionUID 1L;...
Datawhale 组队学习 Ollama教程 task1
一、Ollama 简介 比喻:Ollama 就像是一个“魔法箱子”,里面装满了各种大型语言模型(LLM)。你不需要懂复杂的魔法咒语(配置),只需要轻轻一按(一条命令),就能让…...
大模型基本原理(二)——ChatGPT的工作原理
如何得到一个ChatGPT? 1、无监督预训练:通过大量的文本数据集进行无监督训练,得到一个基座模型(只会续写文本) 2、监督微调:通过一些人类撰写的高质量对话数据对基座模型进行监督微调,得到一个…...
成为高能量体质:从身体神庙到精神圣殿的修炼之路
清晨五点,当城市还在沉睡,瑜伽垫上的汗水已经折射出第一缕阳光。这不是苦行僧的自虐,而是高能量体质者的日常仪式。在这个能量稀缺的时代,如何把自己修炼成一座小型核电站?答案就藏在身体的每个细胞里。 一、能量管理…...
51c自动驾驶~合集50
我自己的原文哦~ https://blog.51cto.com/whaosoft/13280022 #VLA 主流方案全解析 旨在让智能体在物理世界中通过感知、决策和行动来实现目标,而视觉 - 语言 - 动作(VLA)模型作为其中的关键技术,近年来备受关注。VLA 模型能够…...
测试自动化落地方向
一、视觉回归自动化测试(低成本高回报) 痛点: UI 频繁迭代导致视觉问题难覆盖 方案: 引入Applitools或SikuliX做视觉比对(无需维护元素定位) 关键路径截图比对,自动检测 UI 错位/样式问题 亮点…...
论文阅读:MGMAE : Motion Guided Masking for Video Masked Autoencoding
MGMAE:Motion Guided Masking for Video Masked Autoencoding Abstract 掩蔽自编码(Masked Autoencoding)在自监督视频表示学习中展现了出色的表现。时间冗余导致了VideoMAE中高掩蔽比率和定制的掩蔽策略。本文旨在通过引入运动引导掩蔽策略࿰…...
【嵌入式Linux应用开发基础】文件I/O基础编程
目录 一、文件I/O简介 二、文件描述符 2.1. 唯一性 2.2. 抽象性 2.3. 有限性 三、文件操作函数 四、标准文件I/O函数 五、文件执行权限 5.1. 权限类型 5.2. 权限分配对象 5.3. 权限表示方法 5.4. 权限设置命令 5.5. 权限设置的重要性 5.6. 实例说明 六、设备文件…...
Java 反射机制的安全隐患与防范措施:在框架开发与代码审计中的应用
前言 在 Java 编程的广阔领域中,反射机制堪称一把神奇且强大的钥匙,它为开发者打开了通往动态编程的全新大门。借助反射,Java 程序获得了在运行时自我审视和操作的独特能力,极大地增强了代码的灵活性与适应性。 简单来讲&#x…...
【JS】实现一个hexo插件并发布
hexo插件生成 在你的 hexo blog 目录,找到 node_modules. 新建一个文件夹。然后执行 npm init npm 会引导你生成 package.json 这是你的包的描述文件。需要注意的是,所有的 hexo 插件必须以 hexo - 开头,否则 hexo 不会加载。 如果hexo g中没…...
【Java 面试 八股文】MySQL 篇
MySQL 篇 1. MySQL中,如何定位慢查询?2. 那这个SQL语句执行很慢,如何分析呢?3. 了解过索引吗?(什么是索引)4. 索引的底层数据结构了解过吗?5. B树和B树的区别是什么呢?6.…...
ES6 Proxy 用法总结以及 Object.defineProperty用法区别
Proxy 是 ES6 引入的一种强大的拦截机制,用于定义对象的基本操作(如读取、赋值、删除等)的自定义行为。相较于 Object.defineProperty,Proxy 提供了更灵活、全面的拦截能力。 1. Proxy 语法 const proxy new Proxy(target, hand…...
vue中使用高德地图自定义掩膜背景结合threejs
技术架构 vue3高德地图2.0threejs 代码步骤 这里我们就用合肥市为主要的地区,将其他地区扣除,首先使用高德的webapi的DistrictSearch功能,使用该功能之前记得检查一下初始化的时候是否添加到plugins中,然后搜索合肥市的行政数据…...
tomcat如何配置保存7天滚动日志
在 Tomcat 中,logging.properties 文件是用于配置 Java 日志框架(java.util.logging)的。若要实现 catalina.out 日志保存 7 天,且每天的日志文件名带有时间戳,可以按以下步骤进行配置: 1. 备份原配置 在修…...
ffmpeg -pix_fmts
1. ffmpeg -pix_fmts -loglevel quiet 显示ffmpeg支持的像素格式 2. 输出 选取部分输出结果 Pixel formats: I.... Supported Input format for conversion .O... Supported Output format for conversion ..H.. Hardware accelerated format ...P. Paletted format ..…...
Python----PyQt开发(PyQt高级:图像显示,定时器,进度条)
一、图像显示 1.1、增加图标 1.直接创建setWindowIcon(QIcon(灯泡.jpg)) import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton from PyQt5.QtGui import QIconclass MainWindow(QMainWindow):def __init__(self):super(MainWindow, self).__init_…...
Tomcat添加到Windows系统服务中,服务名称带空格
要将Tomcat添加到Windows系统服务中,可以通过Tomcat安装目录中“\bin\service.bat”来完成,如果目录中没有service.bat,则需要使用其它方法。 打到CMD命令行窗口,通过cd命令跳转到Tomcat安装目录的“\bin\”目录,然后执…...
2025.2.10 每日学习记录3:技术报告只差相关工作+补实验
0.近期主任务线 1.完成小论文准备 目标是3月份完成实验点1的全部实验和论文。 2.准备教资笔试 打算留个十多天左右,一次性备考笔试的三个科目 1.实习申请技术准备:微调、Agent、RAG 据央视财经,数据显示,截至2024年12月…...
普通用户授权docker使用权限
1、检查docker用户组 sudo cat /etc/group |grep docker 若显示:docker:x:999: # 表示存在否则创建docker用户组: sudo groupadd docker2、查看 /var/run/docker.sock 的属性 ll /var/run/docker.sock 显示: srw-rw---- 1 root root 0 1月…...
微生物学术语和定义 | 微生物学词汇表
微生物学作为一门研究微生物及其与环境、宿主和其他生物相互作用的科学,涵盖了广泛的学科领域和专业术语。然而,由于微生物学的快速发展和跨学科融合,许多术语的定义和使用在不同领域中可能存在差异甚至混淆。 随着新冠疫情的全球蔓延&am…...
Java集合List详解(带脑图)
允许重复元素,有序。常见的实现类有 ArrayList、LinkedList、Vector。 ArrayList ArrayList 是在 Java 编程中常用的集合类之一,它提供了便捷的数组操作,并在动态性、灵活性和性能方面取得了平衡。如果需要频繁在中间插入和删除元素…...
后盾人JS -- 异步编程,宏任务与微任务
异步加载图片体验JS任务操作 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title&g…...
Go语言的内存分配原理
Go语言的内存分配原理 Go语言的内存管理分为两个主要区域:栈(Stack) 和 堆(Heap)。理解这两个区域的工作原理,可以帮助你写出更高效的代码,并避免一些常见的性能问题。 1. 栈(Stac…...
