分布式锁的三种实现方式: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://www.rhkb.cn/news/386672.html

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

相关文章

angular入门基础教程(九)依赖注入(DI)

依赖注入 Angular 中的依赖注入&#xff08;DI&#xff09;是框架最强大的特性之一。可以将依赖注入视为 Angular 在运行时为你的应用 提供所需资源的能力。依赖项可以是服务或其他资源。 使用服务的一种方式是作为与数据和 API 交互的方式。为了使服务可重用&#xff0c;应该…

实战:ZooKeeper 操作命令和集群部署

ZooKeeper 操作命令 ZooKeeper的操作命令主要用于对ZooKeeper服务中的节点进行创建、查看、修改和删除等操作。以下是一些常用的ZooKeeper操作命令及其说明&#xff1a; 一、启动与连接 启动ZooKeeper服务器&#xff1a; ./zkServer.sh start这个命令用于启动ZooKeeper服务器…

SSM学习9:SpringBoot简介、创建项目、配置文件、多环节配置

简介 SpringBoot式用来简化Spring应用的初始搭建以及开发过程的一个框架 项目搭建 File -> New -> Project 选中pom.xml文件&#xff0c;设置为maven项目 项目启动成功 可以访问BasicController中的路径 配置文件 在resources目录下 application.properties 默…

Linux——管理本地用户和组(详细介绍了Linux中用户和组的概念及用法)

目录 一、用户和组概念 &#xff08;一&#xff09;、用户的概念 &#xff08;二&#xff09;、组的概念 补充组 主要组 二、获取超级用户访问权限 &#xff08;一&#xff09;、su 命令和su -命令 &#xff08; 二&#xff09;、sudo命令 三、管理本地用户账户 &…

WPF---Prism视图传参

Prism视图传参方式。 实际应用场景 点击tabitem中的列表数据&#xff0c;同步更新到ListStatic Region对应的界面。目前用两种方式实现了传参数据同步。 第一&#xff0c;事件聚合器&#xff08;EventAggregator&#xff09; 1. 定义事件 创建一个事件类&#xff0c;用于传…

微信小程序配置访问服务器失败所发现的问题及解决方案

目录 事前现象问题1&#xff1a;问题现象&#xff1a;问题分析&#xff1a; 问题2&#xff1a;问题现象&#xff1a;问题分析&#xff1a;解决方案&#xff1a; 事后现象 事前现象 问题1&#xff1a; 问题现象&#xff1a; 在本地调试时&#xff0c;一切顺利&#xff0c;但一…

JAVA开源的html转pdf、png转pdf项目

总览 Open HTML to PDF是一个纯Java库,用于使用CSS 2.1(以及更高版本的标准)对合理子集的格式良好的XML/XHTML(甚至一些HTML5)进行布局和格式化,输出为PDF或图像。 使用该库生成漂亮的PDF文档。但请注意,您不能将现代HTML5+直接应用于该引擎并期望获得出色的结果。您必…

妈吖,看过这个大厂的oracle主键自增,我的信心暴增!信创,国产数据库也能行。

创作不易 只因热爱!! 热衷分享&#xff0c;一起成长! “你的鼓励就是我努力付出的动力” 1.数据库oracle自增主键字段思维导图 在Oracle数据库中&#xff0c;可以通过创建序列&#xff08;SEQUENCE&#xff09;来实现自增功能。但也可以不在数据库中实现&#xff0c;而是通过程…

Docker中使用自定义网络方式实现Redis集群部署与测试流程

场景 Docker中Docker网络-理解Docker0与自定义网络的使用示例&#xff1a; Docker中Docker网络-理解Docker0与自定义网络的使用示例-CSDN博客 参考上面的流程实现自定义网络的实现。 下面记录其应用实例&#xff0c;使用Docker的自定义网络实现redis集群部署。 注&#xf…

vue3数据结构的渲染01

