docker安装nextcloud,聪明人已经收藏了!

2023-05-16

1.为什么要使用分布式锁

使用分布式锁的目的,无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作。

1.1举一个很长的例子

系统 A 是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。由于系统有一定的并发,所以会预先将商品的库存保存在 Redis 中,用户下单的时候会更新 Redis 的库存。此时系统架构如下:

但是这样一来会产生一个问题:假如某个时刻,Redis 里面的某个商品库存为 1。

此时两个请求同时到来,其中一个请求执行到上图的第 3 步,更新数据库的库存为 0,但是第 4 步还没有执行。

而另外一个请求执行到了第 2 步,发现库存还是 1,就继续执行第 3 步。这样的结果,是导致卖出了 2 个商品,然而其实库存只有 1 个。

很明显不对啊!这就是典型的库存超卖问题。此时,我们很容易想到解决方案:用锁把 2、3、4 步锁住,让他们执行完之后,另一个线程才能进来执行第 2 步。

按照上面的图,在执行第 2 步时,使用 Java 提供的 Synchronized 或者 ReentrantLock 来锁住,然后在第 4 步执行完之后才释放锁。

这样一来,2、3、4 这 3 个步骤就被“锁”住了,多个线程之间只能串行化执行

当整个系统的并发飙升,一台机器扛不住了。现在要增加一台机器,如下图:

增加机器之后,系统变成上图所示,假设此时两个用户的请求同时到来,但是落在了不同的机器上,那么这两个请求是可以同时执行了,还是会出现库存超卖的问题。

因为上图中的两个 A 系统,运行在两个不同的 JVM 里面,他们加的锁只对属于自己 JVM 里面的线程有效,对于其他 JVM 的线程是无效的。

因此,这里的问题是:Java 提供的原生锁机制在多机部署场景下失效了,这是因为两台机器加的锁不是同一个锁(两个锁在不同的 JVM 里面)。

那么,我们只要保证两台机器加的锁是同一个锁,问题不就解决了吗?此时,就该分布式锁隆重登场了。

分布式锁的思路是:在整个系统提供一个全局、唯一的获取锁的“东西”,然后每个系统在需要加锁时,都去问这个“东西”拿到一把锁,这样不同的系统拿到的就可以认为是同一把锁。

至于这个“东西”,可以是 Redis、Zookeeper,也可以是数据库。此时的架构如图:

通过上面的分析,我们知道了库存超卖场景在分布式部署系统的情况下使用 Java 原生的锁机制无法保证线程安全,所以我们需要用到分布式锁的方案。

2.高效的分布式锁

在设计分布式锁的时候,应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,以下几点是必须要考虑的:

(1) 互斥

在分布式高并发的条件下,最需要保证在同一时刻只能有一个线程获得锁,这是最基本的一点。

(2) 防止死锁

在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

(3) 性能

对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。

所以在锁的设计时,需要考虑两点。

1、 锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。

2、 锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。

(4) 重入

我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。关于这点之后会做演示。

3.基于Redis实现分布式锁

3.1 使用Redis命令实现分布式锁

3.1.1加锁

加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。

使用的命令**:SET lock_key random_value NX PX 5000**

值得注意的是:

random_value 是客户端生成的唯一的字符串。

NX 代表只在键不存在时,才对键进行设置操作。

PX 5000 设置键的过期时间为5000毫秒。

也可以使用另外一条命令:SETNX key value

只不过过期时间无法设置。

这样,如果上面的命令执行成功,则证明客户端获取到了锁。

3.1.2解锁

解锁的过程就是将Key键删除,但要保证安全性,举个例子:客户端1的请求不能将客户端2的锁给删除掉。

释放锁涉及到两条指令,这两条指令不是原子性的,需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的。脚本如下:

if redis.call('get',KEYS[1]) == ARGV[1] then 
  return redis.call('del',KEYS[1]) 
else
  return 0 
end

这种方式比较简单,但是也有一个最重要的问题:锁不具有可重入性

3.2使用Redisson实现分布式锁

3.2.1Redisson介绍

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

