关于分布式锁

article/2025/10/13 5:23:23

先别说了别的,先来一个总结

synchronized 单机版可以,但是上了分布式就不行了。

nginx 分布式服务单机锁就不行

取消单机锁,上redis分布式锁setnx

注意的问题:

如果只加了锁,没有释放锁,出现异常的话。可能无法释放锁,所有必须代码层finally释放锁。

宕机了,部署了微服务代码层根本就没有走到finally这块,没办法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定。

为redis的分布式锁key,增加过期时间,此外,还必须要setnx+过期时间必须在一行,保证原子性。

必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1删2,2删3.

Redis集群环境下,我们自己写的也不ok,就直接上RedLock之Redssion落地实现

redis分布式锁01

*synchronized 是一个关键字

*ReentraLock 是一个类,可以进行异常处理。

class X {private final ReentrantLock lock = new ReentrantLock();// ...public void m() {lock.lock();  // block until condition holds//不见不散try {// ... method body} finally {lock.unlock()}}public void m2() {if(lock.tryLock(timeout, unit)){//过时不候try {// ... method body} finally {lock.unlock()}   }else{// perform alternative actions}}}

redis分布式锁02

分布式部署后,单机锁还是出现超卖现象,需要分布式锁。

redis cluster

Nginx配置负载均衡,Nginx学习笔记or备份

Nginx配置文件修改内容

upstream myserver{server 127.0.0.1:1111;server 127.0.0.1:2222;
}server {listen       80;server_name  localhost;#charset koi8-r;#access_log  logs/host.access.log  main;location / {# 负责用到的配置proxy_pass  http://myserver;root   html;index  index.html index.htm;}#error_page  404              /404.html;# redirect server error pages to the static page /50x.html#error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}
}

启动两个微服务:1111,2222,多次访问http://localhost/buy_goods,服务提供端口在1111,2222;两者之间横跳。

上边点击,下边模拟高并发

用JMeter模拟测试高并发,100个线程同时访问http://localhost/buy_goods。

 启动测试,后台日志:

 这就是所谓分布式部署后出现超卖的现象。

Reids具有极高的性能,且具有命令对分布式支持友好,借助SET命令既可以实现枷锁。

EX seconds – Set the specified expire time, in seconds.
PX milliseconds – Set the specified expire time, in milliseconds.
NX – Only set the key if it does not already exist.
XX – Only set the key if it already exist.

在java代码里

public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);if(!flag) {return "抢锁失败";}...//业务逻辑stringRedisTemplate.delete(REDIS_LOCK);
}

Reids分布式锁03

上边Java源码分布式锁的问题:出现异常的话,可能无法释放锁,必须在代码finally释放锁。

解决方法:try…finally…

public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try{Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);if(!flag) {return "抢锁失败";}...//业务逻辑}finally{stringRedisTemplate.delete(REDIS_LOCK);   }
}

另一个问题:部署了微服务jar包的机器挂了,代码层根本没有走到finally这块,没办法保证释放锁,这个key没有被删除,需要加一个过期时间限定key.

public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try{Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//设定时间stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);if(!flag) {return "抢锁失败";}...//业务逻辑}finally{stringRedisTemplate.delete(REDIS_LOCK);   }
}

Reids分布式锁04

新问题:设置key+过期时间分开了,必须要合并成一行具备原子性。

解决办法:

public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try{Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//设定时间//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);if(!flag) {return "抢锁失败";}...//业务逻辑}finally{stringRedisTemplate.delete(REDIS_LOCK);   }
}

另一个新的问题:

张冠李戴,删除了别人的锁

解决方法:只能删除自己的,不许动别人的。

public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try{Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//设定时间//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);if(!flag) {return "抢锁失败";}...//业务逻辑}finally{if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {stringRedisTemplate.delete(REDIS_LOCK);}}
}

Reids分布式锁05

finally块判断 + del删除操作不是原子性的

用lua脚本

用redis的自身的事务

事务介绍

Redis的事条是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成。
Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合。
Redis将命令集合序列化并确保处于一事务的命令集合连续且不被打断的执行。
Redis不支持回滚的操作。

