分布式锁三种方案

基于数据库的分布式锁(基于主键id和唯一索引)

1基于主键实现分布式锁

2基于唯一索引实现分布式锁

其实原理一致,都是采用一个唯一的标识进行判断是否加锁。

原理:通过主键或者唯一索性两者都是唯一的特性,如果多个服务器同时请求到数据库,数据库只会允许同一时间只有一个服务器的请求在对数据库进行操作,其他服务器的请求就需要进行阻塞等待或者进行自旋。如何实现的呢?可以理解为同一时间只有一个请求能够拿到锁,当方式执行完成过后,对锁进行释放过后,其他请求就可以拿到锁再对数据库进行操作,这样就避免了数据不安全问题。

阻塞:线程等待锁释放的一种方式

自旋:自旋包括了递归自旋,while自旋。意思就是不断地去尝试获取锁,只有获取锁才会停止自旋过程,没有拿到就会一直尝试获取锁。

以下是一个基于MySQL实现分布式锁的示例代码:

import java.sql.*;
import java.util.Properties;public class DatabaseLock {private Connection conn;private static final String dbUrl = "jdbc:mysql://localhost:3306/test";private static final String username = "xxxx";private static final String password = "xxxxx";// 构造函数,建立数据库连接public DatabaseLock() throws SQLException {Properties props = new Properties();props.setProperty("user", username);props.setProperty("password", password);conn = DriverManager.getConnection(dbUrl, props);}// 尝试获取锁public boolean tryLock(String lockId) throws SQLException {PreparedStatement stmt = conn.prepareStatement("INSERT INTO locks (id) VALUES (?)");stmt.setString(1, lockId);try {stmt.executeUpdate();return true;} catch (SQLException e) {if (e.getErrorCode() == 1062) { // 锁已存在return false;} else {throw e;}}}// 释放锁public void releaseLock(String lockId) throws SQLException {PreparedStatement stmt = conn.prepareStatement("DELETE FROM locks WHERE id=?");stmt.setString(1, lockId);stmt.executeUpdate();}
}

在使用时,通过创建一个DatabaseLock实例来获取和释放锁:

