会会大厂面试官四-----Redis-Springboot+redisson【实现高并发超买超卖,解决9大bug】

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 会会大厂面试官四-----Redis-Springboot+redisson【实现高并发超买超卖,解决9大bug】

一、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 测试

20210204095420891.png

20210204095540853.png


四、找上面程序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分布式微服务,单机锁会不会有问题?

image.png


单机版的解决不了的,需要加入分布式锁


4.2.1 引入反向代理Nginx


# 权重一半一半
vi nginx.conf


20210204103052289.png

测试访问nginx


192.168.11.147/buy_goods


似乎轮询策略没有发现神魔问题。


4.2.2 Jmeter性能压测会不会有问题?


压测:1S中100个线程进行并发访问

20210204103546317.png


20210204103612688.png

结果:发现了严重的超卖现象,单机版的锁是控制不住问题的。

20210204103719238.png


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代码块,用来释放锁。


20210204105420616.png

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 时间过期了,第一个线程业务还没完成怎么办?


程序不严谨带来隐患,第一个线程超过了过期删除的时间限制,删除了别人的锁。

20210204112708579.png

只能删除自己的锁,不能删除别人的锁。


20210204113154296.png


4.7 finally块的判断+del删除操作不是原子性的?


redis官网的解决办法

20210204124350570.png

4.7.1 如果不可以使用lua脚本,你还有其他办法吗? 【RedisTemplate版本】


提示,redis自身的事务来解决。


20210204124724544.png

20210204125909617.png

4.7.2 lua脚本来解决【Jedis+lua】


20210204130404635.png20210204130208457.png


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性能压测


20210204134747139.png


通过查询1号机与2号机,并没有查到问题。基本解决了超卖问题。

细节弥补,为了解决解锁时候不是解决的自己的锁问题。 做一个判断。


20210204135028130.png

目录
相关文章
|
5天前
|
存储 缓存 NoSQL
Redis常见面试题全解析
Redis面试高频考点全解析:从过期删除、内存淘汰策略,到缓存雪崩、击穿、穿透及BigKey问题,深入原理与实战解决方案,助你轻松应对技术挑战,提升系统性能与稳定性。(238字)
|
5月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
3月前
|
存储 NoSQL 定位技术
Redis数据类型面试给分情况
Redis常见数据类型包括:string、hash、list、set、zset(有序集合)。此外还包含高级结构如bitmap、hyperloglog、geo。不同场景可选用合适类型,如库存用string,对象存hash,列表用list,去重场景用set,排行用zset,签到用bitmap,统计访问量用hyperloglog,地理位置用geo。
101 5
|
4月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
1119 7
|
4月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
218 6
|
5月前
|
NoSQL 算法 安全
redis分布式锁在高并发场景下的方案设计与性能提升
本文探讨了Redis分布式锁在主从架构下失效的问题及其解决方案。首先通过CAP理论分析,Redis遵循AP原则,导致锁可能失效。针对此问题,提出两种解决方案:Zookeeper分布式锁(追求CP一致性)和Redlock算法(基于多个Redis实例提升可靠性)。文章还讨论了可能遇到的“坑”,如加从节点引发超卖问题、建议Redis节点数为奇数以及持久化策略对锁的影响。最后,从性能优化角度出发,介绍了减少锁粒度和分段锁的策略,并结合实际场景(如下单重复提交、支付与取消订单冲突)展示了分布式锁的应用方法。
382 3
|
6月前
|
存储 NoSQL Redis
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 +  无锁架构 +  EDA架构  + 异步日志 + 集群架构
|
7月前
|
NoSQL Java Redis
springboot怎么使用Redisson
通过以上步骤,已经详细介绍了如何在Spring Boot项目中使用Redisson,包括添加依赖、配置Redisson、创建配置类以及使用Redisson实现分布式锁和分布式集合。Redisson提供了丰富的分布式数据结构和工具,可以帮助开发者更高效地实现分布式系统。通过合理使用这些工具,可以显著提高系统的性能和可靠性。
2297 34
|
9月前
|
缓存 NoSQL 架构师
Redis批量查询的四种技巧,应对高并发场景的利器!
在高并发场景下,巧妙地利用缓存批量查询技巧能够显著提高系统性能。 在笔者看来,熟练掌握细粒度的缓存使用是每位架构师必备的技能。因此,在本文中,我们将深入探讨 Redis 中批量查询的一些技巧,希望能够给你带来一些启发。
Redis批量查询的四种技巧,应对高并发场景的利器!
|
9月前
|
存储 缓存 NoSQL
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
244 1