分布式锁方案学习

news2024/11/26 14:40:03

很久没有写文章了,前些天的面试被问到了分布式锁的解决方案,回答的比较简单,只知道Redis,Mysql,Zookeeper能够作为分布式锁应用,今天就来详细的学习一下这三种分布式锁的设计思想及原理。

能够来看这篇文章的小伙伴我觉得应该都知道什么是分布式锁吧,但我还是多说一下吧,在现在普遍微服务架构的时代,我们的项目架构一般是这样的:

我自己画的这个结构图比较简单,我也不借用网上的图片了,因为太复杂的结构对于初学的同学不是很友好,我们只需要知道在项目中不会只运行单个服务,而是会运行同一套代码的多个服务。

我们描绘一个业务场景。春运时,我们需要在数据库中查询车票数量,返回给服务后进行业务处理,扣减后的车票数量再更新到数据库。因为会并发访问,购买车票,所以我们需要对车票数据进行加锁。又因为java中的锁都是单机锁,作用范围只限于单个服务中,所以我们需要分布式锁来解决多个服务并发访问数据库所造成的数据不安全问题。(如果还是不太懂的小伙伴可以私聊探讨哦)

接下来我们就逐个介绍一下分布式锁的设计方案。

1.通过Mysql创建分布式锁

我们可以使用 select * from table where xxx = xxx for update 通过for update关键字为数据加锁(排他锁,for share为共享锁),如果有查询条件则对查询条件加锁,无查询条件则对加锁加锁后需要设置手动提交(重要,不然会造成死锁),当我们通过for update加锁后,就可以对数据进行update操作了,操作后通过commit将整个事务提交进行解锁。但是在工作中mysql分布式锁是不推荐的,因为这会影响数据库的性能;有时不精确的查询条件会造成锁定较多数据;业务流程中出现异常时不好处理(业务异常可以通过事务回滚取消,但宕机等异常会在一段时间内影响数据库)。

2.通过Redis创建分布式锁

redis分布式锁是工作中常用的一种分布式锁,也是我之前工作中使用的类型。

如果小伙伴们有不太知道Redis的可以看一下其他文章。

Redis基本命令集这里有Redis操作使用的基本命令。

在Redis中我们可以使用setnx 命令来进行加锁。通过del 命令进行释放锁。

但这样会有一个问题,那就是当一个服务通过setnx命令获取了锁后,业务处理时异常,这时Redis锁就无法释放——会产生死锁问题,这个问题我们可以通过expire命令添加过期时间,当时间到了该锁会自动释放。但是两个命令不具备原子性,所以我们可以通过 setex 命令或者 set 锁名称 锁的值 EX 过期时间 NX 来创建Redis锁。

上面我们解决了死锁问题后,又发现针对一个锁,任意的服务通过 del 命令都可以释放锁,但正确情况应该是只有当前服务的当前线程才可以进行锁的释放,所以我们在加锁时需要通过将锁的值设置为当前线程ID,或者将线程ID作为部分进行加锁释放锁前通过 get 锁名称 方式来判断释放锁的线程与加锁线程是否为同一个,只有加锁线程与解锁线程为同一个时才可以进行释放。释放锁时为了保证 get 命令与 del 命令的原子性,我们可以使用lua脚本去进行处理。

如果Redis锁的过期时间不好确定,此时我们可以通过守护线程+延时队列(DelayQueue)去延长锁的过期时间。设置守护线程,当主线程执行时,守护线程通过延时队列去获取需要延长时间的锁,并对其进行延时,如果获取不到锁则进行阻塞等待

我之前的工作中使用的是redission框架生成的分布式锁,我也将简单的引用放在下面的连接中了,有需要的小伙伴们可以看一下:

Redis分布式锁

此时我们的分布式锁还是存在问题的,上面说的内容只限于单机Redis,但在实际工作中一般都是Redis集群,主从+哨兵的配置模式,如果不了解分布式集群的小伙伴们,我这里也有文章,可以稍微了解一下:

Redis学习笔记(六)——发布订阅、主从复制及缓存击穿、穿透、雪崩

那么就有以下问题,当我们在Redis主节点中创建了一个分布式锁后,准备去执行业务逻辑时,Redis主节点不小心挂掉了,此时主节点的数据还没有完全同步到从节点,所以此时从节点中选举产生的新的主节点中就没有该锁存在,其他线程可以重新加锁执行业务,那么就会导致线程不安全。这种问题我们可以通过RedLock(红锁)处理

