- 什么是Redis?
- Redis优缺点有哪些?
- 为什么要用Redis?
- Redis为什么快?
- Redis有哪些数据类型?
- Redis的应用场景有哪些?
- 什么是Redis持久化?
- Redis 的持久化机制有哪些?
- 如何选择合适的持久化方式?
- 过期键的删除策略?
- Redis key的过期时间和永久有效怎么设置?
- Redis的内存淘汰策略有哪些?
- Redis的内存用完了会发生什么?
- Redis如何做内存优化?
- Redis线程模型?
- 什么是事务?
- Redis事务的概念?
- Redis事务的三个阶段?
- Redis事务相关命令?
- 事务管理(ACID)概述
- Redis事务支持隔离性吗?
- Redis事务保证原子性吗,支持回滚吗?
- Redis事务其他实现?
- Redis实现分布式锁
- 如何解决Redis的并发竞争 Key 问题
- 什么是 RedLock
- 缓存雪崩
- 缓存穿透
- 缓存击穿
- 缓存预热
- 如何保证缓存与数据库双写时的数据一致性?
- Redis回收进程如何工作的?
- Redis回收使用的是什么算法?
什么是Redis?
Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。与传统数据库不同的是 Redis 数据存在内存中,所以读写速度非常快
Redis优缺点有哪些?
优点
读写性能优异:读
110000次/s
,写81000次/s
支持数据持久化:支持
AOF
和RDB
两种持久化方式支持事务:Redis的所有操作都是原子性的,同时还支持对几个操作合并后的原子性执行
支持多种数据结构:除了支持
String
类型外还支持Hash
、Set
、Zset
、List
等数据结构支持主从复制:主机会自动将数据同步到从机,可以进行读写分离
缺点
- 缓存和数据库双写一致性问题
- 缓存雪崩问题
- 缓存击穿问题
- 缓存的并发竞争问题
为什么要用Redis?
- 高性能
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可
- 高并发
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库
Redis为什么快?
完全基于内存:绝大部分请求是纯粹的内存操作非常快速。数据存在内存中类似于 HashMap。查找和操作的时间复杂度都是
O(1)
采用单线程:避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
使用多路 I/O 复用模型,非阻塞 IO
传统并发模型:每个
I/O
流(快递)都有一个新的线程管理
多路复用:只有单个线程,通过跟踪每个I/O
流的状态来管理多个I/O
流
Redis有哪些数据类型?
5种基本数据类型
- String
- List
- Set
- Zset
- Hash
3种特殊数据类型
- Geospatial 地理位置
- Hyperloglog 基数统计
- Bitmap位图场景
Redis的应用场景有哪些?
计数器
可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量
缓存
将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率
会话缓存
可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性
消息队列(发布/订阅功能)
List
是一个双向链表,可以通过 lpush
和 rpop
写入和读取消息实现消息队列(不过最好使用 Kafka、RabbitMQ 等消息中间件)
分布式锁实现
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX
命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock
分布式锁实现
其它
Set 可以实现交集、并集等操作,从而实现共同好友等功能。ZSet 可以实现有序性操作,从而实现排行榜等功能
什么是Redis持久化?
持久化就是把内存的数据写到磁盘中去,防止服务宕机内存数据丢失
Redis 的持久化机制有哪些?
Redis 提供两种持久化机制 RDB
(默认) 和 AOF
机制
RDB:是Redis DataBase
缩写快照,Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb
。通过配置文件中的save
参数来定义快照周期
优点
只有一个文件
dump.rdb
,方便持久化容灾性好,一个文件可以保存到安全的磁盘
性能最大化,
fork
子进程来完成写操作,让主进程继续处理命令,所以是IO
最大化。使用单独子进程来进行持久化,主进程不会进行任何IO
操作,保证了 Redis 的高性能数据集较大时时,比
AOF
的启动效率更高
缺点
- 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 Redis 发生故障,会发生数据丢失
- AOF(Append-only file): 将所有的命令行记录以 Redis 命令请求协议的格式完全持久化存储,保存为
aof
文件
AOF:Append Only File
,是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据
优点
数据安全,AOF 持久化可以配置
appendfsync
属性,有always
,每进行一次 命令操作就记录到aof
文件中一次通过
append
模式写文件,即使中途服务器宕机,可以通过redis-check-aof
工具解决数据一致性问题AOF 机制的
rewrite
模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点
- aof文件比 rdb文件大,且恢复速度慢
- 数据集大的时候,比rdb启动效率低
对比
- aof文件比rdb更新频率高,优先使用aof还原数据
- aof比rdb更安全也更大
- rdb性能比aof好
- 如果两个都配了优先加载aof
如何选择合适的持久化方式?
一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
如果非常关心数据, 但仍然可以承受数分钟以内的数据丢失,那么可以只使用RDB持久化
如果只希望数据在服务器运行的时候存在,也可以不使用任何持久化方式
过期键的删除策略?
Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理
过期策略通常有以下三种
定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。Redis中同时使用了惰性过期和定期过期两种过期策略
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),还可以根据具体的业务需求进行自定义的缓存淘汰策略
Redis key的过期时间和永久有效怎么设置?
EXPIRE
和PERSIST
命令
Redis的内存淘汰策略有哪些?
Redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略;Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据
全局的键空间选择性移除
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key
设置过期时间的键空间选择性移除
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
Redis的内存淘汰策略的选取并不会影响过期的key的处理。
- 内存淘汰策略用于处理内存不足时的需要申请额外空间的数据
- 过期策略用于处理过期的缓存数据
Redis的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回)或者可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容
Redis如何做内存优化?
可以利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以应该尽可能的将数据模型抽象到一个散列表里面。比如web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而应该把这个用户的所有信息存储到一张散列表里面
Redis线程模型?
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型
参考:https://www.cnblogs.com/barrywxx/p/8570821.html
什么是事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行
Redis事务的概念?
Redis 事务的本质是通过MULTI
、EXEC
、WATCH
等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中
总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
Redis事务的三个阶段?
事务开始
MULTI
命令入队
事务执行
EXEC
事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求将会把请求放入队列中排队
Redis事务相关命令?
Redis事务功能是通过
MULTI
、EXEC
、DISCARD
和WATCH
四个原语实现的Redis会将一个事务中的所有命令序列化,然后按顺序执行
Redis不支持回滚,Redis 在事务失败时不进行回滚,而是继续执行余下的命令, 所以 Redis 的内部可以保持简单且快速
如果在一个事务中的命令出现错误,那么所有的命令都不会执行
如果在一个事务中出现运行错误,那么正确的命令会被执行
WATCH
命令是一个乐观锁,可以为 Redis 事务提供check-and-set
(CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC
命令MULTI
命令用于开启一个事务,它总是返回OK。MULTI
执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC
命令被调用时,所有队列中的命令才会被执行EXEC
:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值nil
通过调用
DISCARD
,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出UNWATCH
命令可以取消Watch
对所有key
的监控
事务管理(ACID)概述
- 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
- 一致性(Consistency)
事务前后数据的完整性必须保持一致
- 隔离性(Isolation)
多个事务并发执行时,一个事务的执行不应影响其他事务的执行
- 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有持久性
Redis事务支持隔离性吗?
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此Redis 的事务是总是带有隔离性的
Redis事务保证原子性吗,支持回滚吗?
Redis中单条命令是原子性执行的,但事务不保证原子性且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行
Redis事务其他实现?
- 基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行。其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完
- 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐
Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX
命令实现分布式锁
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX
是『SET if Not eXists』(如果不存在,则 SET)的简写
返回值:设置成功返回 1 | 设置失败返回 0
如何解决Redis的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和期望的顺序不同,也就导致了结果的不同
方案一:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
方案二:并发量过大的情况下,可以通过消息中间件进行处理,把Redis.Set操作放在队列中使其串行化必须依次执行
什么是 RedLock
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
- 安全特性:互斥访问,即永远只有一个 client 能拿到锁
- 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
- 容错性:只要大部分 Redis 节点存活就可以正常提供服务
缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉
使缓存集中失效的原因:
1、Redis服务器宕机
2、对缓存数据设置了相同的过期时间,导致某时间段内缓存集中失效
解决方案
1、针对原因1,可以实现Redis的高可用,Redis Cluster 或者 Redis Sentinel(哨兵) 等
2、针对原因2,设置缓存过期时间时加上一个随机值,避免缓存在同一时间过期
3、使用双缓存策略,设置两个缓存,原始缓存和备用缓存,原始缓存失效时,访问备用缓存,备用缓存失效时间设置长点
缓存穿透
缓存穿透表示查询一个一定不存在的数据,由于没有获取到缓存,所以没写入缓存,导致这个不存在的数据每次都需要去数据库查询,失去了缓存的意义。
请求的数据大量的没有获取到缓存,导致走数据库,有可能搞垮数据库,使整个服务瘫痪
解决方案
- 接口层增加校验,如用户鉴权校验,
id
做基础校验,id<=0
的直接拦截 - 从缓存取不到的数据,在数据库中也没有取到,这时也可以将
key-value
写为key-null
,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击 - 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的
bitmap
中,一个一定不存在的数据会被这个bitmap
拦截掉,从而避免了对底层存储系统的查询压力
缓存击穿
缓存击穿表示某个key的缓存非常热门,有很高的并发一直在访问,如果该缓存失效,请求会同时打到数据库,压垮数据库
缓存击穿与缓存雪崩的区别是:
- 缓存击穿针对的是某一热门key缓存
- 缓存雪崩针对的是大量缓存的集中失效
解决方案
设置热点数据永远不过期
加互斥锁
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据
解决方案
直接写个缓存刷新页面,上线时手工操作一下
数据量不大,可以在项目启动的时候自动进行加载
定时刷新缓存
如何保证缓存与数据库双写时的数据一致性?
TODO
Redis回收进程如何工作的?
- 一个客户端运行了新的命令,添加了新的数据。
- Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
- 一个新的命令被执行,等等。
- 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
Redis回收使用的是什么算法?
LRU算法