redis面试1-37

目录

1.Redis你比较熟吧,说说它机制为什么快?

1.它是纯存内存操作;
2.核心是基于非阻塞的IO多路复用机制;
3.单线程的设计避免了多线程上下文切换的资源开销;
4.简单且解析性能高的序列化协议。

因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。

2.redis是单线程吗?

	目前所说的Redis单线程,指的是"其网络IO和键值对读写是由一个线程完成的",也就是说,Redis中只有网络请求模块和数据操作模块是单线程的。
	而其他的如持久化存储模块、集群支撑模块等是多线程的。

3.为什么redis需要把所有数据放到内存中?

1.Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。
2.如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。
3.如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

4.Redis的回收策略有哪些?

(1)noeviction: 如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端
(2)allkeys-lru: 就是我们常说的LRU算法,移除掉最近最少使用的那些keys对应的数据,ps最长用的策略
(3)volatile-lru: 也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉
(4)allkeys-random: 随机选择一些key来删除掉
(5)volatile-random: 随机选择一些设置了TTL的key来删除掉
(6)volatile-ttl: 移除掉部分keys,选择那些TTL时间比较短的keys

5.MySQL里有2000w数据, redis中只存20w的数据,如何保证 redis中都是热点数据?

限定 Redis占用的内存,Redis会根据自身数据淘汰策略,留下热数据到内存。
所以,计算一下 50W 数据大约占用的内存,然后设置一下Redis内存限制即可,并将淘汰策略为volatile-lru或者allkeys-lru。

6.使用Redis有哪些好处?

1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
2.支持丰富数据类型,支持string,list,set,sorted set,hash
3.丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

7.redis相比memcached有哪些优势?

1.memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
2.redis的速度比memcached快很多
3.redis可以持久化其数据

8.Redis 常见的性能问题都有哪些?如何解决?

1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以:Master最好不要写内存快照。
2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。
Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

9.redis常见性能问题和解决方案

1.Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
2.如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
3.为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
4.尽量避免在压力很大的主库上增加从库
5.主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
 这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

10.redis缓存穿透,缓存击穿,缓存雪崩问题?

# 缓存穿透:
	-缓存和数据库都没有数据,爬虫,恶意攻击
  -解决:
  	-1 接口层增加校验,id小于0的直接处理掉
    -2 对应数据库中也不存在的,也在缓存中放一份,只不过value值是空
    -3 通过布隆过滤器实现,把所有用户id都放到布隆过滤器中,来了一个请求,拿着id
    	去布隆过滤器看,没有表示是恶意请求,直接返回

# 缓存击穿
	-缓存没有,数据库有,缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
  -解决:
  	-设置热点数据永远不过期。

# 缓存雪崩
	-缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机
  -解决方案:
    1 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
    2 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
    3 设置热点数据永远不过期。

11.缓存预热

缓存预热:就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决方案:
  1.直接写个缓存刷新页面,上线时手工操作一下;
  2.数据量不大,可以在项目启动的时候自动进行加载;
  3.定时刷新缓存;

12.redis一个字符串类型的值能存储最大容量是多少?

512M

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

使用keys指令可以扫出指定模式的key列表。

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

14.Redis回收进程如何工作的?

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

15.有几种数据结构,底层分别是怎么存储的?

string:字符串
Strings 数据结构是key-value类型,value其实不仅是String,也可以是数字
Hash:哈希字典
Hash 数据结构是key-object类型
list:列表
list 就是双链表
set:集合
set 是可以自动排重的,实际就是通过计算hash的方式来快速排重的
zset:有序集合
zset zset的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

16.MySQL/redis有几种高可用方案,你们用的是哪一种?

1.基于主从复制;(我们使用)

2.基于Galera协议;

3.基于NDB引擎;

4.基于中间件/proxy;

5.基于共享存储;

6.基于主机高可用;

17.redis集群模式和哨兵模式