处理数据&#xff1a; //现有原始数据showCertificateUrl “url01;url02” 使用以下代码将两条通过分号";"分割的url进行处理 const parseUrls () > {urls.value [];// 每次重新赋值前一定要清空之前的旧数据&#xff01;if (!showCertificateUrl.value) {retu…

数模打怪(八)之图论模型

一、作图 图的数学语言描述&#xff1a; G( V(G), E(G) )&#xff0c;G&#xff08;graph&#xff09;&#xff1a;图&#xff0c;V&#xff08;vertex&#xff09;&#xff1a;顶点集&#xff0c;E&#xff08;edge&#xff09;&#xff1a;边集 1、在线作图 https://csac…

自闭症儿童能否上学:家长的选择与困惑

在自闭症儿童的成长旅程中&#xff0c;上学这一关键议题常常使家长陷入异常艰难的抉择和无尽的困惑之中。对于自闭症儿童究竟能否上学&#xff0c;家长们不得不面对众多纷繁复杂且至关重要的考量因素。 一方面&#xff0c;家长们怀揣着美好的期望&#xff0c;渴望孩子能够融入正…

C# 使用pythonnet 迁入 python 初始化错误解决办法

pythonnet 从 3.0 版本开始&#xff0c;必须设置Runtime.PythonDLL属性或环境变量 例如&#xff1a; string pathToVirtualEnv ".\\envs\\pythonnetTest"; Runtime.PythonDLL Path.Combine(pathToVirtualEnv, "python39.dll"); PythonEngine.PythonHom…

知识图谱增强的RAG(KG-RAG)详细解析

转自&#xff1a;知识图谱科技 这是一个与任务无关的框架&#xff0c;它将知识图谱&#xff08;KG&#xff09;的显性知识与大型语言模型&#xff08;LLM&#xff09;的隐含知识结合起来。这是该工作的arXiv预印本 https://arxiv.org/abs/2311.17330 。 我们在这里利用一个名为…

linux中mysql的安装使用(普通版版本+docker版本)

linux中mysql的安装使用 一、普通安装1.下载安装包2.流程 二、用docker安装1.拉取mysql镜像2.启动镜像3.开启权限第一种情况第二种情况 三、用Navicat连接 一、普通安装 1.下载安装包 挑选个你喜欢的目录&#xff0c;用wget下载并且解压 wget http://dev.mysql.com/get/Down…

Java人力资源招聘社会校招类型招聘系统PC端

&#x1f50d;【揭秘】人力资源新利器&#xff01;社会校招一站式PC端招聘系统全攻略&#x1f680; &#x1f308; 开篇引言&#xff1a;招聘新纪元&#xff0c;效率为王&#xff01; Hey小伙伴们&#xff0c;你是否还在为繁琐的招聘流程头疼不已&#xff1f;&#x1f92f; 面…

京东商品详情API:多规格商品的返回值处理

处理京东商品详情API中关于多规格商品的返回值&#xff0c;首先需要了解京东API的返回数据结构。通常&#xff0c;对于多规格商品&#xff08;如不同颜色、尺寸等选项的商品&#xff09;&#xff0c;API会返回一个包含多个规格选项和对应价格、库存等信息的复杂数据结构。 以下…

【“微软蓝屏”事件暴露了网络安全哪些问题?】建设安全稳固的网络基础设施

目录 前言一、软件更新流程中的风险管理和质量控制机制&#xff08;一&#xff09;测试流程及风险识别&#xff08;二&#xff09;风险管理策略&#xff08;三&#xff09;质量控制措施 二、预防类似大规模故障的最佳方案或应急响应对策&#xff08;一&#xff09;冗余系统设计…

.NET 一款获取主流浏览器存储密码的工具

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

【解决方案】华普微基于CMT2150A自发电无线遥控解决方案

一、方案概述 自发电无线遥控设备的概念是指设备自身能够通过能量收集技术&#xff0c;如太阳能、动能收集或其他可再生能源&#xff0c;产生所需的电能来供电&#xff0c;而无需更换电池或外部电源。这种技术的应用可以减少对电池的依赖&#xff0c;降低对环境的影响&#xf…