java面试题之-Redis篇(持续更新)

2021/9/28 19:12:21

本文主要是介绍java面试题之-Redis篇(持续更新),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

    • Redis基础
      • 01、Redis里有哪些数据类型?
      • 02、Redis为什么能够快速执行?
      • 03、那Redis怎样防止异常数据不丢失的(Redis的如何持久化)?
      • 04、缓存穿透、缓存击穿、缓存雪崩解决方案
      • 05、Redis的io有什么优化
      • 06、Redis的sds 是什么
    • Redis进阶
      • 01、Redis主从复制模式和哨兵模式?
      • 02、Redis故障时哨兵模式下自动切换过程?
      • 03、Redis的集群方案怎么做?
      • 04、Redis 主从复制、哨兵和集群原理与区别
      • 05、Redis的过期策略与内存淘汰机制?
      • 06、Redis内存用完会发生什么情况?
      • 07、Redis的事务(Transactions)
      • 08、非关系型数据的CAP
      • 09、项目中有没有用Redis事务
      • 10、Redis中的分布式锁
      • 11、Redis常见性能问题和解决方案:
      • 12、MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据
      • 13、Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
      • 14、关系型数据库和非关系型数据库的区别

Redis基础

01、Redis里有哪些数据类型?

Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。

string:

  • string是redis最基本的类型,一个key对应一个value。
  • string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。
  • string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

list:

  • Redis 的列表相当于 Java 语言里面的 LinkedList,底层实现结构是链表。
  • 这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是查询很慢,时间复杂度为 O(n)。
  • 当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

hash:

  • Redis 的哈希相当于 Java 语言里面的 HashMap,它是无序字典。
  • 内部实现结构上同 Java 的 HashMap 也是一致的,同样的数组 + 链表二维结构。
  • 第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。

set:

  • Redis 的集合相当于 Java 语言里面的 Set,它内部的键值对是无序的唯一的,好比HashSet只要键值,value值都为NULL。
  • 它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL。 当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

zset :

  • Redis的zset类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。
  • 简单说就是有序的Set

02、Redis为什么能够快速执行?

1、绝大部分请求是纯粹的内存操作(非常快速)

2、采用单线程,避免了不必要的上下文切换和竞争条件(所以不需考虑并发安全性)

3、非阻塞IO - IO多路复用

注意: Redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis(集群)操作的复合操作来说,依然需要锁,而且有可能是分布式锁

先说一下同步阻塞IO:

  • 同步阻塞IO模型是最简单的IO模型
  • 如果一个线程请求服务器,在这个线程请求期间其他线程阻塞

说以下I/O多路复用(多路网络连接复用一个io线程。):

  • I/O就是指的我们网络I/O,多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程。串起来理解就是很多个网络I/O复用一个或少量的线程来处理这些连接

03、那Redis怎样防止异常数据不丢失的(Redis的如何持久化)?

大家是否知道,MySQL服务器如果突然宕机,怎样保证数据的不丢失:

  • 使用的就是MySQL日志,MySQL的redo日志会在数据库宕机重启之后,对其进行前滚,保证MySQL数据的一致性

Redis中也是采用这种方式,先说结论,然后在做分析?

  • 结论:Redis使用持久化的方式来保证异常之后数据不丢失,持久化使用的文件有两种:RBD文件AOF文件

何为Redis的持久化?

  • 在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里
  • Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
  • 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能