3.2.2Redisson简单使用

Config config = new Config(); 
config.useClusterServers() 
.addNodeAddress("redis://192.168.31.101:7001") 
.addNodeAddress("redis://192.168.31.101:7002") 
.addNodeAddress("redis://192.168.31.101:7003") 
.addNodeAddress("redis://192.168.31.102:7001") 
.addNodeAddress("redis://192.168.31.102:7002") 
.addNodeAddress("redis://192.168.31.102:7003"); 

RedissonClient redisson = Redisson.create(config); 

RLock lock = redisson.getLock("anyLock"); 

lock.lock(); 

lock.unlock(); 

只需要通过它的 API 中的 Lock 和 Unlock 即可完成分布式锁,而且考虑了很多细节:

l Redisson 所有指令都通过 Lua 脚本执行,Redis 支持 Lua 脚本原子性执行

l Redisson 设置一个 Key 的默认过期时间为 30s,但是如果获取锁之后,会有一个WatchDog每隔10s将key的超时时间设置为30s。

另外,Redisson 还提供了对 Redlock 算法的支持,它的用法也很简单:

RedissonClient redisson = Redisson.create(config); 
RLock lock1 = redisson.getFairLock("lock1"); 
RLock lock2 = redisson.getFairLock("lock2"); 
RLock lock3 = redisson.getFairLock("lock3"); 
RedissonRedLock multiLock = new RedissonRedLock(lock1, lock2, lock3); 
multiLock.lock(); 

multiLock.unlock(); 

3.2.3Redisson原理分析

(1) 加锁机制

线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。

线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

(2) WatchDog自动延期机制

在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生。但是在实际情况中会有一种情况,业务处理的时间可能会大于锁过期的时间,这样就可能**导致解锁和加锁不是同一个线程。**所以WatchDog作用就是Redisson实例关闭前,不断延长锁的有效期。

如果程序调用加锁方法显式地给了有效期,是不会开启后台线程(也就是watch dog)进行延期的,如果没有给有效期或者给的是-1,redisson会默认设置30s有效期并且会开启后台线程(watch dog)进行延期

多久进行一次延期:(默认有效期/3),默认有效期可以设置修改的,即默认情况下每隔10s设置有效期为30s

(3) 可重入加锁机制

Redisson可以实现可重入加锁机制的原因:

l Redis存储锁的数据类型是Hash类型

l Hash数据类型的key值包含了当前线程的信息

下面是redis存储的数据

这里表面数据类型是Hash类型,Hash类型相当于我们java的 <key,<key1,value>> 类型,这里key是指 ‘redisson’

它的有效期还有9秒,我们再来看里们的key1值为078e44a3-5f95-4e24-b6aa-80684655a15a:45它的组成是:

guid + 当前线程的ID。后面的value是就和可重入加锁有关。value代表同一客户端调用lock方法的次数,即可重入计数统计。

举图说明

上面这图的意思就是可重入锁的机制,它最大的优点就是相同线程不需要在等待锁,而是可以直接进行相应操作。

3.2.4 获取锁的流程

其中的指定字段也就是hash结构中的field值(构成是uuid+线程id),即判断锁是否是当前线程

3.2.5 加锁的流程

3.2.6 释放锁的流程

4. 使用Redis做分布式锁的缺点

Redis有三种部署方式

l 单机模式

l Master-Slave+Sentienl选举模式

l Redis Cluster模式

如果采用单机部署模式,会存在单点问题,只要 Redis 故障了。加锁就不行了

采用 Master-Slave 模式,加锁的时候只对一个节点加锁,即便通过 Sentinel 做了高可用,但是如果 Master 节点故障了,发生主从切换,此时就会有可能出现锁丢失的问题。

基于以上的考虑,Redis 的作者也考虑到这个问题,他提出了一个 RedLock 的算法。

这个算法的意思大概是这样的:假设 Redis 的部署模式是 Redis Cluster,总共有 5 个 Master 节点。