哨兵模式是redis高可用的实现方式之一
使用一个或者多个哨兵(Sentinel)实例组成的系统,对redis节点进行监控,在主节点出现故障的情况下,能将从节点中的一个升级为主节点,进行故障转义,保证系统的可用性
选举一个从节点升为主节点

18.哨兵们是怎么感知整个系统中的所有节点(主节点/从节点/哨兵节点)的

哨兵(Sentinel)节点会每秒一次的频率向建立了命令连接的实例发送PING命令,如果在down-after-milliseconds毫秒内没有做出有效响应包括(PONG/LOADING/MASTERDOWN)以外的响应,哨兵就会将该实例在本结构体中的状态标记为SRI_S_DOWN主观下线

19.redis数据幂等性是怎么保证的

幂等性:多次发起同一个请求, 必须保证操作只能执行一次

1.token机制,防止页面重复提交
2.唯一索引,防止新增脏数据
3.悲观锁,获取数据的时候加锁(锁表或锁行)
4.乐观锁,基于版本号version实现, 在更新数据那一刻校验数据
5.分布式锁, redis(jedis、redisson)或zookeeper实现
6.状态机,状态变更, 更新数据时判断状态

20.redis分布式锁怎么使用

在redis放一把锁,使用完立即解锁

// 加锁
func Lock(key string) bool {
	// ex:设置默认过期时间10秒,防止死锁
	ex:=10*time.Second
	mutex.Lock()
	defer mutex.Unlock()
	bool,err := rdb.SetNX(key, `{"lock":1}`, ex).Result()
	if err != nil {
		return bool
	}
	return bool
}

// 解锁
func UnLock(key string) int64 {
	nums, err := rdb.Del(key).Result()
	if err != nil {
		log.Println(err.Error())
		return 0
	}
	return nums
}

21.redis分布式锁实现原理?

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:

在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
高可用的获取锁与释放锁;
高性能的获取锁与释放锁;
具备可重入特性;
具备锁失效机制,防止死锁;
具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

通常分布式锁以单独的服务方式实现,目前比较常用的分布式锁实现有三种:

基于数据库实现分布式锁。
基于缓存(redis,memcached,tair)实现分布式锁。
基于mq,kafka队列实现分布式锁。
尽管有这三种方案,但是不同的业务也要根据自己的情况进行选型,他们之间没有最好只有更适合!

22.redis热key发现及解决?

所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key。那么,这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机。
那接下来这个key的请求,就会直接怼到你的数据库上,导致你的服务不可用。

方法一:redis Monitor命令,该命令可以实时抓取出redis服务器接收到的命令。然后统计出热key
方法二:凭借业务经验,进行预估哪些是热key
	其实这个方法还是挺有可行性的。比如某商品在做秒杀,那这个商品的key就可以判断出是热key。缺点很明显,并非所有业务都能预估出哪些key是热key。

解决热key:
1.二级缓存(本地缓存)
2.读写分离,备份热key,分散压力

23.分布多台redis的话,如果某一台缓存过期了,流量过大该怎么办?

1.设置热点key永不过期
2.立即重新添加到缓存
3.有访问缓存时间延长

24.redis持久化方式有哪些?有什么区别?

两种持久化方式:RDB,AOF
一:RDB持久化-(备份文件)
  SAVE或者BGSAVE来生成RDB文件,它的作用是将某个时间点上的数据库状态保存到RDB文件中
	save命令会阻塞redis,期间不能接受任何命令
	bgsave命令会新开一个子进程,然后由子进程生成RDB文件,不会阻塞redis

RDB优点:
	1.Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的
  2.对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
  3.性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
  4.相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB缺点:
  1.在持久化之前出现宕机,此前没有来得及写入磁盘的数据都将丢失
  2.由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

RDB配置文件:
  save 900 1  # 超过900秒有1个键值对操作,会自动调用save完成数据持久化
  save 300 10  # 超过300秒有10个键值对操作,会自动调用save完成数据持久化
  save 60 10000  # 超过60秒有10000个键值对操作,会自动调用save完成数据持久化

