分布式锁的三种实现方式:Redis、基于数据库和Zookeeper

分布式锁的实现

  • 操作共享资源:例如操作数据库中的唯一用户数据、订单系统、优惠券系统、积分系统等,这些系统需要修改用户数据,而多个系统可能同时修改同一份数据,这时就需要使用分布式锁来控制访问,防止数据不一致。
  • 在电商系统中,如果多个用户同时购买同一商品,可能会出现超卖现象。通过使用分布式锁,可以确保在同一时间只有一个用户能够进行购买操作,从而避免库存超卖的问题。‌
  • 防止重复调用第三方接口:在分布式系统中,如果多个节点同时调用同一个第三方接口,可能会导致接口调用失败或数据错误。使用分布式锁可以确保在同一时间只有一个节点进行接口调用,避免重复调用问题。

转自:https://lingkang.top/archives/lock333

Redis

当客户端需要获取锁时,向Redis发送SETNX命令,如果返回1,说明客户端获得了锁;如果返回0,则说明锁已被其他客户端占用。当客户端释放锁时,使用DEL命令删除对应的键即可。

1、Maven中添加依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.8.27</version>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.3</version>
</dependency>

2、启动好Redis

在这里插入图片描述

window可以在这里下载一个:https://gitee.com/lingkang_top/redis-window

3、编写java代码

