• 八股文--->Redis


    目录

    一:关系型数据库和非关系型数据库区别

    二:什么是Redis

    三:Redis的优缺点

    四:Redis支持事务持久化--- RDB 和 AOF

    五:为什么要用Redis/为什么要用缓存

    六:为什么Redis这么快

    七:Redis有哪些数据类型

    String

    List

    Set

    Sorted Set

    Hash

    八:Redis的过期键和删除策略

    九:MySQL里有2000w数据,redis中只存20w的数据,如何 保证redis中的数据都是热点数据

    十:Redis的内存淘汰策略有哪些

    全局的键空间选择性移 

    设置过期时间的键空间选择性移除

    十一:Redis到底是不是单线程的

    十二:Redis I/O多路复用

    (1)select, poll, epoll 都是I/O多路复用的具体的实现

    (2)IO多路复用(Reactor)

    十三:Redis事务相关的命令

    十四:事务与Redis

    十五:Redis主从复制

    十六:Redis哨兵模式

    十七:redis缓存穿透

    十八:redis缓存雪崩

    十九:缓存击穿


    一:关系型数据库和非关系型数据库区别

    1.关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织

    优点:

    (1)易于维护:都是使用表结构,结构一致

    (2)使用方便:SQL语言使得我们操作数据库更加方便

    (3)支持SQL:可以通过SQL语句进行复杂查询

    缺点

    (1)读写性能比较差,尤其是海量数据的高效率读写

    (2)固定的表的结构,灵活性差

    (3)硬盘I/O要求高:网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I/O是一个很大的瓶颈

    2.非关系型数据库不是数据库,而是数据结构化存储方法的集合,可以是文档或键值对等

    优点

    (1)格式灵活:存储数据格式可以是key,value格式,文档格式,图片格式等等,使用灵活,应用场景广泛,而关系型数据库只支持基础的数据类型 

    (2)速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;

    (3)高扩展性;Nosql基于键值对,数据之间没有耦合性,所以非常容易水平扩展

    缺点

    (1)不支持事务的ACID特性

    (2)不提供SQL支持,学习和使用成本较高 

    (3)只适合存储一些较为简单的数据,需要进行较复杂查询的数据,关系型数据库显的更为合适

    二:什么是Redis

    Redis是用C语言编写的,开源的高性能非关系型数据库(Nosql),是一个高性能的key--value数据库;

    Redis可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值得类型可以为字符串,List列表,Set集合,Sorted set有序集合,Hash散列表。

    与传统数据库不同的是Redis的数据库是存储与内存中的,所以读写速度非常快,所以Redis被广泛应用与缓存方向,每秒可以处理超过10w次读写操作,是已执性能最快的Key--value DB;

    此外,Redis也经常用来做分布式锁。而且Redis还支持事务,持久化,LUA脚本,LRU驱动事件,多种集群方案。 

    三:Redis的优缺点

    优点:

    (1)读写性能优异

    (2)支持数据持久化---AOF持久化和RDB持久化

    (3)支持事务,Redis的所有操作都是原子性的

    (4)数据结构丰富,除了支持String类型之外,还支持hash,set,zset,list等数据结构

    (5)支持主从复制,主机会自动将数据同步到从机,可进行读写分离

     缺点

    (1)数据库容量容易受到物理内存的限制,不能用作高性能海量数据读写,因此Redis主要适用于较小数据量的高性能操作和运算上

    (2)Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写需求失败,需要等待机器重启或者手动切换前端的IP才能恢复

    (3)主机宕机,宕机前有部分数据未能同步到从机,切换IP后会引入数据不一致的问题

    (4)Redis较难支持在线扩容,在集群容量达到上限时,扩容会变得很复杂;所有运维人员在系统上线时必须确保足够的空间,这对资源造成很大的浪费

    四:Redis支持事务持久化--- RDB 和 AOF

    RDB持久化可以在指定时间间隔内生成数据集的时间点快照

    在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。

    以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次 数据集: # save 60 1000 //关闭RDB只需要将所有的save保存策略注释掉即可

    AOF持久化是记录服务器执行的所有读写操作命令,并在服务器启动时,通过执行这些命令来还原数据集。AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾;

    你可以通过修改配置文件来打开 AOF 功能:

    1 # appendonly yes

    你可以配置 Redis 多久才将数据 fsync 到磁盘一次。 有三个选项:

    1 appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。

    2 appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。

    3 appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。

    Redis还可以在后台对AOF文件进行重写,使AOF文件的体积不会超出保存数据集状态所需的实际大小,。

    Redis还可以同时使用RDB和AOF持久化。在这种情况下,当Redis重启时,他会优先使用AOF文件来还原数据集,因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。

    五:为什么要用Redis/为什么要用缓存

    主要从高性能高并发这两个角度来看待问题

    高性能:

    假如用户第一次访问数据库中的某些数据,这个过程时比较慢的,因为使用硬盘上直接读取的。将该用户访问的数据存储在缓存中,这样下次再次访问这些数据的时候,就可以直接去缓存中拿了。操作缓存就是直接操作内存,所以速度特别快。如果数据库中的数据改变后,同步改变缓存中相对应得数据即可。

    高并发:

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

    六:为什么Redis这么快

    (1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存 中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1);

    (2)数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的

    (3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者 多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁 操作,没有因为可能出现死锁而导致的性能消耗;

    (4)使用多路 I/O 复用模型,非阻塞 IO

    七:Redis有哪些数据类型

    Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分 的使用要求

    String

    常用命令 :set/get/decr/incr/mget等;

    应用场景 :String是最常用的一种数据类型,普通的key/value存储都可以归为此类;

    实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr、decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

    List

    常用命令 :lpush/rpush/lpop/rpop/lrange等;

    应用场景 :Redis list的应用场景 非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现

    实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

    Set

    常用命令 :sadd/spop/smembers/sunion等;

    应用场景 :Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;

    实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

    Sorted Set

    常用命令 :zadd/zrange/zrem/zcard等;

    应用场景 :Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

    实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

    Hash

    常用命令 :hget/hset/hgetall等

    应用场景 :我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;

    实现方式:Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。如图所示,Key是用户ID, value是一个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field),也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时redisObject的encoding字段为int。

    八:Redis的过期键和删除策略

    我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过 期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

    过期策略通常有以下三种

     (1)定时过期:每个过期时间的Key都需要一个定时器,到过期期间就会立即清楚。该策略可以立即清楚过期的key,对内存很友好;但是会占用大量CPU去处理过期的数据,从而影响缓存的响应时间和吞吐量。

    (2)惰性过期:只有当访问一个key时,才会去判断这个Key是否已经过期,过期则清除;该策略可以最大化的节省CPU资源,但对内存十分不友好,极端情况可能出现大量的 过期key没有再次被访问,从而不会被清除,占用大量内存

    (3)定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数 量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定 时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达 到最优的平衡效果。

    九:MySQL里有2000w数据,redis中只存20w的数据,如何 保证redis中的数据都是热点数据

    Redis内存数据集大小上升到一定大小时,就会实施数据淘汰策略

    十:Redis的内存淘汰策略有哪些

    全局的键空间选择性移 

    no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错。

    allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用 的key。            (这个是最常用的)

    allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

    设置过期时间的键空间选择性移除

    volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中, 移除最近最少使用的key。

    volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空 间中,随机移除某个key。

    volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除将要过期的key,ttl的值越大优先被移除。 

    Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于 处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。

    十一:Redis到底是不是单线程的

    Redis是单线程的;通常说的单线程,主要是指Redis对外提供的键值存储服务的主要流程是单线程,也就是网络I/O和数据读写是由单个线程来完成的。

    除此之外Redis的其他功能,例如持久化,异步删除,集群数据同步等,是由额外线程来执行的。

    因此,严格来说Redis并不是全面单线程。

    十二:Redis I/O多路复用

    首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。

    I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流。

    (1)select, poll, epoll 都是I/O多路复用的具体的实现

    之所以有这三个鬼存在,其实是他们出现是有先后顺序的。

    I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。

    一、select 被实现以后,很快就暴露出了很多问题。

    • select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
    • select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
    • select 只能监视1024个链接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见FD_SETSIZE。
    • select 不是线程安全的

    二、于是14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题,比如

    • poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。
    • poll 从设计上来说,不再修改传入数组

    但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。

    于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.

    三、epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:

    • epoll 现在是线程安全的。
    • epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。 

    可是epoll 有个致命的缺点,只有linux支持。

    虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

    IO多路复用模型使用了Reactor设计模式实现了这一机制。

    (2)IO多路复用(Reactor)

    通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。

    注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。

    十三:Redis事务相关的命令

    Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的 Redis会将一个事务中的所有命令序列化,然后按顺序执行。

    (1)redis不支持回滚:““Redis 在事务失败时不进行回滚,而是继续执行 余下的命令”

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

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

    • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS) 行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务 就不会执行,监控一直持续到EXEC命令。
    • MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以 继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列 中,当EXEC命令被调用时,所有队列中的命令才会被执行。
    • EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执 行的先后顺序排列。 当操作被打断时,返回空值 nil 。 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端 会从事务状态中退出。
    • UNWATCH命令可以取消watch对所有key的监控。

    十四:事务与Redis

    Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服 务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务 也具有耐久性。

    Redis事务支持隔离性吗

    Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可 以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有 隔离性的。

    Redis事务保证原子性吗,支持回滚吗

    Redis中单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务 中任意命令执行失败,其余的命令仍会被执行。

    Redis事务其他实现

    基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行, 其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是 会继续运行完

    基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时 先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐

    十五:Redis主从复制

    如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC 命令给master请求复制数据。

    master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期 间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。

    当持久化进行完 毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后 再加载到内存中。

    然后,master再将之前缓存在内存中的命令发送给slave。

    当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多 个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送 给多个并发连接的slave。

    数据部分复制

    当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支 持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。

    master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。

    十六:Redis哨兵模式

    有以下功能:

    (1)集群监控:负责监控 redis master 和 slave 进程是否正常工作。

    (2)消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给 管理员。 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。

    (3)配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

    (4)哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去 运行,互相协同工作。 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可 用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。

    哨兵的核心知识

    (1)哨兵至少需要 3 个实例,来保证自己的健壮性。

    (2)哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群 的高可用性。

    (3)对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进 行充足的测试和演练。 

    十七:redis缓存穿透

    理 解

    • 重在穿透吧,也就是访问透过redis直接经过mysql,通常是一个不存在的key,在数据库查询为null。每次请求落在数据库、并且高并发。数据库扛不住会挂掉。

    解决方案

    • 可以将查到的null设成该key的缓存对象。

    • 当然,也可以根据明显错误的key在逻辑层就就行验证

    • 同时,你也可以分析用户行为,是否为故意请求或者爬虫、攻击者。针对用户访问做限制。

    • 其他等等,比如采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层 存储系统的查询压力

    十八:redis缓存雪崩

    是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库 上,造成数据库短时间内承受大量请求而崩掉。

    解决方案

    • 通常的解决方案是将key的过期时间后面加上一个随机数,让key均匀的失效。

    • 考虑用队列或者锁让程序执行在压力范围之内,当然这种方案可能会影响并发量。

    • 热点数据可以考虑不失效

    十九:缓存击穿

    缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,好像蛮力击穿一样。

    击穿和穿透不同,穿透的意思是想法绕过redis去使得数据库崩掉。而击穿你可以理解为正面刚击穿,这种通常为大量并发对一个key进行大规模的读写操作。这个key在缓存失效期间大量请求数据库,对数据库造成太大压力使得数据库崩掉。就比如在秒杀场景下10000块钱的mac和100块的mac这个100块的那个订单肯定会被抢到爆,不断的请求(当然具体秒杀有自己处理方式这里只是举个例子)。所以缓存击穿就是针对某个常用key大量请求导致数据库崩溃。

    解决方法

    • 可以使用互斥锁避免大量请求同时落到db。

    • 可以将缓存设置永不过期(适合部分情况)

  • 相关阅读:
    架构中的“大象”
    再学Blazor——组件
    xargs 命令详解
    2022年全球市场硝普钠原料药总体规模、主要生产商、主要地区、产品和应用细分研究报告
    工作流引擎:Workflow Engine 7.0 Crack
    关于我用xhtmlrenderer将html转换img结果样式飞了的这档事
    JUnit 5 单元测试教程
    批量剪辑视频怎么做?附保姆级教程,新手小白也能3分钟50+短视频。
    【 背包九讲——完全背包问题】
    【高效开发工具系列】Windows 系统下将 Windows 键盘的 ctrl 和 alt 互换
  • 原文地址:https://blog.csdn.net/weixin_47188125/article/details/125706438