RedLock处理方式:

1.需要多个非集群的单个主节点,单独用来进行RedLock逻辑处理(一般为5台,我们也用5台来举例)。

2.客户端先获取当前的时间戳T1

3.客户端依次向Redis实例发起加锁请求

4.如果大于半数(此时为3台)成功,且大于半数成功时的时间戳T2-T1 < 锁的过期时间则成功

5.释放锁时需要向5台Redis发起释放请求

大于半数成功是因为只要大于半数实例加锁成功,其他实例肯定加锁失败(因为达不到半数加锁成功),并且加锁成功的实例越少,性能越好,所以大于半数加锁成功为最终成功

其次需要判断时间戳T2-T1 < 锁的过期时间,防止我们依次加锁的时候最后一个锁添加成功时第一个实例的锁已经过期

释放锁需要向所有实例发起请求,是防止有部分实例残存已经释放的锁

RedLock存在NPC问题N (Network Delay) 网络延迟P (Process Pause) 进程暂停(GC)C (Clock Drift) 时钟漂移 (多个服务中的时间可能存在差异)

前两种可以通过时间戳判断进行解决,时钟漂移很难解决。工作中不太建议用,但是需要了解。

3.通过Zookeeper创建分布式锁

Zookeeper实际就是一个文件系统,通过数据目录的原子性特征实现分布式锁(原理与同一目录层级下无法创建两个相同名称的文件或文件夹相似)。

我们通过创建文件节点的方式来创建锁,文件系统天然唯一同一层级下不会出现多个相同名称的文件节点,保证了锁的唯一性

死锁问题我们可以通过创建临时节点来解决,当前线程在zookeeper中会通过会话session来创建临时节点,当线程挂掉后临时节点也会随之释放,所以不会产生死锁问题

锁被释放后,其他等待锁的线程如何通知?这个问题可以通过

1.等待线程主动轮询的方式解决,但此方式会导致zookeeper服务压力过大,并且会有轮询间隔时间延迟,比如5秒轮询一次,当前轮询完毕后1秒锁被释放,其他线程还需要等待4秒才能再次轮询。

2.我们可以通过watch机制来解决延迟问题,监听节点是否释放删除,当锁释放后第一时间等待线程就可以进行请求,但是此时服务过多还是会导致压力问题。

3.所以最终方式是我们通过排队,按序号创建临时节点,每个节点watch监听前一个节点,如果前一个节点释放删除了,我们再判断是否为第一个节点,如果为第一个节点则获取锁,如果非第一个节点则重新watch上一个节点(zk分布式锁是公平锁)

在项目中使用zookeeper创建分布式锁

zk属于不可重入锁,借助curator框架实现可重入性。

先导入依赖:

		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.8.0</version>
		</dependency>
<!--		zookeeper客户端-->
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
			<version>5.2.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
			<version>5.2.1</version>
		</dependency>

编写配置文件:

#Zookeeper配置信息
curator:
  #服务器连接地址
  connectString: 127.0.0.1:2181
  #重试次数,当会话超时后,curator会间隔elapsedTimeMs毫秒重试一次,共重试retryCount次
  retryCount: 5
  elapsedTimeMs: 5000
  #会话超时时间
  sessionTimeoutMs: 60000
  #连接超时时间
  connectionTimeoutMs: 5000

编写配置信息:

package com.project.springtest.zk;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @description:
 * @author: Me
 * @createDate: 2023/6/4 15:26
 * @version: 1.0
 */
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class WrapperZk {
    private int retryCount;
    private int elapsedTimeMs;
    private String connectString;
    private int sessionTimeoutMs;
    private int connectTimeoutMs;
}

创建客户端对象:

package com.project.springtest.zk;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description:
 * @author: Me
 * @createDate: 2023/6/4 15:26
 * @version: 1.0
 */
@Configuration
public class CuratorConfig {
    @Autowired
    private WrapperZk wrapperZk;

    // 返回一个zookeeper客户端的bean
    @Bean
    public CuratorFramework curatorFramework() {
        // 配置一个超时策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(wrapperZk.getElapsedTimeMs(), wrapperZk.getRetryCount());
        CuratorFramework client = CuratorFrameworkFactory.newClient(wrapperZk.getConnectString(),
                wrapperZk.getSessionTimeoutMs(),
                wrapperZk.getConnectTimeoutMs(),
                retryPolicy);
        client.start();
        return client;
    }
}