package redis;import cn.hutool.core.thread.ThreadUtil;
import redis.clients.jedis.Jedis;import java.util.ArrayList;
import java.util.List;/*** @author lingkang* @create by 2024/7/25 15:28*/
public class Demo01 {/*** 假设是库存*/private static int number = 100;public static void main(String[] args) {// 提前初始化好redis连接List<Jedis> redis = new ArrayList<>();for (int i = 0; i < 10; i++) {Jedis jedis = new Jedis("localhost", 6379);redis.add(jedis);}// 操作前先清空锁redis.get(0).del("lock");// 假设有10个线程进行锁操作for (int i = 0; i < 10; i++) {final Jedis jedis = redis.get(i);new Thread(() -> {// 每隔线程减扣 5次for (int j = 0; j < 5; j++)dd(jedis);jedis.close();}, "t-" + i).start();}ThreadUtil.sleep(15000);// 应该输出 100-50=50System.out.println("库存: " + number);}/*** 业务处理*/private static void dd(Jedis jedis) {for (; ; ) {long lock = jedis.setnx("lock", "1");if (lock == 1) {// 给key设置一个过期时间,防止死锁jedis.expire("lock", 20);// 获取到锁System.out.println("当前线程获得锁:" + Thread.currentThread().getName());// 进行减扣库存等一系列操作....number--;// 假设处理业务延迟一下ThreadUtil.sleep(200);// 处理完毕要移除锁jedis.del("lock");break;}// 等待一下ThreadUtil.sleep(200);}}
}

4、结果正确

在这里插入图片描述

基于数据库

基于数据库的分布式锁主要依赖于数据库的唯一索引或主键约束。具体实现时,当客户端需要获取锁时,向数据库中插入一条记录,该记录的唯一键表示锁。如果插入成功,说明客户端获得了锁;如果插入失败(如主键冲突),则说明锁已被其他客户端占用。当客户端释放锁时,删除该记录即可。

1、Maven中添加依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.8.27</version>
</dependency>
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>9.0.0</version>
</dependency>

2、添加表格

CREATE TABLE `mylock` (`id` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,`create_time` timestamp NULL DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

3、编写java代码

package mysql;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;import java.sql.*;
import java.util.ArrayList;
import java.util.List;/*** @author lingkang* @create by 2024/7/25 16:58*/
public class Demo01 {/*** 假设是库存*/private static int number = 100;public static void main(String[] args) throws Exception {Class.forName("com.mysql.cj.jdbc.Driver");String URL = "jdbc:mysql://localhost:3306/mylock?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";// 提前初始化好redis连接List<Connection> connectionList = new ArrayList<>();for (int i = 0; i < 10; i++) {Connection conn = DriverManager.getConnection(URL, "root", "123456");connectionList.add(conn);}// 开始前,先将表表的锁数据清理connectionList.get(0).prepareStatement("delete from mylock where id='1'").executeUpdate();// 假设有10个线程进行锁操作for (int i = 0; i < 10; i++) {final Connection conn = connectionList.get(i);new Thread(() -> {// 每隔线程减扣 5次for (int j = 0; j < 5; j++) {try {dd(conn);} catch (Exception e) {throw new RuntimeException(e);}}IoUtil.close(conn);}, "t-" + i).start();}ThreadUtil.sleep(15000);// 应该输出 100-50=50System.out.println("库存: " + number);}/*** 业务处理*/private static void dd(Connection conn) throws Exception {for (; ; ) {int success = 0;try {PreparedStatement statement = conn.prepareStatement("insert into mylock(id,create_time) values('1',now())");success = statement.executeUpdate();} catch (Exception e) {}if (success == 1) {// 获取到锁System.out.println("当前线程获得锁:" + Thread.currentThread().getName());// 进行减扣库存等一系列操作....number--;// 假设处理业务延迟一下ThreadUtil.sleep(200);// 处理完毕要移除锁PreparedStatement statement = conn.prepareStatement("delete from mylock where id='1'");statement.executeUpdate();statement.close();break;} else {ResultSet query = conn.prepareStatement("select create_time from mylock where id='1'").executeQuery();if (query.next()) {Date date = query.getDate(1);// 防止死锁,超过20秒删除if (date.getTime() + 20000L > System.currentTimeMillis()) {conn.prepareStatement("delete from mylock where id='1' and create_time='" + date.getTime() + "'").executeUpdate();// 等待一下ThreadUtil.sleep(1000);}}query.close();}// 等待一下ThreadUtil.sleep(200);}}
}
4、执行结果正确

在这里插入图片描述

Zookeeper

使用zookeeper有多钟方案,如下:

  • 每个客户端在zookeeper的一个指定目录下创建一个临时节点,通过判断当前目录下的所有节点与自己的节点的顺序,就可以确定自己是否获取到锁。
  • 每个客户端在zookeeper的一个指定目录下创建一个有序节点,通过判断自己节点在所有子节点中的顺序,就可以确定自己是否获取到锁。

第一种方案与数据库类似

Zookeeper 方案一

每个客户端在zookeeper的一个指定目录下创建一个临时节点,通过判断当前目录下的所有节点与自己的节点的顺序,就可以确定自己是否获取到锁。

1、Maven中添加依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.8.27</version>
</dependency>
<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.9.2</version>
</dependency>

2、找个Zookeeper启动好

3、java实现代码

package zk;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;/*** @author lingkang* @create by 2024/7/25 18:17*/
public class Demo01 {/*** 假设是库存*/private static int number = 100;private static final String lockPath = "/lock";public static void main(String[] args) throws Exception {// 提前初始化好redis连接List<ZooKeeper> zooKeeperList = new ArrayList<>();for (int i = 0; i < 2; i++) {ZooKeeper zooKeeper = new ZooKeeper("10.8.4.191:2181", 20000, null);// 用来做一个初始化调用zooKeeper.exists(lockPath, false);zooKeeperList.add(zooKeeper);}// 操作前先清空锁ZooKeeper zk = zooKeeperList.get(0);Stat exists = zk.exists(lockPath, false);if (exists != null) {zk.delete(lockPath, exists.getVersion());}// 假设有2个线程进行锁操作List<Thread> threadList = new ArrayList<>();for (int i = 0; i < 2; i++) {final ZooKeeper zooKeeper = zooKeeperList.get(i);Thread thread = new Thread(() -> {// 每隔线程减扣 5次for (int j = 0; j < 5; j++) {try {dd(zooKeeper);} catch (Exception e) {throw new RuntimeException(e);}}IoUtil.close(zooKeeper);// 注意此线程id,此线程id用于模拟全局唯一业务处理id,实际开发可以用具体业务id替代,必须全局唯一}, "t-" + i);thread.start();threadList.add(thread);}for (Thread thread : threadList)thread.join();ThreadUtil.sleep(1000);// 应该输出 100-5*2 --> 90System.out.println("库存: " + number);}/*** 业务处理*/private static void dd(ZooKeeper zooKeeper) throws Exception {String threadName = Thread.currentThread().getName();for (; ; ) {Stat stat = zooKeeper.exists(lockPath, false);if (stat != null) {byte[] data = zooKeeper.getData(lockPath, null, stat);if (!threadName.equals(new String(data))) {// 说明已经被其他服务获取锁了,等待一下跳过此次锁创建ThreadUtil.sleep(300);// 等待一下continue;}}try {// 创建者将会获得锁,注意:注意此线程id,此线程id用于模拟全局唯一业务处理id,// 实际开发可以用具体业务id替代,必须全局唯一zooKeeper.create(lockPath, threadName.getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL);} catch (Exception e) {// 创建锁失败}// 等待一下ThreadUtil.sleep(200);stat = zooKeeper.exists(lockPath, false);// 如果是当前session创建的,就获得锁if (stat == null) {continue;} else {byte[] data = zooKeeper.getData(lockPath, null, stat);if (!threadName.equals(new String(data))) {// 说明已经被其他服务获取锁了,等待一下跳过此次锁创建ThreadUtil.sleep(200);// 等待一下continue;}}// 获取到锁System.out.println("当前线程获得锁:" + threadName);// 进行减扣库存等一系列操作....number--;// 假设处理业务延迟一下ThreadUtil.sleep(200);// 处理完毕要移除锁zooKeeper.delete(lockPath, stat.getVersion());// 处理完成break;}}
}
4、执行结果正确

在这里插入图片描述

Zookeeper 方案二(推荐)

每个客户端在zookeeper的一个指定目录下创建一个有序节点,通过判断自己节点在所有子节点中的顺序,就可以确定自己是否获取到锁。

1、Maven中添加依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><version>5.8.27</version>
</dependency>
<!-- 由于我的zk版本是3.4.8 版本较低,所以使用低版本curator-recipes -->
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>2.13.0</version>
</dependency>

2、找个Zookeeper启动好

v3.4.8

3、java实现代码

package zk;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;import java.util.ArrayList;
import java.util.List;/*** @author lingkang* @create by 2024/7/26 10:43*/
public class Demo02 {/*** 假设是库存*/private static int number = 100;private static final String lockPath = "/lock";public static void main(String[] args) throws Exception {// 提前初始化好zk连接List<CuratorFramework> curatorFrameworks = new ArrayList<>();for (int i = 0; i < 2; i++) {RetryPolicy retryPolicy = new ExponentialBackoffRetry(2000, 3);CuratorFramework client = CuratorFrameworkFactory.newClient("10.8.4.191:2181", retryPolicy);client.start();curatorFrameworks.add(client);}// 操作前先清空锁CuratorFramework curator = curatorFrameworks.get(0);Stat stat = curator.checkExists().forPath(lockPath);if (stat != null)curator.delete().withVersion(stat.getVersion()).forPath(lockPath);// 假设有JVM进行锁操作List<Thread> threadList = new ArrayList<>();for (int i = 0; i < 2; i++) {final CuratorFramework curatorFramework = curatorFrameworks.get(i);Thread thread = new Thread(() -> {// 每个线程减扣 5次for (int j = 0; j < 5; j++) {try {dd(curatorFramework);} catch (Exception e) {throw new RuntimeException(e);}}}, "t-" + i);threadList.add(thread);thread.start();}for (Thread thread : threadList)thread.join();ThreadUtil.sleep(1000);for (CuratorFramework curatorFramework : curatorFrameworks)IoUtil.close(curatorFramework);System.out.println("-------------------------------------------------------------------");// 应该输出 100-5*2 --> 90System.out.println("库存: " + number);}/*** 业务处理*/private static void dd(CuratorFramework curatorFramework) {String threadName = Thread.currentThread().getName();InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);try {lock.acquire();// 获取到锁,执行业务逻辑System.out.println("当前线程获得锁:" + threadName);// 进行减扣库存等一系列操作....number--;// 假设处理业务延迟一下ThreadUtil.sleep(200);} catch (Exception e) {// 处理异常e.printStackTrace();} finally {try {lock.release();// 释放锁} catch (Exception e) {// 处理释放锁时的异常e.printStackTrace();}}}
}
4、执行结果正确

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3269440.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

最新爆火的开源AI项目 | LivePortrait 本地安装教程

LivePortrait 本地部署教程&#xff0c;强大且开源的可控人像AI视频生成 1&#xff0c;准备工作&#xff0c;本地下载代码并准备环境&#xff0c;运行命令前需安装git 以下操作不要安装在C盘和容量较小的硬盘&#xff0c;可以找个大点的硬盘装哟 2&#xff0c;需要安装FFmp…

项目开发实战案例 —— Spring Boot + MyBatis + Hibernate + Spring Cloud

作者简介 我是本书的作者&#xff0c;拥有多年Java Web开发经验&#xff0c;致力于帮助更多开发者快速掌握并运用Java Web技术栈中的关键框架和技术。本书旨在通过实战案例的方式&#xff0c;带领读者深入理解并实践Spring Boot、MyBatis、Hibernate以及Spring Cloud等热门技术…

2-46 基于matlab的声音信号的短时能量、短时过零率、端点检测

基于matlab的声音信号的短时能量、短时过零率、端点检测。通过计算计算短时能量、调整能量门限&#xff0c;然后开始端点检测。输出可视化结果。程序已调通&#xff0c;可直接运行。 2-46 短时能量 短时过零率 端点检测 - 小红书 (xiaohongshu.com)

Vue element ui分页组件示例

https://andi.cn/page/621615.html

Camera Raw:预设

Camera Raw 的预设 Presetss模块能够简化和加速照片编辑过程。预设不仅能大大提升工作效率&#xff0c;还能确保处理结果的一致性和专业性。 快捷键&#xff1a;Shift P 预设 Preset与配置文件、快照有其异同之处&#xff0c;它们都可以快速改变照片的影调和颜色。 不同是&…

SQL labs-SQL注入(三,sqlmap使用)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 引言&#xff1a; 盲注简述&#xff1a;是在没有回显得情况下采用的注入方式&#xff0c;分为布尔盲注和时间盲注。 布尔盲注&#xff1a;布尔仅有两种形式&#xff0c;ture&#…

python-学生排序(赛氪OJ)

[题目描述] 已有 a、b 两个链表&#xff0c;每个链表中的结点包括学号、成绩。要求把两个链表合并&#xff0c;按学号升序排列。输入格式&#xff1a; 输入共 NM1 行。 第一行&#xff0c;输入 a、b 两个链表元素的数量 N、M&#xff0c;中间用空格隔开。下来 N 行&#xff0c;…

全网爆火的AI老照片变视频项目来了,简单易上手,1单69,日入1000+

每天为大家带来一个可实操落地的副业项目&#xff0c;创业思维&#xff0c;只要你认真看完&#xff0c;多少都能够为你带来帮助或启发。 最近在短视频上看到很多怀旧视频流量真的大&#xff0c;同时也看到朋友圈很多人在培训这个项目。 既然有这么多人在做&#xff0c;就证明…

一天搞定React(5)——ReactRouter(下)【已完结】

Hello&#xff01;大家好&#xff0c;今天带来的是React前端JS库的学习&#xff0c;课程来自黑马的往期课程&#xff0c;具体连接地址我也没有找到&#xff0c;大家可以广搜巡查一下&#xff0c;但是总体来说&#xff0c;这套课程教学质量非常高&#xff0c;每个知识点都有一个…

C语言文件操作,文件读写

目录 为什么要使用文件&#xff1f; 文件概念 1. 什么是文件&#xff1f; 2. 程序文件 3. 数据文件 4. 文件名 文件的使用 1. 文件指针 2. 文件的打开与关闭 文件的顺序读写 1. 顺序读写函数 2. scanf系列与printf系列 文件的随机读写 1. fseek 2. ftell 3. …

B端:用弹框还是用抽屉,请说出你的依据。

选择浮层&#xff08;弹出框&#xff09;还是抽屉&#xff08;侧边栏&#xff09;作为B端系统的浮层&#xff0c;需要根据具体情况来决定。以下是一些依据供您参考&#xff1a; 1.功能需求&#xff1a; 浮层的选择应该符合系统的功能需求。如果需要在当前页面上提供一些额外的操…

C++ 基础(类和对象下)

目录 一. 再探构造函数 1.1. 初始化列表&#xff08;尽量使用列表初始化&#xff09; 二. static成员 2.1static成员初始化 三.友元 3.1友元&#xff1a;提供了⼀种 突破类访问限定符封装的方式. 四.内部类 4.1如果⼀个类定义在另⼀个类的内部&#xff0c;这个内部类就叫…

Google Android 2024年7月最新消息汇总

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 Google Android 2024年7月最新消息汇总 2024年7月&#xff0c;Google在Android生态系统中发布了多项更新和政策调整&#xff0c;涵盖了Google …

6万字,让你轻松上手的大模型 LangChain 框架

本文为我学习 LangChain 时对官方文档以及一系列资料进行一些总结&#xff5e;覆盖对Langchain的核心六大模块的理解与核心使用方法&#xff0c;全文篇幅较长&#xff0c;共计50000字&#xff0c;可先码住辅助用于学习Langchain。** 一、Langchain是什么&#xff1f; 如今各类…

开源多协议分布式压力测试工具Tsung详解

目录 1、Tsung简介 2、Tsung安装 3、Tsung的使用流程 4、Tsung配置详解 4.1、客户端配置 4.2、服务端配置 4.3、压力阶段配置 4.4、选项参数配置 4.5、会话动作配置 5、Tsung压测后的结果分析 5.1、统计数据表 5.2、曲线图 6、其他 6.1、record录制 6.2、Plotte…

多区域DNS以及主从DNS的搭建

搭建多域dns服务器&#xff1a; 搭建DNS多区域功能&#xff08;Multi-Zone DNS&#xff09;主要是为了满足复杂网络环境下的多样化需求&#xff0c;提高DNS服务的灵活性、可扩展性和可靠性。 适应不同网络环境&#xff1a; 在大型组织、跨国公司或跨地域服务中&#xff0c;网…

【算法】单向环形链表解决Josephu(约瑟夫)问题

应用场景 n 个小孩标号&#xff0c;逆时针站一圈。从 k 号开始&#xff0c;每一次从当前的小孩逆时针数 m 个&#xff0c;然后让最后这个小孩出列。不断循环上述过程&#xff0c;直到所有小孩出列&#xff0c;由此产生出一个队列编号。 提示 用一个不带头节点的循环链表来处…

使用Dependency Walker和Process Explorer排查瑞芯微工具软件RKPQTool.exe启动报错的问题

目录 1、问题说明 2、使用Dependency Walker查看工具程序的库依赖关系 3、在可以运行的电脑上使用Process Explorer查看依赖的msvcr120.dll和msvcp120.dll库的路径 4、C/C++运行时库介绍 5、可以下载安装VC_redist.x86.exe或VC_redist.x64.exe解决系统库缺失问题 C++软件异…

Radon(拉当) 变换:超详细讲解(附MATLAB,Python 代码)

Radon 变换 Radon 变换是数学上用于函数或图像的一种积分变换&#xff0c;广泛应用于图像处理领域&#xff0c;尤其是在计算机断层成像 (CT) 中。本文档将详细介绍 Radon 变换的数学含义及其在图像处理中的应用。 数学定义 Radon 变换的数学定义是将二维函数 f ( x , y ) f…

vue 开发环境配置

1. nvm 安装 在 github上下载 最新的 nvm 包 https://github.com/coreybutler/nvm-windows/releases或者在 csdn 上下载&#xff08;从github上迁移&#xff0c;方便下载&#xff09;https://download.csdn.net/download/u011171506/89585197 下载后不用修改任何配置&#x…