Java Redis-NoSql缓存面试题

Java Redis-NoSql缓存面试题


前言

最新的 Java 面试题,技术栈涉及 Java 基础、集合、多线程、Mysql、分布式、Spring全家桶、MyBatis、Dubbo、缓存、消息队列、Linux...等等,会持续更新。

如果对老铁有帮助,帮忙免费点个赞,谢谢你的发财手!

1、谈谈你对Redis的理解?

Redis是一个开源的基于Key-Value结构的NoSql内存数据库。一般情况下,Redis 用作客户端和数据库之间操作的缓存,主要目的是减少数据库 IO。

Redis的工作流程是这样的:

当用户需要去读取某个数据的时候,首先会去 Redis 里面查询,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后返回给用户,再把这个数据缓存到 Redis 里面。

2、为什么Redis这么受欢迎呢?

  • 1)、读写特别快,Redis是纯内存操作,读写速度特别快;
  • 2)、支持数据持久化,支持RDB和AOF两种持久化方式;
  • 3)、支持数据备份,即主从模式的数据备份,提高redis可用性
  • 4)、支持多种数据结构,
    5种常用类型:String、list、hash、set、zset
    3种特殊数据类型:
  • geospatial(地理空间),计算两个地点之间的距离,场景:查找附近的人
  • Hyperloglog(基数统计),场景:统计集合中不重复的数的个数
  • Bitmap(位图),场景:用户签到,用户在线状态,统计日活跃用户等 )

3、缓存穿透,缓存击穿,缓存雪崩?

  • 缓存穿透:是指在同一时段有大量key 请求,但这些key在缓存中不存在,就导致大量的并发请求打到数据库,就会被恶意攻击。
    常见的解决方案有2种:1、缓存空对象;2、布隆过滤器
  • 缓存击穿:也叫热点key问题,就是一个被高并发访问的key突然过期了,就导致使大量的并发请求打到数据库,带来巨大压力。
    常见的解决方案有2种:1热点key设置永久有效(persist);2、分布式锁
  • 缓存雪崩:是指在同一时段有大量的key同时过期了,就导致大量的并发请求打到数据库,带来巨大压力,甚至会崩溃。
    常见的解决方案有2种:1、给不同的key随机设置过期时间;2、利用Redis集群提高服务的可用性。
    其实,数据库本身也有最大连接数的限制,超过限制的请求会被拒绝;总的来说,具体选择哪种方式,还是看具体的业务场景。

4、Redis 和 Mysql 如何保证数据一致性?

当用户需要去读取某个数据的时候,首先会去 Redis 里面查询,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后返回给用户,再把这个数据缓存到 Redis 里面。这会出现一个问题,就是一份数据,同时保存在Redis和Mysql里面,当数据发生变化的时候,就会出现数据一致性问题:

在这种情况下,能够选择的方法只有2种。

  • 第1种,先更新Mysql,再更新Redis:但如果更新Redis失败,就会导致Mysql和 Redis 中的数据不一致;
  • 第2种,先删除Redis缓存,再更新Mysql:但是极端情况下,删除 Redis缓存和更新Mysql这两个操作并不是原子的,所以还是会存在数据不一致问题。
    那么就只能采用最终一致性方案:
    第1种方案 :基于 MQ 的可靠性消息队列,来实现最终一致性;
    第2种方案 :通过alibaba开源的Canal组件,监控 Mysql 中 的binlog 日志,把更新后的数据同步到 Redis 里面;
    如果业务场景不能接受数据的短期不一致性,那就不能使用这些方案来做。

5、Redis 的持久化机制是什么?各自的优缺点?

Redis提供了 RDB(默认) 和 AOF 两种持久化机制:

RDB: 也叫做Redis数据快照,简单来说就是把内存数据都写入到磁盘中的一个dump.rdb二进制文件中(bgsave)。

