一、分布式锁

1.1、分布式锁是什么?

是一种在分布式系统中协调多个进程/服务对共享资源进行互斥访问的机制;确保在任意时刻,只有一个客户端可以访问资源。
在这里插入图片描述

1.2、为什么需要分布式锁?

  • 解决多个服务/进程对同共享资源竞争,比如双11,618购物节,多名用户对同一个商品下单,导致库存超卖问题。
  • 防止重复操作,比如用户连续点击导致重复下单

1.3、分布式锁需要具备的条件和刚需有哪些?

  • 独占性:任何时刻只能有且仅有一个线程持有
  • 高可用:
    • 若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
    • 高并发请求下,依旧性能好使
  • 防死锁:杜绝死锁,必须有超时控制机制或撤销操作,有个兜底终止跳出方案
  • 不乱抢:不能unlock别人的锁,只能自己加锁自己释放,自己的锁自己解
  • 重入性:同一个节点的同一个线程如果获取锁之后,它也可以再次获取这个锁

1.4、建立分布式锁

# 第一种
setnx key value
EXPIRE KEY 60# 第二种
set key value [EX seconds][PX milliseconds][NX|XX]

使用setnx确保只有一个客户端能成功设置键,通过EXPIRE设置过期时间,防止死锁,但setnx + expire不安全,两条命令非原子性。可以通过 Lua脚本 来实现分布式锁。

-- 加锁
if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 thenredis.call('PEXPIRE', KEYS[1], ARGV[2])return 1
end-- 释放锁
if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

二、RedLock

官网地址:https://redis.io/docs/latest/develop/clients/patterns/distributed-locks/

2.1、RedLock是什么?

ReadLock是一种算法,是一个更为规范的算法来使用Redis实现分布式锁,实现了比普通单实例方法更安全的DLM(分布式锁管理器)。

2.2、为什么使用RedLock?

在这里插入图片描述

在这里插入图片描述

导致问题:

    1. 主机宕机了,从机上位,变成新的master,用户依旧可以建锁成功,出现 一锁被多建多用 ,违法安全规定
    1. 多个线程获取到同一把锁,可能会导致各种预期之外的情况发生,比如脏数据

2.3、RedLock算法设计理念

在这里插入图片描述

大致方案如下:

假设有5个Redis主节点,不使用复制或任何其他隐式协调系统,为了获得锁客户端执行以下操作:

步骤说明
1获取当前时间,以毫秒为单位
2依次尝试从5个实例,使用相同的key和随机值(如UUID)获取值,当向Redis请求获取值时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间。这样可以防止客户端在试图与一个宕机的Redis节点对话时,长时间处于阻塞状态。如果一个实例不可用,客户端应该尽快尝试去另外一个Redis实例请求数据
3客户端通过当前时间减去步骤1记录的时间 = 获取锁使用的时间。当且仅当从大多数(N/2+1)的Redis节点都渠道锁,并且获取锁使用的时间 < 锁失效时间时,锁才算获取成功
4如果取到锁,其真正有效时间 = 初始有效时间 - 获取锁使用时间
5如果由于某些原因未能获得锁(无法在至少N/2 + 1个Redis实例获取锁、或获取锁的时间超过了有效时间),客户端应该在所有的Redis实例上进行解锁

注意:

客户端只有在满足以下两个条件时,才认为加锁成功

    1. 客户端从超过半数(>= N/2 + 1)的Redis实例上成功获取了锁
    1. 客户端获取锁的总耗时没有超过锁的有效时间

2.4、容错公式

N:最终部署机器数,X:容错机器数

N = 2X + 1

为什么是奇数?

从成本上来考虑,用最少的机器,达到最多的产出效果

三、实际操作

3.1、手写分布式锁

  • 注意点:
    • lock关键:
      • 加锁:在redis中,设置键,并设置过期时间
      • 自旋
      • 续期
    • unlock关键:不能unlock别人的锁,只能自己加锁自己释放,自己的锁自己解

3.2、利用redlock算法实现分布式锁

使用开源库redis-plus-plus: https://github.com/sewenew/redis-plus-plus