RDB持久化(默认的持久化方式)

  • RDB持久化即通过创建快照(压缩的二进制文件)的方式进行持久化,保存某个时间点的全量数据。

  • RDB持久化的触发包括手动触发与自动触发两种方式。

  • 优势

    • 适合大规模的数据恢复
    • 对数据完整性一致性要求不高
  • 劣势

    • 在一定间隔时间做一次备份,所以如果Redis服务器意外down掉的话,就会丢失最后一次快照后的所有修改(因为有时间间隔
    • Fork的时候,内存中的数据被克隆了一份,会占用更多的内存空间

AOF持久化

  • AOF(Append-Only-File)持久化即记录所有变更数据库状态的指令,以append的形式追加保存到AOF文件中,在服务器下次启动时,就可以通过载入和执行AOF文件中保存的命令,来还原服务器关闭前的数据库状态。

AOF和RDB持久化方式对比:

  • RDB方式在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他I0操作,所以RDB持久化方式可以最大化redis的性能。

  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些,但是数据丢失风险大。

  • RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候fork的过程是非常耗时间的,可能会导致Redis在一些毫秒级不能回应客户端请求

  • RDB的恢复,直接把RDB文件里面的内容读进来就可以了

  • AOF文件里面存放的都是指令,如果那条指令存入时有问题了,或者被人为修改了,恢复的时候就会出问题,就算不出问题把全部执行执行一遍非常耗时。

RDB、AOF混合持久化

  • Redis从4.0版开始支持RDB与AOF的混合持久化方案。
  • 首先由RDB定期完成内存快照的备份,然后再由AOF完成两次RDB之间的数据备份,由这两部分共同构成持久化文件。
  • 该方案的优点是充分利用了RDB加载快、备份文件小及AOF尽可能不丢数据的特性。
  • 缺点是兼容性差,一旦开启了混合持久化,在4.0之前的版本都不识别该持久化文件,同时由于前部分是RDB格式,阅读性较低。

Redis 持久化方案的建议

  • 如果Redis只是用来做缓存服务器,比如数据库查询数据后缓存,那可以不用考虑持久化,因为缓存服务失效还能再从数据库获取恢复。

  • 如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。如果你可以接受灾难带来的几分钟的数据丢失,那么可以仅使用RDB。

  • 通常的设计思路是利用主从复制机制来弥补持久化时性能上的影响。即Master上RDB、AOF都不做,保证Master的读写性能,而Slave上则同时开启RDB和AOF(或4.0以上版本的混合持久化方式)来进行持久化,保证数据的安全性。

04、缓存穿透、缓存击穿、缓存雪崩解决方案

缓存穿透:

  • 指查询一个在Redis中不存在的数据,而且在存储层还查不到该数据无法写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,可能导致 DB 挂掉。

  • 简言之:在Redis和MySQL中都不存在,导致每次都去访问DB,导致MySQL挂掉。

解决方案:

  • 查询返回的数据为空时,仍把这个空结果进行缓存,但过期时间会比较短

缓存击穿:

  • 对于设置了过期时间的 key,缓存在某个时间点过期的时候,恰好这时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。
  • 简言之:在Redis中不存在MySQL中存在,导致大量请求都去访问DB,导致MySQL挂掉。

解决方案:

  • 使用互斥锁:当缓存失效时,不立即去 load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db 的操作并回设缓存,否则重试 get 缓存的方法。

  • 永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)。

缓存雪崩:

  • 设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB, DB 瞬时压力过重雪崩。
  • 与缓存击穿的区别:雪崩是很多 key,击穿是某一个key 缓存。
  • 简言之:在Redis中不存在MySQL中存在,导致大量请求都去访问DB,导致MySQL挂掉。

解决方案:

  • 将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

05、Redis的io有什么优化

Redis中采用的是非阻塞IO

06、Redis的sds 是什么

String类型的底层实现,动态字符串

Redis进阶

01、Redis主从复制模式和哨兵模式?

先说一下主从复制模式是咋回事:

  • 主从复制是为了数据备份,和MySQL一样,主从复制之后可以进行读写分离(写主库,读从库),因为是复制,所以里面的数据都一样。