二:AOF持久化-(备份操作记录的命令,查询不会记录)
AOF优点:
  1.该机制可以带来更高的数据安全性,即数据持久性。
  2.AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
  3.更好的保证数据的安全

AOF缺点:
  1.对于相同数量的数据集而言,AOF文件通常要大于RDB文件
  2.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。RDB相当于数据复制的操作,AOF数据重建
  3.根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效

AOF配置文件:
在Redis的配置文件中存在三种同步方式,它们分别是:

appendfsync always     #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec  #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no          #从不同步。高效但是数据不会被持久化。


总结:二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。

25.redis集群的原理吗?

主从+哨兵
故障转移

26.限流是怎么做的?有几种策略?

1.协程池:限制gorouting数量,redis分布式锁100。拿到锁-1,释放锁+1
2.redis漏斗桶:限流100,redis里面放key=100,进来一个请求-1,请求结束+1。小于0时,丢弃
3.信号量:channel管道,缓冲区,信号量实现

上面3种缺点:可以被恶意攻击,占用资源。
解决方法:用户需要登陆,把token,存起来。访问计数,如果访问异常加入黑名单(redis给一个假数据)

27.Redis集群跟MySQL的一致性?

原则:一般不更新缓存,写的操作操作数据库。因为如果更新缓存就会出现缓存不一致的情况,很难维护	
  读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存和数据库之间的数据一致性问题。不管是先写数据库,在删除缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举个例子:

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

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

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

方案一:
	设置缓存过期时间,是保证最终一致性的解决方案,只要达到缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。(看数据实时性,精确性)

方案二:先写入数据库,再删除缓存,删除后只要有请求再次加入缓存(写入时,还没有删除缓存,其他并发来还是获取的老缓存脏数据,看数据实时性,精确性)

方案三:先删除缓存,再写入数据库。同时并发访问redis没有数据时,等待0.05秒(等待写入最新数据到mysql,防止读取到脏数据),再访问mysql,再次写入最新缓存。(比较精确和实时,但是需要等待0.05秒)

网络上懒加载,利用消息队列等个人觉得代码复杂化了。主要还是要看业务等实时性要求高不高

28.Redis挂了跟MySQL的一致性?

redis只放需要加入缓存的数据,这些数据如果要更新直接更新mysql数据库,而不会更新缓存。删除缓存,或者让缓存过期。这样redis挂了跟一致性没关系,只会影响其他的业务,或者缓存击穿导致mysql宕机

29.分布式锁如果master挂了,然后锁没有同步到其他机器,这时别的线程也拿到锁了,怎么办?

Redis做分布式并非绝对安全,最保底的方式就是保证业务幂等
多主多从,多主不可能同时挂

30.如果锁即将过期,但业务没处理完,该怎么处理?

可以参考Redission的看门狗设计,就是定时对即将失效的锁续期

31.项目有用到布隆过滤器吗,如果用的话要多大内存?

  没用到,说不上来,但也别这么直接,可以适当延伸一下,比如说经过综合考虑,项目场景在这方面不需要做那么复杂的功能,或者是运维层面做了大量的异常请求拦截之类的,别让面试官觉得你这方面没什么思考。

  上一家公司使用过,业务场景:
    舆情系统的产品需要一批抖音达人数据做分析,接口是买的。
		启个定时任务,每天把产品给我的一批达人用户id的发文,评论点赞等存起来。布隆过滤器过滤已经在库里面的作品,及评论。防止大量的重复入库,给数据库造成压力。

32.分表为什么要停服这种操作,如果不停服可以怎么做?

