Redis面试知识点全解
2021-07-11 22:20:37 4 举报
AI智能生成
Redis面试知识点全解
作者其他创作
大纲/内容
数据结构<br>
String:简单动态字符串<br>
Hash:压缩列表、hash表
压缩列表:类似数组,表头保存了列表长度、尾偏移量、元素个数,<br>头尾操作O(1),中间元素需要遍历O(n)<br>
Set:整数数组、hash表<br>
Sorted Set:跳表、压缩列表<br>
跳表:多级索引,加速查找,复杂度O(logN)<br>
List:双向链表、压缩列表<br>
不同操作复杂度
1.单元素操作是基础;<br>2.范围操作非常耗时:集合类型的遍历操作通常很耗时,推荐scan;<br>3.统计操作通常高效:数据结构包含了个数;<br>4.例外情况只有几个:压缩列表和双向链表的头尾增删很快,O(1);<br>
<br>
<br>
日志
AOF<br>
写机制:AOF是写后日志,与WAL相反,先写内存后写日志<br>
日志内容:记录命令,类似于Mysql的statement格式的binlog
写策略:<br>1.Always:命令执行完,立马写;基本不丢失数据,但影响性能。<br>2.No:命令执行完,只写到内存缓冲区,由操作系统决定写时机;丢数据风险大。<br>3.EverySec:命令执行完,只写到内存缓冲区,每秒写入;折中【推荐用法】。<br>
AOF日志由主线程写入,重写过程由子线程bgrewriteaof完成<br>
AOF重写机制:多变一,多个指令合并。<br>
解决AOF文件过大问题
原理:根据数据库里数据的最新状态,生成这些数据的插入命令,作为新的AOF日志<br>
一处拷贝:<br>主线程fork子进程,将数据库最新数据(父进程的内存页表,虚实映射关系,非物理内存)拷贝到子进程,<br>父子进程共享内存,子进程再将最新数据转换为指令写入新文件。在之后父进程写入新日志,<br>才会复制一份给子进程(copyOnWrite),写时复制;<br>两份日志:<br>一份是正在使用的AOF日志,新来的操作会写入缓冲区,另一份是重写的AOF日志,新来的操作也会写入。<br>
RDB
内存快照,生成RDB文件:<br>save:主线程中执行,会阻塞;<br>bgsave:(默认)主线程fork子线程执行,fork时阻塞,子线程执行时不阻塞;<br>
bgsave子进程由主线程fork生成,可以共享主线程的内存,<br>fork后开始读取主进程内存数据写入RDB文件,<br>此时如果主线程有读取操作,没有影响,有写入操作时,<br>主线程会将这块数据复制一份进行修改(CopyOnWrite),<br>子进程依然写入原始快照数据
频繁创建RDB快照时,fork子进程会经常阻塞主线程<br>
最推荐的日志姿势:RDB+AOF混合模式,内存快照按照频率生成,<br>AOF记录两次快照间的操作,下次生成RDB快照时清空AOF日志<br>
高可靠性
数据尽量少丢失:AOF、RDB<br>
服务尽量少中断:增加副本冗余量<br>
哨兵机制
概念:哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。<br>哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。<br>
哨兵使用ping命令检测它自己和主、从库的网络连接情况,用来判断实例的状态<br>
判断下线:如果哨兵发现主库或从库对 PING 命令的响应超时了<br>,哨兵就会先把它标记为“主观下线”,只有大多数的哨兵实例,<br>都判断主库已经“主观下线”了,主库才会被标记为“客观下线”,<br>【少数服从多数】
选主:筛选+打分<br>
Redis实现分布式锁
单节点
使用Redis实现分布式锁存在的问题
加锁关键步骤:SET lock_key unique_value NX PX 10000<br>关键点:<br>NX:保证获取、判断、设置为原子命令;<br>PX:防止获取锁后在执行业务操作时无法释放,导致其他线程无法获取锁;<br>unique_value:防止被其他线程误释放锁,所以需要加上客户端标识,在获取、判断、删除锁的时候使用lua脚本<br>
多节点
Redlock算法实现多实例分布式锁,基本思路:<br>让客户端和多个独立的 Redis 实例依次请求加锁,<br>如果客户端能够和半数以上的实例成功地完成加锁操作,<br>那么就认为,客户端成功地获得分布式锁了,否则加锁失败<br>
Redlock实现步骤:<br>1.客户端获取时间;<br>2.客户端依次向各个示例执行加锁操作,加锁操作需要设置超时时间,一般几十ms,要小于锁过期时间;<br>3.各个节点完成加锁操作后,判断半数以上节点完成加锁并且客户端获取锁的总耗时小于锁过期时间,<br>认为加锁成功,否则释放锁。<br>
如何解决缓存与数据库的一致性
新增数据时:缓存中没有数据,此时不存在一致性问题
删除或更新数据时,因为删除缓存和更新数据库不是原子操作,所以存在一致性问题<br>
缓存污染
volatile-random、allkeys-random、volatile-ttl<br>这三种并不能有效治理缓存污染问题
LRU策略:通过数据的时效性,并不能精准解决污染问题;<br>实现:<br>1.Redis 是用 RedisObject 结构来保存数据的,RedisObject 结构中设置了一个 lru 字段,用来记录数据的访问时间戳;<br>2.Redis 并没有为所有的数据维护一个全局的链表,而是通过随机采样方式,选取一定数量(例如 10 个)的数据放入候选集合,后续在候选集合中根据 lru 字段值的大小进行筛选。<br>
LFU策略:通过两个维度,时效性+数据访问次数,来淘汰数据;<br>实现:Redis 在实现 LFU 策略的时候,只是把原来 24bit 大小的 lru 字段,<br>又进一步拆分成了两部分,第一部分放ldt时间戳,第二部分放counter计数。<br>计数器实现:并非访问一次就增加一次计数,为了避免数据短时间内都达到最大次数,<br>非线性递增,设置参数越大,增长越慢,根据业务调整。
Redis调优之网络中断程序+Redis进程绑定同一个CPU核<br>
如果网络中断程序和Redis不在同一个核上,Redis就要跨核读取缓冲区数据<br>
Redis集合统计<br>
聚合统计:交叉并集统计<br>
set<br>
基数统计:统计一个集合中不重复的元素个数<br>
HyperLogLog:有误差但是存储数量极大<br>
排序统计
sorted set
二值状态:只有两种状态,0或1<br>
bitmap
Redis问题排查<br>
全局哈希表
作用:组织Redis中所有的键值对<br>
哈希桶中元素保存的是具体值的指针<br>
解决哈希冲突:redis会准备两张全局hash表<br>1.链式哈希;<br>2.rehash
rehash:数据重新哈希分发到哈希表2<br>
渐进式rehash:每处理一个请求将索引位置上所有entry拷贝到hash表2<br>
Redis线程模型<br>
Redis单线程:主要指网络IO和键值对读写由一个线程完成,<br>Redis的其他操作,如持久化、异步删除、集群数据同步则是由额外线程处理的<br>
为什么不用多线程?
单线程为什么这么快?<br>
1:高效的数据结构,如Hash表、跳表
2:采用了IO多路复用机制<br>
常见问题
Redis单线程处理IO请求的性能瓶颈<br>
1.在server中处理的耗时操作<br>
2.并发量非常大时,单线程读写客户端IO数据存在瓶颈<br>
缓存雪崩
产生原因:<br>1:大量缓存同时失效;<br>解决a:设置过期时间时,再额外添加一小段的随机时间;<br>解决b:服务降级,比如发生雪崩时直接返回业务预设信息,如果访问的是核心数据就允许访问数据库;<br>2:实例宕机;<br>解决:业务实现熔断或服务限流方案;<br>
缓存击穿
产生场景:热点数据不能在缓存中处理,直接击穿到了数据库;<br>产生原因:通常发生在热点数据失效时;<br>解决方案:热点数据不设置过期时间;<br>
缓存穿透
访问的数据既不在缓存中也不在数据库中。<br>方案1:设置空值或缺省值;<br>方案2:使用布隆过滤器快速判断数据是否存在;<br>方案3:在业务前端针对恶意请求进行鉴别;<br>
主从库模式
读写分离:主从库承担读、主库承担写,由主库同步给从库<br>
主从同步
主从级联模式:主-从-从<br>
主从网络断开怎么处理?<br>
切片集群
解决大量数据存储
Redis Cluster 方案采用哈希槽(Hash Slot),<br>来处理数据和实例之间的映射关系<br>
实例和哈希槽的关系变化<br>
Redis事务
MULTI & EXEC使用命令队列保存事务操作,执行错误也不会影响其他正确操作的执行,<br>因此Redis事务机制并不能保证原子性,RDB+AOF的日志也不能保证持久性<br>
Redis实现布隆过滤器
使用bit数组(默认值0)和多个hash函数,<br>数据经过多个hash算法得到多个位置,多个位置被设置成1。<br>使用:访问时,在数据对应的多个位置依次判断,只要有一个位置不为1,则数据一定不存在;<br>注意:布隆过滤器只能用来判断数据是否不存在,而不能用来判断数据是否存在。、<br>
布隆过滤器可以放在缓存和数据库的最前面
Redis实现的布隆过滤器bigkey问题:<br>Redis布隆过滤器是使用String类型实现的,存储的方式是一个bigkey,<br>建议使用时单独部署一个实例,专门存放布隆过滤器的数据<br>
Redis调优之CPU绑核<br>
场景:如果Redis进程现在socket1运行,后切换到socket2运行属于【远端内存访问】,<br>和直接内存访问,会增加应用程序的延迟。<br>
一个应用程序访问所在 Socket 的本地内存和访问远端内存的延迟并不一致,<br>,我们也把这个架构称为非统一内存访问架构<br>
L1、L2 缓存中的指令和数据的访问速度很快,充分利用 L1、L2 缓存,<br>可以有效缩短应用程序的执行时间<br>
使用 taskset 命令把一个程序绑定在一个核上运行<br>
在 CPU 多核的环境下,通过绑定 Redis 实例和 CPU 核,可以有效降低 Redis 的尾延迟
绑核的弊端:主线程、后台线程、fork子进程争抢同一个CPU;<br>方案1:Redis绑定物理核,这样主线程和其他线程可以共享两个逻辑核以减少竞争;<br>
Redis阻塞点<br>
1:集合全量查询和聚合操作;<br>2:bigkey 删除操作;-> 可以异步;<br>3:清空数据库;-> 可以异步;<br>4:AOF 日志同步写;-> 可以异步;<br>5:加载 RDB 文件;<br>
判断Redis操作是否可以异步处理?<br>看操作是否在关键路径上,即是否需要返回给客户端,<br>读操作是典型的关键路径
String类型
Redis的String类型需要额外的空间存储,使用压缩列表可以节省空间<br>
1:Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries<br>2:写入的单个元素大小超过了 hash-max-ziplist-value<br>两种场景有一个出现,hash集合就会从压缩列表转为哈希表。<br>
0 条评论
下一页