一、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,Time
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号机,并没有查到问题。基本解决了超卖问题。
细节弥补,为了解决解锁时候不是解决的自己的锁问题。 做一个判断。