1.介绍一下Redis
Redis 是一款使用 C 语言编写的高性能 key-value 数据库,开源免费,遵守 BSD 协议。
性能极高,能到 100000 次/s 读写速度
支持数据的持久化,对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上
丰富的数据类型,String(字符串)、List(列表)、Hash(字典)、Set(集合)、Sorted Set(有序集合)
原子性:Redis 的所有操作都是原子性的,多个操作通过 MULTI 和 EXEC 指令支持事务
丰富的特性:key 过期、publish/subscribe、notify
支持数据的备份,快速的主从复制
节点集群,很容易将数据分布到多个Redis实例中
2.Redis支持哪些数据类型?
Redis 支持五种数据类型
string:字符串
hash:哈希
list:列表
set:集合
sorted set:有序集合
3.Redis有哪些优缺点?
优点:
性能极高,能到 100000 次/s 读写速度
支持数据的持久化,对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上
丰富的数据类型,String(字符串)、List(列表)、Hash(字典)、Set(集合)、Sorted Set(有序集合)
原子性:Redis 的所有操作都是原子性的,多个操作通过 MULTI 和 EXEC 指令支持事务
丰富的特性:key 过期、publish/subscribe、notify
支持数据的备份,快速的主从复制
节点集群,很容易将数据分布到多个Redis实例中
缺点:
数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
适合的场景主要局限在较小数据量的高性能操作和运算上
4.Redis使用单线程模型为什么性能依然很好?
避免了线程切换的资源消耗
单线程不存在资源共享与竞争,不用考虑锁的问题
基于内存的,内存的读写速度非常快
使用非阻塞的 IO 多路复用机制
数据存储进行了压缩优化
使用了高性能数据结构,如 Hash、跳表等
5.Redis持久化机制有哪些?各有什么优缺点?
RDB 和 AOF
指用数据集快照的方式半持久化模式,记录 redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,可恢复数据。
优点:
只有一个文件 dump.rdb,恢复操作简单,容灾性好
性能较高,fork 子进程进行写操作,主进程继续处理命令
大数据集比 AOF 的恢复效率高
缺点:
数据安全性低,RDB 是每间隔一段时间进行持久化,若期间 redis 发生故障,可能会发生数据丢失
AOFAppend-only file
指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储,保存为 aof 文件
优点:
数据安全,aof 持久化可以配置 appendfsync 属性为 always,记录每个命令操作到 aof 文件中一次;通过 append 模式写文件,即使中途服务器宕机,也可以通过 redis-check-aof 工具解决数据一致性问题
AOF 机制的 rewrite 模式,AOF 文件没被 rewrite 之前可以进行处理,如删除文件中的 flushall 命令
缺点:
AOF 的持久化文件比 RDB 大,恢复速度慢
6.Redis使用过程中的注意事项?
主库压力很大,可以考虑读写分离
Master 最好不要做持久化工作,如 RDB 内存快照和 AOF 日志文件。(Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程,文件较大时会间断性暂停服务;AOF 文件过大会影响 Master 重启的恢复速度)
如果数据比较重要,使用 AOF 方式备份数据,设置合理的备份频率
保证主从复制的速度和网络连接的稳定性,主从机器最好在同一内网
官方推荐,使用 sentinel 集群配合多个主从节点集群,解决单点故障问题实现高可用
7.Redis过期键的删除策略有哪些?
定时删除:在设置键的过期时间的同时,创建一个定时器,达到过期时间,执行键的删除操作
惰性删除:不主动删除过期键,从键空间中获取键时,都检查取得的键是否过期,过期则删除;没过期则返回
定期删除:每隔一段时间对数据库进行一次检查,删除里面的过期键。删除多少过期键、检查多少个数据库,由算法决定。
8.说说Redis的回收策略
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)中,随机淘汰数据
noenviction:Redis 的默认策略,不回收数据,当达到最大内存时,新增数据返回 error
9.zset的内部原理
zset内部是通过skiplist实现的,在链表的基础上加了跳跃的功能。o(logn)。在插入性能上明显优于平衡树。
不需要逐级比较,而是分了很多层,每一个节点的层数是随机的。先从最高层的开始,遇到比目标值大的,就返回。
10.redis为什么这么快
1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
11.redis的原子性
因为redis是单线程的,很多时候以为所有的操作都是原子性的,也就是一组事务里的操作要么全部执行,要么全部不执行,不存在中间状态。
12.redis的五种数据结构及其使用场景
1. String
常用命令:
get、set、incr、decr、mget等
应用场景:计数,缓存功能:存取一个对象,注:键的取名以“业务名:对象名:id:[属性]”,如mysql:employee:1:name;
2.Hash
应用场景:存储用户信息
3.list,redis的list类型其实就是每个元素都是String类型的双向链表
如好友队列,粉丝队列,消息队列,最新消息排行,微博 TimeLine等
4.set
redis为集合提供了求交集、并集、差集等操作
集合有取交集、并集、差集等操作,因此可以求共同好友、共同兴趣、分类标签等。
利用唯一性,可以统计访问网站的所有独立 IP。
5.sorted Set--有序集合
和Sets相比,Sorted Sets是将 Set 中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列。
按时间排序的时间轴
带有权重的元素,比如一个游戏的用户得分排行榜
比较复杂的数据结构,一般用到的场景不算太多
6.缓存穿透
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
(1)布隆过滤器
使用布隆过滤器进行过滤,把所有可能的查询放在布隆过滤器中。当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。
(2)缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存雪崩
缓存雪崩是指,缓存层出现了错误,不能正常工作了。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
解决方案:
(1)redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,
(2)限流
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
(3)数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求持久层数据库。
解决方法,
1.使用互斥锁
该方法是比较普遍的做法,即,在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试。
7.redis集群
1.主从复制
一主多从, 主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。 所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。这样也可以很轻松实现水平扩容,支撑读高并发。高可用性,读写分离。
主从复制原理:
Redis主从复制分为全量复制和增量复制。
从服务器连接主服务器,发送psync命令;
主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
增量复制一般是 Slave初始化后开始正常工作时,主服务器发生的写操作同步到从服务器的过程。步骤如下:
如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
当主服务器发生故障后,特别麻烦,需要手动将从节点,升级为主服务器,
哨兵模式
每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)
如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态
当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
主从可以自动切换,系统更健壮,可用性更高。
缺点:
Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
3.Redis-Cluster集群
Redis Cluster 集群节点最小配置 6 个节点以上(3 主 3 从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容。
Redis-Cluster采用无中心结构,它的特点如下:
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
节点的fail是通过集群中超过半数的节点检测失效时才生效。
客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
工作方式:
在redis的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。所有的键根据哈希函数映射到 0~16383 个整数槽内,,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
优点:
无中心架构;
数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升;
降低运维成本,提高系统的扩展性和可用性。
8.缓存和数据库双写一致性
如果仅仅查询的话,缓存的数据和数据库的数据是没问题的。但是,当我们要更新时候呢?各种情况很可能就造成数据库和缓存的数据不一致了。
解决这个问题的最经典的模式,就是Cache Aside Pattern。
Cache Aside Pattern:
(1)读的时候先读缓存,如果缓存不存在的话就读数据库,取出数据库后更新缓存;如果存在的话直接读取缓存的信息。
(2)写的时候,先更新数据库,再删除缓存。
1.写的时候为什么是删除缓存,而不是更新缓存?
删除缓存而不是更新缓存,是一种延迟加载的思想,不是每次都重复更新缓存,只有用到的时候才去更新缓存,同时即使有大量的读请求,实际也就更新了一次,后面的请求不会重复读。
2.Cache aside pattern 存在的问题
先更新数据库,再删除缓存,如果删除缓存失败了,导致数据库中是新数据,缓存中是旧数据,就出现数据不一致的问题。
解决思路:先删除缓存,再更新数据库
1.缓存删除失败:如果缓存删除失败,那么数据库信息没有被修改,保持了数据的一致性;
2.缓存删除成功,数据库更新失败:此时数据库里的是旧数据,缓存是空的,查询时发现缓存不存在,就查询数据库并更新缓存,数据保持一致。
3.上述方案依然存在问题
上面的方案存在不足,在并发情况下会出现问题。如果删除完缓存更新数据库时,如果一个请求过来查询数据,缓存不存在,就查询数据库的旧数据,更新旧数据到缓存中。随后数据更新完成,修改了数据库的数据,此时缓存和数据库的数据就会出现不一致了。高并发下会出现这种数据库+缓存不一致的情况。 如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
解决方案:采用双删除策略。写请求先删除缓存,再去更新数据库,等待一段时间后异步删除缓存。这样可以保证在读取错误数据时能及时被修正过来。还有一个策略是将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。
9.redis是如何做到高可用的
如果系统99%的时间都用于对外服务,那么系统可以说是高可用的。
哨兵模式可以实现高可用。因为从服务器挂掉了,并不会有太大的影响。主服务器挂掉了,可以将从服务器升级为主服务器。