Java面试——Redis

2021-05-14 16:25:25 浏览数 (1)

一、Redis 为什么那么快


【1】完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中。 【2】数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的。 【3】采用单线程,避免不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。 【4】使用多路IO复用模型,非阻塞IO。利用epoll可以同时监察多个流的 IO事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 IO事件时,就从阻塞态中唤醒,epoll就轮询哪些真正发生了事件的流,并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。多路指的是多个网络连接,“复用”指的是复用同一个线程。

二、Redis 与 Memcached 的区别


Redis 与 Memcached 的相似之处:【1】Redis 和 Memcached 都是内存数据存储系统,都用作内存中的键值数据存储; 【2】Redis 和 Memcached 都属于NoSQL系列数据管理解决方案,两者都基于键值数据模型; 【3】Redis 和 Memcached 都将所有数据保存在 RAM 中,这当然使它们作为缓存层非常有用; Redis 与 Memcached 的区别:【1】Redis 和 Memcached都是将数据存放在内存中,都是内存数据库。不过 Memcached 还可用于缓存其他东西,例如图片、视频等等; 【2】Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 listsetzsethash等数据结构的存储; 【3】虚拟内存 Redis 当物理内存用完时,可以将一些很久没用到的 value 交换到磁盘; 【4】过期策略 Memcached 在 set 时就指定,例如:set key1 0 0,即永不过期。Redis 可以通过例如 expire 设定,例如:expire name 10; 【5】存储数据安全性 Memcached 挂掉后,数据就没了。Redis可以定期保存到磁盘(持久化); 【6】灾难恢复 Memcached 挂掉后,数据不可恢复。Redis 数据丢失后可以通过 rdb 或 aof恢复; 【7】Memcached不支持复制,Redis支持主从复制,允许从属 Redis服务器成为主服务器的精确副本;来自任何 Redis服务器的数据都可以复制到任意数量的从属服务器。 【8】应用场景不一样:Redis 除了作为 NoSQL数据库使用外,还能用做消息队列、数据堆栈和数据缓存等;Memcached适合于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和 session等。 【9】Memcached 的读写速度高于 Redis,因为Redis 是单线程的,而 Memcached 是多线程的; 【10】Memcached 单个 key-value大小有限,一个 value最大只支持1MB,而 Redis最大支持512MB;

三、Redis 有哪几种数据淘汰策略


Redis 检查内存使用情况,如果已使用的内存大于 maxmemory的值则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。maxmemory=0 的时候表示我们对 Redis的内存使用没有限制。淘汰策略如下: 【1】noeviction:返回错误。当内存使用达到阈值的时候,所有引起申请内存的命令会报错。 【2】allkeys-lru:在主键空间中,优先移除最近未使用的key。 【3】volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。 【4】allkeys-random:回收随机的键使得新添加的数据有空间存放。 【5】volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。 【6】volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

四、Redis 支持哪几种数据类型


Reids 支持7中数据结构,最常用的是 String、List、Set、Sorted Set、Hash 其内部结构及使用参照博客链接;

五、Redis 集群方案什么情况下会导致整个集群不可用


假如有A,B,C三个节点的集群,在没有主从复制模型的情况下,其中一个节点挂掉,那么整个集群就会缺少某个范围的槽而不可用。所以从节点很重要。

六、Redis 哈希槽的概念


Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384个哈希槽,每个 key通过 CRC16校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash槽。

七、Redis 集群会有写操作丢失吗


Redis 并不能保证数据的强一致性,这意味这在实际生产中集群在特定的条件下可能会丢失写操作。

八、Redis 集群之间是如何复制的


博客连接】:链接

九、Redis 集群如何选择数据库


Redis 集群目前无法做数据库选择,默认在0数据库。

十、Redis 中的管道(Pipeline)有什么用


客户端将请求传送给服务器,服务器处理完毕后,再将响应回复给客户端。这要花费一个网络数据包来回的时间。如果连续执行多条指令,那么就会花费多个网络数据包来回的时间(多条指令,一条一条发送给服务器)。 管道的本质是:将多个连续的读写操作合并后总共花费一次网络来回。就好像连续的写操作合并了,连续的读操作也合并了一样。服务器根本没有任何区别对待,还是走着收着一条消息、执行一条消息、回复一条消息的正常流程。Redis 自带了管道的压力测试工具 redis-benchmark,使用此工具可以进行管道测试:

代码语言:javascript复制
redis-benchmark -t set -q
SET: 54938.05 requests per second

# -P 表示管道内并行的请求数量。  当P=2 时,QPS 达到 9W/s
# 当P达到一定值时,会下降,因为单线程CPU消耗已经达到 100%,所以无法上升
redis-benchmark -t set -P 2 -q
SET: 93238.15 requests per second

将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

十一、怎么理解 Redis事务


事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。但是如果某个事务执行失败,也不会进行回滚的。

十一、Redis 事务相关的命令有哪几个


MULTI、EXEC、DISCARD、WATCH、UNWATCH

十二、Redis key 的过期时间和永久有效分别怎么设置


EXPIRE 和 PERSIST命令。

十三、Redis 如何做内存优化


尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。

十四、Redis 回收进程如何工作的


【1】一个客户端运行了新的命令,添加了新的数据。 【2】Redis 检查内存使用情况,如果大于 maxmemory的限制, 则根据设定好的策略进行回收。 【3】一个新的命令被执行,等等。 【4】所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

十五、Redis 回收使用的是什么算法


Redis 中采用两种算法进行内存回收,引用计数算法以及 LRU算法(最近最久未使用算法)。

十六、Redis 如何做大量数据插入


Redis2.6 开始 redis-cli 支持一种新的被称之为 pipe mode 的新模式用于执行大量数据插入工作。

十七、为什么要做 Redis分区


分区可以让 Redis管理更大的内存,Redis 将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使 Redis的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。

十八、你知道有哪些 Redis分区实现方案


【1】客户端分区:就是在客户端就已经决定数据会被存储到哪个 Redis节点或者从哪个 Redis节点读取。大多数客户端已经实现了客户端分区。 【2】代理分区:意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些 Redis实例,然后根据 Redis的响应结果返回给客户端。Redis 和 memcached 的一种代理实现就是 Twemproxy。 【3】查询路由(Query routing) :意思是客户端随机地请求任意一个 Redis实例,然后由 Redis将请求转发给正确的 Redis节点。Redis Cluster 实现了一种混合形式的查询路由,但并不是直接将请求从一个 Redis节点转发到另一个 Redis节点,而是在客户端的帮助下直接 Redirected到正确的 Redis节点。

十九、Redis 持久化数据和缓存怎么做扩容


【1】如果 Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。 【2】如果 Redis被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

二十、分布式 Redis是前期做还是后期规模上来了再做好?为什么?


既然 Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让 Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。 一开始就多设置几个Redis实例,例如 32或者 64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。 这样的话,当你的数据不断增长,需要更多的 Redis服务器时,你需要做的就是仅仅将 Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的 Redis实例从第一台机器迁移到第二台机器。

二十一、Twemproxy 是什么


Twemproxy 是 Twitter 维护的(缓存)代理系统,代理 Memcached 的 ASCII协议和 Redis协议。它是单线程程序,使用c语言编写,运行起来非常快。它是采用 Apache 2.0 license的开源软件。 Twemproxy 支持自动分区,如果其代理的其中一个 Redis节点不可用时,会自动将该节点排除(这将改变原来的 keys-instances的映射关系,所以你应该仅在把 Redis当缓存时使用Twemproxy)。 Twemproxy 本身不存在单点问题,因为你可以启动多个 Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy 实例。 Twemproxy 是 Redis客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。

二十二、Redis 与其他 key-value存储有什么不同


【1】Redis 有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis 的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。 【2】Redis 运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

二十三、Redis 的内存占用情况怎么样


给你举个例子: 100万个键值对(键是0到999999值是字符串“hello world”)在我的 32位的Mac笔记本上用了100MB。同样的数据放到一个 key里只需要16MB, 这是因为键值有一个很大的开销。 在 Memcached上执行也是类似的结果,但是相对 Redis的开销要小一点点,因为 Redis会记录类型信息引用计数等等。当然,大键值对时两者的比例要好很多。64位的系统比32位的需要更多的内存开销,尤其是键值对都较小时,这是因为 64位的系统里指针占用了8个字节。 但是,当然,64位系统支持更大的内存,所以为了运行大型的 Redis服务器或多或少的需要使用 64位的系统。

二十四、哪些办法可以降低 Redis的内存使用情况


如果你使用的是 32位的 Redis实例,可以好好利用 Hash、list、sorted set、set等集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放到一起。

二十五、查看 Redis使用情况及状态信息用什么命令


