学习资料:图灵诸葛
分布式锁问题及解决方案
模拟高并发情况下分布式集群的锁应对情况。
1、分布式集群的问题复现
制造一个双程序负载场面,共同访问单个redis减扣库存
场景方案图
减库存程序
@RequestMapping("/reduceStock")
public String reduceStock(){
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock>0){
stringRedisTemplate.opsForValue().set("stock",--stock+"");
System.out.println("扣减成功。当前库存:"+stock);
}else{
System.out.println("扣减失败!当前库存:"+stock);
}
}
return "ok";
}
nginx
upstream myserver{
server 192.168.0.101:8080;
server 192.168.0.101:8090;
}
server {
listen 80;
server_name 192.168.1.106;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
}
单机访问
访问Nginx
两次访问都落在8080应用上
使用Jmeter简单压测
结论:分布式环境下出现超卖情况。
2、使用Redis setnx实现分布式锁
@RequestMapping("/reduceStock")
public String reduceStock(){
String productKey = "book001";//产品key
try {
String productUUid = UUID.randomUUID().toString();
//使用nx确保只有一个应用线程能拿到产品的key,然后进行后续操作
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(productKey, productUUid, 10, TimeUnit.SECONDS);
/*
关于key超时的后业务代未完成却提前过期的问题,
可以在获取到redis锁之后,开启一个新线程,去循环判断当前redis锁是否存,
:若存在则设置为10/3,既业务代码大概进行实践的三分之一。
:若存在则关闭线程。
目的是控制超时时间在业务代码完成之后超时,让业务代码finally去施放redis锁。
*/
if (result) {
return "try again.";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
stringRedisTemplate.opsForValue().set("stock", --stock + "");
System.out.println("扣减成功。当前库存:" + stock);
} else {
System.out.println("扣减失败!当前库存:" + stock);
}
} finally {
//施放产品key
stringRedisTemplate.delete(productKey);
}
return "ok";
}
3、使用redisson实现分布式锁
官网:https://redisson.org
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
程序
@RequestMapping("/reduceStock")
public String reduceStock(){
String productKey = "book001";//产品key
RLock lock = redisson.getLock(productKey);//创建产品锁
try {
lock.lock(10,TimeUnit.SECONDS);//上锁
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
stringRedisTemplate.opsForValue().set("stock", --stock + "");
System.out.println("扣减成功。当前库存:" + stock);
} else {
System.out.println("扣减失败!当前库存:" + stock);
}
} finally {
//施放产品key
lock.unlock();
}
return "ok";
}
redisson原理图解(概括)
4、分布式锁性能提升
1、产品库存分段存储:
- p_001:10
- p_002:10
- p_003:10
- …
5、Redis集群,主从切换导致锁失效
- redlock(不推荐)
- zookeeper(更完善)