Redis面试题

什么是 Redis?

Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 非关系型数据库

Redis 与其他 key - value 缓存产品有以下三个特点:

Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用

Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储

Redis 支持数据的备份,即 master-slave 模式的数据备份

Redis 优势:

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s

丰富的数据类型 – Redis支持二进制案例的Strings、Lists、Hashes、Sets及Ordered Sets数据类型操作

原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,

即原子性,通过 MULTI 和 EXEC指令包起来

丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

Redis持久化策略
实现:

单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结

束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放

Redis持久化策略有两种:RDB和AOF

RDB是Redis默认的持久化策略。RDB持久化的机制是在指定时间间隔内生成数据快照,就是将内存中的数据以快照的方式写入

到二进制文件中(默认保存到dump.rdb文件中),然后每次Redis重新启动都会自动加载快照文件中的数据到内存中

还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,每次命令执行都会将所有

redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件

save是同步命令,bgsave是异步命令,bgsave会从redis主进程fork(fork()是linux函数)出一个子进程专门用来生成rdb快照文件

save与bgsave对比:配置自动生成rdb文件后台使用的是bgsave方式

命令savebgsave
IO类型同步异步
是否阻塞redis其他命令否(在生成子进程执行调用fork函数时会有短暂阻塞)
复杂度O(n)O(n)
优点不会消耗额外内存不阻塞客户端命令
缺点阻塞客户端命令需要fork子进程,消耗内存
RDB优点:

1、只有一个文件 dump.rdb,方便持久化

2、容灾性好,一个文件可以保存到安全的磁盘

3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO最大化。使用单独子进程来进行持久化,主进程不会

​ 进行任何 IO 操作,保证了 redis的高性能) 4.相对于数据集大时,比 AOF 的启动效率更高

RDB缺点:

数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求

不严谨的时候)

AOF原理是监听执行的命令,如果发现执行了修改数据的操作,就会把该命令记录到日志当中。所以使用AOF的话,是可以通过记录

在日志当中的命令来恢复数据

AOF默认是关闭的,若要开启需要到配置文件redis.conf中修改。AOF优先级高于RDB,Redis支持AOF和RDB同时生效,所以同时使

用的话,Redis重新启动时会使用AOF进行数据恢复

AOF优点:

1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次

2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题

3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如

​ 误操作的 flushall))

AOF缺点:

1、AOF 文件比 RDB 文件大,且恢复速度慢

2、数据集大的时候,比 rdb 启动效率低

两者对比:

RDB文件是一个紧凑文件,对于相同数据的话,AOF日志文件通常是要比RDB数据快照文件更大。并且在恢复大量数据情况下,RDB

效率要高于AOF。但是AOF相对RDB而言,数据更加安全一些。因为如果Ridis在RDB持久化点之间发生意外关闭的话,还未持久化的

数据会丢失

Redis主从架构
Redis主从工作原理:

如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个SYNC命令(redis2.8版本之前的命令)

给master请求复制数据

master收到SYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的

请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,

slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。

当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只

会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave

当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,master和slave断开重连后支持部分复制。

数据部分复制

从2.8版本开始,slave与master能够在网络连接断开重连后只进行部分数据复制

master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标

offset和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果

master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制

从2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据

主从复制(全量复制)流程图:

img

主从复制(部分复制)流程图:

img

Redis哨兵高可用架构

img

sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点

哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主

节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实

现了订阅功能,订阅sentinel发布的节点变动消息)

哨兵leader选举流程:

当一个master服务器被某sentinel视为客观下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。

每个发现master服务器进入客观下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个

sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如果所有超过一半的sentinel选举某

sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举

很类似。

哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader

了,可以正常选举新master

Redis脑裂:

当出现网络问题,哨兵sentinel无法感应到到master时,认为master出现异常,会从slave中选择一个提升为master,这时如果用户

还是在之前的master进行操作时,那么当网络问题被解决时,原来的master就会被降为slave就会造成用户的数据丢失。

