K8s 中的分布式锁

K8s 中的分布式锁

K8s中分部署锁一般用cm或者ep来完成,那数据库具体是如何写入到etcd中,boltdb写入在其中起到了哪些作用?

这是一个很核心的问题,涉及到 Kubernetes 中的资源对象(如 ConfigMap、Endpoints)如何经由 API Server 最终写入到底层存储(etcd),以及 etcd 中的 BoltDB(准确说是 bbolt)在这个链条中的作用。下面我们从整体架构逐层拆解,最后详细解释数据库写入和 BoltDB 的角色。


🌐 一、K8s 中的分布式锁为何使用 ConfigMap 或 Endpoints?

在 K8s 中,实现分布式锁的方式之一是利用某种共享资源作为锁的“媒介”:

  • ConfigMap:常用于写入锁信息(如持有者 Pod 名称、时间戳),适合较小、频繁更新的数据。
  • Endpoints:有时用于 Leader Election,因为它能快速地被 kube-apiserver 修改且具备 TTL 特性。

这类“锁”其实是将资源对象视为一个 CAS(Compare-And-Swap)机制的载体,通过 kube-apiserver 对资源对象的 乐观并发控制(基于 resourceVersion)实现。


🧭 二、资源对象是如何写入到 etcd 的?

Kubernetes 中的每个状态变更,都是通过如下路径流转:

用户 / 控制器
   ↓ REST(如 kubectl patch)或 Go client
Kube-APIServer
   ↓(验证、准入、默认值填充、审计等)
Storage Layer(GenericStorage)
   ↓(调用 etcd3 client)
etcd
   ↓
bbolt 数据库(写入磁盘)

具体步骤如下:

  1. 用户或控制器发起请求(如 PATCH ConfigMap)

    • 通常由 Leader Election Controller、Client-go 等发起请求。
  2. kube-apiserver 收到请求

    • 对请求做身份校验、准入控制(admission)、序列化等处理;
    • 最终将该对象变更提交到 storage layer
  3. storage layer 使用 etcd client(v3)与 etcd 通信

    • 存储实现基于 k8s.io/apiserver/pkg/storage/etcd3
    • 使用 etcd 的 gRPC 接口调用 Put()Txn() 等方法。
  4. etcd 接收数据并写入 BoltDB

    • etcd 使用嵌入式 KV 存储库:bbolt(从 BoltDB 演进而来);
    • etcd 把这次写入以 事务的形式(带版本号)写入到 BoltDB;
    • 所有变更以 WAL(预写式日志)+ Snapshots 实现 强一致性、可恢复性。

📦 三、BoltDB(bbolt)在其中起到什么作用?

etcd 并不是一个远程数据库,而是一个 嵌入式 Key-Value 存储系统。它本身就是一个单独进程,以下是 bbolt 在其中的作用:

组件层说明
etcd server实现了 gRPC 服务、Raft 协议、API 等
bboltetcd 的实际存储引擎(KV 存储,支持事务、B+树)
文件系统bbolt 将数据持久化为 .db 文件,并支持 WAL 日志

bbolt 的关键特性:

  • 内嵌式数据库(单文件存储)
  • 支持事务操作(ACID)
  • 高性能读写(树结构 + mmap)
  • 支持 snapshot、压缩等

在 etcd 中,所有通过 API 写入的数据(例如 ConfigMap、Endpoints 对象的 JSON 序列化体)最终都会:

存储为以 key 为路径(如 /registry/configmaps/kube-system/leader-lock)、value 为 Protobuf 编码对象的 KV 对 → 写入 bbolt 的树结构中。


🔄 四、数据一致性与分布式锁保障机制

  • etcd 作为 CP 系统(CAP 理论),确保写入强一致

  • 分布式锁使用 resourceVersion 来做乐观并发控制:

    • Controller 每次尝试写入 ConfigMap 时,都会检查当前的 resourceVersion
    • 若有竞争,API Server 会返回 409 Conflict,从而触发重试;
  • 最终只有一个 controller 能成为锁的持有者。


🧪 示例(Leader Election 写入流程)

kubectl get cm kube-controller-manager -o yaml

可以看到 ConfigMap 中包含如下结构:

metadata:
  name: kube-controller-manager
  resourceVersion: "8647133"
data:
  leader-election: |
    {
      "holderIdentity": "kube-controller-manager-0_d92ad3cc-c83b-4e23-bbf6",
      "leaseDurationSeconds": 15,
      "acquireTime": "2025-07-29T14:30:00Z",
      ...
    }