主从复制的工作原理:

  • Slave启动成功连接到Master后会发送一个sync命令

  • Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,Master将传送整个数据文件到Slave,以完成一次完全同步

  • 全量复制(首次):Slave服务在接收到数据库文件数据后,将其存盘并加载到内存中

  • 增量复制(继续):Master继续将新的所有收集到的修改命令依次传给Slave,完成同步

  • 但是只要是重新连接Master,一次完全同步(全量复制)将被自动执行

  • 主从复制是乐观复制,当客户端发送写执行给主,主执行完立即将结果返回客户端,并异步的把命令发送给从,从而不影响性能(主先执行完,在传给从,乐观复制,认为期间不会宕机)。也可以设置至少同步给多少个从主才可写。

  • 缺点:一旦主节点由于故障不能提供服务, 需要人工将从节点晋升为主节点, 同时还要通知应用方更新主节点地址

为了解决主从复制模式的这种缺点,Redis从2.8开始正式提供了Redis Sentinel(哨兵) 机制来解决这个问题

哨兵模式(在数据库的配置文件中设置要监控主库的IP地址即可)
在这里插入图片描述

  • Sentinel(哨兵)进程是用于监控Master主服务器工作的状态
  • 在Master主服务器发生故障的时候,可以实现Master和Slave服务器的自动切换,保证系统的高可用(HA);
  • 注意:哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis集群的高可用性

哨兵进程的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
  • 然而一个哨兵进程对Redis服务器进行监控,不是太可靠,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式,如下图。
    在这里插入图片描述

02、Redis故障时哨兵模式下自动切换过程?

先说一下什么是主观下线和客观下线?

  • 主观下线(Subjectively Down, 简称 SDOWN):指的是单个 Sentinel 实例对服务器做出的下线判断。
  • 客观下线(Objectively Down, 简称 ODOWN):指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。
  • 主观下线状态切换到客观下线状态并没有使用严格的法定人数算法(strong quorum algorithm), 而是使用了流言协议: 如果 Sentinel 在给定的时间范围内, 从其他 Sentinel 那里接收到了足够数量的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线。,如果之后其他 Sentinel 不再报告主服务器已下线, 那么客观下线状态就会被移除。
  • 客观下线条件只适用于主服务器, 对于任何其他类型的 Redis 实例(例如:slave实例), Sentinel 在将它们判断为下线前不需要进行协商(单个哨兵就可以将其判断为下线状态), 所以从服务器或者其他 Sentinel 永远不会达到客观下线条件

在说一下哨兵是如何进行监控的?

  • 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。

  • 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过down-after-milliseconds选项所指定的值,则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。

  • 如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有
    Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。

  • 当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。

  • 在一般情况下, 每个Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。

  • 当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向被标记为客观下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

  • 若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

最后来说一下Redis故障时哨兵模式下自动切换过程:

  • 假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线
  • 当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。
  • 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

03、Redis的集群方案怎么做?

当我们的需要缓存的数据非常多的时候,一个Redis实例可能就容不下了,这时候我们需要多Redis进行横向扩容,把数据分到若Redis中,这样就形成一个多master(每个master有自己的slave)的结构,每个master中存放的数据还不一样,如何实现那,redis从3.0版本开始引入了redis-cluster,就使用这个redis-cluster来实现。

redis-cluster:

  • 支撑N个redis master node ,每个master node 都可以挂载多个slave node ,读写分离的架构,对于每个master 来说,写就写到master ,然后读就从master对应的slaver去读,实现高可用。
  • 因为每个master都有slaver节点,那么如果master挂掉,redis-cluster 这套机制,就会自动将某个slaver切换成master,
  • redis cluster(多master+读写分离+高可用),横向扩展Redis内存

redis-cluster vs replication +sentinel

  • 如果数据量很少,主要是承载高并发性能的场景,比如几个G的缓存,使用单个master就可以,也就是主从复制+哨兵模式
  • redis-cluster: 主要针对海量数据+高并发的场景

04、Redis 主从复制、哨兵和集群原理与区别

