0

1

2

3

4

5

6

7

8

9

0

1

{{ noReadMessageTotal }}

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

尚硅谷Java技术之上海高频面试题:第六章 Redis数据库篇

晴天 晴天 | 798 | 759天前

尚硅谷Java技术之上海高频面试题

版本:V1.0

尚硅谷Java技术中心

第六章 Redis数据库篇

1、简单介绍一下redis

【答案解析】

(1)redis是一个key-value类型的非关系型数据库,基于内存也可持久化的数据库,相对于关系型数据库(数据主要存在硬盘中),性能高,因此我们一般用redis来做缓存使用;并且redis支持丰富的数据类型,比较容易解决各种问题

(2)Redis的Value支持5种数据类型,string、hash、list、set、zset

以下为5中类型比较经典的使用场景

类型 使用场景
string String类型是最简单的类型,一个key对应一个value,项目中我们主要利用单点登录中的token用string类型来存储;商品详情
hash Hash类型中的key是string类型,value又是一个map(key-value),针对这种数据特性,比较适合存储对象,在我们项目中由于购物车是用redis来存储的,因此选择redis的散列(hash)来存储;
list List类型是按照插入顺序的字符串链表(双向链表),主要命令是LPOP和RPUSH,能够支持反向查找和遍历,如果使用的话主要存储商品评论列表,key是该商品的ID,value是商品评论信息列表;消息队列
set Set类型是用哈希表类型的字符串序列,没有顺序,集合成员是唯一的,没有重复数据,底层主要是由一个value永远为null的hashmap来实现的。可以基于 Set 玩儿交集、并集、差集的操作,比如交集吧,我们可以把两个人的好友列表整一个交集,看看俩人的共同好友是谁?
zset zset(sorted set)类型和set类型基本是一致的,不同的是zset这种类型会给每个元素关联一个double类型的分数(score),这样就可以为成员排序,并且插入是有序的。这种数据类型如果使用的话主要用来统计商品的销售排行榜,比如:items:sellsort 10 1001 20 1002 这个代表编号是1001的商品销售数量为10,编号为1002的商品销售数量为20/附件的人

2、单线程的redis为什么读写速度快?

  1. 纯内存操作

  2. 单线程操作,避免了频繁的上下文切换

  3. 采用了非阻塞I/O多路复用机制

3、redis为什么是单线程的?

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了Redis利用队列技术将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操作

2)采用单线程,避免了不必要的上下文切换和竞争条件

4、redis服务器的的内存是多大?

配置文件中设置redis内存的参数:。

该参数如果不设置或者设置为0,则redis默认的内存大小为:

32位下默认是3G

64位下不受限制

一般推荐Redis设置内存为最大物理内存的四分之三,也就是0.75

命令行设置config set maxmemory <内存大小,单位字节>,服务器重启失效

config get maxmemory获取当前内存大小

永久则需要设置maxmemory参数,maxmemory是bytes字节类型,注意转换

5、为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

Redis的操作之所以是原子性的,是因为Redis是单线程的。

Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。

多个命令在并发中也是原子性的吗?

不一定, 将get和set改成单命令操作,incr
。使用Redis的事务,或者使用Redis+Lua==的方式实现.

6、你还用过其他的缓存吗?这些缓存有什么区别?都在什么场景下去用?

对于缓存了解过redis和memcache,redis我们在项目中用的比较多,memcache没用过,但是了解过一点;

Memcache和redis的区别:


比较项 reids memcache


存储方式 redis可以持久化其数据 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小

数据支持 支持丰富的数据类型,提供list,set,zset,hash等数据结构的存储  memcached所有的值均是简单的字符串

底层模型 Redis直接自己构建了VM 机制 无

value值大小 Redis 最大可以达到 512M value 不能超过 1M 字节

速度 Redis 采用单线程模式处理请求。这样做的原因有 2 MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU
个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 多核的优势,性能非常优秀
IO 时间不会太长,单线程可以避免线程上下文切换产生的代价