这段 JSON 序列化体就是最终写入 etcd(通过 kube-apiserver → etcd client → bbolt)的内容。


✅ 总结

层级说明
Kubernetes 对象(如 ConfigMap)被用于 Leader 选举、分布式锁
kube-apiserver提供统一的 API 接入层,执行合法性校验与序列化
etcd client(apiserver 内部)gRPC 调用 etcd3,进行写入与 CAS 操作
etcd server分布式强一致数据库,管理 Raft 日志与快照
bbolt(BoltDB)etcd 的存储引擎,最终将数据写入磁盘中的树状结构

如果你需要我进一步提供:

  • etcd 中的数据实际路径示例(如 key 的前缀)
  • bbolt 数据结构的内部图示(Bucket、Page、node)
  • 具体的 leader election 源码分析路径(如 client-go 中的 lock 机制)

我可以继续深入。是否需要?

resourceVersion

非常好,你提到了一个 Kubernetes 非常核心但容易被忽略的概念:resourceVersion

我们来逐步理解它,从定义作用机制应用场景(比如分布式锁)实际示例几个维度帮助你完全搞懂。


🧩 一、什么是 resourceVersion

resourceVersion 是 Kubernetes 中每个对象的版本号,用于标识该对象自创建以来经历的变更序号。

它由 etcd 自动生成,每次资源发生任何变更(包括 label、annotation、data、status 等字段),其 resourceVersion 都会更新。

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-config
  namespace: default
  resourceVersion: "123456"

上面这个 "123456" 就是该对象在 etcd 中的“当前版本”。

可以简单理解为:resourceVersion 是 etcd 中所有写操作的“全局递增序号”。


🚦 二、resourceVersion 的核心作用

用途说明
乐观并发控制进行并发写入时判断是否有其他人改动过(防止覆盖)
列表与监控优化(List/Watch)watch 操作从某个版本之后开始,提高性能
高并发控制器协调机制在 leader 选举、资源竞争等场景下作为 CAS 条件使用

🔐 三、与分布式锁的关系(ConfigMap Lock)

Kubernetes 中分布式锁(比如 client-go/tools/leaderelection)通常这样使用 resourceVersion

🔁 模式:基于版本号的 CAS(Compare And Swap)写入流程

  1. 控制器 A 读取 ConfigMap,获取当前 resourceVersion = "123456"

  2. 它准备修改该 ConfigMap 并持有锁

  3. 写入请求带上旧的 resourceVersion = "123456"

  4. kube-apiserver 在写入前校验:当前 resourceVersion 是否还等于 “123456”

    • ✅ 如果等于:写入成功,A 获得锁
    • ❌ 如果已变化:写入失败,返回 409 Conflict,A 必须重试(意味着有其他人也在竞争)

✅ 特点:

  • 不用加锁(mutex),靠版本号判断是否有“其他人偷偷动过”
  • 等价于 “只有当前版本等于 X,我才允许你修改” 的乐观并发写法

🧪 四、一个实际例子(模拟抢锁)

假设两个 Pod 并发抢同一个 ConfigMap 作为锁资源:

# 初始版本
resourceVersion: "123"

# Pod-A 获取锁
→ Get ConfigMap(resourceVersion = "123")
→ PATCH data.holder = Pod-A(条件:resourceVersion = "123")
→ 成功(新版本 = "124")

# Pod-B 尝试获取锁
→ Get ConfigMap(resourceVersion = "123")
→ PATCH data.holder = Pod-B(条件:resourceVersion = "123")
→ ❌ 被拒绝(因为当前版本是 124,不再是 123)
→ 返回 409 Conflict,需重新 Get → Retry

你可以看到,这种机制本质上就是 CAS(Compare-And-Swap)操作,避免了分布式环境下的并发写入冲突。


🛠 五、怎么看实际的 resourceVersion

可以通过 kubectl getkubectl get -o yaml 看到:

kubectl get cm leader-lock -o yaml

输出中会看到:

metadata:
  name: leader-lock
  resourceVersion: "987654"

每次更新该资源,这个数字就会单调递增。


🔍 六、在代码中怎么使用它?

在 Client-go 中的 PatchUpdate 操作中,封装的对象 metadata 中需要携带:

configMap.ObjectMeta.ResourceVersion = "987654" // 原始值
client.CoreV1().ConfigMaps("default").Update(ctx, configMap, metav1.UpdateOptions{})

