redis的常见面试题

为什么要用redis

减少了mysql数据库的压力,
在这之前mysql一个人承受,然后要承受大量的数据请求,
大部分都是读操作。而且经常都是重复查一个东西,浪费了很多时间进行磁盘io

redis将数据都存在内存中,而不用去进行磁盘io操作。节省了很多时间

内存和硬盘的区别:

redis数据储存在内存,mysql数据储存在硬盘,所以我们要了解一下内存和硬盘的概念:

  • 内存是计算机中硬盘数据和CPU数据交换的中转站,属于临时存储器,随操作随时改写存储内容,断电后,内存中的信息全部丢失,存储介质是集成块的RAM类型,电子读写,存储容量较小。

  • 硬盘的存储介质是磁存储,靠磁头读写。硬盘可以长期存储数据,不受断电影响。存储容量大。

如何解决redis缓存数据过多

给缓存的内容加上一个过期时间,由应用程序设置
使用随机算法随机筛选出一些缓存,如果过期则删除 (定期删除)
在查询的时候,查到的刚好是过期的缓存数据,则直接删除(被动式触发/惰性删除)
内存不足时,内存淘汰策略
内存淘汰策略
解决方式: 定时删除 + 惰性删除 + 内存淘汰

缓存穿透

接收到请求,发现缓存数据不存在,同时mysql也没有这个数据
这时候每次碰到查这种数据请求的时候都要再次发送给mysql
这时候就要用到: 布隆过滤器

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力

缓存击穿

一个热点数据到了过期时间,删掉的同时 有一大波查询该数据请求刚好发了过来,所以只能将请求发送给mysql

缓存雪崩

就是缓存击穿的升级版。
一大波数据刚好过期,然后同时又接收到了这些数据的大量查询请求,
就将一大波请求发送到了mysql那里。mysql直接干崩了

解决方法: 过期时间分散随机 + 热点数据永不过期
1、让应用程序在 设置缓存过期时间的时候 设置得更加分散,不那么集中,这就不会导致一大波缓存数据在同一时间过期。可以设置随机过期
2、设置热点数据永不过期

遇到电脑突然异常关机导致缓存数据丢失

数据备份

  • RDB
  • AOF
    保存每次查询的日志...
    将redis的指令记录下来,重启的时候再次执行一次

如何保证redis和数据库的数据一致?

先淘汰缓存 然后再更新数据库

Redis平时的一些应用场景

缓存数据,一些热点数据,,或者是一些定时信息,例如验证码

还可以做线程的并发的操作,比如一些 ++操作

redis分布式锁

加锁:

    /**
     * 加锁
     * @param stringRedisTemplate Redis客户端
     * @param lockKey 锁的key
     * @param requestId 竞争者id
     * @param expireTime 锁超时时间,超时之后锁自动释放
     * @return 
     */
    public static boolean getDistributedLock(StringRedisTemplate stringRedisTemplate, String lockKey, String requestId, int expireTime) {
        return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
    }

这个setIfAbsent()方法一共五个形参:

第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,这里写的是锁竞争者的id,在解锁时,我们需要判断当前解锁的竞争者id是否为锁持有者。
第三个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期时间的设置,具体时间由第五个参数决定;
第四个参数为time,与第四个参数相呼应,代表key的过期时间。
总的来说,执行上面的setIfAbsent()方法就只会导致两种结果:

1.当前没有锁(key不存在),那么就进行加锁操作,并对锁设置一个有效期,同时value表示加锁的客户端。
2.已经有锁存在,不做任何操作。上述解锁请求中,缓存超时机制保证了即使一个竞争者加锁之后挂了,也不会产生死锁问题:超时之后其他竞争者依然可以获取锁。通过设置value为竞争者的id,保证了只有锁的持有者才能来解锁,否则任何竞争者都能解锁,那岂不是乱套了。

