分布式知识整理

分布式锁

以商场系统超卖现象举例

 超卖现象一

现象:

商品卖出数量超出了库存数量。

产生原因:

扣减库存的动作在程序中进行,在程序中计算剩余库存,在并发场景下,导致库存计算错误。

代码复现

es.shutdown();

cyclicBarrier  作用是确保多个线程同时扣减库存。

countDownLatch  确保执行完之前,线程池不关闭

解决方法

注意,库存做个大于0的判断

超卖现象二

现象

系统中库存变为负数
 

产生原因

1  并发检验库存,造成库存充足的假象

2  update更新库存,导致库存为负数

1  检索商品库存,如果商品库存为负数,抛出异常,则更新操作回滚

2 加锁

单体加锁

synchronized 关键字

当使用 synchronized 关键字时,可以分别应用于实例方法、静态方法和代码块,以确保线程安全。以下是分别对这三种情况的加锁示例:

1. 实例方法加锁示例:

public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}
}

在上面的示例中,increment 方法使用 synchronized 关键字修饰,因此在每次调用该方法时,会对当前对象进行加锁,确保同一时刻只有一个线程可以执行 increment 方法。

2. 静态方法加锁示例:

public class SynchronizedExample {private static int count = 0;public static synchronized void increment() {count++;}
}

在这个示例中,increment 方法被声明为静态方法并使用了 synchronized 关键字修饰。这会导致对类的 Class 对象进行加锁,确保同一时刻只有一个线程可以执行这个静态方法。

3. 代码块加锁示例:

public class SynchronizedExample {private static final Object lock = new Object();private int count = 0;public void increment() {synchronized (lock) {count++;}}
}

在这个示例中,使用了一个私有的静态对象 lock 作为锁,并在 increment 方法中使用 synchronized 块来对一段代码进行加锁,确保同一时刻只有一个线程可以执行这段代码块。

以上这些示例展示了 synchronized 关键字在不同场景下的应用,可以确保线程安全,并避免出现并发访问问题。

ReentrantLock

当需要更灵活地控制加锁和解锁的时候,ReentrantLock 是一种好的选择。下面是一个使用 ReentrantLock 加锁的示例:

import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private int count = 0;private ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}
}

在上面的示例中,首先创建了一个 ReentrantLock 对象,然后在 increment 方法中使用 lock() 来获取锁,在操作完成后使用 unlock() 来释放锁。这样可以确保在加锁的过程中发生异常时,锁仍然能够被正确释放,避免出现死锁的情况。

ReentrantLock 还提供了更多的灵活性,如可以实现公平锁和非公平锁、可以中断等待锁的线程等功能,更适用于复杂的并发控制场景。

单体加锁的局限性

上述示例中单体加锁的两种方式(synchronized 关键字和 ReentrantLock)虽然可以在某些情况下提供线程安全性,但也存在一些局限性,这些局限性包括:

1. 互斥性:单体加锁是一种悲观锁机制,当一个线程持有锁执行临界区代码时,其他线程必须等待释放锁才能执行。这可能导致性能问题,特别是在并发度较高的情况下。

2. 潜在的死锁风险:如果在加锁的过程中发生异常或逻辑错误,可能导致锁没有被正确释放,从而引发死锁问题。

3. 缺乏灵活性:synchronized 关键字和 ReentrantLock 的加锁范围仅限于方法或代码块内部,这可能限制了并发控制的粒度和灵活性。有些复杂的并发控制场景可能需要更细粒度的加锁或释放锁的控制,此时单体加锁机制可能无法满足要求。

5. 无法跨节点同步:单体加锁是基于进程内的锁机制,无法在分布式环境中实现跨节点的同步。在多个节点之间无法保证互斥性,并且无法实现全局的锁机制。

6. 性能瓶颈:在分布式环境中,如果使用单体加锁来保护共享资源,会导致性能瓶颈。由于加锁的范围扩大到整个服务实例或整个代码块,会造成并发度的降低,影响整体的吞吐量和性能。

7. 高开销和复杂性:由于需要维护全局一致的状态,单体加锁需要进行大量的网络通信和同步操作,增加了额外的开销和复杂性。

为了解决这些问题,分布式环境中常用的并发控制机制是分布式锁。分布式锁是一种基于分布式协议和算法实现的锁机制,可以在分布式环境中保证互斥性和一致性。常见的分布式锁实现包括基于数据库的锁、基于缓存的锁(如 Redis 锁),以及基于 ZooKeeper 等分布式协调服务的锁。