【博客连接】:链接

二十六、Redis 的内存用完了会发生什么


如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis当缓存来使用配置淘汰机制,当 Redis达到内存上限时会冲刷掉旧的内容。

二十七、Redis 是单线程的,如何提高多核CPU的利用率


可以在同一个服务器部署多个 Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个 CPU,你可以考虑一下分片(shard)。

二十八、一个 Redis实例最多能存放多少的keys?List、Set、Sorted Set他们最多能存放多少元素


【1】理论上 Redis可以处理多达 232的 keys,并且在实际中进行了测试,每个实例至少存放了 2亿5千万的 keys。我们正在测试一些较大的值。 【2】任何 list、set、和 sorted set都可以放 232个元素。换句话说,Redis 的存储极限是系统中的可用内存值。

二十九、Redis 常见性能问题和解决方案


【1】Master 最好不要做任何持久化工作,如 RDB内存快照和 AOF日志文件; 【2】如果数据比较重要,某个 Slave 开启 AOF备份数据,策略设置为每秒同步一次; 【3】为了主从复制的速度和连接的稳定性,Master 和 Slave最好在同一个局域网内; 【4】尽量避免在压力很大的主库上增加从库; 【5】主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1 做 Master,其他不变。

三十、Redis 提供了哪几种持久化方式


【1】RDB 持久化方式能够在指定的时间间隔能对你的数据进行快照存储; 【2】AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis 还能对 AOF文件进行后台重写,使得AOF文件的体积不至于过大。 【3】如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。 【4】你也可以同时开启两种持久化方式,在这种情况下,当 Redis 重启的时候会优先载入 AOF文件来恢复原始的数据,因为在通常情况下 AOF文件保存的数据集要比 RDB文件保存的数据集要完整。 【5】最重要的事情是了解 RDB和 AOF持久化方式的不同,让我们以RDB持久化方式开始。

三十一、如何选择合适的持久化方式


一般来说, 如果想达到足以媲美 PostgreSQL的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB持久化。有很多用户都只使用 AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF恢复的速度要快,除此之外, 使用 RDB还可以避免之前提到的 AOF 程序的 bug。

三十二、修改配置不重启 Redis会实时生效吗


针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。

三十三、Redis 中的哨兵模式(sentinel) 的作用


典型的问题就是,当主节点宕机后,sentinel 是如何进行主从切换的。【博客链接】

三十四、谈谈你对 Redis 集群 Cluster的理解


博客连接】:链接

三十五、Redis 的常用配置有哪些


博客连接】:链接

三十六、Redis 的线程模型


博客连接】:链接

三十七、Redis 分布式锁的理解


博客连接】:链接

三十八、Redis 平常用到的调优


博客连接】:链接​​​​​​​

三十九、Scan 命令的使用


scan 提供了三个参数,第一个是 cursor 整数值,第二个是 key 的正则模式,第三个是遍历的 limit hint。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor,一直遍历到返回的 cursor 值为 0时结束。举个栗子:

代码语言:javascript复制
127.0.0.1:6379> scan 0 match key99* count 1000
1) "13976"
2)  1) "key9933"
    2) 此处省略9个

从上面的过程中可以看出,虽然提供的 limit 是 1000,但是返回的结果却只有 10个左右。因为这个 limit 不是限定返回结果的数量,而是限定服务器单次遍历的字典槽数量(约等于)。返回的值是游标,目前游标的值不为零,意味着遍历还没有结束。

四十、


四十一、Redis 中时间复杂度为 O(n) 的命令


List: lindex、lset、linsert; Hash: hgetall、hkeys、hvals; Set: smembers、sunion、sunionstore、sinter、sinterstore、sdiff、sdiffstore; Sorted Set:zrange、zrevrange、zrangebyscore、zrevrangebyscore、zremrangebyrank、zremrangebyscore;

四十二、ZSet进行排序的上限是多少?


【哪位大牛解答一下】

四十三、Redis 中 key是怎么设置的?


【1】建议全部大写; 【2】key不能太长也不能太短,键名越长越占资源,太短可读性太差; 【3】key 单词与单词之间以分开; 【4】Redis 使用的时候注意命名空间,一个项目一个命名空间,项目内业务不同命名空间也不同。一般情况下:  1) 第一段放置项目名或缩写 如 project: 2) 第二段把表名转换为 key前缀 如 user:  3) 第三段放置用于区分区 key的字段,对应 Mysql中的主键的列名,如userid: 4) 第四段放置主键值,如18,16;结合起来  PRO:USER:UID:18  是不是很清晰; 【5】设置过期时间,节省内存;