数据备份 Redis支持数据的备份,即master-slave模式的数据备份,能够提供高可用服务 当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key
进行清理,还会按 LRU 策略对数据进行剔除。

应用场景 适用于对读写效率要求高、数据处理业务复杂、安全性要求较高的系统  适合多读少写,大数据量的情况(一些官网的文章信息等)

7、Redis在你们项目中是怎么用的?

【答案解析】

(1)门户(首页)系统中的首页内容信息的展示。(商品类目、广告、热门商品等信息)门户系统的首页是用户访问量最大的,而且这些数据一般不会经常修改,因此为了提高用户的体验,我们选择将这些内容放在缓存中;

(2)单点登录系统中也用到了redis。因为我们是分布式系统,存在session之间的共享问题,因此在做单点登录的时候,我们利用redis来模拟了session的共享,来存储用户的信息,实现不同系统的session共享;

(3)我们项目中同时也将购物车的信息设计存储在redis中,购物车在数据库中没有对应的表,用户登录之后将商品添加到购物车后存储到redis中,key是用户id,value是购物车对象;

(4)因为针对评论这块,我们需要一个商品对应多个用户评论,并且按照时间顺序显示评论,为了提高查询效率,因此我们选择了redis的list类型将商品评论放在缓存中;

(5)在统计模块中,我们有个功能是做商品销售的排行榜,因此选择redis的zset结构来实现;

还有一些其他的应用场景,主要就是用来作为缓存使用。

8、对redis的持久化了解不?

Redis是内存型数据库,同时它也可以持久化到硬盘中,redis的持久化方式有两种:

(1)RDB(半持久化方式):

按照配置不定期的通过异步的方式、快照的形式直接把内存中的数据持久化到磁盘的一个dump.rdb文件(二进制文件)中;

这种方式是redis默认的持久化方式,它在配置文件(redis.conf)中的格式是:save
N M,表示的是在N秒之内发生M次修改,则redis抓快照到磁盘中;

原理:当redis需要持久化的时候,redis会fork一个子进程,这个子进程会将数据写到一个临时文件中;当子进程完成写临时文件后,会将原来的.rdb文件替换掉,这样的好处是写时拷贝技术(copy-on-write),可以参考下面的流程图;

image.png

优点:只包含一个文件,对于文件备份、灾难恢复而言,比较实用。因为我们可以轻松的将一个单独的文件转移到其他存储媒介上;性能最大化,因为对于这种半持久化方式,使用的是写时拷贝技术,可以极大的避免服务进程执行IO操作;相对于AOF来说,如果数据集很大,RDB的启动效率就会很高

缺点:如果想保证数据的高可用(最大限度的包装数据丢失),那么RDB这种半持久化方式不是一个很好的选择,因为系统一旦在持久化策略之前出现宕机现象,此前没有来得及持久化的数据将会产生丢失;rdb是通过fork进程来协助完成持久化的,因此当数据集较大的时候,我们就需要等待服务器停止几百毫秒甚至一秒;

(2)AOF(全持久化的方式)

把每一次数据变化都通过write()函数将你所执行的命令追加到一个appendonly.aof文件里面;

Redis默认是不支持这种全持久化方式的,需要将no改成yes
image.png

实现文件刷新的三种方式:

image.png

no:不会自动同步到磁盘上,需要依靠OS(操作系统)进行刷新,效率快,但是安全性就比较差;

always:每提交一个命令都调用fsync刷新到aof文件,非常慢,但是安全;

everysec:每秒钟都调用fsync刷新到aof文件中,很快,但是可能丢失一秒内的数据,推荐使用,兼顾了速度和安全;

原理:redis需要持久化的时候,fork出一个子进程,子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令;父进程会继续处理客户端的请求,除了把写命令写到原来的aof中,同时把收到的写命令缓存起来,这样包装如果子进程重写失败的话不会出问题;当子进程把快照内容以命令方式写入临时文件中后,子进程会发送信号给父进程,父进程会把缓存的写命令写入到临时文件中;接下来父进程可以使用临时的aof文件替换原来的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。下面的图为最简单的方式,其实也是利用写时复制原则。

