前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【新梦想老师分享】分布式锁的正确"姿势"

【新梦想老师分享】分布式锁的正确"姿势"

原创
作者头像
新梦想IT职业教育
修改2019-12-24 18:21:32
4100
修改2019-12-24 18:21:32
举报
文章被收录于专栏:新梦想软件测试

一、概述

在如今高并发、分布式大行其道的今天,如果你还只会单体项目,那未免也太落伍了。撇开技术落伍、受人耻笑外(脸皮厚的人根本不在乎耻笑),更为现实的问题是:如果你是刚进入职场的新人,即将面临找工作,估计连面试机会都没有;如果你是已经在职的人士,不知晓分布式的各种成人姿势,那你也只有在公司任人玩弄的份。说到分布式这么重要,那今天我作为一个潜伏IT圈多年的老将,跟大家分享下分布式下的分布式锁的各种成人姿势,注意是成人哦,未成年人勿入。

二、问题现场还原---秒杀系统下单功能

1.mysql数据库有2张表:stock(库存表) ,stock_order(订单表)。

2.后台通过spring boot构建下单的业务接口(下单流程=查库存--下单--减库存)。

3.打开浏览器正常业务流程再现,刷新多少次,卖出多少份皮蛋粥,没毛病。

新梦想干货分享
新梦想干货分享

4.使用压测工具(ab/jemter/loaderrunner)进行压力测试ab  -n 100 -c 100 http://127.0.0.1:8080/second-kill3/skill/order/123456

5.再次打开浏览器查看库存

新梦想干货分享
新梦想干货分享

各位看官,看到这个结果是不是有一种蛋碎一地的感觉!!! 怎么可能10000份皮蛋粥可以卖出(9989+109),如果你感觉奇怪,那说明你的技术已经out了。好了到此场景还原就到此结束。接下来给各位介绍下解决这种问题的各种姿势。

三、姿势一:synchronized

了解多线程的同学肯定会想到,并发线程安全问题,可以用jdk同步工具synchronized解决。正确的说法给大家更正下,叫做数据库丢失更新。能想到这里的我算你有点社会实践姿势,但是效果如何,请看:

1.给下单方法加synchronized,做同步。

新梦想干货分享
新梦想干货分享

2.继续压力测试

ab  -n 100 -c 100 http://127.0.0.1:8080/second-kill3/skill/order/123456

新梦想干货分享
新梦想干货分享

3.synchronized姿势总结:

1)是一种解决方案。

2)synchronized无法实现细粒度的锁。

在下单的方法中加synchronized会将所有商品下单都做同步,如果另外一件商品并没有很高并发量。也会导致很请求 很慢,锁的粒度太大。

3)只适合单点情况。(而现实是高并发、分布式集群当道)

四、姿势二:分布式锁

接下来是我们的主角:分布式锁登场了。

分布式锁的三种实现方式:数据库分布式锁、redis分布式锁、zookeeper分布式锁。今天打算给大家介绍下redis分布式锁的实现方式。

1.安装redis【不知道怎么安装的,请咨询我的官方秘书度娘】

2.maven工程中导入spring-redis依赖。

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

3.编写redisLock实现加锁与解锁

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

</dependency>

3.编写redisLock实现加锁与解锁

/**

* 加锁

* @param key

* @param value 当前时间+超时时间

* @return

*/

public static  boolean lock (String key ,String value){

//setIfAbsent=setNX 如果不存在,就设置值并返回true,否返回false

//1,加锁成功        if(redisTemplate.opsForValue().setIfAbsent(key,value)){  return true; }

//2,避免死锁(线程1加锁成功,结果在解锁前出现异常,没有解锁,导致死锁)

//2.1获取过期时间     StringcurrentValue=redisTemplate.opsForValue().get(key);

//2.2判断过期时间于当前时间的关系

if(!StringUtils.isEmpty(currentValue)

&&Long.parseLong(currentValue)<System.currentTimeMillis()){String oldValue = redisTemplate.opsForValue().getAndSet(key,value);     if(!StringUtils.isEmpty(oldValue)&&oldValue.equals(currentValue)){return true; 

}

}

//3,加锁失败  t2结束 返回false

return false;

}

/**

* 解锁

* @param key

* @param value

*/

public void unLock(String key,String value){

try {

String currentValue = redisTemplate.opsForValue().get(key);

if(!StringUtils.isEmpty(currentValue)&&currentValue.equals(value)){

redisTemplate.opsForValue().getOperations().delete(key);

}

}catch (Exception e){

log.error("【redis分布式锁】 解锁异常");

}

}

4.下单方法加锁、解锁

@Override

public void orderProductMockDiffUser(String productId) throws Exception {

//【加锁】

long time = System.currentTimeMillis()+TIMEOUT;

if(!redisLock.lock(productId,String.valueOf(time))){

throw new Exception("人也太多了,换个姿势在试试,~~~~");

}

//1.查询该商品库存,为0则结束活动

int  stockNum = stock.get(productId);

if(stockNum==0){

throw new Exception("活动结束");

}else{

//2.下单(模拟不同的用户openid 不同)

orders.put(KeyUtil.genUniqueKey(),productId);

//3.减库存

stockNum-=1;

try {

Thread.sleep(100);

}catch (Exception e){

e.printStackTrace();

}

stock.put(productId,stockNum);

}

//3.解锁

redisLock.unLock(productId,String.valueOf(time));

}

5.redis姿势总结:

1)锁的粒度小。(多个商品同时秒杀不会阻塞)

2)适合高并发,分布式集群部署。

好啦,今天的内容就到此结束了,希望对大家理解分布式锁有所帮助。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档