ROF:Redis每次修改数据的命令,都会追加到aof文件中,然后可以设置每秒刷盘一次,兼顾性能和安全。

我认为 RDB 和 AOF 的优缺点有两个。

  • 1、RDB 是每隔一段时间触发持久化,因此数据安全性低,而AOF 可以做到实时持久化,数据安全性较高 ;
  • 2、RDB 文件默认采用压缩的方式持久化,而AOF 存储的是执行指令,文件比较大,所以 RDB 在数据恢复的时候性能更好
    在我看来,所谓优缺点,本质上其实是哪种方案更适合当前的应用场景而已。

6、Redis 的主从同步机制:

Redis可以使用主从同步,同步分为全量同步和增量同步:

全量同步:

  • 第1步:master节点收到salve节点的psync命令后,执行bgsave命令生成RDB文件,并且在缓冲区中记录之后所有的写操作;
  • 第2步:master将生成好的RDB文件发送给slave节点;
  • 第3步:slave节点在接收到RDB后,会先将自身的数据全部丢弃,然后加载RDB文件
  • 第4步:加载完RDB后,通知主节点将积压在缓冲区的所有写指令发送给自己,然后执行这些写指令,从而实现全量同步。
    在下面2种情况下,都会执行全量同步:第1种是从节点第一次同步;第2种是从节点断开时间太久,导致缓冲区里的指令被覆盖,只能再次全量同步了。

增量同步:

  • master节点通过服务器运行ID和偏移量,把积压在缓冲区的所有写指令发送给slave节点,然后salve节点执行这些写指令,从而达到增量同步。
    在我看来,Redis使用主从同步,是为了提高Redis服务器可用性。

7、Redis 存在线程安全问题吗?为什么?

好的,关于这个问题,我从两个层面来回答。

  • 第一个 ,从 Redis 服务端层面。 Redis Server 本身是单进程单线程的,所以是线程安全的。虽然 Redis 6.0 里面,增加了多线程的模型,但是它只是用来处理网络 IO 事件,对于指令的执行过程,仍然是由主线程来处理,所以不会存在线程安全问题。
    那为什么 Redis 没有采用多线程来执行指令,我认为有以下3个方面的原因。
  • 1、Redis是纯内存操作,执行速度特别快,它的性能瓶颈是内存或网络带宽
  • 2、引入多线程会有过多的上下文切换,还会面临线程安全问题,反而性能会下降;
  • 3、Redis内部使用了基于epoll的IO多路复用,从而可以避免无效的等待,充分利用CPU资源。
  • 第二个 ,从 Redis 客户端层面。 虽然 Redis Server 中的指令执行是原子的,但是如果有多个 Redis 客户端同时获取服务端上的某个 key,然后同时进行修改,那么就存在线程安全问题了。
    当然,对于客户端层面的线程安全性问题,也有解决办法,比如尽可能的使用 Redis 里面的原子指令,或者通过 Lua 脚本 来实现多个指令的操作等等。

8、Redis 有哪些部署方式?

  • 单机模式:架构简单,部署方便,缺点是机器故障、QPS瓶颈。
  • 主从模式:具有高可用性(HA)且读写分离,会采用全量同步跟增量同步两种机制。
  • 哨兵模式:本身是一个独立运行的进程,它具备了集群监控、故障转移、消息通知、配置中心等功能。缺点是存在脑裂问题,解决方案就是将之前的主节点降级为从节点就行了。
    cluster集群模式:多主多从模式,采用虚拟哈希槽分区(0-16383),将数据进行分区,每个master上放一部分数据,即使某个master挂了,还可以继续提供服务。

9、Redis哨兵模式(sentinel)主要功能:

  • 1)集群监控:负责监控Redis主从节点是否正常工作,每隔1秒发送一次ping命令;
  • 2)消息通知:如果某个节点超过一定时间段没有回复,哨兵就主观认为该节点故障了,就会发送消息通知给管理员;
  • 3)故障转移:如果是主节点故障了,就会把优先级高的从节点提升为新的主节点;
  • 4)配置中心:如果有了新的主节点,哨兵就会把新的主节点地址发给Redis客户端;

