分布式锁三种方案

基于数据库的分布式锁(基于主键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.rhkb.cn/news/354740.html

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

相关文章

Linux远程管理日志

实验介绍 本实验旨在实现主机将日志远程发送到堡垒机或远程服务器上,实现通过一台机器管理整个网络内的主机的效果。 准备两台虚拟机作为生产主机和管理机,保证网络通畅,展示如下: 关闭firewalld,通过配置rsyslog&a…

【查缺补漏】python

python查缺补漏 底板除 还有一种除法是//,称为地板除,两个整数的除法仍然是整数: >>> 10 // 3 3你没有看错,整数的地板除//永远是整数,即使除不尽。要做精确的除法,使用/就可以。 因为//除法只…

react用ECharts实现组织架构图

找到ECharts中路径图。 然后开始爆改。 <div id{org- name} style{{ width: 100%, height: 650, display: flex, justifyContent: center }}></div> // data的数据格式 interface ChartData {name: string;value: number;children: ChartData[]; } const treeDep…

主窗体设计

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Python、QT与PyCharm配置完成后&#xff0c;接下来需要对快手爬票的主窗体进行设计&#xff0c;首先需要创建主窗体外层为&#xff08;红色框内&…

SwiftUI 6.0(iOS/iPadOS 18)中全新的 Tab 以及 Sidebar+悬浮 TabView 样式

概览 看来苹果一直对 iPadOS 中标签栏&#xff08;TabView&#xff09;不甚满意。这不&#xff0c;在 WWDC 2024 中苹果又对 TabView 外观做了大幅度的进化。 现在我们可以在顶部悬浮条和左侧的 Sidebar 两种不同布局之间恣意切换 TabView 的外观啦。而且&#xff0c;这在 Swi…

(超详细)基于动态顺序表实现简单的通讯录项目

前言&#xff1a; 我们在上一章节用c语言实现了线性表中的的动态顺序表&#xff0c;那么顺序表就只是顺序表吗&#xff1f;当然不是&#xff0c;使用顺序表结构可以实现很多项目&#xff0c;许多项目的数据结构都会用到顺序表&#xff0c;本章节我们就要使用顺序表实现一个简易…

腾讯云部署的java服务,访问阿里云的mysql数据库,带宽异常偏高,可能是什么原因

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

js语音识别,语音转文字,speech recognition(需要翻墙才能识别)

先上代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width,initial-scale1.0"><title>test</title> </head><body><div id"…

人工智能的头号威胁:投毒攻击

随着掌管数字生活入口的万亿美元俱乐部企业——苹果公司跳入人工智能&#xff08;AI&#xff09;赛道&#xff0c;AI技术民主化的大幕正式拉开&#xff0c;同时也将AI安全问题推向舆论的风口浪尖。 根据瑞银本周一的智能手机调查报告&#xff0c;在中国以外的智能手机用户中&am…

Java多线程基础知识-2

线程的3个方法&#xff1a; Thread.sleep()&#xff1a;当前线程睡眠多少毫秒&#xff0c;让给其他线程去执行。 Thread.yield()&#xff1a;当前线程退出一下&#xff0c;进入到等待队列&#xff0c;让其他线程执行&#xff0c;即让出线程一下。 Thread.join()&#xff1a;…

《模拟联合国2.9—团队协作》

感谢上海财经大学持续的邀请&#xff0c;今天在阶梯教室举办的《模拟联合国2.0—团队协作》沙盘课程圆满结束。尽管场地的限制带来了一定的挑战&#xff0c;但得益于系统思考中“结构影响行为”的原则&#xff0c;我得以在不同场景中巧妙设计课程结构&#xff0c;极大地促进了大…

【JavaScript复习二】选择结构if和Switch(1)

### []( )2、单分支条件分支语句if (条件表达式) { // 条件为真时&#xff0c;做的事情 } else { // 条件为假时&#xff0c;做的事情 } ### []( )2,、多分支的 if 语句if (条件表达式1) { // 条件1为真时&#xff0c;做的事情} else if (条件表达式2) { // 条件1不满足&…

股票交易系统

效果展示&#xff0c;如下动图&#xff1a; 首先简述一下股票交易规则&#xff1a; 买卖股票&#xff0c;股民可以自行选择股票的买入或卖出价格和股票的数量&#xff0c;但是用户不一定马上就交易成功&#xff0c;只有当股票价格低于买入价才有机会买入&#xff0c;高于卖出价…

ccie在香港值钱吗?ccie认证很难考吗?

思科ccie认证可以算得上是网络高级工程师的一个标配证书&#xff0c;特别是在香港工作的朋友更是需要尽早拿下这个认证&#xff0c;它能让你的求职之路更为顺利。而已经入职的工程师为了不被时代所淘汰&#xff0c;也需要该证书保驾护航。 你知道ccie在香港值钱吗?ccie认证是不…

redis高可用-哨兵机制

一&#xff1a;背景 上一节我们已经实现了redis的主从同步&#xff0c;从而实现服务的流量分摊和数据高可用&#xff0c;但是出现故障以后&#xff0c;需要人工手动接入&#xff0c;手动切换主从&#xff0c;来实现故障转移。这是比较麻烦的&#xff0c;毕竟人不能实时盯着服务…

Covalent实现对1000亿笔链上交易解析,支持AI长期数据可用性

在区块链与人工智能&#xff08;AI&#xff09;交汇处&#xff0c;讨论往往集中于去中心化推理和去中心化训练等方面。然而&#xff0c;这一数据的关键组成部分却一直未得到足够的重视。一个主要问题是&#xff1a;我们如何保护 AI 模型中的数据不受偏见和操纵的影响&#xff1…

【计算机组成原理】指令系统考研真题详解之拓展操作码!

苏泽 “弃工从研”的路上很孤独&#xff0c;于是我记下了些许笔记相伴&#xff0c;希望能够帮助到大家 另外&#xff0c;利用了工作之余的一点点时间&#xff0c;整理了一套考研408的知识图谱&#xff0c; 我根据这一套知识图谱打造了这样一个408知识图谱问答系统 里面的每一…

C语言的网络编程

目录 引言 一、TCP/IP概述 1. TCP&#xff08;Transmission Control Protocol&#xff09; 2. UDP&#xff08;User Datagram Protocol&#xff09; 二、Socket编程基础 1. 服务器端 2. 客户端 三、URL与HTTP编程 1. 使用libcurl进行HTTP请求 表格总结 TCP/IP与Socke…

镭速传输界面优化之静态文件加载

镭速一直是众多企业传输大文件和大数据的优选对象&#xff0c;速度快、稳定且安全是市场上传输软件脱颖而出的立杆标签&#xff0c;那么同样在界面优化和体验的强大也能够给企业用户带来许多直观的感受&#xff0c;那么今天我们就来谈谈镭速是如何做到这些的&#xff0c;在界面…

【锐捷】VSU环境下部署VAC

配置要求 1.两台核心交换机部署VSU&#xff0c;Domain ID为1&#xff0c;S1的Switch ID为1&#xff0c;优先级为150&#xff0c;设备描述为VSU-S1&#xff1b;S2的Switch ID为2&#xff0c;优先级为120&#xff0c;设备描述为VSU-S2&#xff1b;两台设备的G0/48口用于BFD双机检…