脑裂数据丢失解决:

通过设置master的最小slave数和连接master最长时间限制对master进行操作

Redis客户端命令

Redis客户端命令对应的RedisTemplate中的方法列表:

String类型结构:
RedisRedisTemplate rt
set key valuert.opsForValue().set(“key”,“value”)
get keyrt.opsForValue().get(“key”)
del keyrt.delete(“key”)
strlen keyrt.opsForValue().size(“key”)
getset key valuert.opsForValue().getAndSet(“key”,“value”)
append key valuert.opsForValue().append(“key”,“value”)
Hash结构:
RedisRedisTemplate rt
hmset key field1 value1 field2 value2…rt.opsForHash().putAll(“key”,map) //map是一个集合对象
hset key field valuert.opsForHash().put(“key”,“field”,“value”)
hexists key fieldrt.opsForHash().hasKey(“key”,“field”)
hgetall keyrt.opsForHash().entries(“key”) //返回Map对象
hvals keyrt.opsForHash().values(“key”) //返回List对象
hkeys keyrt.opsForHash().keys(“key”) //返回List对象
hmget key field1 field2…rt.opsForHash().multiGet(“key”,keyList)
hsetnx key field valuert.opsForHash().putIfAbsent(“key”,“field”,“value”
hdel key field1 field2rt.opsForHash().delete(“key”,“field1”,“field2”)
hget key fieldrt.opsForHash().get(“key”,“field”)
List结构:
RedisRedisTemplate rt
lpush list node1 node2 node3…rt.opsForList().leftPush(“list”,“node”)
lpush list node1 node2 node3…rt.opsForList().leftPushAll(“list”,list) //list是集合对象
rpush list node1 node2 node3…rt.opsForList().rightPush(“list”,“node”)
rpush list node1 node2 node3…rt.opsForList().rightPushAll(“list”,list) //list是集合对象
lindex key indexrt.opsForList().index(“list”, index)
llen keyrt.opsForList().size(“key”)
lpop keyrt.opsForList().leftPop(“key”)
rpop keyrt.opsForList().rightPop(“key”)
lpushx list nodert.opsForList().leftPushIfPresent(“list”,“node”)
rpushx list nodert.opsForList().rightPushIfPresent(“list”,“node”)
lrange list start endrt.opsForList().range(“list”,start,end)
lrem list count valuert.opsForList().remove(“list”,count,“value”)
lset key index valuert.opsForList().set(“list”,index,“value”)
Set结构:
RedisRedisTemplate rt
sadd key member1 member2…rt.boundSetOps(“key”).add(“member1”,“member2”,…)
sadd key member1 member2…rt.opsForSet().add(“key”, set) //set是一个集合对象
scard keyrt.opsForSet().size(“key”)
sidff key1 key2rt.opsForSet().difference(“key1”,“key2”) //返回一个集合对象
sinter key1 key2rt.opsForSet().intersect(“key1”,“key2”)//同上
sunion key1 key2rt.opsForSet().union(“key1”,“key2”)//同上
sdiffstore des key1 key2rt.opsForSet().differenceAndStore(“key1”,“key2”,“des”)
sinter des key1 key2rt.opsForSet().intersectAndStore(“key1”,“key2”,“des”)
sunionstore des key1 key2rt.opsForSet().unionAndStore(“key1”,“key2”,“des”)
sismember key memberrt.opsForSet().isMember(“key”,“member”)
smembers keyrt.opsForSet().members(“key”)
spop keyrt.opsForSet().pop(“key”)
srandmember key countrt.opsForSet().randomMember(“key”,count)
srem key member1 member2…rt.opsForSet().remove(“key”,“member1”,“member2”,…)
Redis缓存高可用集群

img

redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完

成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性

扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单

集群原理:

Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。

当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个

key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息

的校验调整

槽位定位算法:

Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。

HASH_SLOT = CRC16(key) mod 16384

跳转重定位:

当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳

转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同

步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。

Redis集群选举原理分析:

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,

从而存在多个slave竞争成为master节点的过程, 其过程如下:

1、slave发现自己的master变为FAIL

2、将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息

3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack

4、尝试failover的slave收集master返回的FAILOVER_AUTH_ACK

5、slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂

​ 了,只剩一个主节点是不能选举成功的)