10、redis 过期键的删除策略?

  • 1、惰性删除:每次获取key时再判断是否过期,如果过期则删除;
  • 2、定时删除:创建一个定时器 timer,定时删除即将过期的key;
  • 3、定期删除:定期抽取一部分key,判断是否过期,如果过期则删除。
    Redis 中实际采用的策略是惰性删除加定期删除的组合方式。

11、Redis 的回收策略(淘汰策略)?

Redis中的回收算法分为两种,volatile-xxx和allkeys-xxx,volatile是指对设置了过期时间的 key 进行淘汰;allkeys是指对所有的key 进行淘汰。

具体的淘汰策略中,比较常用的有LRU和LFU两种:

  • LRU(The Least Recently Used):叫做最近最久使用算法,也就是淘汰访问时间最旧的数据。
  • LFU(Least Frequently Used):叫做最近最少使用算法,也就是淘汰最近使用次数最少的数据。
    每个数据都会有一个额外的字段,前16位保存了时间戳,后8位保存访问次数,redis还提供了一个衰减机制,随着 key的空闲时间越来越长,对应的访问次数也会慢慢减少。
    Redis默认是noeviction:当内存不足的时候,写入操作会报错(OOM),读操作不影响。

12、Redis 与Memcache的区别都有哪些?

  • 1、存储方式,Redis支持数据持久化,重启后数据不会丢失,Memecache 重启后会丢失;
  • 2、数据支持类型,Redis支持多种数据类型,Memcache 只支持简单的数据类型;
  • 3、数据备份, Redis支持主从模式的数据份,Memcache不支持。

13、Redis 最适合的场景?

  • 1、缓存
    Redis可以用做缓存,存放热点数据(读多写少的数据)。
  • 2、数据库
    Redis可以用做一个非关系型的nosql数据库,它所有数据都存放在内存中,并提供持久化。
  • 3、消息队列
    一般使用list双向链表结构作为队列,实现点对点的消息队列,lpush生产消息,lpop消费消息,list还有个指令叫blpop,在没有消息的时候,它会阻塞直到消息到来;还有发布/订阅功能,实现生产一次消费多次,但都无法避免消息丢失或漏读。
  • 4、分布式锁
    可以利用Redis提供的setnx命令,如果设置key返回1,说明获取锁成功,返回0获取锁失败。
  • 5、分布式会话
    当在应用增多且相对复杂的系统中,一般都会搭建以Redis为中心的session服务,session不再由Tomcat容器管理,而是由session服务及Redis管理
  • 6、秒杀
    在某些秒杀活动中,存在高并发,需要流量削峰,我们可以利用Redis的incr命令,以访问者的ip和其他信息作为key,访问一次增加一次计数,当同一用户的访问次数超过我们设定的次数,则返回提示信息(比如提示用户操作过于频繁,一定时间之后再重新操作等等)。
  • 7、抽奖
    利用set结构的无序不重复性,通过SADD无序添加元素,Spop随机移除并返回元素
  • 8,排行榜
    如淘宝的月销量榜单、商品最新排行榜、热度排行榜、点赞前10名等。利用Redis的zset结构能实现各种复杂的排行榜应用。
  • 9、计数器
    Redis的递增(incr)或递减(decr)指令。如文章的阅读量、视频的播放量、微博点赞量等,如果并发量很高时,我们可以先写入Redis,再定时同步到数据库。
    1)、比如需要显示阅读量,就可以使用string结构作为计数器
    2)、比如微博都有点赞数、评论数、转发数和浏览数四个属性,这时用hash结构进行计数会更好;

14、redis常用命令?

Redis五种数据类型:

字符串string:value可以为string、int、float类型;