简单使用:

package com.project.springtest.controller;

import lombok.Data;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Data
public class MyService {

    @Autowired
    private CuratorFramework curatorFramework;
    static InterProcessLock lock;

    public String service() throws Exception {
        // 保证同一个应用中只有一个线程来创建锁
        synchronized (this) {
            if (lock == null) {
                lock = new InterProcessMutex(curatorFramework, "/加锁的业务内容");
            }
        }
        // 5秒钟去获取一次锁
        if (lock.acquire(5, TimeUnit.SECONDS)) {
            // 加锁成功后执行业务逻辑
        }

        // 锁释放
        lock.release();
        return "OK";
    }
}

工作中一般还是用redis来构建分布式锁,因为redis的性能太好了,但zk也有自己特殊的应用场景,例如并发量不高,且需要保持强一致性(简单来说就是任意时刻所有节点的数据都是一致的)的场景下就需要使用zk来作为分布式锁了。简单来说就是看项目中对可用性和强一致性的侧重点来选择啦。

今天分布式锁就学习到这里了,希望对大家能够有帮助。

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

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

相关文章

05WEB系统的通信原理图

WEB系统的通信原理 名称作用URL统一资源定位符, 例如:http://www.baidu.com域名在https://www.baidu.com/这个网址中www.baidu.com 是一个域名IP地址计算机在网络当中的一个身份证号, 在同一个网络当中IP地址是唯一的, 有了IP地址两台计算机直接才能建立连接通信端口号一个计算…

如何让你的汇报更有说服力?数据监控是关键!

第5讲中玩过一个扫雷游戏&#xff0c;目标是排除计划中的“延期地雷”&#xff0c;但是&#xff0c;总有些“雷”防不胜防。我们在做计划的时候&#xff0c;明明已经想得非常周全了&#xff0c;可是&#xff0c;真正开工几天之后才发现&#xff0c;很多事情并没有那么简单。 1…

4-1 活动安排问题

1.什么是贪心算法 我的理解&#xff1a; 贪心算法是一种常用的问题求解方法&#xff0c;它在每个步骤上都选择当前看起来最优的解&#xff0c;而不考虑整体的最优解。简单来说&#xff0c;贪心算法采取局部最优的决策&#xff0c;希望通过每个局部最优解的选择&#xff0c;最终…

网络安全面试题大全(整理版)500+面试题附答案详解,最全面详细,看完稳了

前言 随着国家政策的扶持&#xff0c;网络安全行业也越来越为大众所熟知&#xff0c;想要进入到网络安全行业的人也越来越多。 为了拿到心仪的Offer之外&#xff0c;除了学好网络安全知识以外&#xff0c;还要应对好企业的面试。 作为一个安全老鸟&#xff0c;工作这么多年&…

全网最全的网络安全技术栈内容梳理(持续更新中)

前言 本文篇幅比较长~~耐心看完哦~ 网络安全真的那么好吗 据我了解现在我国网络安全人才缺口相当大&#xff0c;预计在2023年这方面人才缺口达到327万&#xff0c;我每年这方面的大学生才2W多。现在各政企都在发展数字化变革&#xff0c;对网络安全方面人才也是垂涎若渴&…

【31】核心易中期刊推荐——电子信息技术计算机技术

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

06SpringCloud rabbitmq安装

rabbitmq安装 说明&#xff1a;请使用资料里提供的CentOS-7-x86_64-DVD-1810.iso 安装虚拟机. 1. 安装依赖环境 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel …

动态规划-概率DP

Bag of mice 题面翻译 https://www.luogu.com.cn/problem/CF148D 袋子里有 w w w 只白鼠和 b b b 只黑鼠 &#xff0c;A和B轮流从袋子里抓&#xff0c;谁先抓到白色谁就赢。A每次随机抓一只&#xff0c;B每次随机抓完一只之后会有另一只随机老鼠跑出来。如果两个人都没有抓到…

【小沐学GIS】基于Cesium实现三维数字地球Earth(CesiumJS入门安装)