6、slave广播Pong消息通知其他集群节点

从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,

slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票

•延迟计算公式:

DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。

Redis缓存设计和性能优化
多级缓存架构原理图:

img

缓存穿透:

缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入

缓存层。

缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。

造成缓存穿透的基本原因有两个:

第一, 自身业务代码或者数据出现问题。

第二, 一些恶意攻击、 爬虫等造成大量空命中。

缓存穿透问题解决方案:

1、缓存空对象

2、布隆过滤器

缓存失效:

由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉,对于这种情

况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同时间。

缓存雪崩:

缓存雪崩指的是缓存层支撑不住或宕掉后, 流量会像奔逃的野牛一样, 打向后端存储层。

由于缓存层承载着大量请求, 有效地保护了存储层, 但是如果缓存层由于某些原因不能提供服务(比如超大并发过来,缓存层支撑不

住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降), 于是大量请求都会达到存储层, 存储层

的调用量会暴增, 造成存储层也会级联宕机的情况。

预防和解决缓存雪崩问题, 可以从以下三个方面进行着手:

1) 保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster

2) 依赖隔离组件为后端限流并降级。比如使用Hystrix限流降级组件

3) 提前演练。 在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基础上做一些预案设定。

热点缓存key重建优化:

开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满足绝大部分需求。 但是有

两个问题如果同时出现, 可能就会对应用造成致命的危害

当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。

重建缓存不能在短时间完成, 可能是一个复杂计算, 例如复杂的SQL、 多次IO、 多个依赖等。

在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃。

要解决这个问题主要就是要避免大量线程同时重建缓存。

我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可。

Redis 单线程如何处理那么多的并发客户端连接?

Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将

事件分发给事件处理器

img

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

512M

Redis常见问题及其解决方案

一、缓存雪崩

由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本

应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反

应,造成整个系统崩溃

解决办法:

大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避

免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时将缓存失效时间分散开

二、缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数

据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问

题。

解决办法;

最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截

掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存

在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存

放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴

三、缓存预热

缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据

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

存数据

解决思路:

1、直接写个缓存刷新页面,上线时手工操作下

2、数据量不大,可以在项目启动的时候自动进行加载

3、定时刷新缓存

四、缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘

汰,常见的策略有两种:

1、定时去清理过期的缓存

2、当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相

对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡

五、缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即

使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)

以参考日志级别设置预案:

1、一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级

2、警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警

3、错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况

​ 自动降级或者人工降级

4、严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库

​ 跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,

​ Redis出现问题,不去数据库查询,而是直接返回默认值给用户

六、缓存击穿
被访问的数据在缓存中没有,但在数据库中有,而此时却出现大量并发访问,这些访问都会去数据库读取数据,导致数据库压力瞬间

增大。一般出现在key刚好过期时刻

解决方案:

1、设置热点数据永不过期

2、加锁。防止出现数据库的并发访问

3、随机设置key的过期时间,防止同一时间数据大量过期现象发生

七、缓存淘汰:

在配置文件中通过设置Maxmemory(缓存最大阈值 )在redis中每次新增数据就会进行一次数据量阈值的判断,当超过阈值时就进

行缓存删除/淘汰策略如下:

Noeviction:不删除key,超过时报错(默认)

volatile-lru:删除过期key中最近没使用的

Volatile-ttl:删除过期key中最早过期的

Volatile-lfu:删除过期key中使用数量最少的

allkeys-lru在所有的key中使用lru算法(头插)

allkeys-lfu在所有的key中使用lfu算法(计数)

当如果出现要设置缓存淘汰时,说明redis集群的内存不足,那么需要给redis扩容

