为什么使用缓存?

因为传统的关系型数据库已经不能适应所有的场景了,如秒杀,当访问流量达到高峰时,不使用缓存中间件,那么流量会打入数据库,很容易打崩数据库,所以这种的场景下需要引入缓存中间件;因为缓存中间件如Redis为内存型缓存中间件,单线程,处理速度比MySQL快几十倍甚至上百倍,所以常用的数据或者临时数据都可以使用缓存来进行提升性能。常见的如Redis、Memcached,考虑到他们的优缺点,最终选择Redis作为缓存中间件。

Redis 与 Memcached 的比较?

Memcached特点:

多线程异步IO、仅支持K/V类型的数据、没有持久化

Redis特点:

单线程异步IO、不仅支持K/V类型数据结构,还有其他的数据结构,如Hash、List等等、支持持久化

Redis基础类型

string

最常用的数据类型,内部由SDS(Simple Dynamic String 简单动态字符串)进行实现(好处是可以通过预分配冗余空间的方式减少内存的频繁分配),常用于缓存简单字符串,计数器,共享用户 session

hash

将结构化的数据缓存在 redis 中,每次读写缓存的只会就操作 Hash 里的某个字段

list

list 为有序列表,常用于列表型数据结构,例如粉丝列表,文章评论列表,还可以做简单的消息队列

set

无序集合,存入的数据自动去重,对于两集合交集、并集、差集处理速度更快,常用于例如朋友圈,查看朋友圈时,能看到朋友的朋友有些是自己共同的,只有自己共同的好友,朋友圈的评论点赞才会被看到

sorted set

有序集合,存入数据自动去重,根据 score 进行排序,常用于排行榜相关场景

做一个简单的消息队列,以及做一个延时消息队列?

简单的消息队列可以通过list数据结构做,通过 rpush 生产消息,lpop 消费消息,当没有消息时进行 sleep,也可以通过 blpop 进行阻塞式获取消息,当没有消息时进行阻塞,直到获取到消息。

延时消息队列可以通过有序集合(sorted set)数据结构做,将时间戳作为 score 值,通过轮训获得 N 秒前的消息。

还可以通过 pub/sub 进行做 1:N的消息队列,缺点就是消费者下线时,这段时间的消息是获取不到的。

Redis 的高级用法

bitmap

位图支持 bit 位来存储信息,可以通过 bitmap 实现布隆过滤器(BloomFilter)

HyperLogLog

提供大规模数据去重统计,如UV

pub/sub

发布与订阅功能,制作简单的消息队列

geo

保存地理位置,并做位置距离计算以及两点之间的距离

lua、pipeline

没用过,就不做介绍

事务

redis 也支持事务,redis 只能保证串行执行命令,并且能保证全部执行,若命令执行失败了,并不会回滚,还是会继续执行下去,官方好像并不推荐使用事务

Redis 持久化

redis 持久化可以分为 AOF,RDB两种持久化方式
AOF:AOF机制对每条写入命令作为日志,以追加(append-only)模式写入日志文件中,所以没有磁盘寻址开销就很快,redis 重启后默认会使用 AOF 进行数据恢复,因为比 RDB 数据更加完整
RDB:对redis的数据进行周期性持久化,多久持久化一次取决于配置
两种方式都可以把数据持久化到磁盘上,RDB更适合做冷备份,AOF 更适合做热备份。

优缺点:
AOF:
AOF为每秒都通过后台的线程 fsync 操作,那最多就丢失一秒的数据,因为AOF对日志文件是追加方式的记录,所以免去的磁盘寻址的开销,增加写入效率,缺点是相同的数据,AOF文件比RDB还要大
RDB:
RDB为某一时刻Redis数据的快照,RDB对Redis的性能影响非常小,是因为在同步数据的时候他只fork了一个子进程进行持久化,而且在数据恢复方面比AOF快很多,缺点是数据的完整性肯定不如AOF的,还有就是假如在同步数据时文件很大,客户端会有短暂的卡顿,假如系统正在进行大规模操作数据,那么RDB fork 的子进程会生成非常大的快照。

两者之间的选择:
应该一起用,在RDB快照这段时间,采用AOF进行数据补全,这样就性能达到较高,系统也更加健壮

Redis 保证集群的高可用

通过哨兵集群(sentinal),哨兵必须通过三个实例来保证自己的健壮性,如果是两个的话,那就达不到高可用

假如是两个哨兵,当master挂了,那就剩下一个子节点了,没有哨兵去允许故障转移了,所以最好的状态是有三个哨兵,当master挂了,那么就选举出一个节点来执行故障转移。

Redis 的主从同步

单机的QPS是有限的,不可能让主节点又读又写,但是只让master去写,子节点去读,分担了读请求那就会好很多。

之间的数据是如何同步的:
当启动子节点时,他会发送一个命令给master,假如是第一次连接master,他会触发一个全量复制,master会启动一个线程,生成RDB快照,还会把新的请求写入内存中,RDB完成后,master会将这个文件发送给子节点,子节点拿到后将RDB文件内容写入磁盘,然后加载到内存,完成后master再将内存中的新请求都发送给子节点。

Redis 的内存淘汰机制

redis 过期策略是定期删除+惰性删除

定期删除就是每100ms就随机抽查设置了过期时间的key,过期就删,惰性删除就是每次取key的时候,判断过期时间,过期了就删。

为什么不是扫描所有key呢,和MySQL扫描全表问题一样,压力过大。

假如没过期也没查询到,redis 还提供了其他内存淘汰机制,两个方面:从所有key考虑,从设置过期时间考虑
volatile-lru(已设置过期时间lru)、volatile-ttl(已设置过期时间,即将过期的数据)、volatile-random(已过期时间随机删)
allkeys-lru(所有keylru)、allkeys-random(所有数据随机删)

缓存场景问题

更新方式问题

假如数据源是DB,则在更新完DB则直接更新缓存,假如数据源是远程服务,对key进行设置失效时间+允许数据不一致时间,在获取新数据时,再对key 进行更新,当远程服务失效了,那么就还继续使用旧数据,直到远程服务恢复

缓存雪崩、击穿、穿透

缓存的雪崩:大面积key失效导致,例如大量key在某一时段过期,那么这段时间缓存是不可用的,这时所有请求都会到DB上,可以通过主从模式或者集群模式来保证缓存的高可用

缓存的穿透:用户查询缓存中不存在的内容,例如id,程序没有过滤 -1 的情况,缓存中不存在,那么请求就会到DB上,假如请求量过大,DB也会挂掉,可以通过程序的健壮性来去除,还可以通过增加布隆过滤器来避免这样的问题

缓存的击穿:和雪崩类似,某一热点key在某一时段过期,那么这段时间热点key是不可用的,请求会到DB上,可以通过避免热点key在某一时段一起失效,在设置过期时间时增加一点随机值。

以上的Redis相关问题是我在面试的这段时间中总结,当做个记录