image.png

优点:

数据安全性高

该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机问题,也不会破坏日志文件中已经存在的内容;

缺点:

对于数量相同的数据集来说,aof文件通常要比rdb文件大,因此rdb在恢复大数据集时的速度大于AOF;

根据同步策略的不同,AOF在运行效率上往往慢于RDB,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效;

针对以上两种不同的持久化方式,如果缓存数据安全性要求比较高的话,用aof这种持久化方式(比如项目中的购物车);如果对于大数据集要求效率高的话,就可以使用默认的。而且这两种持久化方式可以同时使用。

9、redis的过期策略以及内存淘汰机制有了解过吗

redis采用的是定期删除+惰性删除策略。

为什么不用定时删除策略?

定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.

定期删除+惰性删除是如何工作的呢?

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

采用定期删除+惰性删除就没其他问题了么?

不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。

在redis.conf中有一行配置

maxmemory-policy volatile-lru

该配置就是配内存淘汰策略的(什么,你没配过?好好反省一下自己)

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据,新写入操作会报错

ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么
volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和
noeviction(不删除) 基本上一致。

10、做过redis的集群吗?你们做集群的时候搭建了几台,都是怎么搭建的?

针对这类问题,我们首先考虑的是为什么要搭建集群?(这个需要针对我们的项目来说)

Redis的数据是存放在内存中的,这就意味着redis不适合存储大数据,大数据存储一般公司常用hadoop中的Hbase或者MogoDB。因此redis主要用来处理高并发的,用我们的项目来说,电商项目如果并发大的话,一台单独的redis是不能足够支持我们的并发,这就需要我们扩展多台设备协同合作,即用到集群。

Redis搭建集群的方式有多种,例如:客户端分片、Twemproxy、Codis等,但是redis3.0之后就支持redis-cluster集群,这种方式采用的是无中心结构,每个节点保存数据和整个集群的状态,每个节点都和其他所有节点连接。如果使用的话就用redis-cluster集群。

集群这块直接说是公司运维搭建的,小公司的话也有可能由我们自己搭建,开发环境我们也可以直接用单机版的。但是可以了解一下redis的集群版。搭建redis集群的时候,对于用到多少台服务器,每家公司都不一样,大家针对自己项目的大小去衡量。举个简单的例子:

我们项目中redis集群主要搭建了6台,3主(为了保证redis的投票机制)3从(

【扩展】高可用),每个主服务器都有一个从服务器,作为备份机。

1、架构图如下:

image.png

(1)所有的节点都通过PING-PONG机制彼此互相连接;

(2)每个节点的fail是通过集群中超过半数的节点检测失效时才生效;

(3)客户端与redis集群连接,只需要连接集群中的任何一个节点即可;

(4)Redis-cluster把所有的物理节点映射到【0-16383】slot上,负责维护

2、容错机制(投票机制)

(1)选举过程是集群中的所有master都参与,如果半数以上master节点与故障节点连接超过时间,则认为该节点故障,自动会触发故障转移操作;

(2)集群不可用?

a:如果集群任意master挂掉,并且当前的master没有slave,集群就会fail;

b:如果集群超过半数以上master挂掉,无论是否有slave,整个集群都会fail;

11、说说Redis哈希槽的概念?

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

12、redis有事务吗?

Redis是有事务的,redis中的事务是一组命令的集合,这组命令要么都执行,要不都不执行,redis事务的实现,需要用到MULTI(事务的开始)和EXEC(事务的结束)命令 ;

image.png

当输入MULTI命令后,服务器返回OK表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不会马上执行,而是返回"QUEUED",这表示命令已经被服务器接受并且暂时保存起来,最后输入EXEC命令后,本次事务中的所有命令才会被依次执行,可以看到最后服务器一次性返回了两个OK,这里返回的结果与发送的命令是按顺序一一对应的,这说明这次事务中的命令全都执行成功了。

