前言
在面试中经常会被面试官问到Redis分布式锁,随着用户量和业务的增加,传统的单体架构已经无法满足我们的业务需求,这时候单体架构项目的必须升级为分布式、集群、微服务。分布式项目能带来性能上的提升,但分布式环境和集群环境下,存在数据一致性的问题。在分布式项目中,因为分布式和集群是部署到不同的服务器,线程资源不能共享,传统的锁已经无法满足我们的需求了,这时候就需要分布式锁。特别是那些具有高并发分布式项目中经常可见分布式锁。
Redis分布式基于Redis的实现
首先我们模拟一下商城下单实现分布式锁,我们需要一个Redis,这边我为了节约时间采用了最近比较火的Docker容器化部署Redis,当然大家也可以使用传统的方式部署Redis,这边不建议大家在生产环境把Redis部署在Windows环境中,因为Windows环境下的Redis缺少了多路复用。这边我的Docker已经成功运行了Redis。
Redis里面已经存在了一个名为stock的key,库存为100
首先我们使用Maven引入相关需要操作Redis的jar包
注意:这边我们先看下传统的单体架构在集群环境中使用Synchronized同步锁,是否可以保持一致性的问题。
1
package com.redisson.demoredis.controller;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/redis")
public class RedisController {
//RedisApi
@Resource
private StringRedisTemplate stringRedisTemplate;
@RequestMapping(value = "/redis_lock")
public synchronized String redisLock() {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int stockCount = stock - 1;
stringRedisTemplate.opsForValue().set("stock", stockCount + "");
System.out.println("扣除成功,库存还有:" + stockCount);
} else {
System.err.println("对不起库存没有了");
}
return "返回";
}
}
我们启动了2个Tomcat集群,使用Nginx完成集群
这边我们使用Jmeter压力测试工具进行测试,启动之后看看控制台的结果,启动了100个线程在0秒之内循环3次请求我们接口
这是集群Tomcat1的库存(Synchronized同步锁)
这是集群Tomcat2的库存(Synchronized同步锁)
这边我们可以观察到我们出现了数据一致性的问题,我们Synchronized锁资源不能共享,所以我们需要Redis分布式同步锁,我们可以用Redisson为我们提供解决方案。Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作(开发者原话)。
这时我们把Synchronized同步锁的代码修改一下,已写了注释
package com.redisson.demoredis.controller;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redis")
public class RedisController {
//RedisApi
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private Redisson redisson;
@RequestMapping(value = "/redis_lock")
public String redisLock() {
// 获取对象
RLock redissonLock = redisson.getLock("lock");
try {
//公平锁,保证了当多个线程同时请求时,锁优先分配给先发出请求的线程。
//加锁,20秒钟以后自动解锁,结束强制解锁,防止死锁 无需调用unlock方法手动解锁,拿到锁的进来,其他的阻塞
redissonLock.lock(20, TimeUnit.SECONDS);
//3秒中拿不到锁则自动返回,20秒结束
redissonLock.tryLock(3L,20L, TimeUnit.SECONDS);
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int stockCount = stock - 1;
stringRedisTemplate.opsForValue().set("stock", stockCount + "");
System.out.println("扣除成功,库存还有:" + stockCount);
} else {
System.err.println("对不起库存没有了");
}
} finally {
//避免出现异常造成死锁,释放锁
redissonLock.unlock();
}
return "返回";
}
}
这时我们再使用Jmeter压力测试工具进行测试
这是集群Tomcat1的库存(这是Redis分布式锁)
这是集群Tomcat2的库存(这是Redis分布式锁)
和前面的Synchronized不同,Redis分布式已经保证了数据一致性的问题。
分布式锁实现原理
线程1获得锁,如果加锁成功,会分配一个新线程,每隔10秒检查线程1是否还有锁,如果有锁就延长过期时间进行续命,如果线程1不释放锁资源,线程2在获取锁的时候会一直while循环自旋,直到线程1释放锁线程可以加锁成功。但这里有个缺点,就是如果Redis是集群,Slave还没有获取Master传送过来的数据,Master宕机,Slave被选举为新的Master,而Slave还没有锁,没有性能保证,但已经到达中小型互联网公司的需求了。
————————————————
原文链接:https://blog.csdn.net/weixin_42962663/article/details/105111788
《Java学习、面试;文档、视频资源免费获取》
Java岗150道面试题
书籍资料