• Redis常见面试题


    来自:小林coding

    Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快,常用于缓存,消息队列、分布式锁等场景

    # 为什么用 Redis 作为 MySQL 的缓存?

     1、Redis 具备高性能

    假如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据缓存在 Redis 中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了,操作 Redis 缓存就是直接操作内存,所以速度相当快。

    2、 Redis 具备高并发

    单台设备的 Redis 的 QPS(Query Per Second,每秒钟处理完请求的次数) 是 MySQL 的 10 倍,Redis 单机的 QPS 能轻松破 10w,而 MySQL 单机的 QPS 很难破 1w。

    所以,直接访问 Redis 能够承受的请求是远远大于直接访问 MySQL 的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

    # Redis的具有哪些优势?

    (1)读写性能高--100000次/s以上的读速度,80000次/s以上的写速度;

    (2)K-V,value支持的数据类型很多:字符串(String),队列(List),哈希(Hash),集合(Sets),有序集合(Sorted Sets)5种不同的数据类型。

    (3)原子性,Redis的所有操作都是单线程原子性的。

    (4)特性丰富--支持订阅-发布模式,通知、设置key过期等特性。

    (5)在Redis3.0 版本引入了Redis集群,可用于分布式部署。

    # Redis 数据类型以及使用场景分别是什么?

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无序集合)及zset(有序集合)。 

    • String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
    • List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
    • Hash 类型:缓存对象、购物车等。
    • Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
    • Zset 类型:排序场景,比如排行榜、电话和姓名排序等。

    (1)String的数据类型是 使用一种叫简单动态字符串(SDS)和直接存储实现的的数据类型。

    (2)list 使用双向链表来实现。当list包含了数量较多的元素,或者列表中包含的元素都是比较长的字符串时,Redis会使用链表作为实现List的底层实现。可以用于消息队列类型场景,入队出队方便。

    字典,又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对的抽象数据结构。 在字典中,一个键(key)可以和一个值(value)进行关联,字典中的每个键都是独一无二的。在C语言中,并没有这种数据结构,但是Redis 中构建了自己的字典实现。

    (3)Redis的 hash 特别适用于存储对象。hashtable编码的哈希对象底层实现是字典,哈希对象中的每个key-value对都使用一个字典键值对来保存。字典键值对即是,字典的键和值都是字符串对象,字典的键保存key-value的key,字典的值保存key-value的value。

    我们存入里面的key 并不是直接的字符串,而是一个hash 值,通过hash 算法,将字符串转换成对应的hash 值,然后在dictEntry 中找到对应的位置。

    这时候我们会发现一个问题,如果出现hash 值相同的情况怎么办?Redis 采用了链地址法来解决hash冲突。这与hashmap的实现类似。

     (4)有序集合键(sorted Sets)用到了跳跃表,

    跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速查找访问节点的目的。跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素,效率和平衡树媲美 ——查找、删除、添加等操作都可以在O(logn)期望时间下完成。Redis 的跳跃表 主要由两部分组成:zskiplist(链表)和zskiplistNode (节点)

    (5)整数集合是集合建的底层实现之一。整数集合是集合建(sets)的底层实现之一,当一个集合中只包含整数,且这个集合中的元素数量不多时,redis就会使用整数集合intset作为集合的底层实现。”我们可以这样理解整数集合,他其实就是一个特殊的集合,里面存储的数据只能够是整数,并且数据量不能过大。

    # Redis 如何实现数据不丢失?

    Redis 的读写操作都是在内存中,所以 Redis 性能才会高,但是当 Redis 重启后,内存中的数据就会丢失,那为了保证内存中的数据不会丢失,Redis 实现了数据持久化的机制,这个机制会把数据存储到磁盘,这样在 Redis 重启就能够从磁盘中恢复原有的数据。

    Redis 共有三种数据持久化的方式:

    • AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
    • RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
    • 混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;

    Redis 内存满了,会发生什么?

    在 Redis 的运行内存达到了某个阀值,就会触发内存淘汰机制,这个阀值就是我们设置的最大运行内存,此值在 Redis 的配置文件中可以找到,配置项为 maxmemory。

    Redis 内存淘汰策略共有八种,这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。

    1、不进行数据淘汰的策略

    noeviction(Redis3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误。

    2、进行数据淘汰的策略

    针对「进行数据淘汰」这一类策略,又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。 

    如何避免缓存雪崩?

    通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

    那么,当大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

    发生缓存雪崩有两个原因:

    • 大量数据同时过期;
    • Redis 故障宕机;

    对于缓存雪崩问题,我们可以采用两种方案解决。

    • 将缓存失效时间随机打散 我们可以在原有的失效时间基础上增加一个随机值(比如 1 到 10 分钟)这样每个缓存的过期时间都不重复了,也就降低了缓存集体失效的概率。
    • 后台更新缓存,设置缓存不过期 我们可以通过后台服务来更新缓存数据,从而避免因为缓存失效造成的缓存雪崩,也可以在一定程度上避免缓存并发问题。

    如何避免缓存击穿?

    我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

     可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。 应对缓存击穿可以采取前面说到两种方案:

    • 互斥锁方案,保证同一时间只有一个业务线程请求缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
    • 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

    如何避免缓存穿透?

     当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。

    当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题.

     缓存穿透的发生一般有这两种情况:

    • 业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
    • 黑客恶意攻击,故意大量访问某些读取不存在数据的业务;

    应对缓存穿透的方案,常见的方案有三种。

    • 非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
    • 设置空值或者默认值:当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。

    # 如何设计一个缓存策略,可以动态缓存热点数据呢?

    由于数据存储受限,系统并不是将所有数据都需要存放到缓存中的,而只是将其中一部分热点数据缓存起来,所以我们要设计一个热点数据动态缓存的策略。热点数据动态缓存的策略总体思路:通过数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据

    # 常见的缓存更新策略?

    常见的缓存更新策略共有3种:

    • Cache Aside(旁路缓存)策略;
    • Read/Write Through(读穿 / 写穿)策略;
    • Write Back(写回)策略;

    Cache Aside(旁路缓存)策略是最常用的,应用程序直接与「数据库、缓存」交互,并负责对缓存的维护,该策略又可以细分为「读策略」和「写策略」。

    写策略的步骤:

    • 先更新数据库中的数据,再删除缓存中的数据。

    读策略的步骤:

    • 如果读取的数据命中了缓存,则直接返回数据;
    • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。

    注意,写策略的步骤的顺序顺序不能倒过来,即不能先删除缓存再更新数据库,原因是在「读+写」并发的时候,会出现缓存和数据库的数据不一致性的问题。

    Cache Aside 策略适合读多写少的场景,不适合写多的场景,因为当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。

  • 相关阅读:
    Grafana + Prometheus监控篇之Windows监控Linux服务器资源
    创新融合,5G+工业操作系统引领未来工厂
    python入门篇08- 函数进阶-参数传递
    直流无刷BLDC六步换相“电流环”、“速度环”的关系
    编译原理—x86汇编指令
    Django笔记十之values_list指定字段取值及distinct去重处理
    文件存储和对象存储的区别是什么?
    SpringBoot——常用注解
    redis常用命令
    JSD-2204-酷莎商城(后端)-Day17,18
  • 原文地址:https://blog.csdn.net/weixin_45780538/article/details/126102211