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

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 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
目录
相关文章
|
1月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
2月前
|
存储 缓存 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的创始人,他叫antirez,让我们Java开发者又要多学一门Redis的始作俑者。我们肯定很难想象Redis创始人竟然学的是是建筑专业,而当年antirez是为了帮网站管理员监控访问者的实时行为才开发的Redis。为啥antirez不用MySQL来开发?MySQL并不适用于实时应用程序,存储数据库需要磁盘读写,大量的数据操作会使网站速度过于缓慢。于是Redis的前身LLOOGG就这样诞生了,后期也发展为了Redis。大家好,我是南哥。
不懂这些,面试都不敢说自己熟悉Redis
|
3月前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
196 0
|
1月前
|
存储 消息中间件 NoSQL
Redis总结篇(附Redis常见面试题)
本文是对Redis系列文章的总结,涵盖了Redis的数据结构、主从复制、哨兵模式、Cluster分片方案等关键知识点,并附带了一些常见的Redis面试题。
Redis总结篇(附Redis常见面试题)
|
21天前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
【Azure Redis 缓存】Redisson 连接 Azure Redis出现间歇性 java.net.UnknownHostException 异常
|
2月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
24天前
|
缓存 NoSQL Redis
redis常见面试题总结(上)
Redis 提升读写性能,减少 MySQL 请求。优点包括:内存存储加速数据获取,支持多样数据结构如哈希和有序集合,事务确保操作原子性,具备队列、主从复制及持久化功能。相较于 Memcache,Redis 数据类型更丰富,支持数据持久化与恢复,单值大小可达 512MB。其单线程设计基于 C 语言实现,使用非阻塞 IO 复用来高效处理请求。主从同步机制确保数据一致性,首次同步需生成 RDB 文件。事务虽保证命令序列化执行但不支持回滚。Bigkey 会增加网络负载并可能导致内存不平衡。缓存雪崩、穿透等问题可通过分散过期时间和布隆过滤器解决。缓存预热则预先填充热点数据。
19 0
|
2月前
|
负载均衡 NoSQL Java