分布式锁的使用
Posted Chiba Nuoyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式锁的使用相关的知识,希望对你有一定的参考价值。
一、分布式锁介绍
由于传统的锁是基于Tomcat服务器的内部对象,搭建了集群之后,导致锁失效,所以要使用分布式锁来处理↓
分布式锁介绍 |
---|
二、分布式锁解决方案
3.1 搭建环境
创建SpringBoot工程,起名为秒杀second-kill,勾选web依赖↓
抢购的业务逻辑代码↓
package com.ljh.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class SecondKillController {
//1. 准备商品的库存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 准备商品的订单
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@GetMapping("/kill")//item表示用户访问的商品名称,等会浏览器地址栏我会输入牙刷
public String kill(String item) throws InterruptedException {
//1. 减库存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品库存数不足!!!";
}
Thread.sleep(100);//用睡眠来模拟消耗时间
itemStock.put(item,stock-1);
//2. 创建订单
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item)+1);
//3. 返回信息
return "抢购成功!!!" + item + ": 剩余库存数为" + itemStock.get(item) + ",订单数为" + itemOrder.get(item);
}
}
用浏览器访问我们的控制器方法,库存和订单确实加起来是10000,没有超卖,但是我们别忘了还有其他用户的访问↓
http://localhost:8080/kill?item=牙刷
下载ab压力测试工具软件,解压软件即可,找到bin目录,cmd打开,输入下面地址来模拟1000个请求和500并发↓
下载链接:https://pan.baidu.com/s/1yHeVgXN94kram3Jxj5V13w
提取码:6qfhab -n 请求数 -c 并发数 访问的路径(复制浏览器地址过来,浏览器地址栏对牙刷进行了url编码所以是这样的↓) ab -n 1000 -c 500 http://localhost:8080/kill?item=%E7%89%99%E5%88%B7
enter回车,看到执行完了请求和并发,然后打开浏览器地址值,再访问发现库存和订单加起来不是10000,超卖了↓
3.2 Zookeeper实现分布式锁原理图解
Zookeeper实现分布式锁原理图解 |
---|
这里用临时节点有个好处,断开连接,锁资源就会消失,不用设置过期时间,而下面用redis做分布式锁需要过期时间
3.3 Zookeeper实现分布式锁代码实现
在上面写的秒杀工程里面导入依赖来解决问题
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
配置类
package com.ljh.config;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZkConfig {
@Bean
public CuratorFramework cf(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);//需要指定重试间隔和重试次数
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("10.20.159.39:2181,10.20.159.39:2182,10.20.159.39:2183")
.retryPolicy(retryPolicy)
.build();
curatorFramework.start();
return curatorFramework;
}
}
在业务代码中添加分布式锁
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
//加锁
lock.acquire();
lock.acquire(1,TimeUnit.SECONDS); //指定排队多久放弃获取锁资源
//----------------业务逻辑代码------------------------
//释放锁
lock.release();
package com.ljh.controller;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SecondKillController {
//1. 准备商品的库存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 准备商品的订单
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@Autowired
private CuratorFramework cf;
@GetMapping("/kill")//item表示用户访问的商品名称,等会浏览器地址栏我会输入牙刷
public String kill(String item) throws Exception {
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
//加锁
lock.acquire();
//lock.acquire(1,TimeUnit.SECONDS); //指定排队多久放弃获取锁资源
//----------------业务逻辑代码↓------------------------
//1. 减库存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品库存数不足!!!";
}
Thread.sleep(100);//用睡眠来模拟消耗时间
itemStock.put(item,stock-1);
//2. 创建订单
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item)+1);
//----------------业务逻辑代码↑------------------------
//释放锁
lock.release();
//3. 返回信息
return "抢购成功!!!" + item + ": 剩余库存数为" + itemStock.get(item) + ",订单数为" + itemOrder.get(item);
}
}
package com.ljh.controller;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SecondKillController2 {
//1. 准备商品的库存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 准备商品的订单
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@Autowired
private CuratorFramework cf;
@RequestMapping(value = "/seckill")
public String seckill(String gname) throws Exception {
InterProcessMutex interProcessMutex = new InterProcessMutex(cf, "/seckill_lock");
//获取锁
//interProcessMutex.acquire();//没有获取到锁就一直等待
if(interProcessMutex.acquire(1, TimeUnit.SECONDS)){//等待1s就放弃获取锁资源
//1.先减库存
Integer stock = itemStock.get(gname);
if (stock <= 0) {
return "库存不足。。。。";
}
itemStock.put(gname, stock - 1);
Thread.sleep(100);
//2.加订单数量
Integer count = itemOrder.get(gname);
itemOrder.put(gname, count + 1);
Thread.sleep(100);
//释放锁
interProcessMutex.release();
return "抢购成功【" + gname + "】,库存:" + itemStock.get(gname) + ",订单:" + itemOrder.get(gname);
}else{
return "请稍后再试";
}
}
}
修改完代码重启秒杀工程,发现不管用浏览器访问,还是用之前的ab压测工具软件访问,库存和订单加起来都是10000
注意,如果启动报下面这个错,要重启防火墙,然后关闭防火墙↓
systemctl start firewalld
systemctl stop firewalld
3.4 Redis实现分布式锁原理图解
Redis实现分布式锁原理 |
---|
这里出现了异常不走后面释放锁资源的代码怎么办?简单,为锁资源设置过期时间来防止死锁的发生
3.5 Redis实现分布式锁代码实现
上面的秒杀工程导入redis依赖,添加配置文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# yml配置文件,配置redis服务器的ip和端口↓
spring:
redis:
host: xx.xx.xx.xx #redis服务器的ip地址
port: xxxx #redis端口
工具类
package com.ljh.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean lock(String key,String value,int second){
return redisTemplate.opsForValue().setIfAbsent(key,value,second, TimeUnit.SECONDS);
}
public void unlock(String key){
redisTemplate.delete(key);
}
}
修改业务逻辑代码
@Autowired
private RedisLockUtil lock;
@GetMapping("/redis/kill")
public String redisKill(String item) throws Exception {
//加锁
if(lock.lock(item,System.currentTimeMillis() + "",1)){
//业务代码...
//释放锁
lock.unlock(item);
}
}
package com.ljh.controller;
import com.ljh.utils.RedisLockUtil;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SecondKillController {
//1. 准备商品的库存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 准备商品的订单
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@Autowired
private RedisLockUtil lock;
@GetMapping("/kill")//item表示用户访问的商品名称,等会浏览器地址栏我会输入牙刷
public String kill(String item) throws Exception {
//加锁
if(lock.lock(item,System.currentTimeMillis() + "",1)){
//业务代码
//----------------业务逻辑代码↓------------------------
//1. 减库存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品库存数不足!!!";
}
Thread.sleep(100);//用睡眠来模拟消耗时间
itemStock.put(item,stock-1);
//2. 创建订单
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item)+1);
//----------------业务逻辑代码↑------------------------
//释放锁
lock.unlock(item);
}
//3. 返回信息
return "抢购成功!!!" + item + ": 剩余库存数为" + itemStock.get(item) + ",订单数为" + itemOrder.get(item);
}
}
修改完代码重启秒杀工程,发现不管用浏览器访问,还是用之前的ab压测工具软件访问,库存和订单加起来都是10000
以上是关于分布式锁的使用的主要内容,如果未能解决你的问题,请参考以下文章
锁的原理和使用场景,乐观锁悲观锁公平锁非公平锁,基于数据库RedisZookeeper实现分布式锁的原理及代码实现