停服:是指停止服务,暂停新的数据写入。停服拆分表
如果不停服:
第一阶段首先编写代理层,代理层通过动态开关决定访问旧表还是新表,此时流量还是全部访问旧表;
第二阶段开启双写,增量数据不仅在旧表新增和修改,也在新表新增和修改,日志或者临时表记录写入新表ID起始值,旧表中小于这个值的数据就是存量数据;
第三阶段进行存量数据同步,通过脚本将存量数据分页写入新表;
第四阶段停读旧表改读新表,此时新表已经承载了所有读写业务,但是不要立刻停写旧表,需要保持双写一段时间。
第五阶段当读写新表一段时间之后,没有发生业务问题则可以停写旧表;

33.redis分布式,如何减少同步延迟

1.为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
2.不要让master压力太大,持久化工作放在slave节点
3.想要稳定,及性能高。就不要用图妆结构绑定slave,用单链表结构。即:Master <- Slave1 <- Slave2 <- Slave3...
4.尽量避免在压力很大的主库上增加从库

34.redis的hash原理

数组+链表。哈希表是一种线性表的存储结构。哈希表由直接寻址表和一个哈希函数构成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标

35.什么是hash冲突,如何解决?

哈希冲突:哈希函数h(k)返回的key相同,导致两个同样的key指向不同的值
解决1:开放寻址法
	如果哈希函数返回的位置已经有值,则可以向后探查新的位置来储存这个值
	探查的方法:
		线性探查:如果位置i被占用,则探查i+1,i+2...
		二次探查:如果位置i被占用,则探查i+1平方,i-1平方,i+2平方,i-2平方...
		二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3,h4...
解决2:拉链法
	哈希表的每一个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。
	缺点:需要考虑实际情况,拉链如果太短,就是冲突的概率太多太多,导致每一个位置后面一长串的链表,就和链表一样了,查找的速度很底,成链状态。如下图:

img

36.什么是渐进式hash

普通hash:	我们知道当hash表满员时(或负载因子高于阈值时)会进行rehash,也就是重新调整空间大小,并拷贝原来的数据。这里rehash就是优化效率的关键。例如假设有1w个元素,rehash时要一次性拷贝1w元素到新的空间,这样势必会成为很大的负担,庞大的计算量可能会导致服务器在一段时间内停止服务。
渐进式hash:为了避免 rehash 对服务器性能造成影响, 服务器不是一次性将 ht[0] 里面的所有键值对全部 rehash 到 ht[1] , 而是分多次、渐进式地将 ht[0] 里面的键值对慢慢地 rehash 到 ht[1] 。

37.zset原理,跳表查询,skiplist

zset原理:有序集合,上层单链表,底层双链表。
跳表查询:跳表可以当做优先队列

基础层:最下层双链表存储数据
上层链表:上层索引链表,平衡二叉搜索树,与mysql索引树类似,插入新元素时需要重新梳理索引树

重新梳理上层链表:新的元素会增加,有些元素会删除(只是标记,惰性更新)

惰性更新
Redis 不会在每次插入或删除时就立即更新上层链表。
而是采取一种"等待时机"的策略,只有在查询时才会触发上层链表的更新。
这样可以把多次小的更新合并成一次较大的更新,提高效率。

跳表查询,skiplist

在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。

假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,如下图:

这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半,(上图中是7, 19, 26),现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中进行查找。比如,我们想查找23,查找的路径是沿着下图中标红的指针所指向的方向进行的:见下图

查找23:

23首先和7比较,再和19比较,比它们都大,继续向后比较。
但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与22比较。
23比22要大,沿下面的指针继续向后和26比较。23比26小,说明待查数据23在原链表中不存在,而且它的插入位置应该在22和26之间。

在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。

利用同样的方式,我们可以在上层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第三层链表。如下图:

在这个新的三层链表结构上,如果我们还是查找23,那么沿着最上层链表首先要比较的是19,发现23比19大,接下来我们就知道只需要到19的后面去继续查找,从而一下子跳过了19前面的所有节点。可以想象,当链表足够长的时候,这种多层链表的查找方式能让我们跳过很多下层节点,大大加快查找的速度。

posted @ 2022-03-05 17:01  Jeff的技术栈  阅读(168)  评论(0编辑  收藏  举报
回顶部