哈希hash:value内是field+value的格式

列表list:一个双向链表,1.有序;2.元素可重复;3.新增删除快;

集合set:1.无序 2.元素不可重复 3.查找快 4.支持交集、并集和差集功能;

有序集合zset:1.可排序 2.元素不重复 3.查询速度快;

  • 1、String:
    (批量)添加/修改:(mset)/set name zs;
    (批量)获取:(mget)/get name ;
    添加/修改并设置过期时间(秒):setex name 10 zs;
    添加/修改,key存在则返回0,key不存在则返回1:setnx status 1
    自增/减: incrby 10/-10
  • 2、hash
    批量)添加/修改:(hmset)/hset user name zs;
    (批量)获取:(hmget)/hget user name ;
    删除user指定字段:hdel user name;
    获取user所有字段:hkeys user;
    添加/修改,key存在则返回0,key不存在则返回1:hsetnx user status 1;
  • 3、list
    将一个或多个值插入到列表头部:lpush names zs zs ls...;
    将一个或多个值插入到列表尾部:rpush names zs zs ls... ;
    根据下标索引范围取值:lrange name 0 1
    移除并返回列表头部的第一个元素,没有返回nil:lpop names;
    移除并返回列表尾部的第一个元素:rpop names;
    获取列表长度:llen names;
  • 4、set:
    添加一个或多个元素:sadd names zs ls ...;
    删除一个或多个元素:srem names zs ls ...;
    获取所有的元素:smembers names;
    获取集合的长度:scard key;
    随机获取一个元素并移除:spop names
    获取两个集合的交集:sinter k1 k2
    获取两个集合的并集:sunion k1 k2
    获取两个集合的差集:sdiff k1 k2
  • 5、zset
    添加一个或多个成员,或者更新已存在成员的分数:zadd user 98 zs 100 1s...
    按升序获取后10名:zrange user 0 9;
    按降序获取前10名:zrevrange user 0 9;
    移除有序集合中的某个元素:zrem user zs;
  • 6、通用命令
    查看当前所有key:keys * ;
    查看某个key是否存在:exists name;
    设置key的有效时间:expire name 10;
    查看key的剩余时间:ttl name(-1:永不过期,-2:表示不存在或已删除);
    获取key所存储的值的类型:type name ;
    删除某个key:del name;
    修改key的名称:rename name newName
    切换库:select index 切换库(多数据库:默认16个库)
    将当前库下的key移动到指定编号的库中:move key index(如果目标库中存在同名的key,则移动失败)
    空当前库下的所有key:flushdb 清空当前库下的所有key
    清空当前redis服务器中所有库中的key:flushall
    设置密码:临时:config set requirepass 123456;永久:redis.config文件。

15、什么是IO多路复用?

IO 多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄。

16、分布式ID是什么?有哪些解决⽅案?

对于⼀个分布式系统,分布式ID就是我们需要的全局唯一的主键,它要求全局唯一、高性能、高可用。常见解决方案:

  • 1.Uuid:这种⽅案实现简单,缺点是会消耗存储空间和性能;
  • 2.雪花算法:底层原理就是在某⼀毫秒内对某⼀个数字⾃增,像美团的leaf,滴滴的tinyid都是基于雪花算法(雪花算法+号段模式提前生成一批ID);
    缺点:严重依赖于机器时钟,如果时钟回拨,就会生成重复ID,可以抛出异常。
  • 3.利用第三方中间件:⽐如redis的⾃增命令、zookeeper的永久顺序节点。

17、分布式事务有哪些实现⽅案?

