一、Redisson【面试复盘】
1.1 Redis除了做缓存,你还见过Redis的什么用法?
1.2 Redis做分布式锁有时候需要注意神魔问题?
1.3 如果是Redis单点部署的,会带来神魔问题?
1.4 集群模式下,比如主从模式。会又什么问题?
1.5 简单介绍下Redlock吧,看你简历上有redissson?
1.6 Redis分布式锁如何续期?看门狗知道吗?
二、Redis分布式锁
1.JVM层面的锁
2.分布式微服务架构,拆分后各个微服务之间为了避免冲突和数据故障而加入的一种锁。
3.显示方案:zookeeper mysql redis【推荐】--redlock----redisson lock/unlock
三、超卖程序采坑案例【Springboot2+Redis5/6】
使用场景:多个服务之间,同一时刻,同一个用户只能有一个请求,防止关键业务数据冲突和并发错误。
3.1 建立Module
- boot_redis01
- boot_redis02
- boot_redis_test
3.2 修改pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pools</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
3.3 修改yaml
# 第一个模块的配置,第二个修改端口号就好2222
server.port=1111
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
3.4 RedisConfig配置类
@Configuration
public class RedisConfig{
@Bean
public RedisTemplate<String,Serializable> redisTemplate(lettuceConnectionFactory connectionFactory){
new RedisTemplate<String,Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
3.5 GoodController
@RestController
public class GoodController{
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping("/buy_Goods")
public String buy_Goods(){
// 1.查看库存数量
String result = stringRedisTemplate.opsForValue().get("goods:001");
int goodsNumber = result == null ? 0 : Interger.parseInt(result);
// 2.卖商品
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
// 3.成功买入,库存减少一件
stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realNumber));
return "成功买入商品,库存还剩下:"+realNumber+"服务端口:"+serverPort;
}else{
System.out.println("商品卖完"+"服务端口:"+serverPort);
}
return "商品卖完!"+"服务端口:"+serverPort;
}
}
3.6 Redis数据
# 放入001库存100个
set goods:001 100
3.7 测试
四、找上面程序Bug
4.1 高并发下,又什么问题?
单机版没有枷锁100%故障的,没有原子性,多线程下没有枷锁是不可以的。
4.1.1 单机版加synchronized锁
关键字,拿不到商品不走,容易造成线程积压,卡在外面,时间比较久。
@GetMapping("/buy_Goods")
public String buy_Goods(){
// 加锁
synchronized(this){
// 1.查看库存数量
String result = stringRedisTemplate.opsForValue().get("goods:001");
int goodsNumber = result == null ? 0 : Interger.parseInt(result);
// 2.卖商品
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
// 3.成功买入,库存减少一件
stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realNumber));
return "成功买入商品,库存还剩下:"+realNumber+"服务端口:"+serverPort;
}else{
System.out.println("商品卖完"+"服务端口:"+serverPort);
}
return "商品卖完!"+"服务端口:"+serverPort;
}
}
4.1.1 单机版加ReentrantLock锁
类 ,try lock,时间内抢的到就去抢,抢不到就走人。
private final Lock lock = new ReentrantLock();
@GetMapping("/buy_Goods")
public String buy_Goods(){
// 加锁,抢锁3S小规模等待
try(lock.tryLock(3L,TimUnit.SECONDS)){
lock.lock();
...
}finally{
lock.unlock();
}else{
}
}
4.2 架构变为Nginx分布式微服务,单机锁会不会有问题?
单机版的解决不了的,需要加入分布式锁
4.2.1 引入反向代理Nginx
# 权重一半一半
vi nginx.conf
测试访问nginx
192.168.11.147/buy_goods
似乎轮询策略没有发现神魔问题。
4.2.2 Jmeter性能压测会不会有问题?
压测:1S中100个线程进行并发访问
结果:发现了严重的超卖现象,单机版的锁是控制不住问题的。
Redis性能极高,岁分布式锁的支持比较优秀。
4.2.3 加入分布式锁
public static final String REDIS_LOCK = "atguiguLock";
@GetMapping("/buy_Goods")
public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value);
// 加锁不成功
if(!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");
int goodsNumber = result == null ? 0 : Interger.parseInt(result);
// 2.卖商品
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
// 3.成功买入,库存减少一件
stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realNumber));
// 4.解锁
stringRedisTemplate.delete(REDIS_LOCK);
return "成功买入商品,库存还剩下:"+realNumber+"服务端口:"+serverPort;
}else{
System.out.println("商品卖完"+"服务端口:"+serverPort);
}
return "商品卖完!"+"服务端口:"+serverPort;
}
4.3 程序异常,没有执行解锁命令怎么办?
出异常可能没有办法释放锁,必须要加入一个finally代码块,用来释放锁。
4.4 部署了微服务的jar包机器挂了,代码没有执行finally怎么办?
key没有被删除,麻烦了,redis中一直有这把锁,需要加入一个过期删除。
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value);
// 保证释放锁【过期时间的限定】
stringRedisTemplate.expire(REDIS_LOCK,10L,TimeUnit.SECONDS);
4.5 加锁和设置KEY分开了,没有保证原子性?
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);
4.6 时间过期了,第一个线程业务还没完成怎么办?
程序不严谨带来隐患,第一个线程超过了过期删除的时间限制,删除了别人的锁。
只能删除自己的锁,不能删除别人的锁。
4.7 finally块的判断+del删除操作不是原子性的?
redis官网的解决办法
4.7.1 如果不可以使用lua脚本,你还有其他办法吗? 【RedisTemplate版本】
提示,redis自身的事务来解决。
4.7.2 lua脚本来解决【Jedis+lua】
4.8 Redis分布式锁如何进行缓存续命?
确保你的业务逻辑时间 > 过期时间
五、 Redisson的实现
5.1 RedisConfig
@Bean
public Redisson redisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson)Redisson.create(config);
}
5.2 GoodsController
@Autowired
private Redisson redisson;
@GetMapping("/buy_Goods")
public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
...
finally{
redisson.unlock();
}
5.3 nginx+jmeter+redisson性能压测
通过查询1号机与2号机,并没有查到问题。基本解决了超卖问题。
细节弥补,为了解决解锁时候不是解决的自己的锁问题。 做一个判断。
评论