使用分布式锁可以解决分布式环境中跨节点同步和锁竞争的问题,提高性能和可伸缩性。但是需要注意,分布式锁也可能引入其他问题,如死锁、性能瓶颈等。因此,在设计和使用分布式锁时需要谨慎考虑,并综合考虑系统的一致性、性能和可伸缩性需求。

常见分布式锁

当在分布式系统中实现分布式锁时,可以使用不同的技术作为后端存储和同步机制,常见的包括 Redis、数据库和 ZooKeeper。下面我们将分别讨论它们作为分布式锁的原理、优缺点等。

当在分布式系统中实现分布式锁时,可以使用不同的技术作为后端存储和同步机制,常见的包括 Redis、数据库和 ZooKeeper。下面我们将分别讨论它们作为分布式锁的原理、优缺点等。

 Redis 分布式锁

原理:


- 使用 Redis 作为后端存储,基于 Redis 的 SETNX(set if not exists)命令来实现分布式锁。当某个客户端成功获取锁时,会在 Redis 中创建一个对应的锁键,并设置合适的超时时间,其他客户端获取不到锁。

优点:


- **高性能:** Redis 是内存数据库,读写性能出色,适合作为分布式锁的后端存储。


- **支持阻塞和非阻塞式获取锁:** Redis 提供了支持阻塞以及非阻塞式的锁获取方式(通过 BLPOP 或者 SETNX 结合 EXPIRE 命令)。


- **支持可重入:** 可以通过为每个锁键加上唯一标识,实现可重入性。

缺点:


- **单点故障:** Redis 单节点故障时可能导致锁不可用,需要通过 Redis Sentinel 或 Redis Cluster 等方式解决高可用性问题。
- **并发数受限:** Redis 作为单机或者主从部署模式下,可能受限于单机的并发连接数。

数据库分布式锁

原理:


- 使用关系型数据库的乐观锁或者悲观锁来实现分布式锁,可以利用数据库的事务和唯一索引来保证原子性和唯一性。

优点:


- **成熟稳定:** 数据库作为企业级数据存储系统,具有成熟的高可用、一致性和持久性解决方案。
- **适用性广:** 已有的数据库系统可以直接用于存储锁信息,无需引入新的中间件。

缺点:


- **性能:** 与 Redis 相比,数据库的读写性能通常较差,可能影响锁的获取和释放性能。
- **数据库连接资源消耗:** 大量并发获取锁可能导致数据库连接池耗尽。
- **死锁风险:** 数据库锁对于跨事务并发控制必须慎重处理,避免出现死锁。

当使用 ZooKeeper 作为分布式系统中的分布式锁时,通常会利用 ZooKeeper 的顺序节点(Sequential ZNode)和 Watches 机制来实现分布式锁。下面是详细的介绍以及和 Redis 的对比:

ZooKeeper

工作原理

1. 创建锁节点: 客户端在尝试获取锁时,在 ZooKeeper 上创建一个临时的、有序的顺序节点,代表自己的锁请求。

2. 获取锁: 客户端检查自己创建的节点是否是当前序列中最小的节点,如果是,则表示获得了锁;如果不是,则注册前一个节点的 Watcher 监听,然后进入等待状态。

3. 释放锁:*当客户端不再需要锁时,直接删除自己创建的节点,其他等待的客户端会通过监听到节点删除事件来尝试获取锁。

优点

- 高可用性:*ZooKeeper 提供了高可用的分布式协调服务,适合作为分布式锁的后端存储,具有良好的稳定性和一致性。
- 顺序性:*ZooKeeper 的顺序节点可以使客户端获取锁的顺序有序,并且通过 Watches 机制实现高效的事件通知,避免了轮询导致的资源浪费。

缺点

- 复杂性:*使用 ZooKeeper 需要额外的运维成本和学习成本,部署和维护相对复杂。
- 性能: ZooKeeper 的性能可能不如 Redis 那样出色,尤其在高并发场景下可能受到影响。

 ZooKeeper 和 Redis 分布式锁的对比

- 一致性和可靠性:ZooKeeper 提供了较高的一致性和可靠性,适合作为分布式锁的后端存储;而 Redis 虽然性能出色,但在一致性和可靠性方面略逊一筹。
- 部署复杂性:ZooKeeper 的部署和维护相对复杂,需要专门的运维人员进行管理;而 Redis 相对来说更加简单和容易上手。
- 性能: Redis 作为内存数据库,读写性能通常更优,适用于对性能要求较高的场景;ZooKeeper 在高并发场景下可能性能较为有限,需要权衡选择。