八、删除策略:

定时执行(设置闹钟。但是如果过期的键比较多,就比较会消耗cpu资源)

惰性执行(每次取值时检查过期时间,过期则删除)

定期删除(每个一段时间去数据库查询,间隔时长要根据实际业务而定)

Redis不可用时机:在没有主从复制时,当其中某一主节点失效时,导致其控制的区域内的槽不可用,那么此时redis集群失效

热点数据和冷数据

热点数据,缓存才有价值

对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考

虑使用缓存

对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,

我们将导航信息,缓存以后可能读取数百万次。

数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。

那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,

这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数

据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力

单线程的redis为什么这么快

1、纯内存操作

2、单线程操作,避免了频繁的上下文切换

3、采用了非阻塞I/O多路复用机制

redis的数据类型,以及每种数据类型的使用场景

1、String

这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存

2、hash

这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户

信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果

3、list

使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极

佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出

的原则

4、set

因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是

集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。另外,就是利用交集、

并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能

5、sorted set

sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作

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,该配置就是配内存淘汰策略的

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据,新写入操作会报错

ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和volatile-ttl 策略的行为, 和

noeviction(不删除) 基本上一致

Redis 常见性能问题和解决方案?

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件

(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3…

Redis事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis会将一个事务中的所有命令序列化,然后按顺序执行

1、redis不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速

2、如果在一个事务中的命令出现错误,那么所有的命令都不会执行

3、如果在一个事务中出现运行错误,那么正确的命令会被执行

1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会

​ 立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行

2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值

3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出

4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删

​ 除),之后的事务就不会执行,监控一直持续到EXEC命令

Redis 如何做内存优化

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽

象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该

把这个用户的所有信息存储到一张散列表里面

Redis 的内存用完了会发生什么

如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis 当缓存来使用配置淘汰

机制,当 Redis 达到内存上限时会冲刷掉旧的内容。

一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素

理论上 Redis 可以处理多达 232 的 keys,并且在实际中进行了测试,每个实例至少存放了 2 亿 5 千万的 keys。我们正在测试一些较

大的值。任何 list、set、和 sorted set 都可以放 232 个元素。换句话说,Redis 的存储极限是系统中的可用内存值。

Redis适用场景

1、会话缓存(Session Cache)

最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:

Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他

们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平

台Magento 也提供 Redis 的插件

2、全页缓存(FPC)

除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用

户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。 再次以 agento 为例,Magento提供一个插件来使用

Redis 作为全页缓存后端。 此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快

速度加载你曾浏览过的页面

3、队列

Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis能作为一个很好的消息队列平台来使用。Redis 作为队列

使用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop 操作。 如果你快速的在 Google中搜索“Redis queues”,你

马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一

个后台就是使用 Redis作为 broker,你可以从这里去查看

4,排行榜/计数器

Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时

候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10个用户–我们称之

为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用

户的分数,你需要这样执行: ZRANGE user_scores 0 10 WITHSCORES Agora Games 就是一个很好的例子,用 Ruby 实现的,它

的排行榜就是使用 Redis 来存储数据的,你可以在这里看到

5、发布/订阅

最后是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅

的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统!

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

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

对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行

完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概

率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长

使用过 Redis 做异步队列么,你是怎么用的?

一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试

如果对方追问可不可以不用 sleep 呢?

list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。如果对方追问能不能生产一次消费多次呢?使用 pub/sub

主题订阅者模式,可以实现1:N 的消息队列

如果对方追问 pub/sub 有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ等

如果对方追问 redis 如何实现延时队列?

我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使

用sortedset,拿时间戳作为score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据

轮询进行处理。到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。

使用过 Redis 分布式锁么,它是什么回事

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放

这时候对方会告诉你说你回答得不错,然后接着问如果在 setnx 之后执行 expire之前进程意外 crash 或者要重启维护了,那会怎么

样?
这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来

的结果是你主动思考出来的,然后回答:我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和expire 合成一条指令

来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值