命令    描述
DISCARD    取消事务,放弃执行事务块内的所有命令。
EXEC    执行所有事务块内的命令。
MULTI    标记一个事务块的开始。
UNWATCH    取消 WATCH 命令对所有 key 的监视。
WATCH key [key …]    监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

Reids分布式锁06

public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try{Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//设定时间//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);if(!flag) {return "抢锁失败";}...//业务逻辑}finally{while(true){stringRedisTemplate.watch(REDIS_LOCK);if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){stringRedisTemplate.setEnableTransactionSupport(true);stringRedisTemplate.multi();stringRedisTemplate.delete(REDIS_LOCK);List<Object> list = stringRedisTemplate.exec();if (list == null) {continue;}}stringRedisTemplate.unwatch();break;} }
}

Reids分布式锁07

Redis调用Lua脚本通过命令保证代码执行的原子性。

RedisUtils:

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;public class RedisUtils {private static JedisPool jedisPool;static {JedisPoolConfig jpc = new JedisPoolConfig();jpc.setMaxTotal(20);jpc.setMaxIdle(10);jedisPool = new JedisPool(jpc);}public static JedisPool getJedis() throws Exception{if(jedisPool == null)throw new NullPointerException("JedisPool is not OK.");return jedisPool;}}
public static final String REDIS_LOCK = "redis_lock";@Autowired
private StringRedisTemplate stringRedisTemplate;public void m(){String value = UUID.randomUUID().toString() + Thread.currentThread().getName();try{Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//设定时间//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);if(!flag) {return "抢锁失败";}...//业务逻辑}finally{Jedis jedis = RedisUtils.getJedis();String script = "if redis.call('get', KEYS[1]) == ARGV[1] "+ "then "+ "    return redis.call('del', KEYS[1]) "+ "else "+ "    return 0 "+ "end";try {Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK),// Collections.singletonList(value));if("1".equals(o.toString())) {System.out.println("---del redis lock ok.");}else {System.out.println("---del redis lock error.");}}finally {if(jedis != null) jedis.close();}}
}

Reids分布式锁08

确保RedisLock过期时间大于业务执行时间的问题

Redis分布式锁如何续期?

集群 + CAP对比ZooKeeper 对比ZooKeeper,重点,CAP

Redis - AP

异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了。那么其他的数据就从 从机获取数据,就会出现数据的错误。他的选举算法是:主节点获得到key的时,就立即返回说ok了。


ZooKeeper - CP  他的选举机制和Reids不一样,他有他自己的选举算法,zooKeeper获得key的时候先不着急回复,他会先慢慢通知另外两个从节点,保证其他的slave节点和主节点数据一样了,我再返回去跟你说ok了,我们这边加锁成功了。也就是全部成功了,他才会跟你说好了。

注意:就理论而言ZooKeeper比较好,但是一切皆平横,但是他的高可用就会下降。
CAP

C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)

终于我们推出了在集群的环境下,最牛逼的分布式锁RedLock之Redisson

Reids分布式锁09

Redisson: Redis Java client with features of In-Memory Data Grid

类Redisson配置

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedisConfig {@Beanpublic Redisson redisson() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);return (Redisson)Redisson.create(config);}}

Redisson模板
 