最终选择 ZooKeeper 还是 Redis 作为分布式锁的后端存储,需要根据具体的业务需求、系统的性能要求以及运维成本等方面进行综合考虑。

curator分布式锁和redisson分布式锁

Curator是Netflix开源的一组Apache ZooKeeper客户端库的高级API。Curator 提供了一套易用

基于分布式锁解决定时任务的重复问题

当使用 Redis 实现分布式锁来解决定时任务的重复问题时,可以通过以下步骤来实现:

1. 首先,实现一个定时任务,例如每隔一定时间执行某个任务。

2. 在任务执行之前,客户端尝试获取 Redis 中特定的锁,比如使用 SETNX 命令来设置一个特定的键。如果返回成功,即获取到了锁,就可以执行任务;否则,任务不应该被执行。

3. 在任务执行完毕后,释放锁,通过 DEL 命令来删除锁对应的键。

这是一个基本的使用 Redis 实现分布式锁解决定时任务重复问题的例子。然而,在实现过程中可能会遇到一些问题,如下所示:

- 锁竞争问题:*在高并发的情况下,多个客户端可能同时尝试获取同一个锁,从而导致竞争。只有一个客户端能够成功获取到锁,其他客户端则不能执行任务。

- 锁超时问题:如果设置的锁超时时间过长,那么在某个客户端执行任务期间,该客户端意外崩溃或失去连接,没有及时释放锁,就会导致其他客户端无法获取锁,进而无法执行任务。

为了解决这些问题,可以采取以下方案:

- 设置锁的有效期限制: 在获取锁的时候,使用 SETNX 命令设置一个过期时间,避免某个客户端崩溃或失去连接而不能释放锁。可以使用 PEXPIRE 命令来设置锁的过期时间。

- 添加唯一标识符:当某个客户端成功获取到锁时,在锁的键名中添加一个唯一标识符,使得只有拥有相应标识符的客户端才能释放该锁。这样可以避免其他客户端误释放锁。(当两个客户端在几乎相同的时间内向 Redis 请求锁时,根据 Redis 的单线程执行特性,Redis 会依次处理这两个客户端的请求。由于执行速度非常快,Redis 无法辨别两个请求的先后顺序,因此有可能会出现两个客户端同时获得锁的情况。)

- 使用 Redlock 或 RediLock 算法:Redlock 或 RediLock 是一种分布式锁算法,在 Redis 集群环境中使用多个 Redis 实例来实现分布式锁,提供更高的可靠性和鲁棒性。

红锁(Redlock)是一种分布式锁算法,被设计用于在Redis集群环境中实现分布式锁。它旨在提供更高的可靠性和鲁棒性,尤其针对网络分区和故障恢复的情况。

红锁算法的原理如下:

1. 客户端根据一致性哈希算法将锁映射到多个Redis实例上,形成一个由n个实例组成的锁集群。

2. 客户端尝试在每个Redis实例上获取锁。通过使用`SETNX`命令来设置一个特定键和值,如果返回成功则客户端获取到了锁。

3. 客户端记录获取锁成功的实例数量,并计算时间戳。

4. 客户端检查是否超过了超时时间(通常是锁的有效期的一半),以及是否获得了大部分实例锁的数量。如果是,则客户端认为成功获取到了锁。

5. 如果客户端无法获取到过半数的实例锁,或者超时了,则客户端尝试释放已经获取的锁。

红锁算法的优点是考虑了分布式环境可能出现的网络分区和故障恢复问题。通过使用多个Redis实例进行锁的获取和释放,能够提供更高的可靠性和鲁棒性,同时保证只有一个客户端能够获得锁。

针对定时任务的重复问题,可以使用红锁算法结合定时任务的处理方式来解决。具体实现步骤如下:

1. 在每个定时任务执行前,客户端使用红锁算法获取一个分布式锁。

2. 获取锁成功后,执行定时任务。

3. 定时任务执行完毕后,客户端释放分布式锁。

这样可以确保在分布式环境中,同一时间只有一个客户端能够获取到锁,从而避免了定时任务的重复执行问题。同时,红锁算法的可靠性和鲁棒性能够应对网络分区和故障恢复的情况,提供更强大的分布式锁功能。

