admin管理员组文章数量:1794759
Redis面试题(一)
1、Redis有哪些数据结构
1、字符串String
以key-value的方式存储数据。
2、哈希(hash)
key为字符串,值分为两部分field和value,视为属性和值。可以把key当作一张表的一行,Key就代表一个id,每个属性可以看作关系型数据库的一个字段。fields不能相同,value可以。
如 value={{field1,value1},…{fieldN,valueN}}
3、列表(List)
key是字符串,value是一个有序的list。特点是有序、可以重复。
优点:
1.列表的元素是有序的,这就意味着可以通过索引下标获取某个或某个范围内的元素列表。 2.列表内的元素是可以重复的。
4、集合(Set)
集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中不允许有重复的元素,并且集合中的元素是无序的,不能通过索引下标获取元素,redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集
5、有序集合(zset)
lement + score
有序集合不能有无重复元素,元素是有序,csore可以重复
但是它和列表的使用索引下标作为排序依据不同的是,它给每个元素设置一个分数,作为排序的依据。
特殊数据结构
6、HyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
PFADD key element [element ...] 添加指定元素到 HyperLogLog 中。 PFCOUNT key [key ...] 返回给定 HyperLogLog 的基数估算值。 PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并为一个 HyperLogLog7、Geo
Redis 的 GEO 是 3.2 版本的新特性。这个功能可以将用户给定的地理位置信储存起来, 并对这些信进行操作
8、Pub/Sub
发布/订阅”在redis中,被设计的非常轻量级和简洁,它做到了消的“发布”和“订阅”的基本能力;但是尚未提供关于消的持久化等各种企业级的特性。
一个Redis client发布消,其他多个redis client订阅消,发布的消“即发即失”, redis不会持久保存发布的消; 消订阅者也将只能得到订阅之后的消,通道中此前的消将无从获得。
2、redis应用场景有哪些?数据缓存
redis访问速度块、支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作。
共享session
出于负载均衡的考虑,分布式服务会将用户信的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信都直接从redis中集中获取。
限时限速业务
redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。
应用:限时的优惠活动信、限制用户获取手机验证码的频率
计数器
redis的incrby命令可以实现原子性的递增
应用:运用于高并发的秒杀活动、分布式序列号id的生成、限制接口的条用次数
排行榜
redis的SortedSet有序集合的score是排序的依据。
分布式锁
Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。设置成功,返回 1 。 设置失败,返回 0
应用:分布式定时任务,两台服务器具有同样定时任务,要求2小时执行一次(只允许一台服务器上定时任务执行,不能重复执行)。通过setnx设置一个key-value,执行Setnx dsrw _key run ,如果设置成果,则执行定时任务,如果设置失败,则表明该定时任务已执行。并且可以给这个key设置一个过期时间,2小时。
队列
redis有list push和list pop这样的命令,所以能够很方便的执行队列操作
3、使用过Redis分布式锁么,它是什么回事?先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
但是在setnx之后执行expire之前进程意外crash或者要重启维护了,会造成锁永远得不到释放了。
原因:
Redis的setnx命令是当key不存在时设置key,但setnx不能同时完成expire设置失效时长,不能保证setnx和expire的原子性。
解决办法:
使用set命令完成setnx和expire的操作,保证操作是原子性的 从 Redis 2.6.12 版本开始,set指令有非常复杂的参数,可以同时把setnx和expire合成一条指令来用
set key value [EX seconds] [PX milliseconds] [NX|XX] EX seconds:设置失效时长,单位秒 PX milliseconds:设置失效时长,单位毫秒 NX:key不存在时设置value,成功返回OK,失败返回(nil) XX:key存在时设置value,成功返回OK,失败返回(nil) 4、Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?KEYS 指令
KEYS pattern #用法 KEYS t?? #查询如two,ttt这类的key KEYS * #查询所有key
Redis的单线程的。KEYS命令一次性返回所有匹配的key,keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
所以应该在生产环境禁止用使用keys和类似的命令smembers,这种时间复杂度为O(N),且会阻塞主线程的命令,是非常危险的。
SCAN 指令
每次执行都只会返回少量元素,所以可以用于生产环境,而不会出现像 KEYS 或者 SMEMBERS 命令带来的可能会阻塞服务器的问题。
SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程
当SCAN命令的游标参数(即cursor)被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
scan缺陷:
在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。 scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
5、使用过Redis做异步队列么,你是怎么用的?方式一:生产者消费者模式
使用list结构作为队列,rpush生产消,lpop消费消,当lpop没有消的时候,要适当sleep一会再重试。
如果不想使用sleep的话,可以使用blpop指令,在没有消的时候,它会阻塞住直到消到。
方式二:发布订阅者模式
使用pub/sub主题订阅者模式,可以实现1:N的消队列。
缺点:在消费者下线的情况下,生产的消会丢失。此场景,建议用MQ。
6、redis如何实现延时队列延迟队列可以使用 zset(有序列表)实现,我们将消序列化成一个字符串作为列表的value,这个消的到期处理时间作为score。
zset会按照时间戳大小进行排序,也就是对执行时间前后进行排序,这样的话,起一个线程轮询取第一个key值,如果当前时间戳大于等于该key值的socre就将它取出来进行消费删除,就可以达到延时执行的目的, 注意不需要遍历整个Zset集合,以免造成性能浪费。
7、如果有大量的key需要设置同一时间过期,一般需要注意什么?如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,一般需要在时间上加一个随机值,使得过期时间分散一些。
8、Redis如何做持久化的?因为Redis的数据都储存在内存中,当进程退出时,所有数据都将丢失。为了保证数据安全,Redis支持RDB和AOF两种持久化机制有效避免数据丢失问题。RDB可以看作在某一时刻Redis的快照(snapshot),非常适合灾难恢复。AOF则是写入操作的日志。
RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
如果突然机器掉电会怎样?
取决于AOF日志sync(同步)属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
RDB的原理是什么?
RDB就像是一台给Redis内存数据存储拍照的照相机,生成快照保存到磁盘的过程。触发RDB持久化分为手动触发和自动触发。Redis重启读取RDB速度快,但是无法做到实时持久化。支持手动触发和自动触发。
- 手动触发分别对应save和bgsave命令:
1、save命令:同步,在主线程中保存快照;阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用;
2、bgsave命令:异步,执行该命令时,Redis会在后台异步进行快照操作。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。 3、BGSAVE命令是针对SAVE堵塞问题做的优化。因此Redis内部所有的设计RDB的操作都采用BGSAVE的方式,而save命令已经废弃。 4、 fork操作:虽然fork同步操作是非常快的,但是如果需要同步的数据量过大,fork就会阻塞redis主进程;内存越大,fork同步数据耗时越长,当然也跟服务器有关
- 自动触发,在redis.conf文件中配置save m n
指定当m秒内发生n次变化时,会自动触发bgsave
AOF的原理是什么?
AOF的出现很好的解决了数据持久化的实时性,AOF以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令来恢复数据。AOF会先把命令追加在AOF缓冲区,然后根据对应策略写入硬盘(appendfsync)
AOF持久化方式的优点:
做到最多丢失1-2s内的数据(最多丢失2s数据,因为AOF追加阻塞)
AOF持久化方式的缺点:
AOF文件比RDB文件大 可能导致追加阻塞
PS: 如果AOF文件fsync同步时间大于2s,Redis主进程就会阻塞;
如果AOF文件fsync同步时间小于2s,Redis主进程就会返回;
其实这样做的目的是为了保证文件安全性的一种策略
9、Pipeline有什么好处,为什么要用pipeline?未使用Pipeline
redis 执行一次操作所需要的时间:1 次时间 = 1 次网络时间 + 1次命令时间 执行 n 次就需要:n 次时间 = n 次网络时间 + n 次命令时间
使用Pipeline
由于命令时间非常短,影响时间开销的主要是网络时间,所以我们可以把一组命令打包,然后一次发送过去。这样的话,时间开销就变为:1 次 pipeline(n条命令) = 1 次网络时间 + n 次命令时间
pipeline 的好处
省略由于单线程导致的命令排队时间,一次命令的消耗时间=一次网络时间 + 命令执行时间 比起命令执行时间,网络时间很可能成为系统的瓶颈
pipeline的作用是将一批命令进行打包,然后发送给服务器,服务器执行完按顺序打包返回。 通过pipeline,一次pipeline(n条命令)=一次网络时间 + n次命令时间
pipeline注意事项
每次pipeline携带数量不推荐过大,否则会影响网络性能 pipeline每次只能作用在一个Redis节点上
10、Redis的同步机制了解么?Redis通过主从同步机制来确保master和salve之间的数据同步。
Redis的主从复制功能分为两种数据同步模式进行:全量数据同步和增量数据同步。
全量复制
redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。
具体步骤如下:
- 从服务器连接主服务器,发送SYNC命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令
增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
具体步骤如下:
- redis 2.8前,当主从服务器之间的连接断掉之后,master服务器和slave服务器之间都是进行全量数据同步。
- 从redis2.8开始,引入了部分同步的概念,即使主从连接中途断掉,slave服务器不需要进行全量同步。
- 部分同步的实现依赖于在master服务器内存中给每个slave服务器维护了一份同步日志和同步标识,每个slave服务器在跟master服务器进行同步时都会携带自己的同步标识和上次同步的最后位置。
- 当主从连接断掉之后,slave服务器隔断时间(默认1s)主动尝试和master服务器进行连接。
- 如果从服务器携带的偏移量标识还在master服务器上的同步备份日志中,那么就从slave发送的偏移量开始继续上次的同步操作。
- 如果slave发送的偏移量已经不再master的同步备份日志中(可能由于主从之间断掉的时间比较长或者在断掉的短暂时间内master服务器接收到大量的写操作),则必须进行一次全量更新。在部分同步过程中,master会将本地记录的同步备份日志中记录的指令依次发送给slave服务器从而达到数据一致。
Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步
Redis(四)------redis主从复制原理
11、是否使用过Redis集群,集群的原理是什么?1、redis主从复制
Redis(四)------redis主从复制原理
Redis(五)------redis主从复制的实现
master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。
2、redis哨兵Sentinal
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis(六)------redis哨兵机制的原理
Redis(七)------redis哨兵机制的实现
3、Redis Cluster
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。cluster模式的出现就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。 cluster可以说是sentinel和主从模式的结合
12、什么是缓存雪崩?由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃
解决办法:
1、使用锁或队列
一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着。在真正的高并发场景下很少使用。
2、添加缓存标记
给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
3、为key设置不同的缓存失效时间
还有一个简单方案就是在缓存的时候给过期时间加上一个随机值,将缓存失效时间分散开。
4、双缓存
缓存A和缓存B
13、如果Redis挂掉了,请求全部走数据库这种情况?前面是由于缓存大面积失效,这里由于Redis挂掉导致请求全部走数据库,导致数据库服务可能崩溃
事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的) 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据
14、什么是缓存穿透?缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
我们有一张数据库表,ID都是从1开始的自增整数,但是可能有黑客想把我的数据库搞垮,每次请求的ID都是负数。这会导致我的缓存就没用了,请求全部都找数据库去了,但数据库也没有这个值啊,所以每次都返回空出去。
或者恶意攻击,猜测你的key命名方式,然后估计使用一个你缓存中不会有的key进行访问。
请求的数据在缓存大量不命中,导致请求走数据库。
缓存穿透如果发生了,也可能把我们的数据库搞垮,导致整个服务瘫痪!
解决办法
1、使用互斥锁排队
根据key去获取value值,如果value为空,获取到锁,同时从数据库中加载数据,数据加载成功就释放锁,并且缓存进redis。
如果在这个过程中,有另一个线程请求这个key,会因为无法获取锁而进入等待排队,等待第一个线程释放锁。
2、接口限流与熔断、降级
重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些服务不可用时候,进行熔断,失败快速返回机制。
3、布隆过滤器
bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。
编写接口的时候,可以规范key命名,不合法的key直接过滤掉,不让这个请求到数据库层!
4、缓存空值
不管数据库中是否有数据,都在缓存中保存对应的key,值为空就行。这样是为了避免数据库中没有这个数据,导致的平凡穿透缓存对数据库进行访问。
当然空值如果太多,也会导致内存耗尽。导致不必要的内存消耗。这样就要定期的清理空值的key。避免内存被恶意占满。导致正常的功能不能缓存数据。
15、什么是缓存预热?缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存预热解决方案:
(1)直接写个缓存刷新页面,上线时手工操作下;
(2)数据量不大,可以在项目启动的时候自动进行加载;
(3)定时刷新缓存;
16、什么是缓存更新?①LRU/LFU/FIFO算法剔除:剔除算法通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。例如Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的剔除策略。
②超时剔除:通过给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire命令。如果业务可以容忍一段时间内,缓存层数据和存储层数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。例如一个视频的描述信,可以容忍几分钟内数据不一致,但是涉及交易方面的业务,后果可想而知。
③主动更新:应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。例如可以利用消系统或者其他方式通知缓存更新。
17、什么是缓存降级?当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
18、热点数据和冷数据是什么?热点数据,就是信修改频率不高,读取频率非常高。一般数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。
频繁修改的数据,看情况考虑使用缓存
比如点赞数,收藏数,分享数是修改频率很高的数据,但是需要使用缓存来减少数据库压力
19、Memcache与Redis的区别都有哪些?1、存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据
2、数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储
3、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4.、value 值大小不同:Redis 最大可以达到 1gb;memcache 只有 1mb。
5、redis的速度比memcached快很多
6、Redis支持数据的备份,即master-slave模式的数据备份。
20、单线程的redis为什么这么快1、纯内存操作
redis是基于内存的,内存的读写速度非常快。
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以Redis具有快速和数据持久化的特性。
特例
Redis 混合存储实例是阿里云自主研发的兼容Redis协议和特性的云数据库产品,混合存储实例突破 Redis 数据必须全部存储到内存的限制,使用磁盘存储全量数据,并将热数据缓存到内存,实现访问性能与存储成本的完美平衡。 参考:developer.aliyun/article/740673?utm_content=g_1000096525
2、单线程操作,避免了频繁的上下文切换
上下文切换就是cpu在多线程之间进行轮流执行(枪战cpu资源),而redis单线程的,因此避免了繁琐的多线程上下文切换。
3、采用了非阻塞I/O多路复用机制
多路-指的是多个socket连接,复用-指的是复用一个线程。
- 传统的多进程并发模型中 ,每一个新的I/O流会分配一个新的进程管理。
- I/O多路复用 ,单个线程,通过记录跟踪每个I/O流的状态,来同时管理多个I/O流 。
I/O多路复用就通过一种机制,监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的操作。具体由select、poll和epoll实现
21、Redis 为什么是单线程的因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问
22、redis 集群方案应该怎么做?都有哪些方案?1、redis主从方案
redis主从模式是最简单的一种集群方案配置起来也比较简单,并且主从复制可以配合Sentinel 哨兵机制。
redis主从方案方案目前使用的越来越少,不过对于个体开发并且对缓存依赖度不高的系统还是可以使用的,毕竟搭建和维护简单。
Redis(四)------redis主从复制原理
Redis(五)------redis主从复制的实现
2、 redis cluster方案
redis cluster3.0 自带的集群。
对于小规模应用最好还是使用官方的cluster方案,在redis3.0后官方社区也在逐步完善cluster方案,小团队可以随着官方版本的升级享受功能和稳定性的提升,也便于日后的维护
3、codis集群方案
Codis是一个豌豆荚团队开源的使用Go语言编写的Redis Proxy使用方法和普通的redis没有任何区别。
Codis是目前用的比较多的一种方案,但是因为Codis是对redis-server做了改动,如果出现问题或者redis升级小团队可能应付不了。
23、有没有尝试进行多机redis 的部署?如何保证数据一致的?主从复制,读写分离、哨兵机制等
一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库
24、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
该配置就是配内存淘汰策略的
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。 2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。 3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。 4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐 5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐 6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
25、redis和数据库双写一致性问题一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。
强一致性
先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
最终一致性
如果仅仅查询的话,缓存的数据和数据库的数据是没问题的。但是,当我们要更新时候呢?各种情况很可能就造成数据库和缓存的数据不一致了。
从理论上说,只要我们设置了键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。
除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生。
首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消队列。
1、先更新数据库,再更新缓存(不建议使用)
如:同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库 (2)线程B更新了数据库 (3)线程B更新了缓存 (4)线程A更新了缓存
如果因为网络等原因,请求B比请求A更早更新缓存,就导致了脏数据,因此不考虑。
缺点:
1、并发情况可能产生脏数据; 2、每次写入操作,更新缓存,如果写操作频繁,造成缓存还未使用就更新了。2、先删缓存,再更新数据库(产生脏数据,可以用延时双删策略来解决)
(1)请求A进行写操作,删除缓存 (2)请求B查询发现缓存不存在 (3)请求B去数据库查询得到旧值 (4)请求B将旧值写入缓存 (5)请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
缺点:
1、脏数据产生解决方案:延时双删策略 (将写操作内x秒内所造成的缓存脏数据,再次删除)
(1)先淘汰缓存 (2)再写数据库(这两步和原来一样) (3)休眠x秒,再次淘汰缓存 需要估算的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。确保读请求结束,写请求可以删除读请求造成的缓存脏数据如果采用mysql的读写分离架构的话,使用双删延时策休眠时间需要加上主从同步耗时时间。
3、先更新数据库,再删除缓存(使用场景多)
优势:减轻服务器压力,避免每次更新数据库后,都更新缓存;解决方案1脏数据问题
也有可能发生并发问题, 若有两个请求P和Q先后过来,P查询数据,Q更新数据:
(1)缓存刚好失效;
(2)请求P查询数据库,得一个旧值;
(3)请求Q将新值写入数据库;
(4)请求Q删除缓存;
(5)请求P将查到的旧值写入缓存;
上去确实发生了读写不一致的问题,但是仔细推敲一下:请求P先到达,发现缓存失效,然后去读取数据库。然后请求Q到达,下数据库,在删除缓存。在数据库中读操作会快于写操作,况且在进行读写分离的分布式环境下,读的速度会远快于写,而上述请求Q写数据库和删除缓存的操作,竟然发生在P写入缓存之前。从概率上讲,考虑到网络延时的问题,上述情况可能发生,但是概率极低。
4、拓展优化:
1.可以使用阿里的方法:CANAL,通过日志来保持一致性;
2.可以直接更新redis数据,先不入db,再某个时间段,再异步的方式通过redis更新DB;
26、如何解决redis的并发竞争key问题分析:这个问题大致就是,同时有多个子系统去set一个key。这个时候要注意什么呢?大家思考过么。需要说明一下,博主提前百度了一下,发现答案基本都是推荐用redis事务机制。博主不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。
回答:如下所示
(1)如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(2)如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00} 系统B key 1 {valueB 3:05} 系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。
27、使用redis有哪些好处?(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消,按key设置过期时间,过期后将会自动删除
28、mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据当redis使用的内存超过了设置的最大内存时,会触发redis的key淘汰机制
在redis 3.0中有6种淘汰策略:
noeviction: 不删除策略。当达到最大内存限制时, 如果需要使用更多内存,则直接返回错误信。(redis默认淘汰策略)
allkeys-lru: 在所有key中优先删除最近最少使用(less recently used ,LRU) 的 key。
allkeys-random: 在所有key中随机删除一部分 key。
volatile-lru: 在设置了超时时间(expire )的key中优先删除最近最少使用(less recently used ,LRU) 的 key。
volatile-random: 在设置了超时时间(expire)的key中随机删除一部分 key。
volatile-ttl: 在设置了超时时间(expire )的key中优先删除剩余时间(time to live,TTL) 短的key。
方案:
因此限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,留下热数据到内存。所以,计算一下 2000w数据大约占用的内存,然后设置一下 Redis 内存限制即可,并将淘汰策略为volatile-lru或者allkeys-lru。
打开redis配置文件,设置maxmemory参数,maxmemory是bytes字节类型
#设置Redis最大占用内存 # maxmemory <bytes> maxmemory 268435456 #设置过期策略: maxmemory-policy volatile-lru 29、Redis事务特点事务提供了一种“将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令
Redis的事务不支持回滚,事务执行时会阻塞其它客户端的请求执行。
Redis事务提供了一种“将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令。
redis > MULTI OK redis > SET "username" "admin" QUEUED redis > SET "password" 123456 QUEUED redis > GET "username" redis > EXEC 1) ok 2) ok 3) "admin"一个事务从开始到执行会经历以下三个阶段:
开始事务。 命令入队。 执行事务。
MULTI
客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行
1、MULTI命令将客户端的 REDIS_MULTI 选项打开, 让客户端从非事务状态切换到事务状态 2、当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回 QUEUED , 表示命令已入队
EXEC
执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
DISCARD
用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消。
备注:Redis 的事务是不可嵌套的, 当客户端已经处于事务状态, 而客户端又再向服务器发送 MULTI 时, 服务器只是简单地向客户端发送一个错误, 然后继续等待其他命令的入队。 MULTI 命令的发送不会造成整个事务失败, 也不会修改事务队列中已有的数据。
WATCH
WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。
备注:只能在客户端进入事务状态之前执行, 在事务状态下发送 WATCH 命令会引发一个错误, 但它不会造成整个事务失败, 也不会修改事务队列中已有的数据
redis> WATCH name OK redis> MULTI OK redis> SET name peter QUEUED redis> EXEC (nil)在同时间 ,客户端 B 也修改了 name 键的值, 当客户端 A 执行 EXEC 时,Redis 会发现 name 这个被监视的键已经被修改, 因此客户端 A 的事务不会被执行,而是直接返回失败。
30、缓存击穿缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
1、设置热点数据永远不过期。
2、提前更新缓存
比如定义定时任务,缓存过期时间30min,定时任务更新缓存20min。
缺点:增加系统复杂度。比较适合 key 相对固定,cache 粒度较大的业务。
3、加互斥锁
版权声明:本文标题:Redis面试题(一) 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686561812a82121.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论