概述
Redis高并发分布式锁实战
- 前言
- 分布式锁的应用场景
- 一、搭建环境
- 二、Jmeter压测引出分布式锁
- 三、进一步优化
- 四、Redisson
- 主从架构锁失效情况
- 压测
前言
分布式锁的应用场景
-
互联网秒杀
-
抢优惠卷
-
接口幂等性校验
本篇将会通过实战模拟一下用redis解决高并发的分布式锁,循序渐进让大家觉得分布式不再那么遥不可及
用到的工具:Springboot项目,ngnix,redis,Jmeter
一、搭建环境
我们要搭建这么个模拟环境
项目:
indexController
@RequestMapping("/deduct_stock0")
public String deductStock0() {
try {
//加锁
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
}
} catch (Exception e){
e.printStackTrace();
}
return "end";
}
电商平台的减库存操作。
启动两个SpringBoot来模拟2台tomcat集群下的场景。一个是8080端口,一个是8090端口
那么接下来组件ngnix代理访问这俩个
Linux安装ngnix不会的参考这篇Linux下安装ngnix,
修改配置文件,做个反向代理,运行:
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
搭建完毕,我们访问下
二、Jmeter压测引出分布式锁
默认是50个库存
运行如下:
可以很明显的发现超卖了,因为两边有相同的剩余库存。所以我们知道
//加锁
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
}
synchronized 的加锁是JVM级别,也就是集群下是不会有效的。那对于分布式的场景应该怎么控制呢?分布式锁就来了。
我的之前博客说过这么一句话setnx,redis里没有就会执行成功
String lockKey = "product_001";
//redis里没有就会执行成功
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "tzlock");
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
stringRedisTemplate.delete(lockKey);
return "end";
那我们看下结果8090服务器
8080服务器
可以很明显的发现锁是成功的了
三、进一步优化
那这么写有没有什么问题
@RequestMapping("/deduct_stock0")
public String deductStock0() {
String lockKey = "product_001";
//redis里没有就会执行成功
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "tzlock");
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
stringRedisTemplate.delete(lockKey);
return "end";
}
如果发生异常怎么办?所以要加处理
如果执行到中间的代码,直接宕机了?就要加个超时时间,我们要把超时的设置和key绑定起来。
@RequestMapping("/deduct_stock0")
public String deductStock0() {
String lockKey = "product_001";
String clientId = UUID.randomUUID().toString();
try{
//redis里没有就会执行成功
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if (!result) {
return "error_code";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
stringRedisTemplate.delete(lockKey);
} finally {
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
String clientId = UUID.randomUUID().toString();
代表我自己线程加的锁不能被别的线程动。谁加的锁谁去释放!!
到这里,我们的分布式锁其实已经是差不多了,那我们看下其他的方案,介绍一款成熟的框架Redisson
四、Redisson
这个在分布式场景下功能很强大,分布式锁,分布式对象等等
在启动类加入
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public Redisson redisson() {
// 此为单机模式
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.3.27:6379").setDatabase(0);
//集群模式
//config.useClusterServers().addNodeAddress("你的ip");
return (Redisson) Redisson.create(config);
}
}
IndexController看下
@RequestMapping("/deduct_stockByRedision")
public String deductStock() {
String lockKey = "product_001";
RLock redissonLock = redisson.getLock(lockKey);
try {
//加锁
redissonLock.lock(); // setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS)
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
redissonLock.unlock();
}
return "end";
}
redisson的原理可以参考此图
采用lua脚本 hset product_1 1000 1,假设线程id为1000的抢到了。
加锁后,每隔10秒判断当前线程(抢到锁的线程)是否还持有锁,有的话进行续命30秒。
支持可重入锁,和JUC中的可重入锁机制一样,利用hash的命令incrby对持有锁的线程的值加1。释放锁的时候每个减1。
没有抢到锁的线程,判断锁的还有多久时间失效,失效后去抢锁。
主从架构锁失效情况
若出现主从架构的锁失效情况,可以采用RedLock来实现,但是不推荐,因为原理是至少有1个从节点响应成功才能保证加锁成功。性能吞吐量上会受影响。
若是出现网络波动还得回滚redis。
当然若非要保证一致性,Zookeeper可以实现,但是性能上不如redis。
压测
库存为50
库存为50的zk
库存为100时
库存为100时的zk
最后
以上就是可靠老虎为你收集整理的来了解下高大上的高并发分布式----redis实战分布式锁前言一、搭建环境二、Jmeter压测引出分布式锁三、进一步优化四、Redisson的全部内容,希望文章能够帮你解决来了解下高大上的高并发分布式----redis实战分布式锁前言一、搭建环境二、Jmeter压测引出分布式锁三、进一步优化四、Redisson所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复