需要注意的是,使用 Redis 实现分布式锁时,仍然需要保证 Redis 的高可用性和故障恢复能力,可以通过 Redis Sentinel 或 Redis Cluster 来实现。同时,还需要谨慎处理 Redis 连接池的资源管理,以避免资源耗尽的问题。

常见面试题

如何解决分布式事务

在解决分布式事务问题时,可以采用以下几种常见的解决思路:

1. 强一致性分布式事务(Two-Phase Commit,2PC):2PC 是一种经典的解决方案,基于协调者和参与者之间的协议来保证所有参与者要么全部提交,要么全部回滚。协调者负责协调参与者的状态,并决定是否进行提交。然而,2PC 存在单点故障问题,并且在分布式系统规模较大时性能和可扩展性会受到限制。

2. 最终一致性分布式事务(Saga Pattern):Saga Pattern 将一个大事务拆分为多个小事务,并通过补偿操作来保证最终一致性。每个小事务可以独立执行,并且在失败时可以执行相应的补偿操作。Saga Pattern 对于长时间运行的分布式事务和异步消息的处理非常有用。但需要注意,Saga Pattern 的设计和实现可能会增加系统的复杂性。

3. 基于消息的分布式事务(Transactional Messaging):在分布式系统中,可以将事务性操作与消息传递相结合。通过消息队列来确保事务的一致性。当一个服务完成其本地事务后,会向消息队列发送消息,其他服务在接收到消息后执行相应的操作。这种方法可以实现比较灵活的事务处理,但需要考虑消息队列的可靠性和幂等性。

4. 分布式事务中间件:一些开源和商业的分布式事务中间件,如Seata、TCC-Transaction、Hmily等,提供了分布式事务的解决方案,可以简化分布式事务的开发和管理。这些中间件通常提供了一致性协议、分布式事务管理和补偿机制等功能。

需要根据具体的业务场景、系统规模和性能要求来选择合适的分布式事务解决方案。同时,需注意分布式事务的设计和实现可能会增加系统的复杂性,并需要充分考虑事务一致性、性能、可靠性和开发复杂度等因素之间的权衡。

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

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

相关文章

Nest.js权限管理系统开发(五)返回格式化

