分布式锁【数据库乐观锁实现的分布式锁、Zookeeper分布式锁原理、Redis实现的分布式锁】(三)-全面详解(学习总结---从入门到深化)

 

目录

 分布式锁解决方案_数据库乐观锁实现的分布式锁

分布式锁解决方案_Redis实现的分布式锁原理

 分布式锁解决方案_Redis实现的分布式锁

分布式锁解决方案_Redis分布式锁误删除问题

分布式锁解决方案_Redis分布式锁不可重入问题

分布式锁解决方案_基于Redisson实现的分布式锁实现

分布式锁解决方案_Zookeeper分布式锁原理

 分布式锁解决方案_基于Zookeeper实现分布式锁

三种分布式锁对比


 

 分布式锁解决方案_数据库乐观锁实现的分布式锁

 什么是乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改, 所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有 去更新这个数据,可以使用版本号机制和CAS算法实现。

 编写乐观锁更新语句

<update id="decreaseStockForVersion" parameterType="int" >UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
</update>

编写创建订单业务层

/*** 创建订单 乐观锁** @return*/@Transactional(rollbackFor = Exception.class)@Overridepublic String createOrder(Integer productId, Integer count) throws Exception {int retryCount = 0;int update = 0;// 1、根据商品id查询商品信息Product product = productMapper.selectById(productId);// 2、判断商品是否存在if (product == null) {throw new RuntimeException("购买商品不存在:" + productId + "不存在");}// 3、校验库存if (count > product.getCount()) {throw new Exception("库存不够");}// 乐观锁更新库存// 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回// 最多重试3次while(retryCount < 3 && update == 0){update = this.reduceStock(product.getId(),count);retryCount++;}if (update == 0){throw new Exception("库存不够");}// 6、 创建订单TOrder order = new TOrder();order.setOrderStatus(1);//待处理order.setReceiverName("张三");order.setReceiverMobile("18587781068");order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格baseMapper.insert(order);// 7、 创建订单和商品关系数据OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());orderItem.setProduceId(product.getId());orderItem.setPurchasePrice(product.getPrice());orderItem.setPurchaseNum(count);orderItemMapper.insert(orderItem);return order.getId();}/*** 减库存* <p>* 由于默认的事务隔离级别是可重复读,produce.findById()* 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。*/@Transactional(rollbackFor = Exception.class)public int reduceStock(int gid,int count) {int result = 0;//1、查询商品库存Product product = productMapper.selectById(gid);//2、判断库存是否充足if (product.getCount() >= count) {//3、减库存// 乐观锁更新库存result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());}return result;}

分布式锁解决方案_Redis实现的分布式锁原理

获取锁

互斥:确保只有一个线程获得锁

# 添加锁 利用setnx的互斥性
127.0.0.1:6379> setnx lock thread1

释放锁

1、手动释放锁

2、超时释放:获取锁时设置一个超时时间

#释放锁 删除即可
127.0.0.1:6379> del lock

超时释放

127.0.0.1:6379> setnx lock tread1
127.0.0.1:6379> expire lock 5
127.0.0.1:6379> ttl lock

两步合成一步

help setSET key value [EX seconds] [PX milliseconds] [NX|XX]summary: Set the string value of a keysince: 1.0.0group: string
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set lock k1 ex 5 nx
OK
127.0.0.1:6379> set lock k1 ex 5 nx
nil

 

 分布式锁解决方案_Redis实现的分布式锁

 引入依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

添加Redis配置

spring:redis:host: localhostport: 6379

编写创建订单实现类

@Overridepublic String createOrderRedis(Integer productId, Integer count) throws Exception {log.info("*************** 进入方法 **********");String key = "lock:";String value = UUID.randomUUID().toString();// 获取分布式锁Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);// 判断是否获取锁成功if (!result){log.info("我进入了锁");return "不允许重复下单";}try {// 1、根据商品id查询商品信息Product product = productMapper.selectById(productId);// 2、判断商品是否存在 if (product == null) {throw new RuntimeException("购买商品不存在:" + productId + "不存在");}// 3、校验库存if (count > product.getCount()) {throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");}// 4、计算库存Integer leftCount = product.getCount() - count;// 5、更新库存product.setCount(leftCount);productMapper.updateById(product);// 6、 创建订单TOrder order = new TOrder();order.setOrderStatus(1);//待处理order.setReceiverName("张三");order.setReceiverMobile("18587781068");order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格baseMapper.insert(order);// 7、 创建订单和商品关系数据OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());orderItem.setProduceId(product.getId());orderItem.setPurchasePrice(product.getPrice());orderItem.setPurchaseNum(count);orderItemMapper.insert(orderItem);return order.getId();}catch (Exception e){e.printStackTrace();}finally {// 释放锁stringRedisTemplate.delete(key+productId);}return "创建失败";}

分布式锁解决方案_Redis分布式锁误删除问题

 

 配置锁标识

private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");

获取锁

//1、获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2、获得锁 setnx key   value   time   type
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);

释放锁

// 获取锁标识String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);// 判断标识是否一致if (s.equals(threadId)){// 释放锁stringRedisTemplate.delete(KEY_PREFIX + produceId);}

分布式锁解决方案_Redis分布式锁不可重入问题

 不可重入问题

 如何解决

 

分布式锁解决方案_基于Redisson实现的分布式锁实现

 Redisson介绍

Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分 布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重 对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。

引入Redisson依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.17.2</version>
</dependency>

添加Reids的配置

spring:redis:host: localhostport: 6379

编写Redis分布式锁工具类

package com.itbaizhan.lock.utils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class DistributedRedisLock {@Autowiredprivate RedissonClient redissonClient;// 加锁public Boolean lock(String lockName) {if (redissonClient == null) {log.info("DistributedRedisLock redissonClient is null");return false;}try {RLock lock = redissonClient.getLock(lockName);// 锁15秒后自动释放,防止死锁lock.lock(15, TimeUnit.SECONDS);log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);// 加锁成功return true;} catch (Exception e) {log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);return false;}}// 释放锁public Boolean unlock(String lockName) {if (redissonClient == null) {log.info("DistributedRedisLock redissonClient is null");return false;}try {RLock lock = redissonClient.getLock(lockName);lock.unlock();log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);// 释放锁成功return true;
} catch (Exception e) {log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);return false;}}
}

