会会大厂面试官四-----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
目录
相关文章
|
8天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
15天前
|
消息中间件 缓存 NoSQL
Redis 高并发竞争 key ,如何解决这个难点?
本文主要探讨 Redis 在高并发场景下的并发竞争 Key 问题,以及较为常用的两种解决方案(分布式锁+时间戳、利用消息队列)。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Redis 高并发竞争 key ,如何解决这个难点?
|
8天前
|
负载均衡 算法 数据库
面试官:如何在高并发下避免交易所宕机?
面试官:如何在高并发下避免交易所宕机?
|
17天前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
106 7
|
1月前
|
存储 NoSQL Java
可能是最漂亮的Redis面试基础详解
我是南哥,相信对你通关面试、拿下Offer有所帮助。敲黑板:本文总结了Redis基础最常见的面试题!包含了Redis五大基本数据类型、Redis内存回收策略、Redis持久化等。相信大部分Redis初学者都会忽略掉一个重要的知识点,Redis其实是单线程模型。我们按直觉来看应该是多线程比单线程更快、处理能力更强才对,比如单线程一次只可以做一件事情,而多线程却可以同时做十件事情。但Redis却可以做到每秒万级别的处理能力,主要是基于以下原因:(1)Redis是基于内存操作的,Redis所有的数据库状态都保存在
可能是最漂亮的Redis面试基础详解
|
1月前
|
缓存 算法 架构师
京东面试:如何设计600Wqps高并发ID?如何解决时钟回拨问题?
资深架构师尼恩在其读者交流群中分享了关于分布式ID系统的设计与实现,特别是针对高并发场景下的解决方案。他强调了分布式ID系统在高并发核心组件中的重要性,并详细介绍了百度的UidGenerator,这是一个基于Snowflake算法改进的Java实现,旨在解决分布式系统中的唯一ID生成问题。UidGenerator通过自定义workerId位数和初始化策略,支持虚拟化环境下的实例自动重启和漂移,其单机QPS可达600万。此外尼恩的技术分享不仅有助于提升面试表现,还能帮助开发者在实际项目中应对高并发挑战。
京东面试:如何设计600Wqps高并发ID?如何解决时钟回拨问题?
|
1月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
1月前
|
NoSQL 算法 Redis
Redis面试篇
Redis面试篇
36 5
|
1月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
62 4
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
55 3