返回格式化拦截器 在上一篇《Nest.js权限管理系统开发(四)Swagger API接入》中,我们在base.controller.ts中创建了多个接口,每个接口都有不同的返回类型。现实中我们往往需要统一返回数据的格式,例如: {&…

【uni-app】路由

(1)路由配置 uni-app 页面路由全部交给框架统一管理,开发者需要在pages.json里配置每个路由页面的路径及页面样式(类似小程序在 app.json 中配置页面路由)。 "pages": [{"path": "pages/ind…

蓝桥杯倒计时47天!DFS基础——图的遍历

倒计时47天! 深度优先搜索——DFS 温馨提示:学习dfs之前最好先了解一下递归的思想。 DFS基础——图的遍历 仙境诅咒 问题描述 在一片神秘的仙境中,有N位修仙者,他们各自在仙境中独立修炼,拥有自己独特的修炼之道…

websocket在django中的运用

14-2 聊天室实现思路:轮训、长轮训、websocket_哔哩哔哩_bilibili 参考大佬的B站学习笔记 https://www.cnblogs.com/wupeiqi/p/6558766.html 参考博客 https://www.cnblogs.com/wupeiqi/articles/9593858.html 参考博客 http: 是短连接,无状态的&…

探索网络通信的遗产:AppleTalk Data Stream Protocol (ADSP) 的全面解析

ADSP简介 AppleTalk Data Stream Protocol (ADSP) 是AppleTalk网络协议套件的一部分,设计用于在AppleTalk网络中提供端到端的可靠数据流服务。在1980年代和1990年代,AppleTalk是Apple计算机用于局域网通信的主要网络技术。ADSP提供了一种类似于现代TCP协…

conda 导出/导出配置好的虚拟环境

一. 导出环境配置(yml文件) 1. 在主目录下激活虚拟环境(UE4是我的虚拟环境名称,请根据你自己的名称进行修改) conda activate UE4 2. 运行此代码 conda env export > environment.yml 二. 导入环境配置&#xf…

创建第一个React项目

React脚手架 npx create-react-app react-demonpx是直接从互联网网上拉最新的脚手架进行创建react 运行React项目 npm start若想找到Webpack配置文件 npm ejectReact的基本使用 基本步骤 导入react和react-dom vue 创建react元素 渲染react元素到页面中导入 import React…

python统计分析——多解释变量的方差分析

参考资料:用python动手学统计学 1、导入库 # 导入库 # 用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 用于绘图的库 from matplotlib import pyplot as plt import seaborn as sns sns.set() # 用于估计…

Android 10 音量UI更新解析

1 VolumeUI 的启动 由于VolumeUI 是继承 SystemUI 的,所以它的启动方式和 SystemUI 的启动方式一样。 直接看 VolumeUI 的start()方法 frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java Override public void start() {boolean …

Linux基础命令—进程管理

基础知识 linux进程管理 什么是进程 开发写代码->代码运行起来->进程 运行起来的程序叫做进程程序与进程区别 1.程序是一个静态的概念,主要是指令集和数据的结合,可以长期存放在操作系统中 2.进程是一个动态的概念,主要是程序的运行状态,进程存在生命周期,生命周期结…

YY调音台:直播主播的体验

我是直播平台的主播,日常工作就是在直播间里打游戏、唱歌、聊天之类的。刚开始的时候我的直播工具只有一台电脑,收音也是用的我自己常用的耳机,设备比较简陋,直播间的用户留存率也不高。但是我相信天道酬勤,每天晚上坚…

javaScript数组去重的几种实现方式——适用非引用数据去重

最传统的使用循环遍历 //最传统的使用循环遍历 function getUnique(arr) {let newArr [];for (let i 0; i < arr.length; i) {for (let j i 1; j < arr.length; j) {if (arr[i] arr[j]) {i; //相同丢掉前面的元素}}newArr.push(arr[i]);}return newArr; } 利用Set实…

Seata分布式事务实战XATCC模式

目录 XA模式 XA 模式的使用 Spring Cloud Alibaba整合Seata XA TCC模式 TCC模式接口改造 TCC如何控制异常 Spring Cloud Alibaba整合Seata TCC XA模式 整体机制 在 Seata 定义的分布式事务框架内&#xff0c;利用事务资源&#xff08;数据库、消息服务等&#xff09;对…

【Python从入门到进阶】49、当当网Scrapy项目实战(二)

接上篇《48、当当网Scrapy项目实战&#xff08;一&#xff09;》 上一篇我们正式开启了一个Scrapy爬虫项目的实战&#xff0c;对当当网进行剖析和抓取。本篇我们继续编写该当当网的项目&#xff0c;讲解刚刚编写的Spider与item之间的关系&#xff0c;以及如何使用item&#xff…

【python】0、超详细介绍:json、http

文章目录 一、json二、http2.1 json 读取 request 序列化 三、基本类型3.1 decimal 四、图像4.1 颜色格式转换 一、json import json f open(data.json) # open json file data json.load(f) # 读出 json object for i in data[emp_details]: # 取出一级属性 emp_details, …

云尚办公-0.3.0

5. controller层 import pers.beiluo.yunshangoffice.model.system.SysRole; import pers.beiluo.yunshangoffice.service.SysRoleService;import java.util.List;//RestController&#xff1a;1.该类是控制器&#xff1b;2.方法返回值会被写进响应报文的报文体&#xff0c;而…

ChatRTX安装教程

介于本人一直想将现有的智慧城市的文档结合大模型RAG实现知识库问答助手&#xff0c;借着Chat With RTX的风潮正好将机器人和知识库合二为一&#xff0c;方便以后对众多文件进行查阅。 一、概要 Chat With RTX 是一个 Demo&#xff0c;用来将您自己的资料&#xff08;文档、笔…

关于硅的制造芯片的过程

芯片是如何制作的&#xff1f; 先将硅融化制成硅晶片&#xff0c;再用光刻机印压电路。 bilibili芯片制作视频 硅晶片作为现代芯片的主要元件&#xff0c;广泛用于集成电路。 首先将多晶硅放入特制的密封炉&#xff0c;排除其中空气后加热到1420摄氏度&#xff0c;将融化的硅放…

第三节:kafka sarama 遇到Bug?

文章目录 前言一、先上结果二、刨根问底总结 前言 前面两节&#xff0c;我们已经简单应用了sarama的两个类型Client和ClusterAdmin&#xff0c;其中有一个案例是获取集群的ControllerId&#xff0c;但是在后面的测试过程过程中&#xff0c;发现一个问题&#xff0c;返回的Cont…

vue菜单栏跳转方案

vue菜单栏跳转方案 <template><div><el-container style"height: 100vh"><el-aside width"200px" style"background-color: #b3c0d1"><el-menuopen"handleOpen"close"handleClose"select"h…