编写创建订单接口实现

/*** Redis锁实现** @param productId* @param count* @return* @throws Exception*/@Overridepublic String createOrderRedis(Integer productId, Integer count) throws Exception {//获取锁对象if (distributedRedisLock.lock(String.valueOf(productId))) {try {// 1、根据商品id查询商品信息Product product = productMapper.selectById(productId);// 2、判断商品是否存在if (product == null) {throw new RuntimeException("购买商品不存在:" + productId + "不存在");}// 3、校验库存if (count > product.getCount()){throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");}// 4、计算库存Integer leftCount = product.getCount() - count;// 5、更新库存product.setCount(leftCount);productMapper.updateById(product);// 6、 创建订单TOrder order = new TOrder();order.setOrderStatus(1);//待处理order.setReceiverName("张三");order.setReceiverMobile("18587781068");order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格baseMapper.insert(order);// 7、 创建订单和商品关系数据OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());orderItem.setProduceId(product.getId());orderItem.setPurchasePrice(product.getPrice());orderItem.setPurchaseNum(count);orderItemMapper.insert(orderItem);return order.getId();} catch (Exception e) {e.printStackTrace();} finally {distributedRedisLock.unlock(String.valueOf(productId));}}return "创建失败";}

分布式锁解决方案_Zookeeper分布式锁原理

 公平锁和可重入锁的原理

 

 这种排队取水模型,就是一种锁的模型。

什么是可重入锁呢?

 

 创建临时顺序节点

[zk: localhost:2181(CONNECTED) 1] create -e  -s
/test 123

 

 ZK分布式锁的实现原理

当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。

 接着,客户端 Client1 会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获得锁。

 

 这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。

 客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它 排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存 在,也就是说 client2 抢锁失败进入等待状态。

 

 此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。

 

 同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。

 

 释放锁

如果是任务完成,Client1 会显式调用删除 lock1 的指令。


 如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除的。

 

 lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2 立刻收到通知,也会查找 locks 下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。

 

 同理,Client2 获得锁之后,Client3 也对它虎视眈眈:

 分布式锁解决方案_基于Zookeeper实现分布式锁

 简介

Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封 装的一套高级API 简化了ZooKeeper的操作。

 

 引入Curator依赖

       <dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>5.2.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.2.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-client</artifactId><version>5.2.0</version></dependency>

编写Zookeeper配置

@Configuration
public class ZookeeperConfig {@Beanpublic CuratorFramework zookeeperClient() {CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000, 3))//.namespace("test").build();client.start();return client;}
}

编写创建订单接口实现

使用InterProcessMutex的acquire和release方法,来获取和释放锁。

@AutowiredCuratorFramework client;@Overridepublic String createOrderZookeeper(Integer productId, Integer count) throws Exception {// client cruator中zk客户端对象   path 抢锁路径同一个锁path需要一致InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");//第一个属性:定时的时间数字//第二个属性:定义时间的单位if (lock.acquire(3, TimeUnit.SECONDS)){try {// 1、根据商品id查询商品信息Product product = productMapper.selectById(productId);// 2、判断商品是否存在if (product == null) {throw new RuntimeException("购买商品不存在:" + productId + "不存在");}// 3、校验库存if (count > product.getCount()){throw new RuntimeException("商品" + productId + "仅剩" +
product.getCount() + "件,无法购买");}// 4、计算库存Integer leftCount = product.getCount() - count;// 5、更新库存product.setCount(leftCount);productMapper.updateById(product);// 6、 创建订单TOrder order = new TOrder();order.setOrderStatus(1);//待处理order.setReceiverName("张三");order.setReceiverMobile("18587781068");order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格baseMapper.insert(order);// 7、 创建订单和商品关系数据OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());orderItem.setProduceId(product.getId());orderItem.setPurchasePrice(product.getPrice());orderItem.setPurchaseNum(count);orderItemMapper.insert(orderItem);return order.getId();} finally {lock.release();}}return "创建失败";}