API Server 会进行校验,只允许修改的是当前版本,防止脏写。


🧠 总结一句话:

resourceVersion 是 Kubernetes 中用于实现强一致性、并发控制和事件监听的核心机制,等价于数据库的版本号字段,常用于 Leader 选举、控制器抢占、Watch 优化等场景。


如果你希望我演示一个用 resourceVersion 实现的简化版“分布式锁”代码或者进一步画个流程图说明它和 etcd 的关系,我可以继续补充。需要吗?

CAS(Compare-And-Swap)

非常好,你现在已经走到了理解 Kubernetes 并发控制核心机制 的关键一环——CAS(Compare-And-Swap)。这个机制不仅存在于 Kubernetes、etcd、数据库,还广泛用于 操作系统、线程并发、分布式锁等底层场景。我们一步一步讲清楚:


🧠 一、什么是 CAS(Compare-And-Swap)?

📌 定义

CAS(比较并交换) 是一种原子性操作,用于在并发环境下安全地更新共享数据。

它的基本思想是:

只有当变量的当前值等于预期值时,才将其更新为新值,否则什么都不做。”

⛓ 操作过程(三个参数):

CAS(address, expected, new_value)
  • address:要更新的内存地址(比如某个变量)
  • expected:期望值(旧值)
  • new_value:想要设置的新值

系统在底层会这样做:

  1. 比较 address 中的当前值是否等于 expected;
  2. 如果相等 → 把它原子性地更新为 new_value,返回成功;
  3. 如果不相等 → 什么也不做,返回失败。

🧪 二、简单示例(线程安全计数器)

假设我们有一个全局计数器 counter = 5,多个线程并发执行 counter++

非线程安全写法(存在竞争):

// counter++
temp := counter
temp = temp + 1
counter = temp

但使用 CAS 就可以做到无锁并发安全:

for {
    old := counter
    new := old + 1
    if atomic.CompareAndSwapInt64(&counter, old, new) {
        break // 成功更新
    }
    // 否则继续重试
}

多个线程同时执行,如果有竞争,CAS 会失败,代码就会重试,直到成功。


⚙️ 三、Kubernetes 中的 CAS 使用例子

Kubernetes 的 API Server 其实并没有直接用 CPU 的 CAS 指令,而是用“版本号 + 比较”来模拟 CAS 操作。比如:

configMap.ResourceVersion = "123456"
client.CoreV1().ConfigMaps("kube-system").Update(context.TODO(), configMap, ...)

此时 server 会检查:

if current.ResourceVersion == "123456" {
    apply update and set new resourceVersion
} else {
    return 409 Conflict
}

✅ 这就是逻辑层面的 CAS

条件:资源版本未被其他人修改 → 成功

否则:失败 → 说明有其他竞争者 → 重试


🔐 四、CAS 与分布式锁的关系?

在无中心锁管理器的环境中(比如多个 Pod 抢一个 leader),最经典的锁实现方式就是 基于 CAS

🌐 典型方案:

  • 利用一个共享资源(如 ConfigMap、etcd Key)作为锁
  • 每个竞争者都用自己的标识去更新该资源
  • 更新时附带 resourceVersion(就是旧值)
  • 如果成功 → 表示我成功获得锁
  • 如果失败 → 说明锁被别人持有 → 重试

这也是为什么 Kubernetes 中的 leader election 使用 resourceVersion 的原因。


🧮 五、CAS 与数据库的对比

系统CAS 表现形式举例
CPU 层指令级别原子操作cmpxchg
Java/Go 并发库原子类AtomicInteger.compareAndSet()
数据库乐观并发控制UPDATE ... WHERE version = X
KubernetesresourceVersion + 409 ConflictUpdate() 接口
etcdCompare + Put(Txn)atomic transaction

🧭 六、CAS 的优缺点

优点缺点
高性能,无需锁(无阻塞)可能自旋重试太多,影响性能
原子性强,线程安全不适合复杂对象结构修改
非阻塞并发编程利器ABA 问题(需要配合版本号)

📌 七、一句话总结:

CAS(Compare-And-Swap) 是一种在并发编程和分布式系统中常用的乐观并发控制机制,只有在当前状态没被其他人改动时才允许写入,否则失败并重试,广泛用于锁、状态机、计数器、Kubernetes resourceVersion 等关键机制中。


是否需要我提供一份 etcd 中使用 CAS 的 transaction 示例(go etcd client v3),或者用图示说明“更新失败/成功的过程”?可以帮助你彻底理解这个机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值