文章目录 1、简介1.1 平台1.1.1 Cesium ion1.1.2 CesiumJS1.1.3 Cesium for Unity1.1.4 Cesium for Unreal1.1.4 Cesium for Omniverse1.1.5 Cesium for O3DE 1.2 支持的数据格式 2、CesiumJS安装3、代码测试3.1 安装node3.2 安装依赖项3.3 运行测试示例3.4 注册获取token 4、扩…

常见的前端框架

随着前端行业的发展&#xff0c;前端框架越来越多出现&#xff0c;为我们的项目开发工作带来了极大的便利&#xff0c;那目前主流的前端框架有哪些呢&#xff1f; 工作中我们常用的前端框架有vue框架、React框架、Bootstrap框架、Angular框架等&#xff0c;下面给大家简单介绍…

【AI绘图】二、stable diffusion环境准备与安装

前一篇&#xff1a;一、stable diffusion的发展史 放一张SD的效果图 硬件配置要求 Stable Diffusion是使用显卡生成图片&#xff0c;对电脑硬件有一定要求。 电脑配置最核心的关键点&#xff1a;看显卡、看内存、看硬盘、看 CPU。 显卡&#xff1a;N 卡&#xff08;英伟达 N…

基于内点法求解最优潮流研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Spring源码(二)— AbstractApplicationContext

上一篇文章简单的介绍了一下Spring框架大体的一个执行流程&#xff0c;整个专栏的内容也会根据第一篇序言中的流程图一步一步的向下梳理&#xff0c;并会慢慢补充更多的细节进去。 Test 创建ClassPathXmlApplicationContext来解析xml。 public class Test {public static vo…

剖析 OpenShift 中的 DNS

深入分析 OpenShift 内部 DNS OpenShift 中的DNS 相关组件及其配置1.1 Pod 中的 DNS 配置1.2 Pod 所在宿主机上的 DNS 配置及服务1.2.1 resolv.conf 文件 DNS 配置DNS 查询流程为什么需要内部 DNS&#xff1f; 本文基于 OpenShift 3.11&#xff0c;Kubernetes 1.11 进行测试 O…

2023/6/4周报

目录 摘要 论文阅读 1、标题和现存问题 2、使用GNN进行文本分类 3、INDUCT-GCN 4、实验准备 5、实验结果 深度学习 1、时空图的种类 2、图在环境中的应用 3、STGNN 总结 摘要 本周在论文阅读上&#xff0c;阅读了一篇InducT-GCN:归纳图卷积文本分类网络的论文。基…

python-pandas按各种时间统计和案例

使用到的库 pandas、matplotlib、numpy 使用到的函数 df.resample(“H”).sum() 参数 B business day frequency C custom business day frequency (experimental) D calendar day frequency W weekly frequency M month end frequency BM business month end frequency CBM…

【奶奶看了都会】云服务器ChatGLM模型fine-tuning微调,让你拥有自己的知识库

1.背景 大家好啊&#xff0c;上次给大家写了ChatGLM-6B的部署使用教程&#xff0c;【奶奶看了都会】云服务器部署开源ChatGLM-6B&#xff0c;让你拥有自己的ChatGPT 但是因为模型比较小的问题&#xff0c;所以日常工作中可能用不上。而且大家更希望的是模型能训练自己的数据&…

【Python Bokeh】零基础也能轻松掌握的学习路线与参考资料

Python Bokeh是一款为开发者提供数据可视化的Python库。它可以帮助开发者轻松地创建交互式网页应用程序&#xff0c;而无需编写大量的JavaScript代码。Bokeh支持各种绘图类型和工具&#xff0c;包括线图、散点图、条形图等。Python Bokeh非常适合在大数据分析、商业智能和数据科…

chatgpt赋能python:Python去除重复元素的几种方法

Python去除重复元素的几种方法 在Python编程中&#xff0c;去除列表、集合、字典等数据结构中的重复元素是一个常见的操作。本文将介绍Python中去除重复元素的几种方法&#xff0c;并分析它们的优缺点。 方法一&#xff1a;使用set去重 Set是Python中的一种集合类数据结构&a…

17_Linux根文件简介与Busybox构建文件系统

目录 根文件系统简介 文件目录简介 BusyBox简介 编译BusyBox构建根文件系统 修改Makefile添加编译器 busybox中文字符支持 配置 busybox 编译busybox 向根文件系统添加lib库 向rootfs的“usr/lib”目录添加库文件 创建其他文件夹 根文件系统初步测试 根文件系统简介…