会会大厂面试官四-----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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
3天前
|
Java 数据库连接 Maven
最新版 | SpringBoot3如何自定义starter(面试常考)
在Spring Boot中,starter是一种特殊的依赖,帮助开发人员快速引入和配置特定功能模块。自定义starter可以封装一组特定功能的依赖和配置,简化项目中的功能引入。其主要优点包括模块化、简化配置、提高代码复用性和实现特定功能。常见的应用场景有短信发送模块、AOP日志切面、分布式ID生成等。通过创建autoconfigure和starter两个Maven工程,并编写自动配置类及必要的配置文件,可以实现一个自定义starter。最后在测试项目中验证其有效性。这种方式使开发者能够更便捷地管理和维护代码,提升开发效率。
最新版 | SpringBoot3如何自定义starter(面试常考)
|
13天前
|
缓存 NoSQL Java
高并发场景秒杀抢购超卖Bug实战重现
在电商平台的秒杀活动中,高并发场景下的抢购超卖Bug是一个常见且棘手的问题。一旦处理不当,不仅会引发用户投诉,还会对商家的信誉和利益造成严重损害。本文将详细介绍秒杀抢购超卖Bug的背景历史、业务场景、底层原理以及Java代码实现,旨在帮助开发者更好地理解和解决这一问题。
41 12
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
消息中间件 缓存 NoSQL
Redis 高并发竞争 key ,如何解决这个难点?
本文主要探讨 Redis 在高并发场景下的并发竞争 Key 问题,以及较为常用的两种解决方案(分布式锁+时间戳、利用消息队列)。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Redis 高并发竞争 key ,如何解决这个难点?
|
1月前
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
|
1月前
|
存储 NoSQL 算法
面试官:Redis 大 key 多 key,你要怎么拆分?
本文介绍了在Redis中处理大key和多key的几种策略,包括将大value拆分成多个key-value对、对包含大量元素的数据结构进行分桶处理、通过Hash结构减少key数量,以及如何合理拆分大Bitmap或布隆过滤器以提高效率和减少内存占用。这些方法有助于优化Redis性能,特别是在数据量庞大的场景下。
面试官:Redis 大 key 多 key,你要怎么拆分?
|
1月前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
211 7
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!