洞察管理小程序实例的关键在于实现跨平台能力与数据安全,如何利用FinClip助力企业在数字化转型中既合规又高效?
667
2022-10-20
含泪整理Redis相关面试题大全
1、什么是redis?redis有哪些优缺点?
redis是一个C语言编写的开源的高性能NOSQL键值对数据库吗,支持5种数据类型:字符串,列表,集合,散列表,有序集合。与传统数据库不一样,Redis数据存储在内存中,读写速度非常快,redis被广泛应用于缓存,每秒可处理超过10w次的读写操作。优点 读写性能优异,读11w次/s, 写8w次/s 支持事务,redis所有操作都是原子性的,还支持几个操作合并也是原子性的
2、Redis有哪些应用场景?
1、DB缓存,减轻服务器压力2、提高系统响应3、做Session分离4、做分布式锁 使用setNX5、做乐观锁 Redis的watch+incr
3、为什么要用redis而不用map/guava?
本地缓存和分布式缓存的原因
4、说一说缓存的读写模式?
1、Cache Aside Pattern(旁路缓存,常用)是最经典的缓存+数据库读写模式。先读缓存,没有则读数据库,去除数据放缓存,同时返回响应。更新的时候,先更新数据库,再删除缓存。为什么 是删除缓存,不是更新缓存呢? 1、缓存的值是一个结构,hash,list,更新需要遍历 2、懒加载,使用的时候才更新缓存(亦可以异步填充)高并发脏读的三种情况 1、先更新数据库,再更新缓存 2、先删除缓存,再更新数据库 3、先更新数据库,再删除缓存(推荐)2、Read/Write Thread Pattern应用只操作缓存,缓存操作数据库ReadThread:应用程序读缓存,缓存没有,由缓存回源数据库,并写入缓存WriteThread:应用程序写缓存,缓存写数据库,比较复杂3、Write Behind Cachiing Parttern应用程序只更新缓存,缓存通过异步将数据批量更新到数据库,不能实时同步,甚至会丢数据
5、Redis为什么是单线程,高并发这么快是为什么呢?
1、高并发快的原因 1、redis是基于内存的,内存的读写速度非常快 2、redis是单线程的,省去了很多上下文切换线程的时间 3、redis使用IO多路复用技术(epoll),可以处理并发的连接。多路是指多个网络连接,复用是复用同一个线程。可以让单线程高效处理多个连接请求 4、数据结构为key-value,读取数据快。比如还有压缩表,跳跃表等加快数据读取 5、redis有使用自己的事件分离器,效率比较高,内部采用非阻塞方式,吞吐能力比较大2、为什么是单线程 因为redis是基于内存,CPU不是redis瓶颈,瓶颈可能是机器内存或者网络带宽,既然单线程容易实现且CPU不能成为瓶颈,就顺利采用单线程方式3、单线程的优势 代码清晰,逻辑简单,不用考虑各种锁问题,不需要上下文切换导致消耗CPU
6、说一说Redis有哪些数据类型?
1、String类型 可存储字符串,整数,浮点型,数字可以自增自减 2、List类型 是一个双向列表,可以从两端压入或弹出,存储一些列表的数据结构 3、SET类型(无序集合) 用于一些不重复并且不需要顺序的数据结构 4、Hash类型(散列表)5、ZSET类型(有序集合)
7、说一说Redis的RDB持久化和AOF持久化?
7.1、简单介绍
1、RDB持久化:可以指定的时间间隔能对数据进行快照存储,然后写入内存,以便在REDIS重启时,可以通过RDB还原数据库2、AOF持久化:记录每次对服务器写的操作命令(RESP),当服务器重启时会重新执行这些命令来恢复数据,AOF每次写操作以append方式追加在文件末尾,redis还能在后台对AOF重写,使得AOF达到瘦身的效果3、如果redis开启了AOF,优先使用AOF,只有AOF关闭,才会使用RDB
7.2、RDB持久化
7.2.1、RDB文件格式(可用winhex打开)
RDB文件是一个经过压缩的二进制文件(默认名:dump.rdb),由多个部分组成,RDB格式为: “REDIS”| RDB_VERSION | AUX_FIELD_KEY_VALUE_PAIRS | DB_NUM | DB_DICT_SIZE | EXPIRE_DICT_SIZE | KEY_VALUE_PAIRS | EOF | CHECK_NUM
1、头部5字节固定为“REDIS”字符串2、4个字节的RDB版本号(不是REDIS的版本号),当前为9,填充为00093、辅助字段,以KEY_VALUE形式,比如:redis-ver(redis版本), redis-bits(64/32),ctime(当期时间戳),used-mem(使用内存)4、存储数据库号码5、字典大小6、过期Key7、主要数据,以key-value形式存储8、结果标志9、校验和,就是看文件是否损坏或被修改
7.2.2、RDB触发方式
1、配置参数定期执行 save "" # 不使用RDB存储, 不能主从 save 900 1 # 表示15分钟至少有1个键被更改则进行快照 # 表示5分钟至少有10个键被更改则进行快照 save 60 10000 # 表示1分钟至少有10000个键被更改则进行快照2、命令显式触发 bgsave
7.2.3、RDB执行流程
1、流程图
2、流程说明
1、比如使用bgsave触发,Redis父进程首先判断,当前是否执行save,或者bgsave/bgwriteaof(aof重写命令)的紫禁城,如果在执行则bgsave命令直接返回2、父进程执行fork创建子进程,这个过程父进程是阻塞的,redis不能执行来自客户端的命令3、父进程fork后,bgsave命令返回“background saving started”,并可以响应其他命令4、子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换(RDB始终完整)5、子进程发送信号给父进程表示完成,父进程更新统计信息
7.3、AOF持久化
7.3.1、AOF原理
AOF存储的是redis命令,同步命令到AOF文件整个分为三个阶段:命令传播,缓存追加,文件保存和写入
1、命令传播:通过网络将协议文本发送给Redis,服务器接受后,根据协议内容,选择适当的函数,将参数从字符串文本转换为stringObject,然后命令参数传播到AOF程序2、缓存追加:AOF根据命令以及命令参数,将命令从字符串转换为原来的协议文本,然后追加到redis.h/redisServer的aof_buf末尾3、文件写入和保存:服务器会调用flushAppendOnlyFile函数,条件是WRITE时将aof_buf中的缓存写入AOF文件,条件是SAVE时,调用fsync或fdatasync,将AOF保存到磁盘
7.3.2、AOF保存模式
1、AOF_FSYNC_NO:不保存 每次调用flushAppendOnlyFile, WRITE会被执行,SAVE会被略过 2、AOF_FSYNC_EVERTSEC:每秒钟保存一次(默认) 这种模式下,SAVE原则上每隔1s钟就会执行一次,因为SAVE是后台子线程,所以不会引起服务器主进程阻塞 3、AOF_FSYNC_ALWAYS:每执行一个命令保存一次(不推荐) 每执行一个命令之后,WRITE和SAVE都会被执行。SAVE是由主进程执行,主进程被阻塞,不能接受请求
7.3.3、AOF重写
AOF记录数据越多,体积就会越大,需要重写瘦身。Redis可以自动在后台队AOF进行重写,重写后包含回复当前数据集所需要的最小命令集合子进程重写期间,主进程还需要继续处理命令,新的命令可能对现有数据进行修改,Redis就增加了AOF重写缓存。fork出子进程后,redis主进程接受命令后,除了将写命令追加到现有的AOF,还会追加到AOF重写缓存中。子进程重写完毕后,会通知主进程,会把AOF重写缓存的内容全部写入新的AOF中
7.3.4、AOF配置(命令bgwriteaof)
appendonly yes # 开启aof auto-aof-rewrite-percentage 100 # 表示aof超过上一次aof的百分之多少会进行重写auto-aof-rewrite-min-size 64mb # 限制允许重写的aof文件大小,也就是小于64mb时,不需要优化
7.3.5、混合持久化
redis4.0开始支持rdb和aof混合持久化,rdb头+aof身体--》appendonly.aof开启混合持久化 aof-use-rdb-preamble yes
7.4、持久化方式总结与抉择
7.4.1、RDB和AOF对比
1、RDB是某个时刻的快照数据,使用二进制存储,AOF存操作命令,采用文本存储(混合)2、RDB性能高,AOF性能比较低3、RDB会丢失最后一次快照以后更改的所有数据,AOF设置每秒保存一次,则最多丢2秒的数据4、Redis主服务器模式运行,RDB不会保存过期key,RDB以从服务器运行,会保存过期key,主从同步时,再清空过期key。AOF写入时,过期key会追加一条del命令,执行aof重写时,会忽略过期key和del命令
7.4.2、如何选择
内存数据库:rdb+aof 数据不容易丢失缓存服务器:rdb 高性能, 不建议只使用aof(性能差)追求高性能:可以都不开,redis宕机,从数据源恢复字典库:可以选择不驱逐,保证数据完整性
8、Redis过期键删除有哪些策略?
redis性能高,官方表示读11w次/s,写81000次/秒,长期使用key不断增加,redis作为缓存,物理内存也会满,内存与磁盘交换虚拟内存,频繁IO导致性能急剧下降.
maxmemory 默认不设置,一般设置物理内存3/4,趋近maxmemory后,会通过缓存淘汰策略,从内存中删除expire数据结构 expire命令在到达过期时间后回自动删除key。删除策略 定时删除:设置过期时间时,创建一个定时器,,让定时器在过期时间来临时立即执行删除,需要创建定时器,耗费CPU,不推荐 惰性删除: 在key被访问时如果发现它已经失效,那么就删除.调用expireIfNeeded函数,读取之前检查一下有没有失效,失效则删除 主动删除: 在redis.conf配置主动删除策略,默认是no-enviction(不删除) 1、allkeys-lru:在不确定时一般采用LRU 2、volatile-lru:比allkeys-lru性能差,存过期时间 3、allkeys-random:随机淘汰,希望请求平均分布时可以选择 4、volatile-ttl 自己控制 5、no-enviction 禁止驱逐(如字典表)
9、谈一谈REDIS中的事务?
9.1、事务命令
multi:用于标记事务开始,redis会将后续命令逐个放入队列中,然后使用exec原子化的执行这个命令队列exec:执行命令队列discard:清除命令队列watch:监视 keyunwatch:清除监视key
9.2、事务机制
1、事务执行:在redisClient中,有flags属性,用来标识是否在事务中 flags=REDIS_MULTI2、命令入队:RedisClient将命令存放在事务队列中(multi, exec,discard,watch除外)3、事务队列:multiCmd *commands 用于存放命令4、执行事务:RedisClient向服务端发送exec命令,RedisServer遍历队列,最后将执行结果一次性返回.如果某条命令发生错误,Redisclient将flags置为REDIS_DIRTY_EXEC,exec命令将会失败返回
9.3、watch执行
使用watch监视数据库键,redisDb有一个watch_keys字典,key是某个被监视的数据的key,值是一个链表,记录了所有监视这个key的客户端监视机制触发:数据修改后,监视这个数据的客户端的flags置为REDIS_DIRTY_CAS事务执行:redisclient向服务器发送exec,服务端判断Redisclient的flags,如果是REDIS_DIRTY_CAS,则清空事务队列
9.4、Redis的弱事务性
1、redis语法错误,整个事务的命令在队列里都清除2、redis运行错误:在队列里正确的命令可以执行且不支持回滚,因为大多数事务失败都是语法错误或者类型错误,一般开发阶段可预见,redis为了性能就忽略了事务
9.5、lua脚本可以保证事务原子性
1、lua命令 1、eval script numkeys key [key ...] arg [arg ... ] 比如:eval"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 2、redis.call和reids.pcall 比如:eval"return redis.call('set',KEYS[1],ARGV[1])"1 n1 zhaoyun 3、evalsha script load "return redis.call('set',KEYS[1],ARGV[1])" 会返回一个sha的编码,执行evalsha + 编码即可执行脚本 4、直接编写lua脚本,使用redis-client --eval 指定 脚本和参数 2、利用Redis整合Lua,主要是为了性能以及事务的原子性。因为redis帮我们提供的事务功能太差。3、lua脚本复制分为脚本传播模式和命令传播模式 1、脚本传播,脚本包含时间,内部状态、随机函数等不能出现 2、命令传播,所有写命令用事务包裹,复制到AOF文件及服务器。 3、redis.replicate_commands()
9.6、管道,事务,脚本的区别
管道无原子性,命令是独立的,脚本事务性强于事务,脚本执行期间,客户端其他脚本或事务无法执行,所以脚本时间应该尽量短
10、REDIS哨兵、复制、集群的设计原理,以及区别?
10.1、主从复制原理
redis为了单点数据库问题,会把数据复制多个副本到其他节点,以实现redis高可用,数据冗余备份
1、从数据库向主数据库发送sync命令,主数据库接受后创建一个rdb快照文件2、主数据库发送rdb文件给从服务器,从服务器接受并载入该文件3、主服务器将同步阶段接收到的新命令写入缓冲区,然后将缓冲区所有的写命令发送给从服务器执行4、处理完之后,主数据库每写一个命令,就会发送给从服务器注意:2.8之后,会根据从服务器断开之前最新命令的偏移量进行增量同步。2.8之后使用psync发送同步命令,并且带上runid和偏移量offerset,主服务器会判断是否是第一次同步,是的话走全量同步,不是的话根据偏移量进行增量同步
10.2、主从复制存在的问题?
主服务器挂了,从服务器可读不可写,无法实现自动化故障转移
10.3、使用哨兵机制实现自动化故障转移
10.3.1、主要功能
1、集群监控,负责监控redis master和slave进程是否正常工作2、消息通知:如果某个redis有故障,哨兵负责发送消息作为报警通知给管理员3、故障转移:如果master挂了,会自动转移到slave上4、配置中心:如果故障转移发生了,通知客户端新的master地址
10.3.2、redis哨兵高可用
redis简历多个哨兵,共同监控数据节点的运行哨兵之间互相通信,交换对主从节点的监控情况每隔1s每个哨兵会向整个集群方ping命令做心跳检测
10.3.3、哨兵中的主观下线和客观下线
主观下线:一个哨兵发现ping主节点时没有响应,主观认为down掉了客观下线:首先发现主节点下线的哨兵将信息发送给其他哨兵,他们也进行ping操作,多个哨兵交换主观判断结果,超过半数以上的哨兵认为主挂了,才判断主节点那客观下线了。投票选举:那个节点最先判断主节点下线,就发起投票机制(raft算法),最终投为主节点的哨兵节点完成主从自动切换过程
10.4、集群
为了解决redis单机容量有限问题,将数据进行分片集群处理,存储到多台服务器,内存不受限与单机
10.4.1、edis Cluster采用去中心化模式,使用gossip协议
meet:sender向receiver发出,请求receiver加入sender集群ping:节点检测其他节点是否在线pong:receiver收到meet或ping后回复,在Failover后,新的master也会广播pongfail:节点A判断B下线后,A节点广播B的fail消息,其他节点收到后标记B下线publish:节点A收到publish,节点A执行该命令,并广播集群,收到广播的节点执行相同的命令
10.4.2、slot(hash槽)
rediscluster把所有的物理节点映射到[0-16384]个slot上,采用平均和连续分配方式。采用crc32算法计算hash槽。为什么是16384个槽,因为作者认为1000台redis之后会出现问题,16384个槽也够用了
10.4.3、集群搭建
开启集群
cluster-enabled yes
创建集群:
redis-client --cluster ip1:port1 ipn:portn --cluster-replicas 1 #【指定副本数量,也就是每个几点的副本节点数量】
集群信息可以在node.conf中 迁移:新的master节点加入后者删除,需要进行槽和槽数据的迁移
1、节点B发送状态变更状态命令,将B的slot状态置为importing2、向节点A发送变更命令,将A的slot状态置为migrating3、向A发送migrate命令,告知A将要迁移的slot对应的key迁移到B4、所有key迁移完后,cluster setslot重新设置槽位
扩容:
./redis-cli --cluster add-node 192.168.127.128:7007 192.168.127.128:7001 # 新加入者 (集群发起者)
重新分槽
./redis-cli --cluster reshard 192.168.127.128:7007
添加从节点
./redis-cli --cluster add-node 192.168.127.128:7008 192.168.127.128:7007 --cluster-slave --cluster-master-id 6ff20bf463c954e977b213f0e36f3efc02bd53d6
11、Redis并发竞争key有什么解决方案?
并发竞争:redis多个client同时set key引起的并发问题, 如何解决并发竞争key问题?
11.1、使用分布式锁
1、整体技术方案:主要是准备一个分布式锁,大家去抢锁,抢到锁就做set操作2、为什么分布式锁:因为传统加锁,只适合单点。不管事zookeeper或redis实现,基本原理都是用一个状态值表示锁,对锁的占用和释放通过状态值来标识3、分布式锁的要求:互斥,无死锁,容错4、分布式锁的实现,数据库,redis(setnx),zookeeper(临时节点)
11.2、使用消息队列
并发过大的情况,可以以通过消息队列处理,把并行读写串行化,把redis set操作放在队列里,必须一个一个的执行,这也是高并发的一种通用解决方案
12、REDIS如何实现分布式锁?
12.1、使用setnx
1、获取锁 使用redis.set(lockKey, requestId, "NX", "PX", expireTime) # 操作是原子性,并发不会有问题 使用result = redis.setnx(lockKey, requestId) ,if(result == 1){ jedis.expire(lockKey, expireTime) } # 并发会有问题2、释放锁 使用del 先查询requestId是否等于查询结果,是的话就删除lockKey,也就是释放锁 # 存在并发问题 使用redis+lua脚本释放锁(推荐) 将操作封装在lua脚本中 ,因为lua脚本具有原子性3、存在问题 单机无法保证高可用。主从无法保证强一致性,主机宕机会造成锁的重复获取。无法续租,超过过期时间后,不能继续使用
12.2、使用使用redission
代码实现
实现原理
如果客户端面对的是一个redis集群,根据hash节点随机选择一台,发送lua脚本到redis服务器获取锁原理 锁互斥机制:首先判断锁是否存在,如果已经有了,判断锁的ID是不是自己,此时返回锁的剩余生存时间 自动延时机制:获取锁后,回启东一个watch dog,是一个后台线程,每隔10s检查一下,如果还持有锁,不断延长key的生存时间 可重入锁机制:某个客户端获取锁后,再次获取,使用incrby 进行累加1释放锁原理 每次对锁的加所次数-1,为0后调用del命令删除key
13、谈一谈你对REDIS缓存穿透,缓存击穿,缓存雪崩的理解?
13.1、缓存穿透
key对应的数据在数据源中不存在,每次针对key的请求从缓存获取不到,请求会到数据源,从而可能压垮数据源解决: 1、布隆过滤器:将所有可能存在的数据hash到一个足够大的bitmap,一个一定不存在的数据会被这个bitmap拦截,从而避免底层查询压力 2、查询空值,我们将空值缓存,但是过期时间很短,最长不超过5分钟
13.2、缓存击穿
key对应的数据存在,但是在redis中过期,若此时有大量的并发请求过来,这些请求发现缓存过期会从DB中获取并回设到缓存,这时候可能瞬间把DB压垮解决: 使用互斥锁:在缓存失效的时候,不立即去load db,而是使用互斥锁保护DB资源,只有一个线程去获取DB数据,然后回填到缓存,其他线程排队等待回设完毕或充缓存中获取
13.3、缓存雪崩
当服务器重启或大量缓存集中在某个时间段失效, 这也会给后端带来极大的压力解决: 1、加锁或者使用队列串行化读写,真正高并发很少使用,性能低 2、将缓存失效时间分散开,缓存同时失效的重复率会降低
14、REDIS缓存和MYSQL数据一致性的解决方案?
读取缓存一般没啥问题,但是涉及到数据更新,就容易出现redis和mysql数据不一致问题
1、如果删除redis缓存,还没来得及写入mysql,此时一个线程俩来读取,缓存为空,则去数据库读取然后写缓存,此缓存中为脏数据2、如果先写库数据库,在删除缓存前,数据库宕机了,没有删掉缓存,此时数据也不一致了
解决方案
14.1、延时双删
先删缓存,再写数据库,休眠一段时间,再次删除缓存(休眠时间根据业务定)设置缓存过期时间,可以保证最终一致性弊端:结合双删策略+缓存超时时间,最差就是在超时时间内数据不一致,而且又增加了写请求的耗时
14.2、异步更新缓存
mysql binlog增量订阅消费+消息队列+增量数据更新到redis
1、读redis:热数据基本在redis2、写mysql:增删改都操作mysql3、更新redis,mysql操作Binlog,来更新redis 1、将数据全量写入redis 2、增量:实时更新
读取Binlog后,利用消息队列推送更新各台redis缓存数据。可以结合阿里的canal,实现对Binlog的订阅
15、热点数据和冷数据?
热数据就是经常会被访问的数据,一般位于redis,命中率尽量要高冷数据是指不经常被访问的数据,一般位于DB中冷热数据交换:可以使用maxmemory+allkeys-lru冷热交换比例:热20w,冷200w
16、什么是缓存热点Key?
就是热key突然过期,然后又大量的并发请求过来,可能会将缓存击穿,直达数据库,导致DB压力过大甚至压垮
17、假如Redis有1亿个key,有10w个key以固定前缀开头,如何查询?
redis是单线程,keys会阻塞,线上服务会停顿。可以使用scan命令,可以无阻塞的提取指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重即可,整体时间可能比keys长
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~