三种分布式锁对比

 数据库分布式锁实现

优点:简单,使用方便,不需要引入 Redis、Zookeeper 等中间件。

缺点:1、不适合高并发的场景 2、db 操作性能较差

Redis 分布式锁实现

优点:1、性能好,适合高并发场景 2、较轻量级 3、有较好的框架支持,如 Redisson

 缺点:1、过期时间不好控制 2、需要考虑锁被别的线程误删场景

Zookeeper 分布式锁实现

优点:1、有较好的性能和可靠性 2、有封装较好的框架,如 Curator

缺点:1、性能不如 Redis 实现的分布式锁  2、比较重的分布式锁。

汇总对比

1、从性能角度:Redis > Zookeeper >= 数据库

2、从实现的复杂性角度:Zookeeper > Redis > 数据库

3、从可靠性角度:Zookeeper > Redis > 数据库

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

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

相关文章

又一家Tier1巨头「杀进」HUD赛道,2025年搭载率或破30%

作为未来智能座舱的新交互入口&#xff0c;HUD赛道正处于关键上车周期。与此同时&#xff0c;不少传统座舱Tier1巨头也在紧盯市场蛋糕。 就在去年&#xff0c;宝马官方发布了全新一代HUD演示效果&#xff0c;将会首发搭载于宝马NFUE KLASSE新世代概念车型&#xff0c;计划2025年…

HBase性能调优(二)

感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01; 版权声明&#xff1a;本文为CSDN博主「开着拖拉机回家」的原创文章&#xff0c;遵循CC 4.0 BY-SA版权协议&#xff0c;转载请附上原文出处链接及本声明。 Hbase性能调优&#xff08;二&#xff09;_开着…

消息中间件RabbitMQ详解

一、 消息中间件 简介 消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流&#xff0c;并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型&#xff0c;它可以在分布式环境下扩展进程间的通信。 使用环境 消息中间件适用于需要可靠的数据传送…

6.Python:如何用Python进行数据可视化?

数据可视化是数据分析过程中不可或缺的一步&#xff0c;Python作为一个强大的编程语言&#xff0c;在数据可视化方面也是非常优秀的。本文将介绍使用Python进行数据可视化的基本步骤和常用工具。 一、Python数据可视化常用工具 Matplotlib Matplotlib是Python中最常用的数据…

「观察者(Observer)」设计模式 Swift实现

这里写目录标题 介绍设计模式介绍举例 iOS 中已有的 观察者设计模式实现Notification什么是通知机制或者说如何实现通知机制&#xff1f; KVOKVO底层实现如何实现手动KVO&#xff1f; 介绍 设计模式介绍 观察者设计模式&#xff08;Observer Pattern&#xff09;是一种行为型…

windows 下载安装Redis,并配置开机自启动

windows 下载安装Redis&#xff0c;并配置开机自启动 1. 下载 地址&#xff1a;https://github.com/tporadowski/redis/releases Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择&#xff0c;这里我们下载 Redis-x64-xxx.zip压缩包&#xff0c;之后解压 打…

简要介绍 | 边缘计算:原理,研究现状与未来展望

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对边缘计算进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 边缘计算&#xff1a;原理&#xff0c;研究现状与未来展望 What is Edge Computing? | Moving Intelligence to the Edge 一、背景介…

滑动奇异频谱分析:数据驱动的非平稳信号分解工具(Matlab代码实现)

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

SPSS方差分析

参考文章 导入准备好的数据 选择分析方法 选择参数 选择对比&#xff0c;把组别放入因子框中&#xff0c;把红细胞增加数放进因变量列表 勾选“多项式”&#xff0c;等级取默认“线性” &#xff0c;继续 接着点击“事后比较”&#xff0c;弹出对话框&#xff0c;勾选“LSD” …

C语言面试经典问题

当准备面试C语言相关职位时&#xff0c;以下是一些常见的C语言面试问题&#xff0c;可以帮助你准备面试。 什么是C语言&#xff1f; C语言的特点是什么&#xff1f; 请解释C语言中的标识符和关键字。 什么是C语言中的数据类型&#xff1f;请列举一些常见的数据类型。 如何…

十大医药数据查询软件(必备网站合集!)

医药数据查询软件从学科来看涵盖了医药学、药理学、临床医学、药物化学、药物分析、生物医药等领域的文献和信息&#xff0c;从类别区分涉及到医学、药学、经济学、法律等多个领域&#xff0c;医药数据查询软件的数据对于医药从业者、医学研究人员、政策制定者等都具有重要的参…

Vmware虚拟机网络配置回顾

如何配置Vmware里的虚拟机网络&#xff1f;这个东西不常用&#xff0c;都是自己练手用的。能用就行&#xff0c;千万不要花时间记&#xff0c;没意义。 很简单&#xff0c;照着敲 首先登陆自己的虚拟机 vim /etc/sysconfig/network-scripts/ifcfg-ens32 TYPE"Ethernet&q…