Redis为什么快
一图胜千言。
- 基于内存存储,数据结构简单
- 单线程处理请求,基于内存操作对cpu利用率不高,单线程减少线程切换的负担
- 基于IO多路复用线程模型,基于epoll实现
对Redis单线程的理解
- 一次完整的redis请求时间有多个阶段
- 客户端到服务器的网络连接
- redis处理读写事件并向服务端发送请求数据(多路复用io模型)
- redis服务端数据处理(单线程)
- 数据返回
- redis的单线程模型本本质上指的是服务端的数据处理阶段,在此阶段所有命令线性处理,与网络连接和数据返回无关
为什么需要用IO多路复用技术
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,I/O多路复用模型就是为了解决这个问题而出现的。
Redis数据结构
- 基础数据结构
- string
- hash
- set
- zset
- list
- 高级数据结构
- stream
- bitmap
- bloomfilter
- hyperlogs
- geo
Redis持久化方式
- RDB
- RDB通过全量快照实现,符合一定条件,redis会自动将内存中的数据进行快照并持久化到磁盘。Redis默认持久化策略
- 触发分为手动触发和自动触发
- 手动触发
- 手动触发通过save命令和bgsave命令都可以生成RDB文件。
- bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。
- bgsave命令执行过程中,只有fork子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃
- 自动触发
- save m n,自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave
- 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点
- 执行shutdown命令时/退出redis,自动执行rdb持久化
- RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。
- 但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。
- Redis集群复制通过RDB做数据同步
- AOF
- REDIS默认开启RDB关闭AOF
- AOF执行流程
- 命令追加(append):将Redis的写命令追加到缓冲区aof_buf;
- 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;
- 文件重写(rewrite):定期重写AOF文件,达到压缩的目的
- 刷盘策略
- 每秒刷盘
- 每次刷盘
- 操作系统决定刷盘
- AOF+RDB混合持久化
- Redis服务器 在执行 AOF重写操作时,就会像执行BGSAVE命令那样,根据数据库当前的状态生成出 相应的RDB数据,并将这些数据 写入新建的AOF文件中,至于那些在AOF重写开始之后执行的Redis命令,则会继续以协议文本的方式 追加到 新AOF文件的末尾,即已有的RDB数据的后面
- 同时使用两种持久化功能需要耗费大量系统资源,系统的硬件必须能够支撑运行这两种功能所需的资源消耗,否则会给系统性能带来影响
- Redis服务器在启动时,会优先使用AOF文件进行数据恢复,只有在没有检测到AOF文件时,才会考虑寻找并使用RDB文件进行数据恢复
- 当Redis服务器正在后台生成新的RDB文件时,如果有用户向服务器发送BGREWRITEAOF命令,或者配置选项中设置的AOF重写条件被满足了,那么服务器 将把 AOF重写操作 推延到 RDB文件创建完毕之后再执行,以此来避免两种持久化操作同时执行并争抢系统资源
- 同样,当服务器正在执行BGREWRITEAOF命令时,用户发送或者被触发的BGSAVE命令也会推延到BGREWRITEAOF命令执行完毕之后再执行
Redis事务
- redis事务的三大特性
- 单独的隔离操作
- 没有事务隔离级别
- 单条命令是原子性执行的,不保证完全原子性,且不会回滚
- redis事务基于commands队列实现,分为三个阶段
- Multi开始事务
- Exec触发事务
- Discard放弃事务
Redis内存淘汰策略
- noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
- allkeys-lru:从所有key中使用LRU算法进行淘汰
- volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
- allkeys-random:从所有key中随机淘汰数据
- volatile-random:从设置了过期时间的key中随机淘汰
- volatile-ttl:在设置了过期时间的key中,淘汰过期时间剩余最短的
- 当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有key可以被淘汰,则和noeviction一样返回错误
Redis缓存穿透、缓存击穿、缓存雪崩
- 缓存穿透
- key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源
- 解决:
- 布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
- 空结果进行缓存,过期时间最长不超过五分钟。
- 缓存击穿
- key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
- 解决:
- 使用互斥锁,简单地来说,在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
- 热点数据做后台刷新缓存存活时间处理
- 缓存雪崩
- 当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
- 解决
- 加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
- 还有一个简单方案就时将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,降低过期时间的重复率。
Redis可能阻塞的原因
- 内在原因
- API或数据结构使用不合理
- CPU饱和
- 持久化阻塞
- 外在原因
- CPU竞争
- 内存交换
- 网络问题