四十四、怎么防止消息丢失


两种数据丢失的情况: 【1】主备切换的过程,可能导致数据丢失:因为master 到 slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了,如果主从延迟特别大,那么丢失的数据就可能会特别多; 【2】脑裂导致的数据丢失:脑裂就是说,某个 maser 所在机器突然脱离了正常的网络,跟其他 slave机器不能连接,但是实际上 master还运行着,此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave切换成了 master,这个时候,集群里面就会有2个 master,也就是所谓的脑裂,此时虽然某个 slave被切换成了master,但是可能 client还没来得及切换到新的 master,还继续写向旧 master的数据可能也丢失了,因此 master再次恢复的时候,会被作为一个 slave挂到新的 master 上去,自己的数据将会清空,重新从新的 master 复制数据;

解决异步复制和脑裂导致的数据丢失

代码语言:javascript复制
# redis.conf 配置
min-slaves-to-write 1    #给指定数量的 slave发送数据
min-slaves-max-lag 10    #salve 返回 ack的超时时间

上面这两个配置可以减少异步复制脑裂导致的数据丢失;

减少异步复制的数据丢失:有了 min-slaves-max-lag这个配置,可以确保说,一旦 slave复制数据和 ack延迟太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以尽量减少 master宕机时由于部分数据丢失。如果想要强一致性就需要牺牲效率,增加 Raft协议,来增加强一致性。

减少脑裂的数据丢失:如果一个 master出现了脑裂,跟其他 slave丢了连接,那么上面这两个配置可以去确保说,如果不能继续给指定数量的 slave发送数据,而且 slave超过10秒没有给自己 ack消息,那么就直接拒绝客户端的写请求,这样脑裂活的旧 master就不会接受 client的新数据,也就避免了数据丢失,上面配置确保了,如果跟任何一个 slave丢了连接,在 10秒后发现没有 slave给自己 ack,那么就拒绝新的写请求,因此脑裂场景下,最多丢失 10秒的数据。

aof 增量持久化数据:aof 持久化最多丢失1秒的数据

四十五、Redis的 AOF文件占用过大


使用云服务器的时候,给Redis用了 32G内存,但是 AOF文件就达到了128G。经常出现如下报错:

代码语言:javascript复制
Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.

解决办法:使用 BGREWRITEAOF,可以优化 aof文件。即使 BGREWRITEAOF执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 BGREWRITEAOF成功之前不会被修改。重写操作只会在没有其他持久化工作在后台执行时被触发,也就是说:如果 Redis 的子进程正在执行快照的保存工作,那么 AOF 重写的操作会被预定(scheduled),等到保存工作完成之后再执行 AOF 重写。在这种情况下, BGREWRITEAOF的返回值仍然是 OK ,但还会加上一条额外的信息,说明 BGREWRITEAOF要等到保存操作完成之后才能执行。在 Redis 2.6 或以上的版本,可以使用 INFO 命令查看 BGREWRITEAOF是否被预定。

如果已经有别的 AOF 文件重写在执行,那么 BGREWRITEAOF返回一个错误,并且这个新的 BGREWRITEAOF请求也不会被预定到下次执行。从 Redis 2.4 开始, AOF 重写由 Redis 自行触发, BGREWRITEAOF仅仅用于手动触发重写操作。自动触发需要修改 redis.conf文件

代码语言:javascript复制
auto-aof-rewrite-percentage 50

然后重启Redis,这段配置,是指当 aof文件增值率达到 50%时,优化一次 aof,也就是执行 BGREWRITEAOF命令,默认是100,因为线上写入比较频率,所以增长率要调低一点。之前 100多个G的 aof,重写一次之后,降至30G。

如果你的不能自动触发,可以写一个脚本去执行:brgewriteaof.sh

代码语言:javascript复制
#!/bin/bash
/usr/local/redis/redis-cli -p 6379 -h 127.0.0.1 BGREWRITEAOF
# 添加权限
chmod 755 brgewriteaof.sh
# 设定任务计划,每天凌晨2点跑一次
0 2 * * * /opt/brgewriteaof.sh

 重写的好处:减少 AOF日志尺寸,减少内存占用,加快数据库恢复时间。 

0 人点赞