通过以下步骤获取一把锁:

  • 获取当前时间戳,单位是毫秒。
  • 轮流尝试在每个 Master 节点上创建锁,过期时间设置较短,一般就几十毫秒。
  • 尝试在大多数节点上建立一个锁,比如 5 个节点就要求是 3 个节点(n / 2 +1)。
  • 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了。
  • 要是锁建立失败了,那么就依次删除这个锁。
  • 只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

但是这样的这种算法,可能会出现节点崩溃重启,多个客户端持有锁等其他问题,无法保证加锁的过程一定正确。例如:

假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:

(1)客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。

(2)节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。

(3)节点C重启后,客户端2锁住了C, D, E,获取锁成功。

这样,客户端1和客户端2同时获得了锁(针对同一资源)。

读者福利

读到这的朋友还可以免费领取一份收集的Java进阶知识笔记和视频资料。

资料免费领取方式:关注后,点击这里即可免费领取

秋招我借这份PDF的复习思路,收获美团,小米,京东等Java岗offer

更多笔记分享

秋招我借这份PDF的复习思路,收获美团,小米,京东等Java岗offer

秋招我借这份PDF的复习思路,收获美团,小米,京东等Java岗offer

一资源)。

读者福利

读到这的朋友还可以免费领取一份收集的Java进阶知识笔记和视频资料。

资料免费领取方式:关注后,点击这里即可免费领取

[外链图片转存中…(img-Kl5NA4zY-1625920043328)]

更多笔记分享

[外链图片转存中…(img-EyJLIFKR-1625920043329)]

[外链图片转存中…(img-nzPMMtsy-1625920043330)]

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

docker安装nextcloud,聪明人已经收藏了! 的相关文章