我们先看一下Redis的发展历程:

  • 主从模式:因为无法实现自动切换,引入了哨兵模式。
  • 主从-哨兵:如果需要缓存的数量太多,这种方式就有点难顶,引入了redis cluster集群。
  • 主从-哨兵-集群:可以看到redis的不断完善

主从模式:读写分离,备份,一个Master可以有多个Slaves。

哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。

集群:为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。

05、Redis的过期策略与内存淘汰机制?

注意:Redis的过期策略和内存淘汰机制不是一个概念:

  • Redis的过期策略是指:如果处理过期的key。
  • Redis的内存淘汰机制指:当Redis的内存不足时如何处理内存里面的key。

Redis的过期策略,在Redis中过期的key不会立刻从内存中删除,而是会同时以下面两种策略进行删除:

  • 定期删除:每隔一段时间,随机检查设置了过期的key并删除已过期的key;维护定时器消耗CPU资源;
  • 惰性删除:当key被访问时检查该key的过期时间,若已过期则删除;已过期未被访问的数据仍保持在内存中,消耗内存资源;

redis 内存淘汰机制有以下几个(常用的):

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除

英文解释:

  • LRU(Least recently used):淘汰最近最少使用的
  • Random:随机的

总结:Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。

06、Redis内存用完会发生什么情况?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回)或者你可以配置内存淘汰机制,当Redis达到内存上限时会删除一部分内容

07、Redis的事务(Transactions)

事务:当一次执行多个命令,本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞,类似于synchronsized锁一样。

相比于MySQL中的事务来说,Redis中的事务都不能称为真正的事务,所以一般也不用Redis中的事务

注意:Redis的事务不是原子性

  • 在redis中,对于一个存在问题的命令,如果在入队的时候就已经出错,整个事务内的命令将都不会被执行(其后续的命令依然可以入队),如果这个错误命令在入队的时候并没有报错,而是在执行的时候出错了,那么redis默认跳过这个命令执行后续命令。也就是说,redis只实现了部分事务。

Redis事务的三阶段三特性

  • 三阶段:
    • 开启:以MULTI开始一个事务
    • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
    • 执行:由EXEC命令触发事务
  • 三特性
    • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
    • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在事务内的查询要看到事务里的更新,在事务外查询不能看到这个让人万分头痛的问题
    • 不保证原子性redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

08、非关系型数据的CAP

在说非关系型数据库的CAP原理之前,先说一下关系型数据库的ACID原理

  • 原子性:
  • 一致性:数据库要一直处于一致的状态
  • 隔离性:指并发的事务之间不会互相影响
  • 持久性:持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使服务器出现宕机也不会丢失

分布式数据库的CAP原理:

  • C:Consistency(强一致性)
  • A:Availability(可用性)
  • P:Partition tolerance(分区容错性)

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。

而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性可用性之间进行权衡,没有NoSQL系统能同时保证这三点

  • CP :Redis、Mongodb

09、项目中有没有用Redis事务

  • 如果项目中采用的是Redis Cluster(集群架构),不同的key是有可能分配在不同的Redis节点上的,在这种情况下Redis的事务机制是不生效的。

  • 其次,Redis事务不支持回滚操作,所以基本不用

10、Redis中的分布式锁

分布式锁:是控制分布式系统不同进程共同访问共享资源的一种锁的实现,秒杀下单、抢红包等等业务场景,都需要用到分布式锁,我们项目中经常使用Redis作为分布式锁

选了Redis分布式锁的几种实现方法,大家来讨论下,看有没有啥问题哈。

  • 命令setnx + expire分开写
  • setnx + value值是过期时间
  • set的扩展命令(set ex px nx)
  • set ex px nx + 校验唯一随机值,再删除

命令setnx + expire分开写:

if(jedis.setnx(key,lock_value) == 1){ //加锁
    expire(key,100); //设置过期时间
    try {
        do something  //业务请求
    }catch(){
  }
  finally {
       jedis.del(key); //释放锁
    }
}
  • 出现的问题:如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash掉或者要重启维护了,那这个锁就长生不老了,别的线程永远获取不到锁啦,所以分布式锁不能这么实现。