1、消息队列:⽬前RocketMQ中⽀持事务消息,它的⼯作原理是:

  • 1.⽣产者订单系统先发送⼀条半(half)消息到Broker,半消息对消费者⽽⾔是不可⻅的;
  • 2.再创建订单,根据订单是否创建成功,向Broker发送提交(commit)或回滚(rollback)命令;
  • 3.⽣产者订单系统还可以向Broker提供回调接⼝,当Broker发现⼀段时间没有收到任何操作命令,则会主动调此接⼝来查询订单是否创建成功;
  • 4.⼀旦半消息提交了,消费者库存系统就会来消费,如果消费成功,则消息销毁,分布式事务成功结束;
  • 5.如果消费失败,则进⾏重试,如果最后还是失败,则进⼊死信队列,等待进⼀步处理。

2、Seata:阿⾥开源的分布式事务框架

  • ⽀持AT、TCC等多种模式,底层都是基于二阶段提交来实现的。

18、Redis的哈希槽用途?

由于一致性哈希算法的数据会出现倾斜问题,因此Redis集群没有使用一致性hash,而是引入了哈希槽;在数据和节点之间又加了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,相当于节点上放的是槽,槽里放的是数据,解决了数据均匀分配的问题。

19、redis集群 Cluster怎么使用哈希槽?

Redis集群中内置了16384个哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在Redis集群中添加一个key-value时,redis先对key使用CRC16算法算出一个结果,然后把结果对16384求余数(mod),这样每个key都会对应一个编号在0--16383之间的哈希槽,也就是映射到某个节点上。

20、如果 Redis 变慢了,有什么排查方法?

  • 1、可能是服务器配置差:通过redis-cli命令获取Redis实例在当前环境下的基线性能(是指在服务器没有任何压力,没有任何干扰的情况下性能);
  • 2、可能使用复杂度过高的命令导致的,即慢查询问题:可以通过配置慢查询日志的方式来排查慢查询问题;
  • 3、可能是同一时刻有大量的key过期,就会造成主线程长时间的阻塞:对于同一批key,可以在每个key的过期时间上加一个随机数,避免同时删除。
  • 4、是否存在bigkey:可以使用scan -i命令扫描bigkey,项目中避免写入bigkey。
  • 5、可能是AOF重写(将多个命令合并成一个命令)导致的,因为每一个写命令,都要同步等待fsync执行完成,所以会阻塞主线程:如果业务允许的话,将appendfsync改为no,即周期性地将日志写入磁盘中,不要每次写操作或每秒刷盘一次;如果同时也允许数据丢失,可以将配置项no-appendfsync-on-rewrite设置为yes,避免AOF重写和fsync竞争磁盘IO资源。如果实在不行,最好使用固态硬盘作为AOF日志的写入盘。
  • 6、可能出现了swap:通过命令查看是否发生了swap,如果是的话,就增加机器内存,或者是使用Redis集群,分摊单机Redis的键值对数量和内存压力。

21、什么是SWAP?

SWAP是操作系统在内存紧张时执行的一种机制,即将部分不常用的内存空间对应的数据写到磁盘,然后将该内存分配给程序使用,当需要用到该部分数据时再将数据从磁盘上读取出来,这里涉及到了磁盘的IO,所以速度会比较慢。

22、Redis有哪几种数据类型,这几种数据类型应用场景分别是怎样的?

  • 1、String:可以存储三种类型,int(整数)、float(单精度浮点数)、string(字符串)
    场景:缓存、Session共享、分布式锁、全局ID、计数器(点赞量和限流)
  • 2、Hash:添加命令hset key mapKey mapValue,最大存储数量 2^32-1(40亿左右)
    场景:购物车、存储对象(key为用户id,mapKey为name/age,mapValue为张三/30);
  • 3、List:存储有序的字符串(从左到右),元素可以重复,底层是双向链表
    场景:用户的消息列表,消息队列
  • 4、Set:是一个无序不重复的键值集合,可以交集,并集,差集
    场景:抽奖(spop)、用户点赞/签到、商品标签、共同好友(取交集sinter)
  • 5、ZSet:存储有序的元素,每个元素都有个score,按照 score 从小到大排序;
    场景:电话号码排序、排行榜前10条zrevrange hotSearch:20230627 0 10 withscores
  • 6、BitMaps:即位图,是一串连续的二进制数组(0和1);
    场景:连续签到、在线用户统计
  • 7、GEO:主要用于存储地理位置信息
    场景:滴滴打车、附件的人