public static final String REDIS_LOCK = "REDIS_LOCK";@Autowired
private Redisson redisson;@GetMapping("/doSomething")
public String doSomething(){RLock redissonLock = redisson.getLock(REDIS_LOCK);redissonLock.lock();try {//doSomething}finally {redissonLock.unlock();}
}

回到实例

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class GoodController{public static final String REDIS_LOCK = "REDIS_LOCK";@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String serverPort;@Autowiredprivate Redisson redisson;@GetMapping("/buy_goods")public String buy_Goods(){//String value = UUID.randomUUID().toString() + Thread.currentThread().getName();RLock redissonLock = redisson.getLock(REDIS_LOCK);redissonLock.lock();try {String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够int goodsNumber = result == null ? 0 : Integer.parseInt(result);if(goodsNumber > 0){int realNumber = goodsNumber - 1;stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;}else{System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);}return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;}finally {redissonLock.unlock();}}}

为了让代码更加严谨在解锁的时候需要注意下:防止出现

IllegalMonitorStateException: attempt to unlock lock,not loked by current thread by node id:da6385f-81a5-4e6c-b8c0
public static final String REDIS_LOCK = "REDIS_LOCK";@Autowired
private Redisson redisson;@GetMapping("/doSomething")
public String doSomething(){RLock redissonLock = redisson.getLock(REDIS_LOCK);redissonLock.lock();try {//doSomething}finally {//添加后,更保险if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {redissonLock.unlock();}}
}


http://chatgpt.dhexx.cn/article/HuBP0OUZ.shtml

相关文章

Redisson分布式锁详解

概述 setnx分布式锁的问题 重入问题 重入问题是指获得锁的线程可以再次进入到相同的锁的代码块中&#xff0c;可重入锁的意义在于防止死锁&#xff0c;比如HashTable这样的代码中&#xff0c;它的方法都是使用synchronized修饰的&#xff0c;假如它在一个方法内&#xff0c;…

redission实现分布式锁

在开始提到Redis分布式锁之前&#xff0c;先说一下redis中的两个命令。 SETNX key valuesetnx 是SET if Not eXists(如果不存在&#xff0c;则 SET)的简写。 用法如图&#xff0c;如果不存在set成功返回int的1&#xff0c;这个key存在了返回0。 SETEX key seconds value上面…

Java分布式锁

文章目录 1.什么是锁&#xff1f;2.什么是分布式&#xff1f;分布式场景 3.什么是分布式锁&#xff1f;4.我们应该怎么设计分布式锁&#xff1f;5.基于数据库的分布锁5.1 基于表主键唯一做分布式锁5.2 基于表字段版本号做分布式锁 6.基于 Redis 做分布式锁6.1 基于 REDIS 的 SE…

Redis分布式锁

概述 日常开发中&#xff0c;秒杀下单、抢红包等等业务场景&#xff0c;都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开&#xff0c;跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方&#xff0c;欢迎大家指出哈&#xff0c;一起学习一…

Zookeeper分布式锁

实现一把分布式锁通常有很多方法&#xff0c;比较常见的有 Redis 和 Zookeeper。 Redis分布式锁可参考之前的文章&#xff1a; Redisson 分布式锁原理分析&#xff1a;https://blog.csdn.net/qq_42402854/article/details/123342331 Zookeeper能实现分布式锁&#xff0c;是因…

分布式锁

分布式锁实践 在不同进程需要互斥地访问共享资源时&#xff0c;分布式锁是一种非常有用的技术手段。有很多三方库和文章描述如何用Redis实现一个分布式锁管理器&#xff0c;但是这些库实现的方式差别很大&#xff0c;而且很多简单的实现其实只需采用稍微增加一点复杂的设计就可…

分布式系列之分布式锁几种实现机制

在分布式系统中&#xff0c;分布式锁用来解决分布式系统中多线程、多进程在不同机器上共享资源访问的问题。本文简要介绍分布式锁的四种实现机制&#xff0c;包括数据库、Redis缓存、Zookeeper和Etcd&#xff0c;以加深了解。 1、分布式锁介绍 在单体应用中&#xff0c;通过锁…

三种分布式锁

----------本文为学习记录如有错误帮忙指正 一、什么是分布式锁&#xff1f; 在单机系统下&#xff0c;如果多个线程同时访问一个变量或者代码片段就会产生多线程问题。&#xff08;被访问的变量或者代码片段被称之为临界区域&#xff09;这时我们就需要让所有线程按顺序一个一…

Redis实现分布式锁

目录 一、前言 为什么需要分布式锁&#xff1f; 二、基于redis实现分布式锁 为什么redis可以实现分布式锁&#xff1f; 如何实现&#xff1f; 锁的获取 锁的释放 三、如何避免死锁&#xff1f;锁的过期时间如何设置&#xff1f; 避免死锁 锁过期处理 释放其他服务的锁…

什么是分布式锁?几种分布式锁分别是怎么实现的?

一、什么是分布式锁&#xff1a; 1、什么是分布式锁&#xff1a; 分布式锁&#xff0c;即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题&#xff0c;而分布式锁&#xff0c;就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是&am…

软件需求最佳实践笔记(一)

1.软件需求最佳实践笔记 | 需求框架 前言&#xff1a;SERU是一套系统全面的需求方法论&#xff0c;可指导我们日常的软件需求工作。曾参加过徐峰老师软件需求最佳实践课程的培训&#xff0c;收益颇多&#xff0c;现通过笔记形式整理出来&#xff0c;以期与具有同样需求的读者共…

声音信号基音提取算法基频和谐波处理分析

1、内容简介 略 293-可以交流、咨询、答疑 2、内容说明 略 一、 实验原理&#xff1a; 傅里叶变换建立了信号频谱的概念。所谓傅里叶分析即分析信号的频谱(频率构成)、频带宽度等。要想合成出一段音乐,就要了解该段音乐的基波频率、谐波构成等。因此,必须采用傅里叶变换这…

软件工程—需求分析阶段

第一步、需求获取 为了保证能全面地获取信息&#xff0c;以更好地服务于产品设计和迭代&#xff0c;产品经理必须利用内部外部等多种渠道来获取用户需求。并且因渠道差异&#xff0c;产品经理所采取的方式与方法也相应会有所差异&#xff0c;所以产品经理还必须根据不同的渠道…

作业1.1利用Audacity软件分析音频

文章目录 前言实验内容实验步骤实验结果结果分析总结 前言 Audacity软件分析其余格式的音频时需要安装FFmpeg库&#xff0c;所以我们下载一个格式转换软件将音频转为MP3格式进行处理。语音信号具有短时平稳性&#xff0c;即在一个短时间范围内&#xff08;10-30ms&#xff09;…

C++ OBS源码分析与屏幕录制软件开发视频教程

本课程主要讲解OBS源码的编译&#xff0c;OBS功能实现&#xff0c;初始化&#xff0c;显示器录制&#xff0c;窗口的实现录制&#xff0c;以及录制模块源码详细分析&#xff0c;最后基于OBS源码开发了一个录制软件&#xff0c;界面如下&#xff1a; 主要有如下功能 &#xff0…

酒店管理系统-需求分析报告

目录 1.引言 1.1编制的目的 1.2术语定义 1.3参考资料 1.4相关文档 2.概述 2.1项目的描述 2.2项目的功能 2.3用户特点 3.具体需求 3.1业务需求 3.1.1主要业务 3.1.2未来增长预测 3.2用户需求 3.3应用需求 3.3.1系统功能 3.3.2主要应用及使用方式 3.4网络基本结构…

基于matlab的声波分析研究,基于MATLAB的声音信号分析与处理(共13页)

设计了一套信号采集与处理系统&#xff0c;建立了傅立叶变换算法模型&#xff0c;可获得其频谱图进行频谱分析&#xff0c;建立滤波器的设计算法模型设计了一个声音滤波器&#xff0c;建立滤波算法模型可对声音信号进行滤波。本套系统的算法建立都是基于MATLAB软件&#xff0c;…

分析评估和定位声音质量

/** * author wangdaopo * email 3168270295qq.com */ 影响音频质量和稳定性的因素 音质好坏的评价&#xff0c;响度、音高、音色&#xff0c; 测试&#xff0c;你的语音引擎是基本可用的&#xff0c;客观评测软件是RMAA&#xff08;RightMark Audio Analyzer&#xff1b;比…

声学计算机软件,常用声学仿真软件汇总

声学仿真软件根据计算原理不同大致分为以下几类&#xff1a; 一、电力声类比法 将振动系统和声学系统转化为等效电路&#xff0c;是一种0维的参数化建模方法&#xff1b; 优点&#xff1a;计算速度快&#xff1b; 缺点&#xff1a;无法预测高频响应以及复杂声波叠加&#xff1b…

荔枝软件如何测试声音,荔枝如何测自己的声音 荔枝测自己的声音方法

您可能感兴趣的话题&#xff1a; 荔枝 测自己的声音 核心提示&#xff1a;  荔枝APP有一个特色功能——声鉴卡&#xff0c;声鉴卡可以用来测试用户的音色&#xff0c;比如女神音、御姐音、少年音等等&#xff0c;很多人都想用声鉴卡测试一下自己的音色&#xff0c;却不知道荔枝…