redlock代码路径:redis-plus-plus/src/sw/redis++/patterns/

类名作用
RedLockUtils工具类,提供ttl(计算时间差)和锁ID
RedMutexTx基于Redis事务的分布式锁实现
RedMutex作者推荐用户直接操作的分布式锁的类,根据配置选择使用脚本(RedLockMutex)或事务(RedMutexTx
RedLockMutex针对单个Redis实例的分布式锁,使用脚本实现
RedLockMutexVessel管理多个Redis实例的分布式锁,使用脚本实现
RedMutexOptionsRedLock的配置选项,包括锁的TTL、重试延迟、是否使用脚本
RedMutexImpl抽象基类
RedMutexImplTpl模板类,继承自RedMutexImpl,根据模板参数(RedLockMutexRedMutexTx)实现具体操作
RedLock是一个模板类,用于封装分布式锁的核心操作,根据模板参数(RedLockMutexRedMutexTx)实现具体操作
LockWatcher用于监控锁的生命周期
/**     RedLock类       **/
/* 作者不是很推荐使用,更为推荐使用RedMutex,RedLock只是RedMutex的简单封装.
*/
template <typename RedisInstance>
class RedLock {
public:RedLock(RedisInstance &mut, std::defer_lock_t) : _mut(mut), _lock_val(RedLockUtils::lock_id()) {}~RedLock() {if (owns_lock()) {unlock();}}// Try to acquire the lock for *ttl* milliseconds.// Returns how much time still left for the lock, i.e. lock validity time.bool try_lock(const std::chrono::milliseconds &ttl) {auto time_left = _mut.try_lock(_lock_val, ttl);if (time_left <= std::chrono::milliseconds(0)) {return false;}_release_tp = std::chrono::steady_clock::now() + time_left;return true;}......void unlock() {try {_mut.unlock(_lock_val);_release_tp = std::chrono::time_point<std::chrono::steady_clock>{};} catch (const Error &) {_release_tp = std::chrono::time_point<std::chrono::steady_clock>{};throw;}}bool owns_lock() const {if (ttl() <= std::chrono::milliseconds(0)) {return false;}return true;}std::chrono::milliseconds ttl() const {auto t = std::chrono::steady_clock::now();return std::chrono::duration_cast<std::chrono::milliseconds>(_release_tp - t);}private:RedisInstance &_mut;std::string _lock_val;// The time point that we must release the lock.std::chrono::time_point<std::chrono::steady_clock> _release_tp{};
};/**         使用方法        **/
// 使用Lua脚本版本
{RedLockMutex mtx({redis1, redis2, redis3}, "resource");RedLock<RedLockMutex> lock(mtx, std::defer_lock);auto validity_time = lock.try_lock(std::chrono::seconds(30));validity_time = lock.extend_lock(std::chrono::seconds(10));lock.unlock();
} // Redis事务版本
{RedMutex mtx({redis1, redis2, redis3}, "resource");RedLock<RedMutex> lock(mtx, std::defer_lock);auto validity_time = lock.try_lock(std::chrono::seconds(30));validity_time = lock.extend_lock(std::chrono::seconds(30));lock.unlock();
}
/**     RedMutex类      **/
class RedMutex {
public:RedMutex(std::shared_ptr<Redis> master,const std::string &resource,std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,const RedMutexOptions &opts = {},const std::shared_ptr<LockWatcher> &watcher = nullptr) :RedMutex(std::initializer_list<std::shared_ptr<Redis>>{master},resource, std::move(auto_extend_err_callback), opts, watcher) {}RedMutex(std::initializer_list<std::shared_ptr<Redis>> masters,const std::string &resource,std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,const RedMutexOptions &opts = {},const std::shared_ptr<LockWatcher> &watcher = nullptr) :RedMutex(masters.begin(), masters.end(),resource, std::move(auto_extend_err_callback), opts, watcher) {}template <typename Input>RedMutex(Input first, Input last,const std::string &resource,std::function<void (std::exception_ptr)> auto_extend_err_callback = nullptr,const RedMutexOptions &opts = {},const std::shared_ptr<LockWatcher> &watcher = nullptr) {if (opts.scripting) {      //根据配置选项,选择脚本还是事务实现_mtx = std::make_shared<RedMutexImplTpl<RedLockMutex>>(first, last, resource,std::move(auto_extend_err_callback), opts, watcher);} else {_mtx = std::make_shared<RedMutexImplTpl<RedMutexTx>>(first, last, resource,std::move(auto_extend_err_callback), opts, watcher);}}...private:std::shared_ptr<RedMutexImpl> _mtx;
};/**         使用方法        **/
auto redis = std::make_shared<Redis>("tcp://127.0.0.1");auto redis1 = std::make_shared<Redis>("tcp://127.0.0.1:7000");
auto redis2 = std::make_shared<Redis>("tcp://127.0.0.1:7001");
auto redis3 = std::make_shared<Redis>("tcp://127.0.0.1:7002");try {{// 单个实例RedMutex mtx(redis, "resource");std::lock_guard<RedMutex> lock(mtx);}{// 多个实例RedMutex mtx({redis1, redis2, redis3}, "resource");std::lock_guard<RedMutex> lock(mtx);}{RedMutexOptions opts;opts.ttl = std::chrono::seconds(5);auto watcher = std::make_shared<LockWatcher>();RedMutex mtx({redis1, redis2, redis3}, "resource",[](std::exception_ptr err) {try {std::rethrow_exception(err);} catch (const Error &e) {}},opts, watcher);std::unique_lock<RedMutex> lock(mtx, std::defer_lock);lock.lock();lock.unlock();lock.try_lock();}
} catch (const Error &err) {
}

先创建多个Redis独立的实例
先开启多个redis

创建多线程模拟多个客户端并发访问,模拟多个客户端并发操作。

/**     创建多线程模拟多个客户端并发访问      **/
std::unique_lock<RedMutex> lock(mtx, std::defer_lock);
auto client_thread = [&](string client_id, int task_count){try{// 尝试获取锁(非阻塞版本)    // if (!lock.try_lock()) {//     cerr << "[" << client_id << "] Failed to acquire lock - operation skipped" << endl;//     return;// }lock.lock();cout << "[" << client_id << "] acquired lock\n";// 模拟临界区操作for (int i = 1; i <= task_count; ++i) {process_order(client_id, i);}// 手动释放锁(析构时也会自动释放)lock.unlock();cout << "[" << client_id << "] released lock\n";// 随机延迟后执行下一个任务dis.param(uniform_int_distribution<>::param_type(50, 300));this_thread::sleep_for(chrono::milliseconds(dis(gen)));}catch(const exception& e) {cerr << "[" << client_id << "] Error: " << e.what() << endl;}catch(const Error& e) {cerr << "Redis error: " << e.what() << endl;}};// 4. 创建多线程模拟多个客户端并发访问vector<thread> clients;vector<string> client_ids = {"ClientA", "ClientB", "ClientC"};for (const auto& id : client_ids) {clients.emplace_back(client_thread, id, 5);}

注意: try_lock() 非阻塞版本,lock() 阻塞版本, 尽量别混合使用,否则会死锁。
在这里插入图片描述


正常运行结果:

在这里插入图片描述

四、总结

4.1、什么是分布式锁?

是一种在分布式系统中协调多个进程/服务对共享资源进行互斥访问的机制;确保在任意时刻,只有一个客户端可以访问资源。

4.2、分布式锁和常见的锁有什么区别?

锁类型作用范围实现方式性能特点典型应用场景
分布式锁跨进程,跨机器外部存储系统(如Redis、Zookeeper等)网络开销大多服务共享资源访问
互斥锁单进程内-单机锁原子操作低延迟,高效率多线程共享内存访问
读写锁单进程内-单机锁计数器+条件变量读操作并发性好读多写少的共享数据
自旋锁单进程内-单机锁CPU忙等待循环低延迟,但浪费CPU短时间占用资源场景

4.3、RedLock分布式锁的数据存储与高可用性分析

    1. RedLock算法,将每个节点看作是独立的,即没有主从关系,每个节点都可以独立地获取和释放锁,并且它们之间没有任何数据同步。
    1. RedLock算法本身不存储业务数据,它只负责管理分布式锁的状态。
    1. 部分节点宕机,只要满足 N/2+1 节点可用,锁服务仍然正常;宕机节点上的锁会在TTL过期后自动清理。

4.4、RedLock适用场景

个人认为,它不适用于需要强一致性的场景,只是在某些时间段,并发量突发时,避免超卖这类问题,比如双11,618等活动,常规时间段还是还原成集群部署模式,使用单机锁,保证数据的一致性。

Code
0vice·GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92702.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92702.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/92702.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring的常用注解汇总

在 Spring 和 Spring Boot 框架中&#xff0c;有许多核心注解被广泛应用。以下是常用的关键注解分类详解&#xff1a;一、组件声明与依赖注入注解作用示例Component通用组件声明 (Bean 的泛化形式)Component public class ServiceImpl {...}Service标记服务层&#xff08;业务逻…

Claude4、GPT4、Kimi K2、Gemini2.5、DeepSeek R1、Code Llama等2025主流AI编程大模型多维度对比分析报告

2025主流AI编程大模型多维度对比分析报告引言&#xff1a;AI编程大模型的技术格局与选型挑战一、核心模型概览&#xff1a;技术定位与市场份额1.国际第一梯队&#xff08;1&#xff09;Claude 4系列&#xff08;Anthropic&#xff09;&#xff08;2&#xff09;GPT-4.1&#xf…

Overleaf中下载.aux和.bbl文件

有些会议提交终稿的时候&#xff0c;可能会让上传.bbl和.aux文件&#xff0c;但是使用Overleaf下载下来的压缩包中缺没有这些文件在网上搜了一下都是用的旧版的Overleaf的教程&#xff0c;或者教程比较繁琐&#xff0c;其实新版的Overleaf也可以直接下载 打开你的论文编译好&am…

uniapp写app做测试手机通知栏展示内容

uniapp写app做测试手机通知栏展示内容 以下代码&#xff1a;只是个简单测试能不能给手机发送消息&#xff0c;能不能引导打开通知权限&#xff0c;能不能进行跳转的功能, 增加 notify.js 以下文件 // 模拟本地通知功能 export function showNotification() {// 1. 检查通知…

分布式云计算:未来计算架构的全新演进

随着信息技术的不断发展,尤其是云计算技术的飞速进步,企业和个人对计算资源的需求已经从传统的单一数据中心向更为灵活、可扩展的分布式架构转变。分布式云计算作为一种新兴的云计算模型,旨在将计算资源和数据存储分布在多个地理位置上,从而提供更加高效、安全和可靠的服务…

2025年海外短剧独立站开发:H5+PC端双平台技术实践与增长策略

引言在全球化内容消费浪潮下&#xff0c;海外短剧市场正经历爆发式增长。据DataEye《2025H1海外微短剧行业数据报告》显示&#xff0c;2025年海外短剧市场规模预计突破45亿美元&#xff0c;其中东南亚、拉美等新兴市场贡献超30%增量。本文将以某头部短剧平台的双平台开发实践为…

OpenAI发布ChatGPT Agent,AI智能体迎来关键变革

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《GPT多模态大模型与AI Agent智能体》&#xff08;跟我一起学人工智能&#xff09;【陈敬雷编著】【清华大学出版社】 清华《GPT多模态大模型与AI Agent智能体》书籍配套视频课程【陈敬雷…

企业级安全威胁检测与响应(EDR/XDR)架构设计

在这个网络威胁如洪水猛兽的时代&#xff0c;企业的安全防护不能再像守城门的老大爷一样只会喊"什么人&#xff1f;口令&#xff01;"了。我们需要的是一套像FBI一样具备全方位侦察能力的智能防护系统。 &#x1f4cb; 文章目录 1. 什么是EDR/XDR&#xff1f;别被这…

Stream流-Java

Stream流的作用&#xff1a;结合了Lambda表达式&#xff0c;简化集合&#xff0c;数组的操作Stream流的使用步骤&#xff1a;1. 先得到一条Stream流&#xff08;流水线&#xff09;&#xff0c;并把数据放上去获取方式方法名说明单列集合default Stream<E> stream()Colle…

Leetcode 327. 区间和的个数

1.题目基本信息 1.1.题目描述 给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中&#xff0c;值位于范围 [lower, upper] &#xff08;包含 lower 和 upper&#xff09;之内的 区间和的个数 。 区间和 S(i, j) 表示在 nums 中&#xff0c;位置从 i 到 j 的元素…

MinIO 版本管理实践指南(附完整 Go 示例)

✨ 前言 在构建企业级对象存储系统时,“对象的版本管理”是一个关键特性。MinIO 作为一款高性能、Kubernetes 原生的 S3 兼容对象存储系统,也支持强大的版本控制功能。 本文将通过 Go 示例代码 + 实操讲解 的形式,手把手带你掌握 MinIO 的版本控制能力,包括开启版本控制、…

数组toString方法及类型检测修复方案

在 JavaScript 中&#xff0c;数组的 toString() 方法被覆盖&#xff08;重写&#xff09;为返回数组元素的逗号分隔字符串&#xff0c;而不是原始的 [object Array] 类型标识。以下是详细解释和修复方案&#xff1a;问题原因Array.prototype.toString 被覆盖数组继承自 Object…

mysql索引底层B+树

B树胜出的关键特性&#xff1a;矮胖树结构&#xff1a;3-4层高度即可存储2000万条记录&#xff08;假设每页存1000条&#xff09; 叶子链表&#xff1a;所有数据存储在叶子节点&#xff0c;并通过双向链表连接 非叶导航&#xff1a;非叶子节点仅存储键值&#xff0c;不保存数据…

AI开放课堂:钉钉MCP开发实战

我们正处在AI技术爆发的时代&#xff0c;也处于企业数字化蓬勃发展的时代。如何利用AI技术&#xff0c;突破模型自身知识的局限&#xff0c;安全、高效地与外部世界连接和交互&#xff0c;是当前所有AI开发者在企业数字化中面临的问题之一。 MCP&#xff08;Model Context Prot…

DigitalOcean 一键模型部署,新增支持百度开源大模型ERNIE 4.5 21B

使用过DigitalOcean GPU Droplet 服务器的用户应该对我们的一键模型部署功能不陌生。DigitalOcean 的一键模型部署 (1-Click Models) 功能是 DO 为开发者和企业提供的一种便捷方式&#xff0c;用于快速部署和运行预训练的生成式 AI 模型&#xff0c;尤其是大型语言模型 (LLM)。…

【嵌入式面试】嵌入式笔试与面试宝典(offer必来)

&#x1f48c; 所属专栏&#xff1a;【嵌入式面试】 &#x1f600; 作  者&#xff1a;兰舟比特 &#x1f43e; &#x1f680; 个人简介&#xff1a;热爱开源系统与嵌入式技术&#xff0c;专注 Linux、网络通信、编程技巧、面试总结与软件工具分享&#xff0c;持续输出实用干…

企业级数据分析创新实战:基于表格交互与智能分析的双引擎架构

引言&#xff1a;数字化转型中数据协同困境与系统融合挑战 在数字化转型实践中&#xff0c;企业普遍面临数据系统与业务运营的协同困境&#xff0c;主要表现为数据处理平台与核心业务流程的架构隔离、分析成果与决策闭环的价值断层、以及双重数据维护带来的资源损耗。这种系统…

openbmc 日志系统继续分析

1.说明 1.1 总体说明 本节是继: https://blog.csdn.net/wit_yuan/article/details/147142407?spm=1011.2415.3001.5331 后的继续分析的文档。 该篇内容主要目的是分析整个openbmc的日志系统。 注意解读文档: https://github.com/openbmc/docs/blob/master/designs/event-l…

【JIRA小白如何使用它进行bug管理】

JIRA小白如何使用它进行bug管理 提示&#xff1a;入职一般来说&#xff0c;公司会提供账号&#xff0c;不需要部署如何提bug&#xff1a; JIRA有两种提交方式 在执行测试用例中在bug管理项目中新建提bug建议或者注意事项&#xff1a; 标题&#xff1a;执行完A之后&#xff0c;发…