23、Redis做消息队列?

  • 缺点是可能丢失消息数据,不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费;
    要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,使得多个消费者可以消费同一条消息。
  • 在Redis 5.0推出了 Stream 类型,支持创建消费组、消息持久化、ack 确认等。
    22、什么是布隆过滤器?
    它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

24、NIO 几个核心部分?

NIO 由以下几个核心部分组成:

  • 1.Channel通道:通道可以异步地读写,既可以从通道中读取数据,又可以往通道中写入数据;有点像流,但流的读写通常是单向的。
  • 2.Buffer缓冲区:数据是从通道读入缓冲区,从缓冲区写入到通道中的,缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
  • 3.Selector选择器:允许单个线程处理多个 Channel,但Channel需要注册到Selector上,(SelectableChannel.register()方法),可以监听四种不同类型的事件:OP_CONNECT连接就绪,OP_ACCEPT接收就绪,OP_READ读就绪,OP_WRITE写就绪。

25、NIO和BIO以及传统IO?

  • 1、NIO:是非阻塞IO,基于网络的IO,通过包(数据块)的方式进行数据传输;
  • 2、BIO:是阻塞IO,基于网络的IO,通过流(字符流和字节流)的方式进行传输;
  • 3、传统IO:是和硬盘打交道,即读写硬盘,和网络没有关系。

26、字节流和字符流的区别?

  • 1、 读写单位不同:字节流一次读入/读出是1个字节(8bit),字符流可能是一个/多个字节;
  • 2、处理对象不同:字节流能处理所有类型的数据(如图片、视频等),而字符流只能处理字符类型的数据;如果是处理纯文本数据,就优先考虑字符流。
  • 3、写入方式不同:字符输出流写数据时是先将数据写入缓冲区,待字符输出流关闭后再写入到文件中,而字节输出流是直接写入到文件中。

27、输入流和输出流的区别?

输入流只能进行读操作,输出流只能进行写操作,需要根据待传输数据的不同特性而使用不同的流。

  • 输入流:InputStream(字节流)或者Reader(字符流):从文件中读到程序中;
  • 输出流:OutputStream(字节流)或者Writer(字符流):从程序中输出到文件中

总结

都已经看到这里啦,赶紧收藏起来,祝您工作顺心,生活愉快!

相关推荐
aibo8603083 分钟前
基于 Spring Boot + Vue3 的菜谱分享网站设计与实现
java·spring boot·后端
凤山老林4 分钟前
Spring Boot 集成 TOTP 双因素认证与恢复码机制
java·spring boot·后端
小冯不疯7 分钟前
物料主数据同步实战:从金蝶云星空到小满OKKICRM的单一策略深度拆解
java·大数据·数据库
打工仔折腾 AI9 分钟前
深入验证 DeepSeek 新模型开源声明:KV 缓存指标与按次计费的后端落地评估
缓存·性能优化·开源·ai agent 实战
vipxieliang11 分钟前
Spring Boot 测试策略:单元测试、切片测试与集成测试
java·spring boot·后端
估值探索者23 分钟前
【Python量化技术指标实战 04】布林带通道突破,怎么知道是真突破还是假突破?
java·开发语言·python·接口·股票数据api接口
未秃头的程序猿23 分钟前
同一个商户号,V2 免密代扣和 V3 分账的证书怎么共存?
java·后端·微信
爱吃牛肉的大老虎33 分钟前
Redis之分布式 Session问题
数据库·redis·分布式
captain37637 分钟前
Spring Boot的配置文件
java·spring boot·后端
captain37638 分钟前
Spring Boot日志
java·数据库·spring boot