Redis的事务除了保证所有命令要不全部执行,要不全部不执行外,还能保证一个事务中的命令依次执行而不被其他命令插入。同时,redis的事务是不支持回滚操作的。

【扩展】

Redis的事务中存在一个问题,如果一个事务中的B命令依赖上一个命令A怎么办?

这会涉及到redis中的WATCH命令:可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,EXEC命令执行完之后被监控的键会自动被UNWATCH)。

应用场景:待定

13、是否了解过redis的安全机制?

1、redis的安全机制(你们公司redis的安全这方面怎么考虑的?)

漏洞介绍:redis默认情况下,会绑定在bind
0.0.0.0:6379,这样就会将redis的服务暴露到公网上,如果在没有开启认证的情况下,可以导致任意用户在访问目标服务器的情况下未授权访问redis以及读取redis的数据,攻击者就可以在未授权访问redis的情况下可以利用redis的相关方法,成功在redis服务器上写入公钥,进而可以直接使用私钥进行直接登录目标主机;

比如:可以使用FLUSHALL方法,整个redis数据库将被清空

解决方案:

(1)禁止一些高危命令。修改redis.conf文件,用来禁止远程修改DB文件地址,比如 rename-command
FLUSHALL “” 、rename-command CONFIG"" 、rename-command EVAL ""等;

(2)以低权限运行redis服务。为redis服务创建单独的用户和根目录,并且配置禁止登录;

(3)为redis添加密码验证。修改redis.conf文件,添加

requirepass mypassword;

(4)禁止外网访问redis。修改redis.conf文件,添加或修改 bind
127.0.0.1,使得redis服务只在当前主机使用;

(5)做log监控,及时发现攻击;

(6)服务器不安装

14、你对redis的哨兵机制了解多少?(redis2.6以后出现的)

哨兵机制:

监控:监控主数据库和从数据库是否正常运行;

提醒:当被监控的某个redis出现问题的时候,哨兵可以通过API向管理员或者其他应用程序发送通知;

自动故障迁移:主数据库出现故障时,可以自动将从数据库转化为主数据库,实现自动切换;

具体的配置步骤面试中可以说参考的网上的文档。要注意的是,如果master主服务器设置了密码,记得在哨兵的配置文件(sentinel.conf)里面配置访问密码

15、redis缓存与mysql数据库之间的数据一致性问题?

不管先保存到MySQL,还是先保存到Redis都面临着一个保存成功而另外一个保存失败的情况。

不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子:

1.如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。

2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

解决:

基于mysql的binlog日志(canal)
消息队列(双删)

16、什么是redis的缓存穿透?如何防止穿透?

缓存穿透是指查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录,并且出于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

解决:空结果也进行缓存,但它的过期时间会很短,最长不超过五分钟。

17、什么是redis的缓存雪崩?如何防止?

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

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

18、什么是redis的缓存击穿?如何防止?

缓存击穿是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常"热点"的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。

与缓存雪崩的区别:

  1. 击穿是一个热点key失效

  2. 雪崩是很多key集体失效

解决:锁

image.png

19、redis中对于生存时间的应用

Redis中可以使用expire命令设置一个键的生存时间,到时间后redis会自动删除;

应用场景:

(1)设置限制的优惠活动的信息;

(2)一些及时需要更新的数据,积分排行榜;

(3)手机验证码的时间;

(4)限制网站访客访问频率;
相关文章:
第一章 面试技巧篇
第一章 Java基础
第三章 Java高级篇
第四章 MySQL数据库篇
第五章 Java框架篇
第六章 Redis数据库篇
第七章 MQ消息队列
第八章 电商项目篇之谷粒商城

文章标签: NoSql

真诚点赞 诚不我欺~

{{ praiseUserVoList.length }}人点赞

item.nickname

尚硅谷Java技术之上海高频面试题:第六章 Redis数据库篇

{{ isPraise ? '已点赞' : '点赞'}}
{{ isCollect ? '已收藏' : '收藏'}}
评论
gOod mornIng
没有更多啦~ 加载中...

关于作者

晴天
晴天

人因梦想而伟大!