解锁:


    /**
     * 释放分布式锁
     * @param stringRedisTemplate Redis客户端
     * @param lockKey 锁
     * @param requestId 锁持有者id
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(StringRedisTemplate stringRedisTemplate, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Collections.singletonList(lockKey), requestId);
        return RELEASE_SUCCESS.equals(result);
    }

解锁的步骤:

1、判断当前解锁的竞争者id是否为锁的持有者,如果不是直接返回失败,如果是则进入第2步。

2、删除key,如果删除成功,返回解锁成功,否则解锁失败。

分析

  • 是否可重入:以上实现的锁是不可重入的,如果需要实现可重入,在SET_IF_NOT_EXIST之后,再判断key对应的value是否为当前竞争者id,如果是返回加锁成功,否则失败。

  • 锁释放时机:加锁时我们设置了key的超时,当超时后,如果还未解锁,则自动删除key达到解锁的目的。如果一个竞争者获取锁之后挂了,我们的锁服务最多也就在超时时间的这段时间之内不可用。

  • Redis单点问题:如果需要保证锁服务的高可用,可以对Redis做高可用方案:Redis集群+主从切换。目前都有比较成熟的解决方案。

更详细可以看这篇文章:分布式锁的三种实现方式

Redis的五种数据类型

redis的五种数据类型为:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。

1、string(字符串)是redis最常用的类型,可以包含任何数据,一个key对应一个value,在Rediss中是二进制安全的。

2、hash(哈希)是一个string 类型的key和value的映射表,适合被用于存储对象。

3、list(列表)是一个链表结构,按照插入顺序排序。

4、set(集合)是 string 类型的无序集合。

5、zset(有序集合)是string类型元素的集合,zset是插入有序的,即自动排序。

Redis的哨兵机制

哨兵模式概述

需要更详细就去搜
(自动选主机的方式),哨兵模式其实就是一个类似于监视器的存在,他还可以管理主从服务器。
主从切换技术:当主机宕机后,需要手动把一台从(slave)服务器切换为主服务器,这就需要人工干预,费时费力,还回造成一段时间内服务不可用,所以推荐哨兵架构(Sentinel)来解决这个问题。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

Redis实现多服务器Seesion共享

也可以通过存放Shiro传入账号密码生成的token

        //获取当前的用户
        Subject currentUser = SecurityUtils.getSubject();
        //封装用户的登陆数据  放入令牌加密
        UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);

引言

Redis的Session共享
大厂很多项目都是部署到多台服务器上,这些服务器在各个地区都存在,当我们访问服务时虽然执行的是同一个服务,但是可能是不同服务器运行的;

在我学习项目时遇到这样一个登录情景,假设有如下三台服务器,就使用session存放用户的登录信息,通过该信息可以判断用户是否登录:

假设本次登录是通过服务器01执行的,那么这次的登录session信息就存放到了内存01中;但是当我再次访问时却是服务器02执行操作,而登录session信息却在内存01中,服务器02无法获取,所以它就会判断我没有登录,返回错误的信息…

我们想要实现的就是通过一台服务器登录所生成的session可以和其他服务器共享,那么该如何实现?

解决方法 思路就是既然这几个服务器自己的内存不能共享,那么只要有一个共享空间供这几个服务器共同访问不就可以了;
具体操作
下面就通过redis配置实现共享session:

首先要下载redis,下载网上找教程;这里我直接用的在服务器上通过docker创建的redis容器(简单好用,强烈推荐):

通过可视化工具可以连接一下:

这样redis就配置好了,下面在项目代码中配置redis:

代码中配置redis

在项目中引入redis依赖和spring-session配置依赖(自动将 session 存储到 redis 中):

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
	<version>2.6.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis -->
<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
	<version>2.6.3</version>
</dependency>

在application.yml文件中配置连接redis和session相关配置:

spring:
  # session配置
  session:
    timeout: 86400 # 设置session失效时间
    store-type: redis # 修改spring-session存储配置,默认存储到服务器内存中,现在设置存到redis中(关键)
  # redis配置
  redis:
    port: 8081 # redis的端口号(这里是我的redis容器在docker中对应的端口号)
    host: xx.xxx.xxx.xxx # 我的云服务器ip
    database: 0 # 设置存入redis的哪一个库(默认是0)

其实关键配置就一个: store-type: redis,只要配置了这个,那么代码中session就会存放到redis中而不是自己的内存中;

接下来就可以测试了:

调用登录接口,生成用户session信息,查看redis:

Redis数据同步:主从库是如何实现数据一致的

redis如何保证数据同步
也可以一个服务器建立两个redis实例一台服务器搭建两个redis或多个redis实例

我们学习了 AOF 和 RDB,如果 Redis 发生了宕机,它们可以分别通过回放日志和重新读入 RDB 文件的方式恢复数据,从而保证尽量少丢失数据,提升可靠性。

不过,即使用了这两种方法,也依然存在服务不可用的问题。比如说,我们在实际使用时只运行了一个 Redis 实例,那么,如果这个实例宕机了,它在恢复期间,是无法服务新来的数据存取请求的。

那我们总说的 Redis 具有高可靠性,又是什么意思呢?其实,这里有两层含义:一是数据尽量少丢失,二是服务尽量少中断。AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。

读操作:主库、从库都可以接收;
写操作:首先到主库执行,然后,主库将写操作同步给从库。

redis监控命令及退出 monitor

大多数情况下使用redis是为了缓存消息,有的时候开发阶段进行调试或者定位问题,想知道redis进行了哪些操作,必要情况下使用monitor命令排查问题,quit退出监控(要注意不要长时间使用监控)。
可以看出什么时间,redis做出了什么操作。
不过开启监控后,比较消耗性能,不建议长时间使用,使用完成后,使用图一中 命令:QUIT 进行退出即可。

Pipeline(管道)用来存入大量数据的方法

可以大幅减少io开销、从而提升整体服务的性能

因此如果遇到大量的批处理,我们可以考虑使用Redis的pipeline(管道)。值得注意的是,管道技术并不是Redis特有的技术,管道技术往往需要客户端-服务器的共同配合,大部分工作任务其实是在客户端完成,很显然Redis支持管道技术,按照官网的意思,Redis的最低版本就考虑了管道技术的支持性设计。

如下图,多个连续的incr指令,使用pipeline(管道)后,多个连续的incr指令只会花费一次网络来回开销,这个开销会随着n数值的增大,大幅减少网络io开销,从而提升整体服务的性能。

posted @   没有烦恼的猫猫  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示