fencing token机制

在分布式系统中,分布式锁其实并不是完全安全的,特别是支持GC的中间件或网络延迟,都会使共享资源不在安全,而fencing token就是避免这一现象的并发控制机制。

文章目录

  • 前言
  • 🧠一、分布式锁的局限性
  • [💡二、fencing token](#💡二、fencing token)
    • [2.1 fencing token的诞生](#2.1 fencing token的诞生)
    • [2.2 fencing token的工作机制](#2.2 fencing token的工作机制)
    • [2.3 fencing token的设计理念](#2.3 fencing token的设计理念)
  • [🤔三、fencing token的实现](#🤔三、fencing token的实现)
  • 总结

前言

在分布式系统中,多个节点同时访问共享资源时,协调成了至关重要的问题。分布式锁作为解决这一问题的关键工具,却存在一个致命的缺陷:它可能在某些情况下失效,导致多个客户端同时操作共享资源,从而造成数据损坏。

fencing token(防护令牌)机制由分布式系统专家Martin Kleppmann提出,用以增强分布式锁的安全性。


🧠一、分布式锁的局限性

在讨论fencing token之前,必须先理解分布式锁存在的问题。分布式锁通常基于Redis、ZooKeeper等系统实现,通过互斥机制确保同一时间只有一个客户端可以访问共享资源。

然而在实际环境中,网络延迟、进程暂停(如GC停顿) 等情况难以避免。考虑以下场景:

  1. Client 1获取锁后开始操作共享资源。
  2. 由于GC停顿或网络延迟,Client 1的锁过期。
  3. Client 2获取锁并开始操作共享资源。
  4. Client 1从停顿中恢复,继续操作共享资源。
  5. 此时两个客户端同时操作共享资源,数据可能被损坏。

这种情况即使在使用了看似可靠的分布式锁方案(如Redis Red Lock)时也可能发生。

💡二、fencing token

2.1 fencing token的诞生

Martin Kleppmann在《How to do distributed locking》一文中详细分析了分布式锁的局限性,并提出了fencing token方案。

fencing token的核心思想很简单:在获取锁的同时,获取一个单调递增的token (令牌),每次访问共享资源时都必须出示这个token。

共享资源端会记录处理过的最大的token值,如果接收到一个更小的token,请求就会被拒绝。这样就建立了一种序列机制,确保了操作的顺序性

2.2 fencing token的工作机制

fencing token机制涉及三个核心组件:

  • 锁服务 (Lock Service):负责分配锁和fencing token的服务。它需要保证token的单调递增性,即使在多个节点上也不能重复。
  • 客户端 (Client):访问共享资源的应用程序。在访问资源前必须从锁服务获取锁和fencing token。
  • 资源服务 (Storage) 提供实际服务的资源存储层。需要验证token的有效性。

下面给出fencing token的工作流程:

  • 步骤 1:申请资源时获取令牌
    客户端需要操作共享资源前,先向协调者(如分布式锁服务、数据库、ZooKeeper 等)申请资源,并同时获取一个 Fencing Token。
  • 步骤 2:执行操作时携带令牌
    客户端执行资源操作(如写入数据库、修改缓存)时,必须将令牌作为操作的一部分传入(通常通过 SQL 条件、API 参数等方式)。
  • 步骤 3:资源端验证令牌有效性
    共享资源在执行操作前,会先验证客户端携带的令牌是否为当前针对该资源的 "最新令牌"

2.3 fencing token的设计理念

fencing token的设计建立在几个核心原则之上:
令牌的单调递增性(如基于数据库自增 ID、Redis 的 INCR 命令等):

fencing token必须是单调递增的,这是机制能够工作的数学基础。这种设计保证了时间的偏序关系,即使物理时间可能不同步,操作序列也能保持正确。
服务端验证:

资源服务端必须维护已处理的最大token值 ,并拒绝所有携带小于等于该值的token的请求。这一验证过程是保证安全性的关键。
最小权限原则:

fencing token只解决授权问题,不涉及身份认证。这种单一职责设计使得系统更加简洁。

尽管fencing token概念上很简单,但在实际系统中实现却面临几个挑战:

  1. token的持久化与一致性
    资源服务需要持久化存储当前最大的token值,以防止系统重启后数据丢失。在分布式资源服务中,这一状态需要在多个副本间同步,保证一致性
  2. 性能考量
    token验证引入了额外的开销。在设计时需要权衡安全性与性能的关系,可能需要在关键操作上才使用fencing机制。
  3. 与现有系统的集成
    许多现有系统并不原生支持fencing token机制。集成时可能需要修改客户端或服务端代码,这可能会很复杂。

到这里相信很多人都有疑问,这不就是乐观锁吗?

是的,两者的设计思想极其相似,两者都用于并发控制,但设计思路和适用场景不同,对比如下:

维度 Fencing Token 乐观锁(如版本号)
核心逻辑 基于 "递增令牌的唯一性",验证是否为最新令牌 基于 "版本号比对",验证资源是否被修改过
适用场景 分布式系统(跨节点、跨服务的资源竞争) 单机数据库(同一节点内的行级并发控制)
令牌生成依赖 需独立协调者(如锁服务、Redis) 依赖数据库自身的版本字段(如 version)
解决的核心问题 防止 "僵尸客户端" 的无效操作 防止 "并发写入覆盖"(如多人同时编辑文档)
性能 验证逻辑简单(整数比较),性能极高 需比对版本字段,性能略逊于令牌验证

🤔三、fencing token的实现

下面是一个表格汇总了 fencing token 在一些常见系统/库中的实现概况:

系统/库名称 实现形式 关键特性 适用场景
Redisson FencedLock 基于 fencing token 理论,提供单调递增的 token。 Redis 为基础的分布式环境
Apache ZooKeeper 序列号 (zxid) 或自定义令牌 利用其强一致性和顺序节点特性生成单调递增的标识,可自行实现 token 验证。 需要强一致性和高可靠性的分布式系统
etcd 修订号 (Revision) 或租约ID 提供全局单调递增的修订号,可用作 fencing token。 Kubernetes、需要分布式锁的场景

⚙️ 实践中的考量

尽管 fencing token 显著提升了分布式锁的安全性,但在实际应用中仍需注意以下几点:

  • fencing token 主要解决的是"持有过期锁的客户端误操作"的问题,但它并不能保证分布式锁在所有场景下的百分百安全,也无法解决锁服务本身不可用(Liveness 属性)的问题。分布式系统的复杂性决定了没有完美的方案。
  • Token 状态的持久化与同步:在集群化部署的资源服务中,如何保证所有节点看到的"当前最大 token"是一致的,或者如何将 token 状态在不同节点间高效同步,是一个需要仔细设计的问题,否则可能引入新的一致性漏洞。
  • 性能权衡:生成全局单调递增的 token 以及每次操作都进行验证,必然会引入额外的开销。需要在安全性和性能之间做出权衡。对于性能极其敏感但可以容忍极小概率锁失效的场景,可能会选择不同的策略。

fencing token 是构建可靠分布式系统的一个重要工具。如果你正在设计一个对数据一致性要求非常高的分布式系统(例如涉及金融交易、关键配置更新),那么强烈建议选择或实现一个提供了 fencing token 机制的分布式锁,并确保你的资源服务能够正确地进行 token 验证。


总结

Fencing Token 是分布式系统中解决 "并发冲突" 和 "僵尸客户端" 问题的轻量级且高效的机制。其核心优势在于:

  1. 通过 "递增令牌" 确保操作的唯一性和有效性;
  2. 验证逻辑简单,无状态,易于扩展;
  3. 可与分布式锁、数据库等组件无缝结合,提升系统的一致性和可靠性。

在设计分布式资源竞争场景时,Fencing Token 通常是比单纯 "超时" 或 "乐观锁" 更优的选择。

相关推荐
叶落阁主2 小时前
Neovim 插件 i18n.nvim 介绍
java·vue.js·vim
渣哥2 小时前
让集合线程安全的几种靠谱方法
java
dylan_QAQ2 小时前
Java转Go全过程06-工程管理
java·后端·go
a587693 小时前
消息队列(MQ)初级入门:详解RabbitMQ与Kafka
java·分布式·microsoft·面试·kafka·rabbitmq
千里码aicood3 小时前
【springboot+vue】党员党建活动管理平台(源码+文档+调试+基础修改+答疑)
java·数据库·spring boot
Chan163 小时前
【智能协同云图库】基于统一接口架构构建多维度分析功能、结合 ECharts 可视化与权限校验实现用户 / 管理员图库统计、通过 SQL 优化与流式处理提升数据
java·spring boot·后端·sql·spring·intellij-idea·echarts
先做个垃圾出来………3 小时前
差分数组(Difference Array)
java·数据结构·算法
BillKu4 小时前
Java核心概念详解:JVM、JRE、JDK、Java SE、Java EE (Jakarta EE)
java·jvm·jdk·java ee·jre·java se·jakarta ee
刘婉晴4 小时前
【Java】NIO 简单介绍
java·nio