package tech.codestory.zookeeper.aalvcai.ConcurrentHashMapLock; import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config; import java.io.*;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream; /**
* @version 1.0.0
* @@menu <p>
* @date 2021/6/10 14:33
*/
public class SegmentDistributeLock {
/**
* 使用redis分布式锁扣减库存,弊端: 请求量大的话,会导致吞吐量降低
* 优化: 分段锁并发扣减库存
* 将表中的库存字段 分为 5个库存字段, 然后导入redis,库存预热, 然后参考ConcurrentHashMap的分段锁思想
* 来一个请求后,对库存字段 加 分段锁, 分段锁扣减库存
* 如果当前分段锁库存不够,就扣减掉当前的库存,然后去锁下一个分段锁,扣减库存
*
* git: https://gitee.com/easybao/segmentDistributeLock.git
* 依赖jar包:
* <dependency>
* <groupId>org.redisson</groupId>
* <artifactId>redisson</artifactId>
* <version>3.13.5</version>
* </dependency>
*/
RedissonClient redissonClient;
RBucket<RedisStock[]> bucket;
private ThreadLocal<StockRequest> threadLocal = new ThreadLocal<>();
static volatile RedisStock[] redisStocks;
private final int beginTotalNum; //初始总库存,避免并发过程中 调用getCurrentTotalNum()获取到的总库存发生变化 public SegmentDistributeLock() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
this.redissonClient = Redisson.create(config); redisStocks = new RedisStock[5];
redisStocks[0] = new RedisStock("pId_stock_00",20);
redisStocks[1] = new RedisStock("pId_stock_01",20);
redisStocks[2] = new RedisStock("pId_stock_02",20);
redisStocks[3] = new RedisStock("pId_stock_03",20);
redisStocks[4] = new RedisStock("pId_stock_04",20);
// 初始总库存
this.beginTotalNum = getCurrentTotalNum(); // 库存预热,存到redis中 , 这里没有采用因为将库存预热存到redis中,取出来的时候,解析异常, 不想花时间解决,所以将库存预热 变成一个类变量
// bucket = redissonClient.getBucket("pId_stock");
// bucket.set(redisStocks); }
public RedissonClient getRedissonClient(){
return this.redissonClient;
} public int getCurrentTotalNum(){
// 获取实时总库存
return Stream.of(redisStocks).mapToInt(RedisStock::getNum).sum();
} /**
* 使用redis分布式锁扣减库存,弊端: 请求量大的话,会导致吞吐量降低
* 优化: 分段锁并发扣减库存
* 将表中的库存字段 分为 5个库存字段, 然后导入redis,库存预热, 然后参考ConcurrentHashMap的分段锁思想
* 来一个请求后,对库存字段 加 分段锁, 分段锁扣减库存
* 如果当前分段锁库存不够,就扣减掉当前的库存,然后去锁下一个分段锁,扣减库存
* @param request
* @return
*/
public boolean handlerStock_02(StockRequest request) {
// 先做校验: 判断扣减库存 是否比 初始总库存还大,是的话就直接false, 避免无限循环扣减不了
if(request.getBuyNum() > this.beginTotalNum){
return false;
}
// 使用本地线程变量保存请求,确保参数只在本线程使用
threadLocal.set(request); // 这里使用 ThreadLocal代码逻辑和ConcurrentHashMap的分段锁
RedissonClient redissonClient = getRedissonClient();
RedisStock[] tab = redisStocks;
int len = tab.length;
int i = request.getMemberId().hashCode() % len; for(RedisStock e = tab[i]; e != null; e = tab[i = nextIndex(i,len)]){ RLock segmentLock = null;
try {
// 2: 对该元素加分布式分段锁
segmentLock = redissonClient.getLock(e.getStockName());
segmentLock.lock(); int buyNum = threadLocal.get().getBuyNum();
if (buyNum <= e.getNum()) {
//扣减库存
e.setNum(e.getNum() - buyNum);
// 扣减成功后,跳出循环,返回结果
return true;
}else{
// 如果并发过程中获取到总库存<= 0 说明已经没有库存了, 如果当前需要扣减的库存 > 此时总库存就返回false,扣件失败
if (getCurrentTotalNum() <= 0 || threadLocal.get().getBuyNum() > getCurrentTotalNum()) {
// 没有库存就false
System.out.println(Thread.currentThread().getName() + " 扣减库存数: " + threadLocal.get().getBuyNum() + "失败" + " 此时总库存为: " + getCurrentTotalNum());
return false;
}
// 扣减掉当前的 分段锁对应的库存,然后对下一个元素加锁
threadLocal.get().setBuyNum( buyNum - e.getNum());
e.setNum(0);
}
} finally {
// 3: 解锁
segmentLock.unlock();
}
}
threadLocal.remove();
return false;
} private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
} // 显示redis中的库存
public void showStocks(){
for (RedisStock redisStock : redisStocks) {
System.out.println(redisStock);
}
} @AllArgsConstructor
class RedisStock implements Serializable {
// 库存字段
String stockName;
// 库存数据, 原子类来保证原子性 num的原子性
AtomicInteger num; public RedisStock(String stockName, int num) {
this.stockName = stockName;
this.num = new AtomicInteger(num);
} public void setNum(int num) {
this.num.set(num);
} public String getStockName() {
return stockName;
} public void setStockName(String stockName) {
this.stockName = stockName;
} public int getNum() {
return this.num.get();
} @Override
public String toString() {
return "RedisStock{" +
"stockName='" + stockName + '\'' +
", num=" + num.get() +
'}';
}
}
}
@Getter
@Setter
@AllArgsConstructor
class StockRequest implements Serializable{
//会员id
String memberId;
//购买数量
int buyNum;
} class SegmentDistributeLockTest{
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 模拟单线程扣减
SegmentDistributeLock segmentDistributeLock = new SegmentDistributeLock();
if(segmentDistributeLock.handlerStock_02(new StockRequest("memberId_001",54))){
System.out.println("扣减成功");
}else{
System.out.println("扣减失败");
}
segmentDistributeLock.showStocks();
/**
* 成功; 结果为:
* RedisStock{stockName='pId_stock_00', num=0} 扣减了20个
* RedisStock{stockName='pId_stock_01', num=10} 扣减了10个
* RedisStock{stockName='pId_stock_02', num=20}
* RedisStock{stockName='pId_stock_03', num=20}
* RedisStock{stockName='pId_stock_04', num=20}
*/
}
} class ConcurrentTest implements Runnable{
// 模拟10个线程并发
private static CountDownLatch countDownLatch = new CountDownLatch(10);
private static SegmentDistributeLock segmentDistributeLock = new SegmentDistributeLock();
int num; //购买数量 public ConcurrentTest(int num) {
this.num = num;
} public static void main(String[] args) throws InterruptedException {
Random random = new Random();
// 模拟并发扣减库存(扣减1-50个)
for (int i = 0; i < 10; i++) {
new Thread(new ConcurrentTest(random.nextInt(50) + 1),"线程"+i).start();
countDownLatch.countDown();
}
TimeUnit.SECONDS.sleep(5);
// 并发扣减库存结束,查询最终库存
System.out.println("-----并发扣减库存结束,查看剩余库存-------");
System.out.println("-----并发扣减库存结束,查看剩余库存-------");
System.out.println("-----并发扣减库存结束,查看剩余库存-------");
segmentDistributeLock.showStocks();
}
@Override
public void run() {
try {
StockRequest request = new StockRequest("memberId_001", this.num);
// 在此阻塞,等到计数器归零之后,再同时开始 扣库存
System.out.println(Thread.currentThread().getName() + "已到达, 即将开始扣减库存: "+ this.num);
countDownLatch.await();
if(segmentDistributeLock.handlerStock_02(request)){
System.out.println(Thread.currentThread().getName() + " 扣减成功, 扣减库存为: " + this.num);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

redis分布式锁扣减库存弊端: 吞吐量低, 解决方法:使用 分段锁 分布式分段锁并发扣减库存--代码实现的更多相关文章

  1. redis 带入的挖矿病毒 qW3xT.2 wnTKYg 解决方法

    最近我的阿里云ecs 老是收到 云盾态势感知系统检测到异常 top -c 后发现一个 疑似病毒  /tmp/qW3xT.2 看到网友们的解决方案 试过之后效果不错,可以用的 知道wnTKYg是什么鬼之 ...

  2. 搭建redis集群时所遇问题及解决方法

    单独一台虚拟机(系统CentOS 7) 问题1 创建redis集群环境时,输入以下命令 [root@localhost redis-cluster]# ./redis-trib.rb create - ...

  3. 调用android方法,出现版本太低解决方法

    原因如下图所示: 调用需要API级别11,当前是8. 解决方法如下图所示: 点击

  4. 没有msdtc服务的解决方法(sql server分布式事务挂掉的解决方法)

    没有msdtc服务的解决方法如下:1.删除注册表中的键:  开始 运行 regedit  打开注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Servic ...

  5. 利用redis实现分布式事务锁,解决高并发环境下库存扣减

    利用redis实现分布式事务锁,解决高并发环境下库存扣减   问题描述: 某电商平台,首发一款新品手机,每人限购2台,预计会有10W的并发,在该情况下,如果扣减库存,保证不会超卖 解决方案一 利用数据 ...

  6. 基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有測试代码(何志雄)

    转载请标明出处. 在分布式系统中,常常会出现须要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁. redis集群的搭建,请见我的另外一篇文章:<>& ...

  7. 自实现CAS原理JAVA版,模拟下单库存扣减

    在做电商系统时,库存是一个非常严格的数据,根据CAS(check and swap)原来下面对库存扣减提供两种方法,一种是redis,一种用java实现CAS. 第一种 redis实现: 以下这个类是 ...

  8. Redis 分布式锁 - 分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  9. EF+MySQL乐观锁控制电商并发下单扣减库存,在高并发下的问题

    下订单减库存的方式 现在,连农村的大姐都会用手机上淘宝购物了,相信电商对大家已经非常熟悉了,如果熟悉电商开发的同学,就知道在买家下单购买商品的时候,是需要扣减库存的,当然有2种扣减库存的方式, 一种是 ...

  10. 基于Redis分布式锁(获取锁及解锁)

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency).可用性( ...

随机推荐

  1. 如何用JS判断推广链接所属的客服

    今天有一个客户提出一个需求:网站有多个在线客服,每个客服都有自己的网站推广链接,当访客通过该客服的推广链接进入网站时,必须指定由该客服接待. 我的实现思路是获取推广链接中特定字符,然后判断字符对应的客 ...

  2. MapReduce之单词计数

    最近在看google那篇经典的MapReduce论文,中文版可以参考孟岩推荐的 mapreduce 中文版 中文翻译 论文中提到,MapReduce的编程模型就是: 计算利用一个输入key/value ...

  3. 浅谈 sql 中数据的约束

    数据约束 --对用户操作表的数据进行约束 1.默认值 --当用户对使用默认值的字段不插入值的时候,就使用默认值 1)对默认值字段插入null是可以的. 2)对默认值字段可以插入非null [例如:ad ...

  4. 黄聪:手机移动站Web响应式开发工具Viewport Resizer插件(360浏览器、谷歌Chrome浏览器兼容)

    插件作用: 移植自@MalteWassermann的脚本,一个可以测试响应式布局的chrome扩展. 插件截图: 插件下载地址(需FQ): https://chrome.google.com/webs ...

  5. 【原创】Mvc学习笔记(1)

    1.新建MVC4项目 在MVC4中有App_Data文件夹,这个文件夹里可以放一些重要的数据,比如说数据库的mdf文件等等,这个文件夹非常安全,因为这个文件夹不允许被别人下载,不允许被浏览器访问. A ...

  6. 某集团BI决策系统建设方案分享

    企业核心竞争能力的提升,需要强壮的运营管理能力,需要及时.准确.全面的业务数据分析作为参考与支撑. 某集团是大型时尚集团,内部报表系统用的QlikView,但是管理分配不够灵活,不能满足数据安全的要求 ...

  7. swust oj 1013

    哈希表(开放定址法处理冲突) 1000(ms) 10000(kb) 2698 / 6177 采用除留余数法(H(key)=key %n)建立长度为n的哈希表,处理冲突用开放定址法的线性探测. 输入 第 ...

  8. LeetCode 929.Unique Email Addresses

    Description Every email consists of a local name and a domain name, separated by the @ sign. For exa ...

  9. Qt QpushButton 实现长按下功能

    做项目需要一个按钮具备长时间按下的功能,才发现Qt原始的按钮是没有这个功能,不过Qt的原生按钮是存在按下和释放信号的,有了这两个信号,再来实现按钮长时间被按下,这就简单了,看下动画演示. 录成GIF效 ...

  10. TeX Live &amp; TeXstudio 安装手记

    数据库课上又看到了那位用 beamer 做 slides 的师兄,想到自己一拖再拖的LaTeX入门,决定赶快动手装个环境再说~在经过一番搜索和研究之后决定先在 windows 底下试用,选择 TeX ...