DatabaseLock databaseLock = new DatabaseLock();// 尝试获取锁,获取成功返回true,获取失败返回false
if (databaseLock.tryLock("lockId")) {try {// do something} finally {databaseLock.releaseLock("lockId"); // 释放锁}
} else {// get lock failed
}

基于Redis的分布式锁 

下面就来介绍基于Redis的分布式锁,直接上图;

虚拟机A和虚拟机B两个虚拟机都想对可变的共享资源(广义的概念,可以是数据库的某一张表和数据库中的某一行数据 ),就会出现线程安全问题,就需要基于锁模型实现同步互斥的手段,保证只有一个虚拟机中的线程进而实现这个线程的相对安全。现在虚拟机要对共享资源上锁,锁对象是Redis,操作的对象就是这个共享资源(假如数据库的一行数据)。

基于Redis实现分布式锁执行流程:

1虚拟机实例A根据Hash算法选择Redis节点(Redis采用的是集群部署,每个服务器都是一个节点),执行Lua脚本加锁(就是通过setnx方法,即set一个key(主键id)到Redis当中,判断Redis中是否有当前key,没有,就返回1,表示加锁成功,有就返回0,表示加锁失败),并且设置锁的过期时间。当虚拟机A对共享资源上锁成功过后,就拥有了对共享数据的操作权限,然后就可以对共享数据的操作处理,执行事务处理。

问题:在从Redis获取锁的过程和进行设置锁的过期时间过程中出现宕机,就会出现锁一辈子不会被释放?出现死锁问题?

        这时候就需要保证获取锁和设置锁的过期时间两行代码的原子性,就是要么同时成功,要么同时失败,如何实现呢?

        这时候只要保证将两行代码变成一行代码即可。

  原本的setnx和expire是两行代码

if(jedis.setnx(lock_stock,1) == 1){	//获取锁
//=========在这里出现宕机了=====死锁问题出现了=========expire(lock_stock,5)		 //设置锁超时try {业务代码} finally {jedis.del(lock_stock)			 //释放锁}
}

 通过set变成一行代码过后,解决了死锁问题。

if(set(lock_stock,1,"NX","EX",5) == 1){	//获取锁并设置超时try {业务代码} finally {del(lock_stock)			 		//释放锁}
}

 2这时候如果虚拟机B也需要对共享资源进行操作,也去执行lua脚本进行加锁(就是采用setnx的方式--通过set一个key到redis,判断redis中是否已经存储了这个key(行数据的主键id)),如果查询到redis中没有,就会返回1表示加锁成功,如果有就会返回0表示加锁失败 ,这就能保证共享资源同一时间不会被多个虚拟机同时操作。

        3当虚拟机A执行完对自己已经加锁的共享资源执行操作完成之后,必须要执行DEL释放锁,不然其他虚拟机包括虚拟机A都不能再对当前共享资源进行加锁操作,

问题:虚拟机A能保证会执行完成过后一定执行DEL释放锁吗? 答案是:不一定的

        4当虚拟机A执行对共享资源事务操作完成之后,在执行DEL释放锁之前,代码出现问题,抛出异常就会出现这种问题,虚拟机A就永远不能执行DEL释放锁了,就会导致后续上锁都会失败。

问题:所以就出现上面这个执行流程,如何解决呢?         

        5这时候起初指执行lua脚本加锁的时候,存储一个过期时间,当不能主动进行DEL释放锁时,到达Redis设置的过期时间,锁就会过期。

这个时候又会出现另一个问题? 就是虚拟机A在设置的过期时间以内还没有执行完对共享资源的操作,锁就过期了,如何解决呢?

        6这时候就会执行最后一个流程,后台守护线程(类似于Redission内部提供一个监控锁的看门狗),来定期的检查锁是否存在,如果存在,延长key的过期时间,还需要判断事务是否还在正常执行,如果是异常已经抛出异常,就不用进行后台守护线程了,然后等待锁自动过期。

说这么多?其实就是明白其执行原理,而实际开发过程中,大佬们已经使用Redission封装好了上面的具体实现细节。
Redission实现分布式锁【封装了基于Redis的分布式锁】

什么是Redission?
       
简单理解为就是操作Redis的一个工具包,让我们使用Redis更加简单,让使用者能够将精力更集中地放在处理业务逻辑上。

Redisson实现分布式锁

        Redisson官方文档对分布式锁的解释总结下来有两点

            1Redisson加锁自动有过期时间30s,监控锁的看门狗发现业务没执行完,会自动进行锁的续期(重回30s),这样做的好处是防止在程序还没有执行结束,锁自动过期被删除问题
            2当业务执行完成不再给锁续期,即使没有手动释放锁,锁的过期时间到了也会自动释放锁。

 // 获取锁RLock lock = redisson.getLock(LOCK_KEY);try {// 加锁lock.lock();int s = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(REDIS_KEY)));if (s > 0) {// 扣库存s--;System.out.printf("秒杀商品个数剩余:" + s + "\n");// 更新库存stringRedisTemplate.opsForValue().set(REDIS_KEY, String.valueOf(s));} else {System.out.println("活动太火爆了,商品已经被抢购一空了!");}} catch (Exception e) {System.out.println(Thread.currentThread().getName() + "异常:");e.printStackTrace();} finally {// 释放锁lock.unlock();}

基于Zookeeper的分布式锁

什么是Zookeep?

ZooKeeper是一个分布式的协调服务,Zookeeper是基于CP,注重数据的一致性,若主机挂掉则Zookeeper不会对外进行提供服务了,需要选择一个新的Leader出来才能提供服务,不保证高可用性。简单来说zookeeper=文件系统+监听通知机制

Zookeeper数据模型

Zookeeper会维护一个具有层次关系的树状的数据结构,它非常类似于一个标准的文件系统,如下图所示:同一个目录下不能有相同名称的

每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。

有四种类型的znode:

PERSISTENT-持久化目录节点

客户端与zookeeper断开连接后,该节点依旧存在

PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点

客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

EPHEMERAL-临时目录节点

客户端与zookeeper断开连接后,该节点被删除

EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点

客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

监听通知机制

客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。

实现方案:临时顺序目录节点+监听机制

​​​​​​​

​​​​​​​在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案

总结

1基于数据库实现:通常基于主键,或者唯一索引来实现分布式锁,但是性能比较差,一般不建议使用

2基于Redis实现分布式锁:可以使用setnx来加锁 ,但是需要设置锁的过期时间来防止死锁,所以要结合expire使用.为了保证setnx和expire两个命令的原子性,可以使用set命令组合【将setnx和expire结合成一行代码】。

        总之自己封装Redis的分布式锁是很麻烦的,我们可以使用Redissoin来实现分布式锁,Redissoin已经封装好了。

3.基于zookeeper : 使用临时顺序节点+监听实现,线程进来都去创建临时顺序节点,第一个节点的创建线程获取到锁,后面的节点监听自己的上一个节点的删除事件,如果第一个节点被删除,释放锁第二个节点就成为第一个节点,获取到锁。

在项目中可以使用curator,这个是Apache封装好的基于zookeeper的分布式锁方案。

 

 

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

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

相关文章

抉择与未来:高考后专业与学校的深度选择思考

引言 随着2024年高考的尘埃落定,数百万考生及其家庭正面临一个至关重要的决策:在有限的分数条件下,是优先选择专业还是学校?这一选择不仅影响着个人的未来职业道路,也关系到大学生活的质量和个人综合素质的培养。本文将…

git 指定某些文件上传不进行冲突检测

文章目录 使用场景配置 .gitattributes配置 .git/config 使用场景 让某些特定文件在 Git 中总是被本地版本覆盖,不进行差异比较这对于某些自动生成的文件或特定配置文件非常有用,因为你可以避免不必要的合并冲突和差异比较 配置 .gitattributes .gita…

【单片机】DS2431芯片,读写128个字节,程序

ds2431pt&r stm32读写程序&#xff1a; 部分程序&#xff1a; #include "sys.h" #include "delay.h" #include "usart.h"#include <stdio.h> #include <stdlib.h> #include <string.h>#include "sys.h" #incl…

2.00002 查询树初体验

文章目录 瞅一眼查询树相关的结构体Var (primnodes.h : 247)enum RTEKind (parsenodes.h :1026)RangeTblEntry (parsenodes.h :1041)RangeTblRef (primnodes.h :2215)JoinExpr (primnodes.h:2249)FromExpr (primnodes.h:2277)瞅一眼查询树 typedef struct Query (parsenodes.h…

参茸网元宇宙 推进农业保险高质量发展

近日&#xff0c;由中国农业大学国家农业农村保险研究中心、国家农业农村发展研究院、经济管理学院等主办的“2024‘三农’保险发展论坛”在北京举行。论坛主题为“持续推进农业保险高质量发展”。 与会专家表示&#xff0c;近年来&#xff0c;我国新型农业经营主体数量持续增长…

openEuler搭建hadoop Standalone 模式

Standalone 升级软件安装常用软件关闭防火墙修改主机名和IP地址修改hosts配置文件下载jdk和hadoop并配置环境变量配置ssh免密钥登录修改配置文件初始化集群windows修改hosts文件测试 1、升级软件 yum -y update2、安装常用软件 yum -y install gcc gcc-c autoconf automake…

Java编译

编程语言分类 通常我们将编程语言分为高级语言和低级语言&#xff0c;通常高级语言因为其易用性和可移植性而被广泛使用&#xff0c;而低级语言在需要对硬件进行精细控制时非常有用&#xff0c;例如在嵌入式系统编程或操作系统开发中。 高级语言更接近人类语言&#xff0c;易于…

现实网络中排障经验

将某工作站连接到交换机上的几个端口后&#xff0c;无法Ping通局域网内其它电脑&#xff0c;但桌面上“本地连接”图标仍然显示网络连通。后来经过排查&#xff0c;发现由于各种交换机配置都不一样&#xff0c;在配置交换机时&#xff0c;会很容易出现配置错误。所以建议&#…

awtk踩坑记录三:移植awtk-mvvm到Awtk Designer项目

从github下载并编译awtk, awtk-mmvm awtk: https://github.com/zlgopen/awtk/tree/master awtk-mvvm: https://github.com/zlgopen/awtk-mvvm 用awtk-designer新建项目并打开项目目录 首先修改project.json&#xff0c;使其awtk和awtk-mvvm指向上个步骤下载的路径&#xff0c…

【APP_汽修宝】数据采集案例APP_数据解密分析

如果不会写代码&#xff0c;那就出书、写博客、做视频、录播客。 &#x1f4da; S35赛季末王者昭君罗 关键代码定位 使用方法【逆向-快速定位关键代码】通过hook常用函数HashMap方法 动态分析 下面是我们通过访问目标页面时 Frida hook 捕获HashMap的调…

Linux下手动修改服务器时间(没网环境下)

在客户服务器上更新程序时&#xff0c;发现服务器时间不对&#xff0c;现在应该是下午13:44:00&#xff0c;但服务器却显示为&#xff1a;21:40:53&#xff0c;所有是不对的。 date解决办法&#xff1a; 1、由于服务器是没有网的&#xff0c;只能手动设置时间&#xff0c;输入…

【YashanDB认证】-国产数据库-崖山的学习认证之路

一、前序 随着近几年国产数据库的发展&#xff0c;如今的很多的项目中对于底层数据核心数据库选型&#xff0c;越来越要求要符合信创标准&#xff0c;所以在日常工作中对国产数据库的关注也在增加。时常会关注国产数据库的一些实事信息&#xff0c;也会积极参与到各种推广活动&…

【Java核心技术12】面向对象编程(OOP):深入理解类与对象

引言 所有文章均为原创验证&#xff0c;您随手的 关注、点赞、收藏 是我创作最大的动力。 示例代码地址&#xff1a;https://gitee.com/code-in-java/csdn-blog.git Java&#xff0c;作为一种面向对象的编程语言&#xff0c;其核心思想是将数据和操作这些数据的方法封装在一起。…

MySQL NDB Cluster 8.4 这是什么版本

MySQL NDB Cluster 8.4 这是什么版本 MySQL NDB Cluster 8.4 是 MySQL 数据库的一种集群版本&#xff0c;主要用于需要高可用性、高性能和横向扩展的应用程序。NDB Cluster 通过分布式内存存储来实现这些特性&#xff0c;适合于需要快速响应时间和高可用性的场景&#xff0c;如…

idea-Spring框架与ioc容器

Sping是轻量级的开源J2EE框架&#xff0c;可以解决企业应用开发的复杂性 Spring有两个核心部分为Ioc和AOP Ioc:控制反转&#xff0c;吧创建对象过程交给Sping进行管理 AOP:面向切面&#xff0c;不修改代码进行功能增强 创建Maven项目 IDEA-2024 就直接创建java项目即可 创…

PostGreSQL遇到的坑

记录一下&#xff1a; 在postgresql中&#xff0c;alter table a add column b integer 和 alter table ”a“ add column b integer是不一样的&#xff0c; 前者是添加了字段a&#xff0c;后者是添加的是区分大小写的字段a,若后面是添加的“A”那么查询的时候也必须是select…

Android WebSocket长连接的实现

一、为什么需要 WebSocket 初次接触 WebSocket 的人&#xff0c;都会问同样的问题&#xff1a;我们已经有了 HTTP 协议&#xff0c;为什么还需要另一个协议&#xff1f;它能带来什么好处&#xff1f; 答案很简单&#xff0c;因为 HTTP 协议有一个缺陷&#xff1a;通信只能由客…

SpringBoot引入外部依赖包

将需要引入的文件放置到与src同级别的目录下 如上&#xff0c;在src的同级&#xff0c;新建了一个lib目录&#xff0c;将jar包放置其中 在POM文件下&#xff0c;加入如下配置 <dependency><groupId>com.aliyun</groupId><artifactId>com.aliyun.filed…

搭建取图系统app源码开发,满足广泛应用需求

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 图片已成为信息传递的重要媒介&#xff0c;广泛应用于各个领域。为满足日益增长的图片需求&#xff0c;搭建一款高效的取图系统&#xff0c;可以为用户提供便捷、全面的…

linux c++学习笔记整理

文章目录 linux c学习笔记整理环境准备静态库与动态库静态库与动态库理论静态库与动态库示例代码 makemain函数的参数gdb调试程序linux的时间&#xff1a;一、linux的时间操作二、time()库函数三、tm结构体四、loacltime()函数五、mktime()库函数六、gettimeofday()库函数七、程…