会会大厂面试官四-----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
目录
相关文章
|
2月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
3月前
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
2月前
|
存储 NoSQL Java
Redis助力高并发网站:在线用户统计不再是难题!
小米带你了解如何使用Redis高效统计网站的在线与并发用户数。通过维护用户的活跃时间,利用Redis有序集合(Sorted Set)特性,可实时更新在线用户列表并统计数量。具体实现包括记录用户上线时间、定期清理离线用户及统计特定时间窗口内的活跃用户数。这种方法适用于高并发场景,保证统计结果的实时性和准确性。跟着小米一起探索Redis的强大功能吧!
52 2
|
3月前
|
NoSQL Java 关系型数据库
不懂这些,面试都不敢说自己熟悉Redis
下面这位就是Redis的创始人,他叫antirez,让我们Java开发者又要多学一门Redis的始作俑者。我们肯定很难想象Redis创始人竟然学的是是建筑专业,而当年antirez是为了帮网站管理员监控访问者的实时行为才开发的Redis。为啥antirez不用MySQL来开发?MySQL并不适用于实时应用程序,存储数据库需要磁盘读写,大量的数据操作会使网站速度过于缓慢。于是Redis的前身LLOOGG就这样诞生了,后期也发展为了Redis。大家好,我是南哥。
不懂这些,面试都不敢说自己熟悉Redis
|
16天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
169 37
|
6天前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot
|
16天前
|
存储 缓存 NoSQL
【Java面试题汇总】Redis篇(2023版)
Redis的数据类型、zset底层实现、持久化策略、分布式锁、缓存穿透、击穿、雪崩的区别、双写一致性、主从同步机制、单线程架构、高可用、缓存淘汰策略、Redis事务是否满足ACID、如何排查Redis中的慢查询
【Java面试题汇总】Redis篇(2023版)
|
16天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
7天前
|
缓存 监控 NoSQL
阿里面试让聊一聊Redis 的内存淘汰(驱逐)策略
大家好,我是 V 哥。粉丝小 A 面试阿里时被问到 Redis 的内存淘汰策略问题,特此整理了一份详细笔记供参考。Redis 的内存淘汰策略决定了在内存达到上限时如何移除数据。希望这份笔记对你有所帮助!欢迎关注“威哥爱编程”,一起学习与成长。
下一篇
无影云桌面