setnx + value值是过期时间:

long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间
String expiresStr = String.valueOf(expires);
 
// 如果当前锁不存在,返回加锁成功
if (jedis.setnx(key, expiresStr) == 1) {
        return true;
} 
// 如果锁已经存在,获取锁的过期时间
String currentValueStr = jedis.get(key);
 
// 如果获取到的过期时间,小于系统当前时间,表示已经过期
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { 
     // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈)
    String oldValueStr = jedis.getSet(key_resource_id, expiresStr);
    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
         // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁
         return true;
    }
}        
//其他情况,均返回加锁失败
return false;
}
  • 问题:
    • 过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步。
    • 没有保存持有者的唯一标识,可能被别的客户端释放/解锁。
    • 锁过期的时候,并发多个客户端同时请求过来,都执行了jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。

set的扩展命令(set ex px nx):

if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加锁
    try {
        do something  //业务处理
    }catch(){
  }
  finally {
       jedis.del(key); //释放锁
    }
}
  • 问题:锁过期释放了,业务还没执行完,锁被别的线程误删。

set ex px nx + 校验唯一随机值,再删除:

if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加锁
    try {
        do something  //业务处理
    }catch(){
  }
  finally {// 代码块中不是原子操作
       //判断是不是当前线程加的锁,是才释放
       if (uni_request_id.equals(jedis.get(key))) {
        jedis.del(key); //释放锁
        }
    }
}
  • 问题:在这里,判断当前线程加的锁和释放锁是不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁

  • 一般也是用lua脚本代替。lua脚本如下

11、Redis常见性能问题和解决方案:

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件(Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照,AOF文件过大会影响Master重启的恢复速度)

  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

  • 尽量避免在压力很大的主库上增加从库

  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…,这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

12、MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据

这个问题主要考察了以下几点内容:

1.Redis的内存淘汰策略。
2.Redis的最大内存设置。

分析题目:保证Redis 中的 20w 数据都是热点数据 说明是被频繁访问的数据,并且要保证Redis的内存能够存放20w数据,要计算出Redis内存的大小。

**a、保留热点数据:对于保留 Redis 热点数据来说,我们可以使用 Redis 的内存淘汰策略来实现,可以使用allkeys-lru淘汰策略,**该淘汰策略是从 Redis 的数据中挑选最近最少使用的数据删除,这样频繁被访问的数据就可以保留下来了。

**b、保证 Redis 只存20w的数据:**1个中文占2个字节,假如1条数据有100个中文,则1条数据占200字节,20w数据 乘以 200字节 等于 4000 字节(大概等于38M);所以要保证能存20w数据,Redis 需要38M的内存。

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。

volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lruttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

使用策略规则:

  • 1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru

  • 2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

13、Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

  • 使用keys指令可以扫出指定模式的key列表。
  • 对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
    • 这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
    • 这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

14、关系型数据库和非关系型数据库的区别

关系型数据库最典型的数据结构是,由二维表及其之间的联系所组成的一个数据组织

  • 优点:
    • 1、易于维护:都是使用表结构,格式一致;
    • 2、使用方便:SQL语言通用,可用于复杂查询;
    • 3、复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。
  • 缺点:
    • 1、读写性能比较差,尤其是海量数据的高效率读写;
    • 2、固定的表结构,灵活度稍欠;
    • 3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。

非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等。

  • 优点:

    • 1、格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
    • 2、速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
    • 3、高扩展性;
    • 4、成本低:nosql数据库部署简单,基本都是开源软件。
  • 缺点:

    • 1、不提供sql支持,学习和使用成本较高;
    • 2、无事务处理;
    • 3、数据结构相对复杂,复杂查询方面稍欠。


这篇关于java面试题之-Redis篇(持续更新)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程