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(字符流):从程序中输出到文件中

总结

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

相关推荐
尚学教辅学习资料2 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
雷神乐乐18 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|23 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
逊嘘42 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
morris1311 小时前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
monkey_meng1 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员1 小时前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU1 小时前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie61 小时前
在IDEA中使用Git
java·git