• 分布式锁的使用场景和常见实现

分布式锁的使用场景和常见实现

2025-08-16 12:33:29 栏目:宝塔面板 78 阅读

今天我想和你聊聊分布式锁的使用场景和常见实现。熟悉多线程编程的同学对锁的概念一定不会陌生。计算机操作系统中,为了解决多线程并发场景下的资源占用问题,引入了锁的概念。通过锁,我们可以保证一个资源在同一时刻只能被一个线程访问。主流的编程语言都会在自己的标准库中提供完备的锁实现,这些实现已经能够很好地解决我们在单进程应用中遇到的并发问题。

但是,随着业务高速发展,业务系统会快速迭代拆分成多个子服务,同时,为了应对逐渐增加的流量,同一个子服务也会包含多个分立的实例,部署在不同的服务器上。整个系统逐步向微服务演进,此时,在单进程中已经被解决的并发问题又重新浮现出来,而分布式锁就是解决这些问题的有效方案。

分布式锁

分布式锁可以协同那些部署在不同服务器上的进程对同一个资源的使用,实现同一个时刻只有一个线程可以访问该资源。常见的分布式锁的实现通常会选择一个存储系统作为全局状态存储,依赖这个存储系统提供的对存储对象原子化的排他性操作,来实现分布式锁的全局排他性。

同时,通常我们也会将锁的状态、过期时间、持有者等信息保存在这个全局状态存储中,以实现更为丰富的锁特性。常见的可以用来作为分布式锁的全局状态存储的系统包括:

  • 数据库
  • Redis
  • ZooKeeper

关于 ZooKeeper 和 Reids 的分布锁实现,我们将会放到下次讲解。今天这节课,我们先来学习一下基于数据库的分布锁实现。

基于数据库的分布式锁实现

数据库本身是一个强一致性的系统,有很多特性可以用来实现分布式锁,如唯一索引约束、for update 子句等。

基于 for update 子句的悲观锁

这种锁是for update子句可以利用 MySQL InnoDB 提供的排他锁。在执行事务操作时,对于包含 for update 子句的 SQL 语句,MySQL 会对查询结果集中的每一行记录都设置一个排他锁,其他线程在更新或删除这些记录时都会被阻塞。

基于这个机制,我们也可以很容易地实现分布式锁的需求,在获取锁的时候开启事务,成功获取到锁即可以执行业务逻辑,在业务逻辑结束后,完成事务即可以释放锁。

本实现方式简单易用,但同样不支持可重入,同时,本实现是阻塞的,锁占用期间会一直占用数据库连接,在高并发下容易出现耗尽连接池的情况,影响系统的稳定性。因此,在实际场景中很少使用这个方案。

基于唯一索引约束的分布式锁实现

一个表如果存在唯一键索引,只有第一次插入操作会成功,其他插入操作都会报错。

首先创建如下_lock表,其中resource_key即为唯一键,表示一个需要抢占的资源。应用在获取锁的时候,实际会往这张表里插入一条 resource_key 为该资源 key 的记录,插入成功即为获取了锁,删除这条记录即为解锁。如果插入失败,则需要重复执行插入操作,直到插入成功或者超过指定的超时时间,抛出异常。

同时,为了避免操作失败等原因导致锁记录没有正确被删除,通常需要额外增加一个定时清理任务来清理过期的锁记录,以避免出现死锁,这一过程的实现代码如下:

CREATE TABLE`_lock` (
    `id`BIGINTNOTNULL AUTO_INCREMENT,
    `resource_key`varchar(64) NOTNULLCOMMENT'锁定的资源 Key,表示一个需要占用的资源'
    PRIMARY KEY (`id`),
    UNIQUEKEY`uk_resource_key` (`resource_key`)  USING BTREE
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='基于唯一索引约束的锁';

基于唯一索引约束的分布式锁实现原理非常简单,使用起来也十分简便,但是它的缺点也十分明显。首先,这个锁实现不支持可重入,为了实现可重入操作,还需要进一步扩展上述表的字段,将锁持有者的主机、线程等信息记录到里面,在获取锁的时候,先判断锁记录中的相关信息是否和当前主机、线程信息一致,如果一致就直接认为已经获取了锁。其次,在并发下,这种插入操作会造成大量死锁,影响数据库的稳定。

基于 CAS 的乐观锁

CAS(Compare And Swap) 是现代 CPU 支持的一个指令级的操作,即在更新数据前先比较该数据当前值是否等于期望值,如果相等,则将其设置为更新的值,否则就不设置。该指令通常用来实现乐观锁,Java 的 Java.util.concurrent.atomic 包提供了大量支持 CAS 操作的原子变量。

在数据库中,我们同样也可以借用这种思想实现 CAS 操作实现分布式乐观锁。同样地,首先创建如下_lock表,这个表相比上一节多了一个 version 字段,这个字段就是在 CAS 操作中用于比较交换的字段。

CREATE TABLE`_lock` (
`id`int(4) NOTNULL AUTO_INCREMENT COMMENT'主键',
`resource_key`varchar(64) NOTNULLDEFAULT''COMMENT'锁定的资源 Key,表示一个需要占用的资源',
`version`int(4) NOTNULLDEFAULT''COMMENT'版本号'
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_resource_key` (`resource_key `) USING BTREE
) ENGINE=InnoDBDEFAULTCHARSET=utf8 COMMENT='乐观锁实现';

乐观锁的执行逻辑如下所示:

do {
    val old_version = (select version from _lock where resource_key = '{resource_key_1}');
    // 通过 CAS 更新 version, 一次事务仅又一个进程可以成功
    bool success = (update _lock set version = '{new_version}' where resource_key = '{resource_key_1}' and version = '{old_version}'")
    if (success) {
        // 获取锁成功,直接返回
        return;
    }
    // 获取失败,重试
} while(true);

乐观锁认为数据的更新在大多数情况下是不会产生冲突的,所以只在更新操作提交时才进行冲突检测。乐观锁通常比较适合多读的场景,可以增加系统的吞吐量。

总结

今天我们主要介绍了基于数据库三种特性的分布式锁实现,分别是基于 for update 子句的悲观锁、基于唯一索引约束的分布式锁和基于 CAS 的乐观锁。基于数据库的分布式锁方案的主要优点是简单可靠,不需要引入额外的依赖(大部分业务系统通常都会使用数据库),同时,缺点也比较明显,就是并发性能较差。因此,基于数据库的分布式锁方案比较适合并发较小的业务场景。

本文地址:https://www.yitenyun.com/340.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 TIME_WAIT 运维 负载均衡 服务器 管理口 JumpServer SSL 堡垒机 跳板机 HTTPS HexHub Docker 服务器性能 JumpServer安装 堡垒机安装 Linux安装JumpServer SQL 查询 生命周期 Deepseek 宝塔面板 Linux宝塔 esxi esxi6 root密码不对 无法登录 web无法登录 锁机制 序列 核心机制 行业 趋势 Windows Windows server net3.5 .NET 安装出错 HTTPS加密 开源 PostgreSQL 存储引擎 Windows宝塔 Mysql重置密码 宝塔面板打不开 宝塔面板无法访问 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 机器学习 Redis Undo Log 机制 Oracle 处理机制 Spring 动态查询 无法访问宝塔面板 响应模型 InnoDB 数据库锁 优化 万能公式 连接控制 监控 异步化 Serverless 无服务器 语言 group by 索引 ES 协同 openHalo 工具 Postgres OTel Iceberg 技术 高可用 缓存方案 缓存架构 缓存穿透 分页查询 SVM Embedding scp Linux的scp怎么用 scp上传 scp下载 scp命令 存储 GreatSQL 连接数 国产数据库 数据 主库 Linux 安全 R edis 线程 日志文件 MIXED 3 SQLite-Web SQLite 数据库管理工具 R2DBC 加密 场景 Netstat Linux 服务器 端口 ​Redis 推荐模型 Recursive OB 单机版 自定义序列化 防火墙 黑客 启动故障 RocketMQ 长轮询 配置 云原生 向量数据库 大模型 共享锁 SQLark AI 助手 Canal PG DBA Hash 字段 信息化 智能运维 不宕机 Rsync Ftp 磁盘架构 同城 双活 Python 向量库 Milvus 架构 电商 系统 分库 分表 PostGIS 修改DNS Centos7如何修改DNS 业务 IT运维 数据分类 语句 传统数据库 向量化 redo log 重做日志 流量 聚簇 非聚簇 线上 库存 预扣 filelock 频繁 Codis MongoDB 数据结构 MVCC MCP 开放协议 缓存 MySQL 9.3 失效 • 索引 • 数据库 Web 接口 开发 数据类型 sftp 服务器 参数 mini-redis INCR指令 人工智能 推荐系统 千万级 大表 Redisson 锁芯 ZODB 工具链 Doris SeaTunnel EasyExcel MySQL8 主从复制 代理 INSERT 分布式架构 分布式锁​ 聚簇索引 非聚簇索引 高效统计 今天这篇文章就跟大家 prometheus Alert 数据备份 崖山 新版本 事务 Java 窗口 函数 虚拟服务器 虚拟机 内存 COMPACT SSH 容器 发件箱模式 数据脱敏 加密算法 MGR 分布式集群 分页 引擎 性能 Web 网络架构 网络配置 网络故障 QPS 高并发 RDB AOF 核心架构 订阅机制 Go 数据库迁移 自动重启 容器化 数据页 数据集成工具 读写 速度 服务器中毒 B+Tree ID 字段 播客 分布式 集中式 Redis 8.0 Redka 模型 OAuth2 Token StarRocks 数据仓库 微软 SQL Server AI功能 SpringAI 池化技术 连接池 Valkey Valkey8.0 DBMS 管理系统 JOIN Entity 排行榜 排序 关系数据库 Caffeine CP 数据字典 兼容性 事务隔离 LRU 原子性 Weaviate 业务场景 网络 dbt 数据转换工具 分页方案 排版 Testcloud 云端自动化 部署 1 事务同步 Pottery InfluxDB ReadView 优化器 AIOPS UUID ID 日志 意向锁 记录锁 sqlmock 单点故障 UUIDv7 主键 仪表盘 悲观锁 乐观锁 Pump 单线程 Order 编程 对象 Crash 代码 RAG HelixDB Ansible 分布式锁 Zookeeper 产业链 字典 订单 IT 双引擎 恢复数据 LLM List 类型 拦截器 动态代理 线程安全 国产 用户 慢SQL优化 count(*) count(主键) 行数 表空间 快照读 当前读 视图 Next-Key RR 互联网 解锁 调优 GitHub Git 神经系统 矢量存储 数据库类型 AI代理 CAS 查询规划 算法 技巧 多线程 并发控制 恢复机制 闪回