随机推荐

  • 《Linux创建新用户》

    本文主要讲解linux下如何进行新用户创建 登录以及删除操作 文章目录 1 创建新普通用户2 登录普通用户 xff08 一 xff09 已经创建过普通用户 xff08 二 xff09 第一次创建普通用户 3 普通用户切换至超级用户 xff0
  • 通用文字识别API推荐

    今天给大家分享推荐一款很不错的API 通用文字识别OCR xff0c 多场景 多语种 高精度的整图文字检测和识别服务 xff0c 多项指标行业领先 xff0c 可识别中 英 日 韩 法 德多种语言 APISpace 上面有各种各样的API
  • 一文带你看透手机号码归属地

    什么是手机号码归属地 xff1f 我国的手机号码为11位 xff0c 前3位是区分运营商的移动接入码 xff0c 中国的移动接入码都是以1开头的 xff0c 比如131 139 138等等 xff0c 这个移动接入码是由运营商向工信部申请
  • 一文带你看透IP归属地

    什么是IP xff1f IP是指网络之间互连的协议 xff0c 英文全称是 Internet Protocol xff0c 是TCP IP体系中的网络层协议 设计IP的目的是提高网络的可扩展性 xff1a 一是解决互联网问题 xff0c 实
  • Linux arm64架构修改软件源 (统信UOS,麒麟系统修改阿里源、华为源) 国产系统UOS修改apt源

    步骤 xff1a 备份原来自带的软件源 gt 修改软件源配置文件 gt 处理报错 xff08 公钥问题 xff09 gt 清理缓存 gt 更新缓存 gt 安装软件 1 备份系统之前的源 cp etc apt sources list etc
  • 各类免费API接口推荐,再也不怕找不到免费API了

    我收集了一些各类免费的API xff0c 在这里分享给大家 xff0c 希望可以帮助到大家 一 高德开放平台 与真实世界联通 高德开放平台为开发者赋能 xff0c 将地图精致地呈现在您的应用中无论基于哪种平台 xff0c 都可以通过高德开放
  • APISpace 的 ChatGPT 它来了 一分钟快速接入没烦恼

    如此火爆的 ChatGPT 大家肯定都已经知道了 xff0c 我就不多说了 但是呢 xff0c OpenAI 的 ChatGPT 官网注册麻烦 xff0c 接入繁琐 xff0c 且需要海外信用卡才能支付 xff0c 这就让广大的国内开发者头
  • ChatGPT 到底强大在哪里?(文末有彩蛋)

    ChatGPT 是由 OpenAI 开发的一个人工智能聊天机器人程序 xff0c 于2022年11月推出 该程序使用基于 GPT 3 5 架构的大型语言模型并通过强化学习进行训练 ChatGPT 以文字方式交互 xff0c 而除了可以通过人
  • 免费常用API接口

    短信API 短信验证码 xff1a 可用于登录 注册 找回密码 支付认证等等应用场景 支持三大运营商 xff0c 3秒可达 xff0c 99 99 xff05 到达率 xff0c 支持大容量高并发 通知短信 xff1a 当您需要快速通知用户
  • 安全可靠的短信验证码API,毫秒级反应

    短信验证码是一种功能强大的网络安全技术 xff0c 可以确保网站的真实性和安全性 它的使用非常简单 xff1a 用户只需要通过他 她的手机发送位编码短信给网站 xff0c 然后在网站上输入这个验证码 xff0c 就可以实现一种双重认证的功能
  • 如何利用 IP 归属地查询 API 精准锁定用户位置

    引言 在互联网时代 xff0c IP 地址扮演着非常重要的角色 xff0c 它可以帮助我们追踪网站访问者 优化网络服务等等 而 IP 归属地则更进一步 xff0c 它可以帮助我们精确地定位 IP 地址所在的地理位置 xff0c 为数据分析
  • 涨知识!细数银行卡三要素 API 的 N 种验证方法

    引言 银行卡三要素验证 API 是一种基于姓名 身份证号码和银行卡号等三种信息的验证服务 xff0c 主要用于绑定银行卡时校验银行卡是否为该身份信息所有 手机号是否为银行卡绑定手机号 银行卡三要素 API 的验证方法 银行卡三要素 API
  • 常用的热门 API 分享

    手机号码归属地 xff1a 提供三大运营商的手机号码归属地查询 空号检测 xff1a 通过手机号码查询其在网活跃度 xff0c 返回包括空号 停机等状态 手机在网状态 xff1a 支持传入三大运营商的号码 xff0c 查询手机号在网状态 x
  • 通知短信 API 技术细节以及发送流程机制原理解析

    引言 短信是一种简单 直接 高效的通信方式 xff0c 被广泛应用于各个领域 在移动互联网时代 xff0c 短信成为了客户服务 政府通知 公共服务等方面的重要工具 为了更好地利用短信这种通信方式 xff0c 通知短信 API应运而生 短信A
  • vSphere ESXI主机网络分析工具

    1 net stats 该命令可以收集端口统计信息 可以通过net stats h 命令显示所有标志 最常见的是使用 net stats l 来确定所有 VMkernel 接口 vmnic 上行链路和 vNIC 端口的交换机端口号和 MAC
  • 算法中的一些数学问题

    1 欧几里得算法 xff08 辗转相除法 xff09 该算法用来快速计算两个整数的最大公约数 递归算法 xff1a int gcd int a int b if b 61 61 0 return a return gcd b a b 非递归
  • 安装pillow已完成,但仍报错No module named ‘PIL‘

    python 3 6 2 xff0c 安装pillow已完成 xff0c 但仍报错No module named 39 PIL 39 no module named 39 pillow 39 python3 6 我加班还不行吗 的博客 CS
  • 使用汇编语言与C语言实现LED1/LED2/LED3三盏灯点亮

    汇编语言代码段 text global start start LED1点灯LED1 gt PE10 64 1 对LED1进行初始化 RCC AHB4 ENSETR MODER OTYPER OSPEEDR PUPDR 64 2 实现LED
  • linux移动文件夹

    使用命令mv 要移动的文件夹 目标文件夹 假如我要将root下的文件amelia txt移动到opt下的文件夹software中 xff0c 我可以使用命令mv root amelia txt opt software amelia txt
  • docker安装nextcloud,聪明人已经收藏了!

    1 为什么要使用分布式锁 使用分布式锁的目的 xff0c 无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作 1 1举一个很长的例子 系统 A 是一个电商系统 xff0c 目前是一台机器部署 xff0c 系统中有一个用户下订单的接口