分布式锁的三种实现方式是什么?
分布式锁三种实现方式:
- 基于数据库实现分布式锁;
- 基于缓存(Redis等)实现分布式锁;
- 基于Zookeeper实现分布式锁;
一, 基于数据库实现分布式锁
- 悲观锁
利用select … where … for update 排他锁
注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。
- 乐观锁
所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。
通过增加递增的版本号字段实现乐观锁
二, 基于缓存(Redis等)实现分布式锁
- 使用命令介绍:
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)delete
delete key:删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
-
实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。 -
分布式锁的简单实现代码:
/*** 分布式锁的简单实现代码 */public class DistributedLock {private final JedisPool jedisPool;public DistributedLock(JedisPool jedisPool) {this.jedisPool = jedisPool;}/*** 加锁* @param lockName 锁的key* @param acquireTimeout 获取超时时间* @param timeout 锁的超时时间* @return 锁标识*/public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {Jedis conn = null;String retIdentifier = null;try {// 获取连接conn = jedisPool.getResource();// 随机生成一个valueString identifier = UUID.randomUUID().toString();// 锁名,即key值String lockKey = "lock:" + lockName;// 超时时间,上锁后超过此时间则自动释放锁int lockExpire = (int) (timeout / );// 获取锁的超时时间,超过这个时间则放弃获取锁long end = System.currentTimeMillis() + acquireTimeout;while (System.currentTimeMillis() < end) {if (conn.setnx(lockKey, identifier) == ) {conn.expire(lockKey, lockExpire);// 返回value值,用于释放锁时间确认retIdentifier = identifier;return retIdentifier;}// 返回-代表key没有设置超时时间,为key设置一个超时时间if (conn.ttl(lockKey) == -) {conn.expire(lockKey, lockExpire);}try {Thread.sleep();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}} catch (JedisException e) {e.printStackTrace();} finally {if (conn != null) {conn.close();}}return retIdentifier;}/*** 释放锁* @param lockName 锁的key* @param identifier 释放锁的标识* @return*/public boolean releaseLock(String lockName, String identifier) {Jedis conn = null;String lockKey = "lock:" + lockName;boolean retFlag = false;try {conn = jedisPool.getResource();while (true) {// 监视lock,准备开始事务conn.watch(lockKey);// 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁if (identifier.equals(conn.get(lockKey))) {Transaction transaction = conn.multi();transaction.del(lockKey);List<Object> results = transaction.exec();if (results == null) {continue;}retFlag = true;}conn.unwatch();break;}} catch (JedisException e) {e.printStackTrace();} finally {if (conn != null) {conn.close();}}return retFlag;}}
- 测试刚才实现的分布式锁
例子中使用50个线程模拟秒杀一个商品,使用–运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。
模拟秒杀服务,在其中配置了jedis线程池,在初始化的时候传给分布式锁,供其使用。
public class Service {private static JedisPool pool = null;private DistributedLock lock = new DistributedLock(pool);int n = 500;static {JedisPoolConfig config = new JedisPoolConfig();// 设置最大连接数config.setMaxTotal(200);// 设置最大空闲数config.setMaxIdle(8);// 设置最大等待时间config.setMaxWaitMillis(1000 * 100);// 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的config.setTestOnBorrow(true);pool = new JedisPool(config, "127.0.0.1", 6379, 3000);}public void seckill() {// 返回锁的value值,供释放锁时候进行判断String identifier = lock.lockWithTimeout("resource", 5000, 1000);System.out.println(Thread.currentThread().getName() + "获得了锁");System.out.println(--n);lock.releaseLock("resource", identifier);}
}
模拟线程进行秒杀服务;
public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {this.service = service;}@Overridepublic void run() {service.seckill();}
}public class Test {public static void main(String[] args) {Service service = new Service();for (int i = 0; i < 50; i++) {ThreadA threadA = new ThreadA(service);threadA.start();}}
}
结果如下,结果为有序的:

若注释掉使用锁的部分:
public void seckill() {// 返回锁的value值,供释放锁时候进行判断//String indentifier = lock.lockWithTimeout("resource", 5000, 1000);System.out.println(Thread.currentThread().getName() + "获得了锁");System.out.println(--n);//lock.releaseLock("resource", indentifier);
}
从结果可以看出,有一些是异步进行的:

三, 基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。
实现源码如下:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;/*** 分布式锁Zookeeper实现**/
@Slf4j
@Component
public class ZkLock implements DistributionLock {
private String zkAddress = "zk_adress";private static final String root = "package root";private CuratorFramework zkClient;private final String LOCK_PREFIX = "/lock_";@Beanpublic DistributionLock initZkLock() {if (StringUtils.isBlank(root)) {throw new RuntimeException("zookeeper 'root' can't be null");}zkClient = CuratorFrameworkFactory.builder().connectString(zkAddress).retryPolicy(new RetryNTimes(2000, 20000)).namespace(root).build();zkClient.start();return this;}public boolean tryLock(String lockName) {lockName = LOCK_PREFIX+lockName;boolean locked = true;try {Stat stat = zkClient.checkExists().forPath(lockName);if (stat == null) {log.info("tryLock:{}", lockName);stat = zkClient.checkExists().forPath(lockName);if (stat == null) {zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(lockName, "1".getBytes());} else {log.warn("double-check stat.version:{}", stat.getAversion());locked = false;}} else {log.warn("check stat.version:{}", stat.getAversion());locked = false;}} catch (Exception e) {locked = false;}return locked;}public boolean tryLock(String key, long timeout) {return false;}public void release(String lockName) {lockName = LOCK_PREFIX+lockName;try {zkClient.delete().guaranteed().deletingChildrenIfNeeded().forPath(lockName);log.info("release:{}", lockName);} catch (Exception e) {log.error("删除", e);}}public void setZkAddress(String zkAddress) {this.zkAddress = zkAddress;}
}
优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。
四,对比
数据库分布式锁实现
缺点:
1.db操作性能较差,并且有锁表的风险
2.非阻塞操作失败后,需要轮询,占用cpu资源;
3.长时间不commit或者长时间轮询,可能会占用较多连接资源
Redis(缓存)分布式锁实现
缺点:
1.锁删除失败 过期时间不好控制
2.非阻塞,操作失败后,需要轮询,占用cpu资源;
ZK分布式锁实现
缺点:性能不如redis实现,主要原因是写操作(获取锁释放锁)都需要在Leader上执行,然后同步到follower。
总之:ZooKeeper有较好的性能和可靠性。
从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库
从性能角度(从高到低)缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库
相关文章:
分布式锁的三种实现方式是什么?
分布式锁三种实现方式: 基于数据库实现分布式锁;基于缓存(Redis等)实现分布式锁;基于Zookeeper实现分布式锁; 一, 基于数据库实现分布式锁 悲观锁 利用select … where … for update 排他锁…...
华为云软件精英实战营——感受软件改变世界,享受Coding乐趣
机器人已经在诸多领域显现出巨大的商业价值,华为云计算致力于以云助端的方式为机器人产业带来全新机会 如果您是开发爱好者,想了解华为云,想和其他自由开发者交流经验; 如果您是学生,想和正在从事软件开发行业的大佬…...
贪心算法总结篇
文章转自代码随想录 贪心算法总结篇 我刚刚开始讲解贪心系列的时候就说了,贪心系列并不打算严格的从简单到困难这么个顺序来讲解。 因为贪心的简单题可能往往过于简单甚至感觉不到贪心,如果我连续几天讲解简单的贪心,估计录友们一定会不耐…...
ICCV 2023 | 港中文MMLab: 多帧光流估计模型VideoFlow,首次实现亚像素级别误差
本文提出了一个多帧光流估计模型 VideoFlow,旨在充分挖掘视频中的时序信息和运动规律,避免当前主流方法只以两帧图片作为输入而面临的信息瓶颈,显著提升了光流估计的性能。 在公开的 Sintel Bechmark 上,VideoFlow 在 Clean 和 Fi…...
【python爬虫】—图片爬取
图片爬取 需求分析Python实现 需求分析 从https://pic.netbian.com/4kfengjing/网站爬取图片,并保存 Python实现 获取待爬取网页 def get_htmls(pageslist(range(2, 5))):"""获取待爬取网页"""pages_list []for page in pages:u…...
自动化运维工具—Ansible
一、Ansible概述1.1 Ansible是什么1.2 Ansible的特性1.3 Ansible的特点1.4 Ansible数据流向 二、Ansible 环境安装部署三、Ansible 命令行模块(1)command 模块(2)shell 模块(3)cron 模块(4&…...
uniapp 安卓平台签名证书(.keystore)生成
安装JRE环境 下载jre安装包:https://www.oracle.com/java/technologies/downloads/#java8安装jre安装包时,记录安装目录(例:C:\Program Files\Java\jdk-20)打开命令行(cmd),将JRE安装路径添加到系统环境变量 d: se…...
缓存中间件Redis常考知识点
缓存中间件Redis常考知识点 1 什么是RDB和AOF2 Redis的过期键的删除策略3 简述Redis事务实现4 Redis 主从复制的核⼼原理5 Redis有哪些数据结构?分别有哪些典型的应⽤场景?6 Redis分布式锁底层是如何实现的?7 Redis主从复制的核⼼原理8 Redis…...
detour编译问题及导入visual studio
Detours是经过微软认证的一个开源Hook库,Detours在GitHub上,网址为 https://github.com/Microsoft/Detours 注意版本不一样的话也是会出问题的,因为我之前是vs2022的所以之前的detours.lib不能使用,必须用对应版本的x64 Native To…...
江西武功山旅游攻略(周末两日游)
一、 往返路线 1: 出发路线 周五晚上上海出发坐火车🚄到江西萍乡(11.5小时,卧铺550左右) 打车到江西武功山景区,120-150元左右,人均30元,1小时10分左右到达 或者 🚗到达萍乡北之后 出站后步行200米到长途汽车站,乘旅游巴士直达武功山游…...
Django静态文件媒体文件文件上传
文章目录 一、静态文件和媒体文件1.在django中使用静态文件实践2.在django中使用媒体文件 二、文件上传单文件上传实践多文件上传 一、静态文件和媒体文件 媒体文件: 用户上传的文件,叫做media 静态文件:存放在服务器的css,js,image,font等 叫做static1.在django中…...
mysql 分库分表实现思路
MySQL的分库分表是一种常用的数据库拆分方案,它可以提高数据库的性能和扩展性。下面是一般的实现步骤: 数据库设计:首先,需要对数据库进行良好的设计。确定要分库分表的实体和关系,并根据业务需求进行合理的拆分。 数…...
Android深思如何防止快速点击
前言 其实快速点击是个很好解决的问题,但是如何优雅的去解决确是一个难题,本文主要是记录一些本人通过解决快速点击的过程中脑海里浮现的一些对这个问题的深思。 作者:流浪汉kylin 链接:https://juejin.cn/post/7197337416096055…...
PHP自己的框架cookie()使用(完善篇七)
1、PHP自己的框架cookie() 2、cookie类(CookieBase.php) <?php class CookieBase {/*** 设置cookie*/public static function set($name, $value, $expire 3600, $path , $domain , $secure false, $httponly false) {setcookie($name, $valu…...
Spring Boot Dubbo Zookeeper(含ZK安装脚本)
文章目录 Spring Boot Dubbo Zookeeper(含ZK安装脚本)简介DubboCommonProviderConsumer Zookeeper Spring Boot Dubbo Zookeeper(含ZK安装脚本) 简介 Dubbo Common 公共依赖 <!-- Spring Boot Starter --> <dependen…...
BigDecimal百科全书
一、BigDecimal简述 Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。 一般情况下,…...
【30天熟悉Go语言】11 数组的全方位使用与解析
作者:秃秃爱健身,多平台博客专家,某大厂后端开发,个人IP起于源码分析文章 😋。 源码系列专栏:Spring MVC源码系列、Spring Boot源码系列、SpringCloud源码系列(含:Ribbon、Feign&…...
静态路由(详细理解+实例精讲)
系列文章目录 华为数通学习(6) 前言 一,静态路由 二,静态路由配置 三,缺省路由 四,缺省路由应用场景 总结 前言 随着华为公司的不断发展,数据通信这门技术也越来越重要,很多人…...
leetcode做题笔记118. 杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 思路一:动态规划 int** generate(int numRows, int* returnSize, int** returnColumnSizes){int **returnnum(int **)…...
stm32之24.RTC闹钟usart端口修改配置
(需要修改) 源码 while(1){//rtc唤醒事件if(g_rtc_wakeup_event){//获取日期RTC_GetDate(RTC_Format_BCD,&RTC_DateStructure);printf("20%02x/%02x/%02xWeek:%x\r\n",RTC_DateStructure.RTC_Year,RTC_DateStructure.RTC_Month,RTC_Date…...
SecGPT-14B模型蒸馏:打造轻量级OpenClaw安全助手
SecGPT-14B模型蒸馏:打造轻量级OpenClaw安全助手 1. 为什么需要轻量级安全助手? 去年在为一个金融客户部署自动化安全监控系统时,我遇到了一个典型困境:他们的边缘设备只能提供4GB内存和2核CPU的算力,但SecGPT-14B这…...
GLM-4v-9b多图对比分析:上传两张产品图→自动识别差异点→生成结构化对比报告
GLM-4v-9b多图对比分析:上传两张产品图→自动识别差异点→生成结构化对比报告 1. 产品对比分析的新选择 在日常工作中,我们经常需要对比两个相似的产品图片——可能是不同版本的设计稿、竞品分析、或者产品质量检查。传统方法需要人工逐像素比对&#…...
标普油气ETF富国(513350.SH)逆势走强、半导体承压:地缘扰动与产业逻辑共振下的ETF分化走势
4月2日,市场全天震荡调整,创业板指、科创50指数均跌超2%。板块方面,医药板块逆势走强,油气股表现活跃,光纤概念反复走强;算力租赁概念集体调整。ETF方面,标普油气ETF富国(513350.SH&…...
TI SAR ADC模型(Matlab) 包含各类非理想因素,时钟偏差,增益偏差
TI SAR ADC模型(Matlab) 包含各类非理想因素,时钟偏差,增益偏差,失调偏差 模型参数均可自由设置直接上干货吧,今天聊聊怎么用Matlab折腾带非理想特性的SAR ADC模型。玩过ADC的都知道,现实中的转…...
Mavlink协议解析:从Pixhawk飞控到QGC地面站的完整通信流程
Mavlink协议深度解析:构建Pixhawk与QGC的高效通信桥梁 当Pixhawk飞控的LED指示灯开始规律闪烁,QGC地面站的地图上突然出现了一个蓝色圆点——这看似简单的连接背后,隐藏着一套精密的通信语言体系。Mavlink协议就像无人机系统的神经网络&#…...
DLSS Swapper:3步解锁游戏性能倍增的AI优化工具
DLSS Swapper:3步解锁游戏性能倍增的AI优化工具 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper DLSS Swapper是一款专为PC游戏玩家设计的深度学习超级采样(DLSS)版本管理工具,通过智能环境诊断、…...
JavaScript开发提效:从ZoomIt、Inspection Lens到Xmind的实战应用
1. ZoomIt:让代码审查和演示更高效的工具 第一次接触ZoomIt是在一次团队代码评审会上。当时同事正在讲解一个复杂的DOM操作逻辑,屏幕上的代码密密麻麻,后排同事根本看不清细节。只见他按下快捷键,屏幕瞬间放大到200%,关…...
集萃智造全自动咖啡机器人:从研磨萃取到清洁运维,一站式商用解决方案
当下商用咖啡场景(连锁咖啡店、机场 / 高铁站、写字楼、无人零售区)普遍面临三大难题:人工成本持续上涨、高峰出杯效率不足、出品稳定性差、门店 24 小时运营难落地。传统半自动 / 全自动咖啡机依赖熟练咖啡师,单杯制作耗时、口味…...
区块链AI骗局:深扒某DeFi项目的测试造假链
当技术信任沦为欺诈工具 在软件测试领域,我们习惯于与代码、流程和标准打交道,致力于构建可靠、可验证的系统。然而,在区块链与人工智能融合的前沿地带,一场针对“信任”本身的系统性造假正在上演。本文旨在从一个软件测试工程师…...
Vibe Coding氛围编程系列:AI 模型 服务选择之哪个模型编程能力最强?
前言 2026年,AI辅助编程早已告别了“单行代码补全”的初级阶段,正式进入了Vibe Coding(氛围编程) 的全新时代。所谓氛围编程,核心是AI能完全贴合开发者的编码思路、节奏与工作流,实现无断点、沉浸式的流畅…...
