Redis面试笔记
2025-10-20 12:41:11 0 举报
Redis面试笔记
作者其他创作
大纲/内容
......
size:4
热点新闻
score=0
主从、哨兵、集群主从复制(一主多从复制:主机写、从机读):1、配置3台Redis,info replication查看信息2、从机关联主机:slaveof 127.0.0.1 6379(主机ip port)3、主机down掉:从机待命(并不会升级为master),主机重启后恢复与从机关系4、某从机down掉:不影响主机与其他从机,再次上线后会变成master,需要再次执行slaveof与主机关联(除非在配置文件中进行关联)1、Redis1主(6379),Redis2(6380)作为Redis1从,Redis3(6381)作为Redis2从2、当Redis1挂掉后,Redis2使用slaveof no one升级为主,Redis3使用slaveof 127.0.0.1 6380(主机ip port)作为Redis2从首次/重连master时全量复制、后续增量复制哨兵模式(主从模式下利用哨兵自动从slave中选举新master)1、修改sentinel.conf文件:sentinel monitor [自定义被监控服务名字] 127.0.0.1 6379 1(主ip port 1表示谁的票数多于1票以上就升级为master)2、启动哨兵:redis-sentinel sentinel.conf3、主down后,由哨兵在从节点中选举除新主4、主重新上线,自动变成新主的从节点哨兵选择条件:1、优先级:redis.conf中slave-priority 100(值越小优先级越高)2、偏移量:获取原主数据最多的3、runid:每个Redis启动会随机生成一个40位的runid,最小的优先
backward
憨憨的初始(MySQL)
41
*
点赞收藏
31
分布式锁分布式锁需要解决的问题:1、使用setnx实现分布式锁,并在finally中释放锁2、问题 - 在finally前程序终止,锁未释放 解决 - 设置锁超时时间。3、问题 - 如果先加锁再设置超时时间可能存在加锁后,设置超时时间之前程序down了 解决 - 使用setex保证原子操作4、问题 - 锁5s过期,A线程执行业务逻辑用了6秒,第5秒释放锁后B线程拿到锁,这时A线程会删除B线程的锁。 解决 - 加锁线程设置唯一标识到value中,解锁前先检查,是自己加的锁才允许删除。5、问题 - 锁检查与锁释放不是原子操作 解决 - Lua表达式:检查、删除封装成一条脚本执行,保证原子性6、问题 - 业务执行时间大于锁过期时间,导致线程A任务还未执行完但锁被释放,其他线程获取锁后进行业务操作 解决 - 锁续期(Redisson:默认30s超时,10s一次续期),加锁redisson.lock();解锁redison.unlock();7、问题 - 在高并发情况下,使用redisson.unlock()解锁可能出现IllegalMonitorStateException异常(需要创建锁的线程进行解锁) 解决 - if(redisson.isLocked() && redisson.isHeldByCurrentThread()){redisson.unlock();}
Redis 不是号称单线程效率也很高吗,为什么又采用多线程了?https://xie.infoq.cn/article/a50862131db36902cb6853f05
score=31
obj
Set(集合)
AOF文件载入过程
Redis有数据,直接返回
跳跃表实现过程
来一个西瓜
free
获取字符串长度复杂度为O(N)API是不安全的,可能会造成缓冲区溢出修改字符串长度N次必然执行N次内存重分配只能保存文本数据可以使用所有<string.h>库中的函数
老板(Redis)
哈希表(hashtable)O(1)
缓存击穿请求将Redis击穿,到达DB层,可以看做小规模雪崩(击穿是一个人的雪崩,雪崩是一群人的击穿)。
1-11-21-31-41-51,经过6次查找比较
跳跃表节点每个表节点中的层级都是随机(1~32层),大层级概率小。backward:后退指针,用于找到前一个节点。score:分值,节点根据分值升序排列。obj:成员对象,指向一个字符串对象。
score=1
len3
used:3
NULL
1
type
菜单(要装入布隆过滤器的数据)
MySQL
*next
分布式锁
抽奖程序
2
dictEntry(元素)
L0
感觉要崩溃...
查询Redis无数据
排行榜
listNode
dictEntry*
L31
商品查询
1、字典又称符号表、关联数组、映射、散列表,是一种用来存储键值对的数据结构。2、Redis使用的C语言并没有内置字典,Redis构建了自己的字典实现。3、字典也是Hash键的底层实现之一,当Hash键包含的键值对比较多,或键值对中的元素都是比较长的字符串时,使用字典作为Hash键的底层实现。4、特征1)可以存储海量数据,键值对是映射关系,可以根据键以O(1)的时间复杂度取出或插入关联值。2)键值对中key的类型可以是字符串、整形、浮点型等,且是唯一的。3)键值对中value的类型可为String、Hash、List、Set、SortedSet。5、数组1)若干个类型相同的对象的集合称为数组,组成数组的各个对象称为数组的元素,用于区分数组的各个元素的数字编号称为下标。2)元素的类型可为:数值、字符、指针、结构体等。3)Redis中使用数组存储dictEntry结构体,实际的key-value。6、哈希作用是把任意长度的输入通过散列算法转换成固定类型、固定长度的散列值。换句话说,Hash函数可以把不同键转换成唯一的整形数据。1)相同的输入经Hash计算后得出相同输出。2)不同的输入经Hash计算后一般得出不同输出值,但也可能会出现相同输出值。Redis服务端收到客户端发送过来的Key实际都为字符串。3)当客户端执行“set 100.86 hello”命令时,此时的键在客户端看来是浮点型数据,但Redis服务端收到的键的值是长度为6的字符串“100.86”。7、字典扩容1)申请一块新内存,初次申请时默认容量大小为4个dictEntry,非初次申请时,容量为Hash表容量的两倍。2)把新申请的内存地址赋予给ht[1],并把字典的rehashidx标识由-1改为0,表示需要进行rehash操作。3)新增的键值对添加到ht[1],修改、删除、查询则再ht[0]与ht[1]中检查后进行操作,最后把ht[0]中的各元素重新计算索引依次迁移到ht[1]中,并清空ht[0]。4)对调ht[0]与ht[1]的值,并将rehashidx的值修改为-1。8、字典缩容1)当字典使用量不到总空间10%时进行缩容。9、渐进式rehash(优化亿级数据量rehash)1)当执行增删改查操作前都先判断是否在进行rehash操作。2)如果正在rehash操作,则只对一个Hash节点进行rehash操作。3)等服务空闲了,则又开始批量rehash,每次对100个Hash节点进行操作,共执行1毫秒。10、添加元素1)调用dictFind函数,查询键是否存在,是则修改对应的值,否则调用dbAdd函数添加元素。2)dbAdd函数进行添加,再次校验key是否存在逻辑,检查是否在进行rehash,插入对应的ht。11、查找元素1)得到键的Hash值。2)遍历查找Hash表ht[0]与ht[1]。3)根据Hash值计算得到索引值。4)如果索引对应的节点存在则遍历单链表。5)找到与键匹配的值,返回节点,没找到则返回NULL。12、修改元素1)调用dictFind查找键是否存在。2)不存在则中断执行。3)修改节点加您只对中的值为新值。4)释放旧值内存。13、删除元素1)调用dictFind查找键是否存在。2)存在则将节点从所在单链表中剔除。3)释放节点对应键、值、节点本身占用的内存。4)对应Hash表的used字段减1。14、字典的遍历1)全遍历(keys):一次命令执行遍历整个数据库。2)间断遍历(hscan):每次命令执行只取部分数据,分多次遍历。
L1
没有查到数据
SDS(simple dynamic string 简单动态字符串)free:记录buf数组中未使用字节数量len:记录buf数组中已使用字节数量,等于SDS长度buf[]:字节数组,用于保存字符串0、Redis中包含字符串值的键值对在底层都是由SDS实现。redis> set msg \"hellod world\"OK底层是两个SDS,分别保存着字符串“msg”和“hello world”。C字符串的内存重分配:C字符串并不记录自身长度,对于一个C字符串来说底层都是一个N+1个字符长的数组(额外保留一个空字符)。每次C字符串增长或缩短都需要对数组进行一次内存重分配(不重分配可能造成内存缓冲区溢出或内存泄漏),而内存重分配涉及复杂的算法,并执行系统调用,是比较耗时的操作。1、常数复杂度获取字符串长度:SDS获取字符串长度的复杂度为O(1),通过len直接获取,而C需要遍历获取长度,复杂度为O(N)。2、杜绝缓存溢出:SDS通过sdscat函数进行字符串拼接时不会出现缓冲区溢出(有未使用空间),sdscat在拼接前会检查当前SDS长度是否足够,不够则先扩容,然后再拼接,C字符串会溢出。2.1、SDS使用了空间预分配和惰性空间释放两种优化策略,减少修改字符串时带来的内存重分配次数,提升性能(C字符串长度每次修改或缩短时,都会对保存这个C字符的数组进行一次内存重分配操作)。2.2空间预分配:1)SDS字符串被增长后,如果未使用空间为0,容量小于1M则增加len数量相同的未使用空间,容量大于1M则增加1M未使用空间。如果未使用空间大于0则不进行预分配。2)用于优化SDS字符串增长操作,通过这种预分配策略,SDS将连续修改N次字符串所需的内存重分配次数从必定N次降低为最多N次。2.3惰性空间释放:1)SDS字符串被缩短时,不会立即回收未使用空间,而是使用free记录未使用空间数量,等待下一次使用,避免字符串缩短时所需的内存重分配操作,同时提供了释放未使用空间的API。3、通过遵循C字符串以空字符('\\0')结尾的惯例,SDS可以在有需要的时候重用C的<string.h>函数库中部分函数。
free0
链表(List)1、C并没有内置链表数据结构,所以Redis构建了自己的链表实现,提供了高效的节点重排能力,以及顺序性访问方式,可以通过增删节点来灵活调整链表长度。2、列表键(list类型的key)的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,或list中包含的元素是比较长的字符串时,Redis使用链表作为列表键的底层实现。3、特性1)双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。2)无环:表头节点的prev指针和表尾结点的next指针都指向NULL,对链表的访问以NULL为终点。3)带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾结点的复杂度为O(1)。4)带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。5)多台:链表节点使用void*指针来确保节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
购物车
k2
'd'
整数集合(intset)1、集合键的底层实现之一。当一个集合只包含整数值元素,且元素数量不多时,作为整数集合的底层实现。2、可以保存类型int16_t、int32_t、int64_t的整数值,并保证集合中元素从小到大排列,且不会出现重复元素。3、整数集合可以通过升级操作来提升灵活性并节约内存,但不支持降级操作。
'e'
Redis
缓存雪崩缓存雪崩的主要原因是缓存系统不够高可用,间接导致整个系统不可用。
Redis5设计与源码分析-5章55页
来一台电视
dictht
score=11
3
raw:简单动态字符串
21
tail
分层有序链表
场景:大量并发请求场景,缓存大面积过期失效,未被Redis缓存承接住,直接请求到DB,导致DB挂掉。解决:对热点数据的过期时间设置尽可能分散,一个较大固定值+一个较小的随机值(具体随机范围看实际业务),避免同一时间大批量缓存失效。
整型集合(intset)O(N)
11
L2
'i'
score=51
查询DB无数据
持久化(RDB、AOF)RDB(默认开启):在某一些刻以(全量)快照形式将内存中的数据写入到磁盘(忽略已过期key),生成dump.rdb(经过压缩的二进制数据文件)。font color=\"9e9e9e\
第0层
从AOF文件读取一条命令
key的过期删除策略1、定时删除:设置key过期时间同时创建一个定时器,定时器负责key过期时删除(对CPU不友好,时间换空间)。2、惰性删除:在key被使用时才判断是否过期,对内存不友好,用存储空间换处理器性能(对内存不友好,空间换时间)。3、定期删除:周期性的轮询查找时效性数据,默认1秒10次(100毫秒/次,通过hz配置),采用随机抽取的策略,利用过期数据占比的方式控制删除频度(随机抽查)。1)测试随机的20个keys进行相关过期检测。2)删除所有已经过期的keys。3)如果还有多于25%的keys过期,重复步奏1。假设Redis当前存放30万个key,并且都设置了过期时间,如果每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。因此,redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。但是呀,如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存,直接会导致内存爆掉。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,运维小哥哥也忘记加大内存了。难道redis直接这样挂掉?不会的!Redis用8种内存淘汰策略保护自己~Redis内存淘汰策略触发时机:当内存超过maxmemory限制时,使用maxmemory-policy指定的策略进行内存回收。1、noevication : 不会驱逐任何 key (默认)2、allkeys-lru: 对所有的 key 使用 lru 算法进行删除3、volatile-lru: 对所有的设置了过期时间的 key 进行 lru 算法进行删除4、allkeys-random: 对所有 key 随机删除5、volatile-random: 对所有设置了过期时间的 key 随机删除6、volatile-ttl :马上删除要过期的 key7、allkeys-lfu: 对所有 key 进行 lfu 算法进行删除8、volatile-lfu: 对所有设置了过期时间的 key 使用 lfu 算法进行删除LRU算法(最近最少使用):以访问时间为参考,淘汰最早被访问的数据(LRU=哈希+链表,可以用LinkedHashMap实现LRU)。LFU算法(最不经常使用):以访问频率为参考,淘汰最少访问频率的数据。配置: 1、配置文件:maxmemory-policy allkeys-lru 2、客户端命令:config set maxmemory-policy allkeys-lru(设置) config get maxmemory-policy(查看)
跳跃表头节点包含32层,每一层结构都包含指向下一节点的同层级的指针,没有下级节点则为NULL。
使用伪客户端执行命令
sizemask:3
否
Hash(哈希)
match
Client
拉面馆(应用)
61
L3
跳跃表(skiplist)1、跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,达到快速访问节点的目的。2、zset转跳跃表实现的条件(任一条件)1)zset中的元素数量大于zset_ziplist_entries(默认128)2)插入元素的字符串长度大于zset_max_ziplist_value(默认64)3、Redis的跳跃表由zskiplist和zskiplistNode两个结构组成,其中zskiplist保存跳跃表信息(表头节点、表尾结点、长度、层高),而zskiplistNode保存跳跃表节点信息。4、每个跳跃表节点的层高都是1~32之间的随机数。5、在同一个跳跃表中,多个节点可以包含相同分值,但每个节点的成员对象必须唯一。6、跳跃表节点按分值升序排列,分值相同时,节点按照成员对象的大小排序。
双向链表(linkedlist)O(N)
0
value......
返回数据给业务
AOF文件中是否还有命令?
Hash冲突
返回数据存入Redis
51
AOF:根据指定的策略,以日志的形式将改变数据集的操作命令追加到appendonly.aof文件中。1、AOF持久化时,父进程会fork出一个子进程,由子进程对数据进行IO写到磁盘。为了提升写入效率,不会将内容直接写入到磁盘中,而是将其放到一个内存缓存区(buffer)中,等到缓冲区写满或调用fsync()函数时才真正将缓存区中的内容写入到磁盘里。2、恢复AOF数据:Redis重启,如果RDB、AOF同时存在,默认加载AOF。配置1、appendonly no # 是否开启aof2、appendfilename \"appendonly.aof\" # 文件名3、磁盘同步策略(默认每秒一次) appendfsync always # 每次对数据集操作执行一次fsyncappendfsync everysec # 每秒执行一次fsync appendfsync no # 由操作系统执行,默认Linux配置最多丢失30秒4、AOF重写策略,AOF文件体积过大时,将重复指令优化成1条指令auto-aof-rewrite-percentage 100 #百分比,第一次64mb时重写,第二次128mb,第三次等再次增加64mb(100%)auto-aof-rewrite-min-size 64mb #表示触发AOF重写的最小文件体积,大于或等于64MB自动触发。命令1、bgrewriteaof重写AOFRedis4.0后,混合持久化策略(必须开启aof),相当于aof的升级版,区别在于当AOF重写时,会将已有的内容优化成RDB二进制的内容存储。aof-use-rdb-preamble yes #开启混合持久化
score=61
跳跃表尾节点指针指向跳跃表尾节点
跳跃表(skiplist)O(logN)
k1
服务端启动载入程序
iterators:0
dictht ht[1]
字典
**table
dup
递增有序链表
Redis无数据大量并发请求直接到DB
计数器
关注模型
跳跃表层高除头节点外,节点中最高层数。
创建伪客户端
举个栗子
来一辆五菱宏光
6
v1
L4
SDS
String(字符串)
缓存穿透表示请求的数据在缓存、数据库都不存在,穿过了缓存,透过了数据库,还是没有数据。
's'
List(列表)
压缩列表(ziplist)1、压缩列表的目的是为了节约内存,由一系列特殊编码的连续内存块组成的顺序型数据结构。2、一个压缩列表可以包含任一多个节点,每个节点可以保存一个字节数组或一个整数值。3、列表建只包含少量列表项,且列表项是小整数或短字符串时用压缩列表作为底层实现。4、哈希建只包含少量键值对,且每个键与值是小整数或短字符串时用压缩列表作为底层实现。
消息队列
privdata
压缩列表(ziplist)O(N)
v3
C字符串
rehashidx:-1
第1层
k1后插入,通过头插法形成单链表
sdshdr
Redis字典数据结构
Hash表节点
score=21
分布式ID
突发大量并发请求
前进指针用于指向同层的下一个节点。跨度记录到达同层的下一节点距离。
1-21-41-61-51,经过5次查询比较,空间换时间
点餐员(布隆过滤器)
dictht ht[0]
跳跃表长度除头节点外,节点的总数量。
int:整数
'\\0'
Redis无数据,查询DB
v2
len5
length=7
没有,别捣蛋
5
level=3
场景:某时刻一批热点数据同时过期失效,那么这一刻的所有请求将访问DB,使DB压力剧增。场景:突然爆发大量并发请求一个未被缓存的冷门数据,如微博热点事件。解决:1)对热点数据的过期时间设置尽可能分散,基础值+2分钟内的随机时间(具体随机范围看实际业务),避免同一时间大批量缓存失效。2)增加分布式DCL读写锁。3)在业务层监控热点数据是否即将过期,如果即将过期则去数据库获取最新数据进行更新并延长该热点key在缓存系统中的时间。
没有没有没有没有没有没有没有......
微博消息
载入完成
1、从2层(最上层)开始,1<51。2、向后比较21<51,向后比较next指针为NULL,下降到1层。3、1层比较41<51。4、向后比较61>51,下降到0层。5、0层比较51=51。
简单动态字符串(SDS)O(1)
4
ZSet(有序集合)
k3
header
embstr:embstr编码的简单动态字符串
获取字符串长度复杂度为O(1)API是安全的,不会造成缓冲区溢出修改字符串长度N次最多执行N次内存重分配只能保存文本或二进制数据可以使用一部分<string.h>库中的函数
Hash表
第2层
head
buf
dictEntry中key、value存储的是实际类型的指针
used:0
用户信息
问:redis默认配置多少内存?1.redis默认内存:如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存。问:生产一般配置多少内存?1.生产上内存设置:一般推荐redis设置内存为最大物理内存的四分之三(0.75)。问:如何设置redis内存大小?1、通过修改配置文件,maxmemory:1024(单位:字节)2、客户端命令:config set maxmemory 1024(实时设置),config get maxmemory(查看当前内存配置)问:如何查看redis当前内存信息?1、客户端命令:info memory问:redis被打满会怎样?1、OOM问:如果一个key过期了,是不是马上就从内存中被删除了?1、不是惰性删除:在key被使用时才判断是否过期,对内存不友好,用存储空间换处理器性能(空间换时间)。定期删除:周期性的轮询(100ms)查找时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度(随机抽查)。Redis中同时使用了这两种策略,但即使是定期删除策略也不是很完善,所以redis提供了8种内存淘汰策略,在内存达到阈值的时候触发进行兜底操作。问:如何保证缓存和数据库数据一致?1、使用Redisson分布式读写锁,读多写少场景。2、使用Canal。问:Redis是单线程还是多线程?1、Redis 正式推出了 6.0 版本,只是针对处理网络请求过程采用了多线程,而数据的读写命令,仍然是单线程处理的。
跳跃表头节点指针指向跳跃表头节点
list
'R'
score=41
场景:在单节点主从架构,突发的大量并发请求(10W+)直接访问Redis,导致Redis承载不住挂掉。解决:1)限流:各级负载均衡层根据后端相关接口的压测指标针对性的做限流控制,避免同时处理大量的请求。2)多级缓存架构:如在JVM层做一个进程级缓存,当该缓存没有数据时才查询Redis。3)Redis集群避免单点故障。
是
场景:遭到恶意攻击,传入一个根本不存在的值产生大量请求,如id=-1,可能导致DB压力过大挂掉。解决:1)做参数校验,过滤掉非法无效参数。2)缓存空值,如id=-1不存在,也缓存一个key=empty:-1,同时设置过期时间。3)BloomFilter(布隆过滤器),系统启动时将需要检查的缓存数据存入,请求来的时候先去BloomFilter查询key是否存在,不存在则直接返回,存在则查缓存、查DB,布隆过滤器也存在一定的误判。
0 条评论
下一页
为你推荐
查看更多